emmetog / ansible-jenkins

Ansible role to install and fully configure Jenkins CI in Docker
MIT License
104 stars 89 forks source link

Ansible Role for Jenkins

Installs and completely configures Jenkins using Ansible.

This role is used when you want all your Jenkins configuration in version control so you can deploy Jenkins repeatably and reliably and you can treat your Jenkins as a Cow instead of a Pet.

If you are looking for a role to install Jenkins and you want to configure everything through the web interface and you don't care about being able to repeatably deploy this same fully-configured Jenkins, then you don't need this role. Instead, have a look at the geerlingguy/ansible-role-jenkins role instead.

Requirements

If deploying using Docker, then you need Docker installed on the server. Docker, apt-get, and yum are the only supported ways at the moment although more ways can easily be added, PRs welcome.

Installation

Install using Ansible Galaxy:

$ ansible-galaxy install emmetog.jenkins

Role Variables

The following variables influence how Jenkins is installed:

The following variables influence how Jenkins is configured:

The following list variables influence the jobs/plugins that will be installed in Jenkins:

For a complete list of variables, see defaults/main.yml.

Example Playbook

- hosts: jenkins

  vars:
    jenkins_version: "2.73.1"
    jenkins_hostname: "jenkins.example.com"
    jenkins_port: 8080
    jenkins_install_via: "docker"
    jenkins_jobs:
      - "my-first-job"
      - "another-awesome-job"
    jenkins_include_secrets: true
    jenkins_include_custom_files: true
    jenkins_custom_files:
      - src: "jenkins.plugins.openstack.compute.UserDataConfig.xml"
        dest: "jenkins.plugins.openstack.compute.UserDataConfig.xml"
    jenkins_plugins:
      - git
      - blueocean
    jenkins_custom_plugins:
      - "openstack-cloud-plugin/openstack-cloud.jpi"

  roles:
    - emmetog.jenkins

Managing Configuration Files

The example above will look for job configuration files in {{ playbook_dir }}/jenkins-configs/jobs/my-first-job/config.xml and {{ playbook_dir }}/jenkins-configs/jobs/another-awesome-job/config.xml.

NOTE: These directories are customizable, see the jenkins_source_dir_configs and jenkins_source_dir_jobs role variables.

The role will also look for {{ playbook_dir }}/jenkins-configs/config.xml This config.xml file will be copied to the server and used as the job configuration template.

The above example will also upload the entire secrets directory under {{ playbook_dir }}/jenkins-configs/secrets, and also copy custom files defined in the {{ jenkins_custom_files }} variable. Note that {{ jenkins_include_secrets }} and {{ jenkins_include_custom_files }} variables should be set to true for features these to work. Additionally, the role can install custom plugins by providing the .jpi or .hpi files in the {{ jenkins_custom_plugins }} list variable.

The config.xml and the custom files are treated as templates so you can put variables in them, including sensitive data from the Ansible vault.

When you want to make a change in a configuration file, or you want to add a new item (such as a job, plugin, etc) the normal workflow is:

  1. Make the change in the Jenkins UI
  2. Copy the resulting XML files back into your VCS
  3. For newly-created files, don't forget to add them to the respective list:
    • For new jobs, these must be added to jenkins_jobs
    • For custom files, these must be added to jenkins_include_custom_files
    • For custom plugins, these must be added to jenkins_custom_plugins

Example Jenkins Configuration File

In {{ jenkins_source_dir_configs }}/config.xml you put your global Jenkins configuration, for example:

<?xml version='1.1' encoding='UTF-8'?>
<hudson>
  <disabledAdministrativeMonitors/>
  <version>2.176.1</version>
  <installStateName>RESTART</installStateName>
  <numExecutors>1</numExecutors>
  <mode>EXCLUSIVE</mode>
  <useSecurity>true</useSecurity>
  <authorizationStrategy class="hudson.security.AuthorizationStrategy$Unsecured"/>
  <securityRealm class="hudson.security.HudsonPrivateSecurityRealm">
    <disableSignup>false</disableSignup>
    <enableCaptcha>false</enableCaptcha>
  </securityRealm>
  <disableRememberMe>false</disableRememberMe>
  <projectNamingStrategy class="jenkins.model.ProjectNamingStrategy$DefaultProjectNamingStrategy"/>
  <workspaceDir>${JENKINS_HOME}/workspace/${ITEM_FULLNAME}</workspaceDir>
  <buildsDir>${ITEM_ROOTDIR}/builds</buildsDir>
  <markupFormatter class="hudson.markup.EscapedMarkupFormatter"/>
  <jdks/>
  <viewsTabBar class="hudson.views.DefaultViewsTabBar"/>
  <myViewsTabBar class="hudson.views.DefaultMyViewsTabBar"/>
  <clouds/>
  <quietPeriod>0</quietPeriod>
  <scmCheckoutRetryCount>0</scmCheckoutRetryCount>
  <views>
    <hudson.model.AllView>
      <owner class="hudson" reference="../../.."/>
      <name>all</name>
      <filterExecutors>false</filterExecutors>
      <filterQueue>false</filterQueue>
      <properties class="hudson.model.View$PropertyList"/>
    </hudson.model.AllView>
  </views>
  <primaryView>all</primaryView>
  <slaveAgentPort>0</slaveAgentPort>
  <disabledAgentProtocols>
    <string>JNLP-connect</string>
    <string>JNLP2-connect</string>
  </disabledAgentProtocols>
  <label>master</label>
  <crumbIssuer class="hudson.security.csrf.DefaultCrumbIssuer">
    <excludeClientIPFromCrumb>false</excludeClientIPFromCrumb>
  </crumbIssuer>
  <nodeProperties/>
  <globalNodeProperties/>
</hudson>

Example Job Configuration File

Here's an example of what you could put in {{ playbook_dir }}/jenkins-configs/jobs/my-first-job/config.xml:

<?xml version='1.0' encoding='UTF-8'?>
<project>
  <actions/>
  <description>My first job, it says "hello world"</description>
  <keepDependencies>false</keepDependencies>
  <properties/>
  <scm class="hudson.scm.NullSCM"/>
  <canRoam>true</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers/>
  <concurrentBuild>false</concurrentBuild>
  <builders>
    <hudson.tasks.Shell>
      <command>echo &quot;Hello World!&quot;</command>
    </hudson.tasks.Shell>
  </builders>
  <publishers/>
  <buildWrappers/>
</project>

Authentication and Security

This role supports the following authentication mechanisms for Jenkins:

  1. API token-based authentication (recommended, requires at least Jenkins 2.96)
  2. Crumb-based authentication with the Strict Crumb Issuer plugin (required if not using API tokens and Jenkins 2.176.2 or newer)
  3. No security (not recommended)

API token-based authentication

API token-based authentication is recommended, but requires a bit of extra effort to configure. The advantage of API tokens is that they can be easily revoked in Jenkins, and their usage is also tracked. API tokens also do not require getting a crumb token, which has become more difficult since Jenkins version 2.172.2 (see this security bulletin.

To create an API token, you'll need to do the following:

  1. All API tokens must belong to a specific user. So either create a special user for deployments, or log in as the administrator or another account.
  2. In the user's configuration page, click the "Add new Token" button.
  3. Save the token value, preferably in an Ansible vault.
  4. Define the following variables in your playbook:
    • jenkins_auth: "api"
    • jenkins_api_token: "(defined in the Anible vault)"
    • jenkins_api_username: "(defined in the Ansible vault)"
  5. Create a backup of the file $JENKINS_HOME/users/the_username/config.xml, where the_username corresponds to the user which owns the API token you just created.
  6. Add this file to your control host, and make sure that is deployed to Jenkins in the jenkins_custom_files list, like so:
jenkins_custom_files:
  - src: "users/the_username/config.xml"
    dest: "users/the_username/config.xml"

Note that you may need to change the src value, depending on where you save the file on the control machine relative to the playbook.

Crumb-based authentication

Crumb-based authentication can be used to prevent cross-site request forgery attacks and is recommended if API tokens are impractical. Note: crumb-based authentication only works with the "Anyone can do anything" access control setting. If your Jenkins configuration requires a stricter security setup, you should use API tokens (documented above).

Crumb-based authentication can also be a bit tricky to configure due to recent security fixes in Jenkins. To configure CSRF, you'll need to do the following:

  1. If you are using Jenkins >= 2.176.2, you'll need to install the Strict Crumb Issuer plugin. This can be done by this role by adding the strict-crumb-issuer ID to the jenkins_plugins list.
  2. In Jenkins, click on "Manage Jenkins" -> "Configure Global Security"
  3. In the "CSRF Protection" section, enable "Prevent Cross Site Request Forgery exploits", and then select "Strict Crumb Issuer" if using Jenkins >= 2.176.2, or otherwise "Default Crumb Issuer". Note that to see this option, you'll need to have the Strict Crumb Issuer plugin installed. Afterwards, you'll also need to backup the main Jenkins config.xml file to the control host.

Likewise, for the above to work, you'll need at least Ansible 2.9.0pre5 or 2.10 (which are, at the time of this writing, both in development. See this Ansible issue for more details).

HTTPS

If you want to enable HTTPS on Jenkins, this can be done as follows:

If you are deploying Jenkins with Docker, then using a reverse proxy such as jwilder/nginx-proxy or traefik is recommended instead of configuring Jenkins itself. This gives a bit more flexibility and allows for separation of responsibilities. See the documentation in those projects for more details on how to deploy the proxies and configure HTTPS.

If using a reverse proxy in front of the Jenkins instance and deploying using Docker you probably want to set the jenkins_docker_expose_port variable to false so that the port is not exposed on the host, only to the reverse proxy.

License

MIT

Author Information

Made with love by Emmet O'Grady.

I am the founder of NimbleCI which builds Docker containers for feature branch workflow projects in Github.

I blog on my personal blog and about Docker related things on the NimbleCI blog.