jenkinsjenkins-pipelinejenkins-pluginsjenkins-groovyjenkins-docker

Best way to create and use new credential in Jenkins pipeline?


So I'm constructing a Jenkins pipeline to provision (Terraform), configure (Ansible) and deploy a set of "latest"-tagged containers from our container registry.

The first two stages are working perfectly. The host is provisioned and configured to run Docker containers. I have the CA cert, client cert and client key I need to create a DockerServerCredential, but am stuck on how to create that credential and use it in the pipeline. In other words, I don't want to end the pipeline after Ansible runs, manually add the credential to Jenkins, and then kick off another pipeline to deploy the containers using the new credential. I would like to use the CA, cert and key values to create a DockerServerCredential in my pipeline and then pass the name of that credential to docker.withServer().

So I know I COULD add this to my Jenkinsfile:

stage('Deploy') {
    steps {
        script {
            def credName = "Docker-Cert-${env.OUT_VM_NAME}"
            
            domain = com.cloudbees.plugins.credentials.domains.Domain.global()
            store = Jenkins.instance.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()

            dockerCertCred = new DockerServerCredentials(
                CredentialsScope.GLOBAL,
                credName,
                '',
                env.OUT_DCKR_CRED_KEY,
                env.OUT_DCKR_CRED_CERT,
                env.OUT_DCKR_CRED_CA
            )

            store.addCredentials(domain, dockerCertCred)
            
            docker.withServer("tcp://${env.OUT_VM_NAME}:2376", credName) {
                // things!
            }
        }
    }
}

but I'm aware the best practice doco says to avoid using Jenkins.getInstance in a pipeline. And it would require me to add "staticMethod com.cloudbees.plugins.credentials.domains.Domain global" and "staticMethod jenkins.model.Jenkins getInstance" to Script Approvals, which I'm rather :/// on.

So my question is: HOW WOULD YOU DO IT? What's the best practice here?

P.S. I don't even need to persist the credential, so it'd be super-ideal if there was a wrapper that created a temporary credential, but I haven't been able to find one.

P.P.S. Haven't created a plugin before and am hoping against hope I can avoid having to do that.


Solution

  • Ended up creating the credential via API calls in the Ansible playbook, which is where the keys and certificates are being created.

    Still using a crumb rather than API token to avoid the manual creation of the API token. Installed the Strict Crumb Issuer plugin to disable session ID check and set a 1-hour expiry on the crumbs.

    - name: Get Jenkins Crumb
      uri:
        url: "{{ jenkins_host }}/crumbIssuer/api/json"
        user: "{{ jenkins_user }}"
        password: "{{ jenkins_password }}"
        force_basic_auth: yes
        return_content: yes
      tags:
        - always
      register: crumb
      when: add_credential | bool
    - name: Fix newlines
      set_fact:
        ca_cert_value: "{{ ca_csr_content['content'] | b64decode | replace('\n', '\\n') | replace('+', '%2B') }}"
        client_cert_value: "{{ client_cert_content['content'] | b64decode | replace('\n', '\\n') | replace('+', '%2B') }}"
        client_key_value: "{{ client_key_content['content'] | b64decode | replace('\n', '\\n') | replace('+', '%2B') }}"
    - name: Add Jenkins Credential
      uri:
        method: POST
        url: "{{ jenkins_host }}/credentials/store/system/domain/_/createCredentials"
        user: "{{ jenkins_user }}"
        password: "{{ jenkins_password }}"
        force_basic_auth: yes
        body_format: form-urlencoded
        follow_redirects: all
        headers:
          Jenkins-Crumb: "{{ crumb.json | json_query('crumb') }}"
        body: |
          json={
              "": "0",
              "credentials": {
                "scope": "GLOBAL",
                "id": "Docker-Cert-{{ ansible_hostname }}",
                "description": "",
                "clientKeySecret": "{{ client_key_value }}",
                "clientCertificate": "{{ client_cert_value }}",
                "serverCaCertificate": "{{ ca_cert_value }}",
                "$class": "org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials"
              }
            }
      when: add_credential | bool