Commit a2caff27 by Jillian Vogel

Uses Github OAuth plugin to secure Jenkins

* Configures github oauth as default jenkins auth realm (was unix system user)
* Uses the Matrix Authorization Strategy at 3 levels: anonymous, administrator,
  and job builder.
* Allows configuration of the list of users/groups and permissions granted to
  each authorization level.
* Adds ssh private key to jenkins user, to maintain CLI tool access
* Sets jenkins password only if using old unix system user realm; locks the
  jenkins system user when using github oauth.
* Adds jenkins public key to Jenkins user config.xml, and creates that user
  config.xml file if it's missing (required to bootstrap jenkins user CLI access)
* Renames the existing JENKINS_ANALYTICS_GITHUB_* credential variables to
  JENKINS_ANALYTICS_GITHUB_CREDENTIAL_* to avoid confusing them with the Github
  OAUTH variables.
parent 8362d927
---
# See README.md for variable descriptions
# Packages required to build edx-analytics-pipeline
JENKINS_ANALYTICS_EXTRA_PKGS:
- libpq-dev
- libffi-dev
# See README.md for variable descriptions
# Change this default password: (see README.md to see how you can do it)
JENKINS_ANALYTICS_USER_PASSWORD_PLAIN: jenkins
JENKINS_ANALYTICS_AUTH_REALM: github_oauth
JENKINS_ANALYTICS_AUTH_ADMINISTRATORS: []
JENKINS_ANALYTICS_AUTH_JOB_BUILDERS: []
JENKINS_ANALYTICS_AUTH_ANONYMOUS_PERMISSIONS: []
JENKINS_ANALYTICS_AUTH_ADMINISTRATOR_PERMISSIONS:
- com.cloudbees.plugins.credentials.CredentialsProvider.Create
- com.cloudbees.plugins.credentials.CredentialsProvider.Delete
- com.cloudbees.plugins.credentials.CredentialsProvider.ManageDomains
- com.cloudbees.plugins.credentials.CredentialsProvider.Update
- com.cloudbees.plugins.credentials.CredentialsProvider.View
- hudson.model.Computer.Build
- hudson.model.Computer.Configure
- hudson.model.Computer.Connect
- hudson.model.Computer.Create
- hudson.model.Computer.Delete
- hudson.model.Computer.Disconnect
- hudson.model.Hudson.Administer
- hudson.model.Hudson.ConfigureUpdateCenter
- hudson.model.Hudson.Read
- hudson.model.Hudson.RunScripts
- hudson.model.Hudson.UploadPlugins
- hudson.model.Item.Build
- hudson.model.Item.Cancel
- hudson.model.Item.Configure
- hudson.model.Item.Create
- hudson.model.Item.Delete
- hudson.model.Item.Discover
- hudson.model.Item.Move
- hudson.model.Item.Read
- hudson.model.Item.Workspace
- hudson.model.Run.Delete
- hudson.model.Run.Update
- hudson.model.View.Configure
- hudson.model.View.Create
- hudson.model.View.Delete
- hudson.model.View.Read
- hudson.scm.SCM.Tag
JENKINS_ANALYTICS_AUTH_JOB_BUILDER_PERMISSIONS:
- com.cloudbees.plugins.credentials.CredentialsProvider.Create
- com.cloudbees.plugins.credentials.CredentialsProvider.Delete
- com.cloudbees.plugins.credentials.CredentialsProvider.ManageDomains
- com.cloudbees.plugins.credentials.CredentialsProvider.Update
- com.cloudbees.plugins.credentials.CredentialsProvider.View
- hudson.model.Hudson.Read
- hudson.model.Hudson.RunScripts
- hudson.model.Item.Build
- hudson.model.Item.Cancel
- hudson.model.Item.Configure
- hudson.model.Item.Create
- hudson.model.Item.Delete
- hudson.model.Item.Discover
- hudson.model.Item.Move
- hudson.model.Item.Read
- hudson.model.Item.Workspace
- hudson.model.Run.Delete
- hudson.model.Run.Update
- hudson.model.View.Configure
- hudson.model.View.Create
- hudson.model.View.Delete
- hudson.model.View.Read
- hudson.scm.SCM.Tag
JENKINS_ANALYTICS_GITHUB_OAUTH_CLIENT_ID: null
JENKINS_ANALYTICS_GITHUB_OAUTH_CLIENT_SECRET: null
JENKINS_ANALYTICS_GITHUB_OAUTH_SCOPES:
- read:org
- user:email
JENKINS_ANALYTICS_GITHUB_OAUTH_WEB_URI: 'https://github.com'
JENKINS_ANALYTICS_GITHUB_OAUTH_API_URI: 'https://api.github.com'
JENKINS_ANALYTICS_GITHUB_CREDENTIAL_ID: 'github-deploy-key'
JENKINS_ANALYTICS_GITHUB_USER: 'git'
JENKINS_ANALYTICS_GITHUB_PASSPHRASE: null
JENKINS_ANALYTICS_GITHUB_CREDENTIAL_USER: 'git'
JENKINS_ANALYTICS_GITHUB_CREDENTIAL_PASSPHRASE: null
JENKINS_ANALYTICS_GITHUB_CREDENTIAL_KEY: null
JENKINS_ANALYTICS_CONCURRENT_JOBS_COUNT: 2
......@@ -36,11 +107,11 @@ ANALYTICS_SCHEDULE_JOBS_DSL_TARGET_JOBS: "jobs/analytics-edx-jenkins.edx.org/*Jo
JENKINS_ANALYTICS_CREDENTIALS:
- id: "{{ JENKINS_ANALYTICS_GITHUB_CREDENTIAL_ID }}"
scope: GLOBAL
username: "{{ JENKINS_ANALYTICS_GITHUB_USER }}"
username: "{{ JENKINS_ANALYTICS_GITHUB_CREDENTIAL_USER }}"
type: ssh-private-key
passphrase: "{{ JENKINS_ANALYTICS_GITHUB_PASSPHRASE }}"
passphrase: "{{ JENKINS_ANALYTICS_GITHUB_CREDENTIAL_PASSPHRASE }}"
description: github access key, generated by ansible
privatekey: "{{ JENKINS_ANALYTICS_GITHUB_KEY }}"
privatekey: "{{ JENKINS_ANALYTICS_GITHUB_CREDENTIAL_KEY }}"
- id: "{{ ANALYTICS_SCHEDULE_MASTER_SSH_CREDENTIAL_ID }}"
scope: GLOBAL
username: "{{ ANALYTICS_SCHEDULE_MASTER_SSH_CREDENTIAL_USER }}"
......@@ -66,11 +137,38 @@ jenkins_credentials_script: "{{ jenkins_credentials_root }}/addCredentials.groov
jenkins_connection_retries: 240
jenkins_connection_delay: 1
jenkins_auth_realm:
jenkins_private_keyfile: "{{ jenkins_home }}/.ssh/id_rsa"
jenkins_public_keyfile: "{{ jenkins_private_keyfile }}.pub"
jenkins_admin_users:
- "{{ jenkins_user }}"
jenkins_auth_realms_available:
unix:
name: unix
service: su
# Change this default password: (see README.md to see how you can do it)
plain_password: "{{ JENKINS_ANALYTICS_USER_PASSWORD_PLAIN }}"
username: "{{ jenkins_user }}"
github_oauth:
name: github_oauth
webUri: "{{ JENKINS_ANALYTICS_GITHUB_OAUTH_WEB_URI }}"
apiUri: "{{ JENKINS_ANALYTICS_GITHUB_OAUTH_API_URI }}"
clientId: "{{ JENKINS_ANALYTICS_GITHUB_OAUTH_CLIENT_ID }}"
clientSecret: "{{ JENKINS_ANALYTICS_GITHUB_OAUTH_CLIENT_SECRET }}"
oauthScopes: "{{ JENKINS_ANALYTICS_GITHUB_OAUTH_SCOPES }}"
jenkins_auth_realm: "{{ jenkins_auth_realms_available[JENKINS_ANALYTICS_AUTH_REALM] }}"
jenkins_auth_users:
anonymous:
- anonymous
administrators: "{{ jenkins_admin_users + JENKINS_ANALYTICS_AUTH_ADMINISTRATORS }}"
job_builders: "{{ JENKINS_ANALYTICS_AUTH_JOB_BUILDERS | default([]) }}"
jenkins_auth_permissions:
anonymous: "{{ JENKINS_ANALYTICS_AUTH_ANONYMOUS_PERMISSIONS }}"
administrators: "{{ JENKINS_ANALYTICS_AUTH_ADMINISTRATOR_PERMISSIONS }}"
job_builders: "{{ JENKINS_ANALYTICS_AUTH_JOB_BUILDER_PERMISSIONS }}"
# For now only a single seed job is supported, adding more would require
# Ansible 2.+ or converting _execute_jenkins_cli to a module
......
---
- fail: msg=for now we can execute commands iff jenkins auth realm is unix
when: jenkins_auth_realm.name != "unix"
- set_fact:
jenkins_cli_root: "/tmp/jenkins-cli/{{ ansible_ssh_user }}"
- set_fact:
jenkins_cli_jar: "{{ jenkins_cli_root }}/jenkins_cli.jar"
jenkins_cli_pass: "{{ jenkins_cli_root }}/jenkins_cli_pass"
- name: create cli dir
file: name={{ jenkins_cli_root }} state=directory mode="700"
- name: create pass file
template: src=jenkins-pass-file.j2 dest={{ jenkins_cli_pass }} mode="600"
- name: Wait for Jenkins CLI
uri:
url: "http://localhost:{{ jenkins_port }}/cli/"
......@@ -32,14 +25,10 @@
url: "http://localhost:{{ jenkins_port }}/jnlpJars/jenkins-cli.jar"
dest: "{{ jenkins_cli_jar }}"
- name: login
command: java -jar {{ jenkins_cli_jar }} -s http://localhost:{{ jenkins_port }}
login --username={{ jenkins_user }}
--password-file={{ jenkins_cli_pass }}
- name: execute command
shell: >
{{ jenkins_command_prefix|default('') }} java -jar {{ jenkins_cli_jar }} -s http://localhost:{{ jenkins_port }}
-i {{ jenkins_private_keyfile }}
{{ jenkins_command_string }}
register: jenkins_command_output
ignore_errors: "{{ jenkins_ignore_cli_errors|default (False) }}"
......
......@@ -7,24 +7,34 @@
tags:
- jenkins
- fail: msg=included unix realm by accident
when: jenkins_auth_realm.name != "unix"
- name: Install httplib2 (required by uri module used in this role)
pip:
name: httplib2
tags:
- jenkins
# Jenkins authentication/authorization
- fail: msg="invalid auth realm {{ jenkins_auth_realm.name }}"
when: jenkins_auth_realm.name != "unix" and jenkins_auth_realm.name != "github_oauth"
tags:
- jenkins-auth
- fail: msg=Please change default password for jenkins user
when: jenkins_auth_realm.plain_password == 'jenkins'
- fail: msg="Please change default password for jenkins user"
when: jenkins_auth_realm.name == "unix" and jenkins_auth_realm.plain_password == jenkins_auth_realm.username
tags:
- jenkins-auth
- name: Install httplib2 (required by uri module used in this role)
pip:
name: httplib2
- fail: msg="Please change default github oauth client key and secret"
when: jenkins_auth_realm.name == "github_oauth" and ((not jenkins_auth_realm.clientId) or (not jenkins_auth_realm.clientSecret))
tags:
- jenkins-auth
- name: generate jenkins user password
shell: "openssl passwd -1 '{{ jenkins_auth_realm.plain_password | quote }}'"
register: jenkins_user_password_hash
no_log: True
when: jenkins_auth_realm.name == "unix"
tags:
- jenkins-auth
......@@ -33,7 +43,17 @@
state: present
groups: shadow
append: yes
password: '!' # locked
update_password: always
generate_ssh_key: yes
ssh_key_file: "{{ jenkins_private_keyfile }}"
tags:
- jenkins-auth
- user:
name: "{{ jenkins_user }}"
password: "{{ jenkins_user_password_hash.stdout }}"
when: jenkins_auth_realm.name == "unix"
tags:
- jenkins-auth
......@@ -46,6 +66,40 @@
tags:
- jenkins-auth
- name: jenkins user config dir
file:
name: "{{ jenkins_home }}/users/{{ jenkins_user }}"
state: directory
owner: "{{ jenkins_user }}"
group: "{{ jenkins_group }}"
tags:
- jenkins-auth
- name: template jenkins user config.xml
template:
src: jenkins.user.config.xml
dest: "{{ jenkins_home }}/users/{{ jenkins_user }}/config.xml"
owner: "{{ jenkins_user }}"
group: "{{ jenkins_group }}"
force: no # don't overwrite if already there
tags:
- jenkins-auth
- name: fetch jenkins user public key
shell: "cat {{ jenkins_public_keyfile }}"
register: jenkins_public_key
tags:
- jenkins-auth
- name: add jenkins user public key
lineinfile:
dest: "{{ jenkins_home }}/users/{{ jenkins_user }}/config.xml"
state: present
regexp: "^\\s*<authorizedKeys>"
line: "<authorizedKeys>{{ jenkins_public_key.stdout }}</authorizedKeys>"
tags:
- jenkins-auth
# Unconditionally restart Jenkins, this has two side-effects:
# 1. Jenkins uses new auth realm
# 2. We guarantee that jenkins is started (this is not certain
......@@ -62,7 +116,7 @@
- name: create credentials dir
file: name={{ jenkins_credentials_root }} state=directory
tags:
- jenkins-auth
- jenkins-credentials
- name: upload groovy script
template:
......@@ -70,7 +124,7 @@
dest: "{{ jenkins_credentials_script }}"
mode: "600"
tags:
- jenkins-auth
- jenkins-credentials
- name: upload credentials file
template:
......@@ -79,19 +133,19 @@
mode: "600"
owner: "{{ jenkins_user }}"
tags:
- jenkins-auth
- jenkins-credentials
- name: add credentials
include: execute_jenkins_cli.yaml
vars:
jenkins_command_string: "groovy {{ jenkins_credentials_script }}"
tags:
- jenkins-auth
- jenkins-credentials
- name: clean up
file: name={{ jenkins_credentials_root }} state=absent
tags:
- jenkins-auth
- jenkins-credentials
# Upload seed job
......
{{ jenkins_auth_realm.plain_password }}
\ No newline at end of file
......@@ -5,46 +5,27 @@
<numExecutors>{{ JENKINS_ANALYTICS_CONCURRENT_JOBS_COUNT }}</numExecutors>
<mode>NORMAL</mode>
<useSecurity>true</useSecurity>
{% if jenkins_auth_realm.name == "unix" %}
<authorizationStrategy class="hudson.security.GlobalMatrixAuthorizationStrategy">
<permission>com.cloudbees.plugins.credentials.CredentialsProvider.Create:jenkins</permission>
<permission>com.cloudbees.plugins.credentials.CredentialsProvider.Delete:jenkins</permission>
<permission>com.cloudbees.plugins.credentials.CredentialsProvider.ManageDomains:jenkins</permission>
<permission>com.cloudbees.plugins.credentials.CredentialsProvider.Update:jenkins</permission>
<permission>com.cloudbees.plugins.credentials.CredentialsProvider.View:jenkins</permission>
<permission>hudson.model.Computer.Build:jenkins</permission>
<permission>hudson.model.Computer.Configure:jenkins</permission>
<permission>hudson.model.Computer.Connect:jenkins</permission>
<permission>hudson.model.Computer.Create:jenkins</permission>
<permission>hudson.model.Computer.Delete:jenkins</permission>
<permission>hudson.model.Computer.Disconnect:jenkins</permission>
<permission>hudson.model.Hudson.Administer:jenkins</permission>
<permission>hudson.model.Hudson.ConfigureUpdateCenter:jenkins</permission>
<permission>hudson.model.Hudson.Read:jenkins</permission>
<permission>hudson.model.Hudson.RunScripts:jenkins</permission>
<permission>hudson.model.Hudson.UploadPlugins:jenkins</permission>
<permission>hudson.model.Item.Build:jenkins</permission>
<permission>hudson.model.Item.Cancel:jenkins</permission>
<permission>hudson.model.Item.Configure:jenkins</permission>
<permission>hudson.model.Item.Create:jenkins</permission>
<permission>hudson.model.Item.Delete:jenkins</permission>
<permission>hudson.model.Item.Discover:anonymous</permission>
<permission>hudson.model.Item.Discover:jenkins</permission>
<permission>hudson.model.Item.Move:jenkins</permission>
<permission>hudson.model.Item.Read:anonymous</permission>
<permission>hudson.model.Item.Read:jenkins</permission>
<permission>hudson.model.Item.Workspace:jenkins</permission>
<permission>hudson.model.Run.Delete:jenkins</permission>
<permission>hudson.model.Run.Update:jenkins</permission>
<permission>hudson.model.View.Configure:jenkins</permission>
<permission>hudson.model.View.Create:jenkins</permission>
<permission>hudson.model.View.Delete:jenkins</permission>
<permission>hudson.model.View.Read:jenkins</permission>
<permission>hudson.scm.SCM.Tag:jenkins</permission>
{% for permission_group, permissions in jenkins_auth_permissions.iteritems() %}
{% for permission in permissions %}
{% for user in jenkins_auth_users[permission_group] | default([]) %}
<permission>{{ permission }}:{{ user }}</permission>
{% endfor %}
{% endfor %}
{% endfor %}
</authorizationStrategy>
{% if jenkins_auth_realm.name == "unix" %}
<securityRealm class="hudson.security.PAMSecurityRealm" plugin="pam-auth@1.2">
<serviceName>{{ jenkins_auth_realm.service }}</serviceName>
</securityRealm>
{% elif jenkins_auth_realm.name == "github_oauth" %}
<securityRealm class="org.jenkinsci.plugins.GithubSecurityRealm">
<githubWebUri>{{ jenkins_auth_realm.webUri }}</githubWebUri>
<githubApiUri>{{ jenkins_auth_realm.apiUri }}</githubApiUri>
<clientID>{{ jenkins_auth_realm.clientId }}</clientID>
<clientSecret>{{ jenkins_auth_realm.clientSecret }}</clientSecret>
<oauthScopes>{{ jenkins_auth_realm.oauthScopes|join(',') }}</oauthScopes>
</securityRealm>
{% endif %}
<disableRememberMe>false</disableRememberMe>
<projectNamingStrategy class="jenkins.model.ProjectNamingStrategy$DefaultProjectNamingStrategy"/>
......
<?xml version='1.0' encoding='UTF-8'?>
<user>
<fullName>{{ jenkins_user }}</fullName>
<description></description>
<properties>
<jenkins.security.ApiTokenProperty>
<apiToken></apiToken>
</jenkins.security.ApiTokenProperty>
<com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty plugin="credentials@1.24">
<domainCredentialsMap class="hudson.util.CopyOnWriteMap$Hash">
<entry>
<com.cloudbees.plugins.credentials.domains.Domain>
<specifications/>
</com.cloudbees.plugins.credentials.domains.Domain>
<java.util.concurrent.CopyOnWriteArrayList/>
</entry>
</domainCredentialsMap>
</com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty>
<hudson.tasks.Mailer_-UserProperty plugin="mailer@1.11">
<emailAddress></emailAddress>
</hudson.tasks.Mailer_-UserProperty>
<jenkins.security.LastGrantedAuthoritiesProperty>
<roles>
<string>edx</string>
<string>shadow</string>
<string>jenkins</string>
<string>authenticated</string>
</roles>
<timestamp>1457073573763</timestamp>
</jenkins.security.LastGrantedAuthoritiesProperty>
<hudson.model.MyViewsProperty>
<primaryViewName></primaryViewName>
<views>
<hudson.model.AllView>
<owner class="hudson.model.MyViewsProperty" reference="../../.."/>
<name>All</name>
<filterExecutors>false</filterExecutors>
<filterQueue>false</filterQueue>
<properties class="hudson.model.View$PropertyList"/>
</hudson.model.AllView>
</views>
</hudson.model.MyViewsProperty>
<hudson.model.PaneStatusProperties>
<collapsed/>
</hudson.model.PaneStatusProperties>
<org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl>
<authorizedKeys></authorizedKeys>
</org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl>
<hudson.search.UserSearchProperty>
<insensitiveSearch>false</insensitiveSearch>
</hudson.search.UserSearchProperty>
</properties>
</user>
......@@ -35,7 +35,7 @@ jenkins_plugins:
- { name: "git-client", version: "1.19.0"}
- { name: "github", version: "1.14.0" }
- { name: "github-api", version: "1.69" }
- { name: "github-oauth", version: "0.20" }
- { name: "github-oauth", version: "0.22.3" }
- { name: "github-sqs-plugin", version: "1.5" }
- { name: "gradle", version: "1.24" }
- { name: "grails", version: "1.7" }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment