-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathinstall-jenkins-sso.yml
584 lines (498 loc) · 20.4 KB
/
install-jenkins-sso.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
---
- name: Install Jenkins
hosts: grpjenkins
remote_user: root
become: true
become_user: root
become_method: sudo
# use this command line: ansible-playbook -vvv --ask-pass --ask-become-pass setup-new-server.yml -i 10.1.1.158, -u jeroen
# Execute ansible-playbook playbookfile.yml -i hosts --vault-password-file .vault-password
# Online doc: https://rafaelit.blog/2021/10/04/openid-connect-jenkins-with-keycloak/
vars:
ansible_python_interpreter: /usr/bin/python3
root_ca_path: /usr/local/share/ca-certificates/mkcert_development_CA_62268663181785622328732999788222374785.crt
jenkins_client_id: "client-jenkins-{{ ansible_fqdn }}"
jenkins_client_name: "client-jenkins-{{ ansible_fqdn }}"
jenkins_server_url: https://jenkins.{{ domain }}
keycloak_server_url: https://ad.{{ domain }}
realm: "{{ realm }}"
tasks:
- debug:
msg: "Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug"
- name: Read global vars
include_vars: global-vars.yml
- name: Read encrypted content
include_vars: encrypted-vars.yml
- set_fact:
fqdn: "{{ inventory_hostname_short }}.{{ domain }}"
# curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo tee \
# /usr/share/keyrings/jenkins-keyring.asc > /dev/null
- name: Download Jenkins repo key
# sudo wget https://pkg.jenkins.io/debian-stable/jenkins.io.key
get_url:
url: https://pkg.jenkins.io/debian-stable/jenkins.io.key
dest: /usr/share/keyrings/jenkins-keyring.asc
mode: '0644'
#Then add a Jenkins apt repository entry:
- name: Download Jenkins repo info
apt_repository:
repo: deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/
state: present
filename: jenkins
#Update your local package index, then finally install Jenkins:
- name: Update repositories cache
apt:
update_cache: yes
- name: Set timezone to Europe/Amsterdam
community.general.timezone:
name: Europe/Amsterdam
# Install all needed software in one go.
- name: Install Jenkins and other packages
apt:
name: "{{ item }}"
state: present
loop:
- openjdk-11-jdk
- ca-certificates
- curl
- openssh-server
- mlocate
- openssl
- autopostgresqlbackup
- jenkins
- nginx
# - name: Disable Jenkins useSecurity in /var/lib/jenkins/config.xml so we can edit the configuration at the end of this playbook.
# lineinfile:
# path: /var/lib/jenkins/config.xml
# regexp: ".*<useSecurity>.*</useSecurity>"
# line: ' <useSecurity>false</useSecurity>'
- name: Enable and start if not running Jenkins
systemd:
name: jenkins
state: started
enabled: yes
- name: Upload vanilla Jenkins config.xml.vanilla file, just in case.
copy:
src: files/jenkins_vanilla_config.xml
dest: /var/lib/jenkins/config.xml.vanilla
owner: jenkins
group: jenkins
mode: '0644'
force: true
# Now install Nginx
- name: Install Nginx
apt:
name: nginx
state: present
- name: Upload mkcert program
copy:
src: files/mkcert
dest: /root/mkcert
owner: root
group: root
mode: '0700'
force: true
- name: Upload CA key and cert
copy:
src: "files/{{ item }}"
dest: "/root/{{ item }}"
owner: root
group: root
mode: '0600'
force: true
with_items:
- rootCA.pem
- rootCA-key.pem
- name: Try to install my root CA on this system
shell:
cmd: CAROOT=/root /root/mkcert -install
- name: Update all CA certificates
shell:
cmd: /usr/sbin/update-ca-certificates
# Check if our CA is already installed
# Should be /usr/local/share/ca-certificates/mkcert_development_CA_62268663181785622328732999788222374785.crt
- name: Verify that our root CA is already installed
stat:
path: "{{ root_ca_path }}"
register: root_ca_stat
- name: Is our root CA already present?
debug:
msg: "Yes it is!"
when: root_ca_stat.stat.exists
# Install nginx config file
- name: Install nginx config file
template:
src: templates/jenkins_nginx_sso.conf.j2
dest: /etc/nginx/sites-available/jenkins
# Do not overwrite when file already exists!!!
force: no
owner: root
group: root
mode: '0644'
failed_when: fqdn is not defined
# Retrieve token url needed. Returns JSON payload with var token-service.
- name: Retrieve token url from server
uri:
url: "{{ keycloak_server_url }}/realms/master"
validate_certs: false
register: tokenurl
- debug:
var: tokenurl.json["token-service"]
- name: Store url for easier retrieval
set_fact:
token_url: "{{ tokenurl.json[\"token-service\"] }}"
# Call info endpoint
- name: "Retrieve endpoint info for our realm {{ realm }}"
uri:
url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration"
validate_certs: false
register: endpointinfo
- debug:
var: endpointinfo
- name: Store authorization_endpoint for faster retrieval
set_fact:
authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}"
# "authorization_endpoint": "https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/openid-connect/auth",
- debug:
var: authorization_endpoint
- name: Store token_endpoint for faster retrieval
set_fact:
token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}"
- debug:
var: token_endpoint
- name: Store userinfo_endpoint for faster retrieval
set_fact:
userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}"
- debug:
var: userinfo_endpoint
# Get authentication token from token-service url
- name: Retrieve authentication token from token-service url
uri:
url: "{{ tokenurl.json[\"token-service\"] }}/token"
method: POST
body_format: form-urlencoded
validate_certs: false
body:
realm: master
client_id: admin-cli
username: "{{ kc_adminid }}"
password: "{{ kc_adminpw }}"
grant_type: password
register: authtoken
- debug:
var: authtoken
- debug:
var: authtoken.json["access_token"]
- name: Store access token into variable for easier retrieval
set_fact:
auth_token: "{{ authtoken.json[\"access_token\"] }}"
- debug:
var: auth_token
#######################################################################################
# Very badly documented: The admin RESTful API has a base path /admin/realms/
#######################################################################################
# Retrieve current list of clients of our type
# So, this works in curl:
# curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq .
- name: Retrieve current list of clients and search for already existing "{{ jenkins_client_id }} "
uri:
url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ jenkins_client_id }}"
headers:
Accept: "application/json"
Authorization: "Bearer {{ auth_token }}"
method: get
validate_certs: false
register: existingclient
- name: Find ID in returned json
debug:
var: existingclient.json[0].id
- name: Delete client id "{{ jenkins_client_id }}" if it already exists.
# Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f"
uri:
url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}"
headers:
Accept: "application/json"
Authorization: "Bearer {{ auth_token }}"
method: DELETE
validate_certs: false
status_code: 204
when: existingclient.json[0].id is defined
register: deleteclient
- debug:
var: deleteclient
- name: Convert Ninja template to variable
set_fact:
jsonbody: "{{ lookup('template', 'jenkins-keycloak-sso.json.j2') }}"
# - debug:
# var: jsonbody
# Generate the json payload to upload
- name: Upload JSON template file to create new Client ID on Keycloak server
uri:
url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients"
headers:
Accept: "application/json"
Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}"
method: POST
validate_certs: false
body_format: json
body: "{{ jsonbody }}"
status_code: 201
register: createclientresult
# - name: What does the 'createclientresult' look like?
# debug:
# var: createclientresult
- name: Get client UUID from locaton
set_fact:
cl_uuid: "{{ createclientresult.location.split(\"/\")[-1] }}"
# - name: What does the 'cl_uuid' look like?
# debug:
# var: cl_uuid
# Get representation of the client
# GET /{realm}/clients/{id}
- name: Download representation of client for later use
uri:
url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ cl_uuid }}"
headers:
Accept: "application/json"
Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}"
method: GET
validate_certs: false
status_code: 200
register: client_representation
# - name: What did we receive?
# debug:
# var: client_representation
# # Good news! Result contains location of new client id in location
# # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c"
# - name: If all went well we now have a locaton of the newly created Client ID
# debug:
# var: createclientresult.location
# GET /{realm}/clients/{id}/client-secret
- name: Retrieve secret for newly created client "{{ jenkins_client_id }} "
uri:
url: "{{ createclientresult.location }}/client-secret"
headers:
Accept: "application/json"
Authorization: "Bearer {{ auth_token }}"
method: get
validate_certs: false
register: clientsecret
- name: Store secret for easy retrieval
set_fact:
client_secret: "{{ clientsecret.json.value }}"
# We have to create 2 groups in Keycloak for Jenkins: Jenkins-admin and Jenkins-user
# First retrieve current list of configured groups
- name: Retrieve current list of groups
uri:
url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups"
headers:
Accept: "application/json"
Authorization: "Bearer {{ auth_token }}"
method: get
validate_certs: false
register: kc_groups
# Example:
# "json": [
# {
# "id": "954df69b-b132-4652-8de5-f48cf83a9b2f",
# "name": "Jenkins-admin",
# "path": "/Jenkins-admin",
# "subGroups": []
# },
# {
# "id": "ac6a3a93-18e3-4cfa-8b41-037d79a1ba5b",
# "name": "Jenkins-user",
# "path": "/Jenkins-user",
# "subGroups": []
# }
# ],
- name: Show currently configured groups
debug:
var: kc_groups
# Search groups, We store the information for later conditional adding of groups if needed
- name: Search for our groups
uri:
url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups?search={{ item }}"
headers:
Accept: "application/json"
Authorization: "Bearer {{ auth_token }}"
method: get
validate_certs: false
with_items:
- Jenkins-admin
- Jenkins-user
register: groupinfo
- name: Find ID in returned json
debug:
var: groupinfo
# Create groups if not exist, else ignore errors
- name: Create groups Jenkins-admin and Jenkins-user or ignore errors
uri:
url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups"
headers:
Accept: "application/json"
Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}"
method: POST
validate_certs: false
body_format: json
body: "{ \"name\": \"{{ item }}\", \"path\": \"/{{ item }}\"}"
status_code: 201, 409
with_items:
- Jenkins-admin
- Jenkins-user
ignore_errors: yes
- name: Retrieve current list of groups
uri:
url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups"
headers:
Accept: "application/json"
Authorization: "Bearer {{ auth_token }}"
method: get
validate_certs: false
register: kc_groups
- name: Show currently configured groups
debug:
var: kc_groups
#- fail:
# Create symlink ln -s /etc/nginx/sites-available/jenkins /etc/nginx/sites-enabled/
- name: Enable virtualhost in nginx
ansible.builtin.file:
src: /etc/nginx/sites-available/jenkins
dest: /etc/nginx/sites-enabled/jenkins
owner: root
group: root
state: link
force: yes
# Disable default configuration by removing symlink /etc/nginx/sites-enabled/default
- name: Disable default nginx site config by removing a symlink
ansible.builtin.file:
path: /etc/nginx/sites-enabled/default
state: absent
- name: Make SSL dir for nginx
ansible.builtin.file:
path: /etc/nginx/ssl
state: directory
mode: '0755'
- name: Copy SSL key and cert to apache ssl dir
copy:
src: "files/{{ item }}"
dest: "/etc/nginx/ssl/{{ item }}"
owner: root
group: root
mode: '0600'
loop:
- _wildcard.{{ domain }}.pem
- _wildcard.{{ domain }}-key.pem
# - name: Install Nginx reverse proxy virtualhost for Keycloak
# copy:
# src: "files/jenkins_nginx.conf"
# dest: "/etc/nginx/sites-available/jenkins"
# owner: root
# group: root
# mode: '0600'
- name: Enable and restart always Nginx
systemd:
name: nginx
state: restarted
enabled: yes
- debug:
msg: "Start waiting for 443"
- name: Wait for port 443 to become open on the host
uri:
url: "https://jenkins.{{ domain }}/"
status_code: 403
register: result
until: result.status == 403
# Measured 12 attempts, so 25 max should work most of the time.
retries: 55
delay: 10
# ignore_errors: yes
- debug:
msg: "Done waiting for 443"
# Default user: admin
# Default password: $JENKINS_HOME/secrets/initialAdminPassword
- name: "Retrieve initialAdminPassword from JENKINS_HOME/secrets/initialAdminPassword"
slurp:
src: "/var/lib/jenkins/secrets/initialAdminPassword"
register: admin_password_encoded
ignore_errors: yes
- name: Decode initialAdminPassword
set_fact:
admin_password: "{{ admin_password_encoded.content | b64decode | trim }}"
ignore_errors: yes
# Create post-install message
- name: test
debug:
msg: "test"
- name: Post-install message IT IS IMPORTANT TO READ THIS
pause:
seconds: 1
prompt: |
********************************************************************************************************
* After a fresh installation Jenkins offers no way to configure SSO using Ansible or the command line.
* Sure you can enable remote admin options, but you might just as well configure SSO from the GUI.
*
* Default Administrator account name is 'admin'
* The password for your installation is: "{{ admin_password }}"
*
* Follow these next steps
*
* - Go to "{{ jenkins_server_url }}" , login with the above mentioned admin password and click on the 'next' button.
* - Select the 'Install suggested plugins' button to create a default installation.
* - When presented with the 'Create First Admin User' click 'Skip and continue as Admin'.
* - Confirm or edit the Jenkins URL in the 'Instance Configuration' screen and click 'Save and Finish'
* - The following message will appear
* You have skipped the setup of an admin user.
* To log in, use the username: "admin" and the administrator password you used to access the setup wizard.
* - Click the 'Start using Jenkins' button.
* - At the moment, the file /var/lib/jenkins/secrets/initialAdminPassword still contains the admin password.
*
* - Time to configure SSO
* - Go in the Jenkins GUI to 'Manage Jenkins' → 'Manage Plugins', url = "{{ jenkins_server_url }}/pluginManager/"
* - Go to the 'available' tab ("{{ jenkins_server_url }}/pluginManager/available") and enter 'OpenId' in the search field.
* - Select the checkbox of the 'OpenId Connect Authentication' plugin and click the 'Install without restart' button.
* - If you like you can verify the installation by going to the 'Installed' tab
* ("{{ jenkins_server_url }}/pluginManager/installed")
*
* - Once you have installed the OpenId plugin. you need to change the authentication mode of the Jenkins server.
* - Go to "Manage Jenkins" -> "Configure Global Security" ("{{ jenkins_server_url }}/configureSecurity/"
* - Under the 'Authentication' section, Change the pull-down list of 'Security Realm' ('Beveiligingszone')
* section to 'Login with openid Connect' option.
* - Enter in the "Client id" field the value "{{ jenkins_client_id }}"
* - Enter in the "Client secret" field the value "{{ client_secret }}"
* - Select as the "Configuration mode" the option "Automatic configuration" and enter as the "Well-known configuration endpoint"
* the value "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" and press the TAB-key afterwards.
* - Open the "Advanced" part of this configuration section
* - Enter in the "User name field name" the text 'sub'
* - Enter in the "Full name field name" the text 'preferred_username'
* - Enter in the "Groups field name" the text 'group-membership'
*
* - Just below the "Advanced" part is the "Authorization" section.
* - Select the "Matrix-based security" option.
* - Click on "Add group..." and add two groups.
* Group "/Jenkins-admin" (Note the leading "/"!) with All (!) "Overall/Administer" rights
* Group "/Jenkins-user" (Note the leading "/"!) with "Overall/Read" rights
* - Save this change by clicking on the 'Save' button at the bottom of the page. It will redirect you to the 'Manage Jenkins' page.
* - Based on the current settings you have no longer access rights. Logout and login as a valid user.
"********************************************************************************************************
# Information about debugging Jenkins
# journalctl -u jenkins.service
# cat /var/lib/jenkins/logging-jb.properties
#handlers = java.util.logging.ConsoleHandler
#
## see https://docs.oracle.com/en/java/javase/11/docs/api/java.logging/java/util/logging/SimpleFormatter.html
#java.util.logging.SimpleFormatter.format = [%1$tF %1$tT][%4$-6s][%2$s] %5$s %6$s %n
#
## Keep this level to ALL or FINEST or it will be filtered before applying other levels
#java.util.logging.ConsoleHandler.level = ALL
#
## Default level
#.level= DEBUG
#
## High verbosity for a d
# vim /etc/default/jenkins
# add to last JENKINS: -Djava.util.logging.config.file=/var/lib/jenkins/logging-jb.properties
# Settings found in
# org.jenkinsci.plugins.KeycloakSecurityRealm.xml: "auth-server-url": "https://ad.{{ domain }}/"
# plugin OpenId Connect Authentication Version 1.8 Authentication and User Management
# https://ad.{{ domain }}:8443/realms/ONESTEIN.LAN/.well-known/openid-configuration