November 21 2017

Ramping Up

The Jenkins Role-Based Authorization Strategy plugin is an excellent approach to normalize the Jenkins users into roles that can then be notified via pipeline input and emails to perform action in the release management workflow.  

This blog focuses on the pipeline script that can be implemented once the plugin is configured for use which consists of the following steps.

1. The "Role-based Authorization Strategy" plugin must first be installed in the "Managed Jenkins...Manage Plugins" dialog. 

2. Then the role-based strategy is selected in the "Manage Jenkins...Configure Global Security" dialog and in the Authorization section select "Role-Based Strategy".     

Creating the Roles

Once the plugin is enabled you can go to "Manage Jenkins...Manage and Assign Roles" to create roles and assign users to them.  There are global and project roles to be defined.

1. The global roles will be created first.  (The "admin" role is already seeded by Jenkins to start with.) 

2. Create a "person" role which very simply provides access to read jobs in the Jenkins dashboard.  All users added to Jenkins should have the "person" role enabled.  

3. Add "test" and "prod" deployer roles with no permissions as the users in these roles will be queried during the pipeline execution to send notifications when builds need to be approved for deployment to test or prod environments.

Creating Project Roles

1. The project roles listed below provide a way to filter the Jenkins job in such a way (using a standard suffix referenced in the "Pattern" field of the project role) that enables developers or test-admins to run build or cancel on appropriate jobs.

2. The Pattern field uses a regex expression of ".*-CI" to pick up job names that end with "-CI" suffix and test-admin to do the same for any job with "-CR" (e.g. Create Release) or "-DR" (Deploy Release) suffices.

Assigning Users to Roles

1. In the Global role assignments add the "authenticated" group so anyone with credentials will be able to at least view the jobs.  There are two persons assigned to the "deployer" roles which is a Pipeline only assignment.  Two different users are assigned to these global roles.

2. The Item Roles are really "Project" roles and here one user is assigned to the developer role so that user will have ability to submit and cancel any CI job. 

3. The use of the "authenticated" group allows anyone given credentials to Jenkins to be able to see the jobs in the dashboard (with no permissions to do anything else).    

In the example below, Adam will be able to see all of the jobs and will be notified by the release management pipeline when there is a production deployment to be done.  Greg can deploy to both TEST and PROD.  Kevin is a developer and can submit or cancel any Continuous Integration build job.  

Using the Roles

At various stages in the deployment pipeline there are notifications made to various types of approvers.  After the component is packaged and ready for deployment to TEST, for example, there is a manual approval stage in the pipeline requiring approval before deploying the package to TEST.  

    stage("Deploy to TEST?") {
        timeout(time: deployToTestTimeout, unit: 'DAYS') {
          notifyAwaitApproval approvers: getApprovers(testApproverRole),
                              message: "Press OK to initiate TEST deployment?",                        
                              emailPrompt: "Build ${currentBuild.description} is ready to deploy to TEST."
        }
    }

The Jenkinsfile above references a Global Variable named "notifyAwaitApproval", shown on line 3 above that will be perform the notification and accept an input from someone in the role to proceed or abort.  That variable "method" is passed a list of approvers (in this case, approvers of type "testApproverRole") , executed by the "getApprovers" variable. First let's cover the notifyAwaitApproval and then approvers.variables. 

The notifyAwaitApproval variable takes one argument, a hashmap of the approvers where the keys are the usernames in the Jenkins users database is the typical case.  An HTML formatted email is sent to the approvers (now comma-delimited). 

def call(options) {
  def rawApprovers = options.approvers
  def csvApproverUsernames = {
    switch(rawApprovers) {
      case String:
        // already csv
        return rawApprovers
      case Map:
        // keys are usernames and values are names
        return rawApprovers.keySet().join(',')
      case ArrayList:
        return rawApprovers.join(',')
      default:
        throw new Exception("Unexpeced approver type ${rawApprovers.class}!")
    }
  }()
  
  def jobName = friendlyJobName()
	node {
      // emailext needs to be inside a node block but don't want to take up a node while waiting for approval
      emailext body: "Build: <b>${jobName}</b><br>Build Number: <b>${env.BUILD_NUMBER}</b><br><br>Action is required to ${options.emailPrompt}  at:<br><b> ${env.JOB_URL}</b>", 
		to: csvApproverUsernames, subject: "Action Required For Build ${jobName} (#${env.BUILD_NUMBER})"
  }
  
  milestone()
  input message: options.message,
        submitter: csvApproverUsernames
  milestone()
}

The approvers variable shown below uses functionality provided within the "RoleBasedAuthorizationStrategy" plugin.  It takes an input "role" name argument (e.g. values of "test-deployer" or "prod-deployer" that are global roles covered in the Global Role creation section).  The method retrieves the role maps for the desired role and iterates through them, using the user id (sid) as the key to the hashmap key and the user full name as the hashmap value (not used in the notifyAwaitApproval variable example).

import com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy

@NonCPS
def call(role) {
    echo "Retrieving users for ${role}..."
    def users = [:]
    def authStrategy = Jenkins.instance.getAuthorizationStrategy()
    if(authStrategy instanceof RoleBasedAuthorizationStrategy){
      def sids = authStrategy.roleMaps.globalRoles.getSidsForRole(role)
      sids.each { sid ->
             users[sid] = Jenkins.instance.getUser(sid).fullName
      }
      return users
    } else {
		throw new Exception("Role Strategy Plugin not in use.  Please enable to retrieve users for a role")
    }
}

Wrapping Up

The role definitions provided here are suggestions but cover the full deployment life cycle for many release management workflows implemented in Jenkins. More specific authorizations can be made via more explicit use of the project role pattern.   For this to work it is essential to establish job name standards then that allow this to be done.

Role definitions and naming standards need to be discussed and agreed to by the team.  Experience has shown that many granular roles are more difficult to administer so keep it simple (if you can).  The roles should not be pipeline specific. 

About the Author

Greg Hughlett

Greg has more than twenty-five years of experience in all phases of design, development, and implementation of software applications.  He has developed and architected BPM/SOA technologies for more than ten of those years from Fuego BPM to BEA AquaLogic BPM to Oracle BPM/SOA 11g.  He has worked for clients within banking, financial services, Life Insurance,  Health Care, public sector and telecommunications industries.  His areas of expertise include Oracle SOA and Oracle BPM (formerly AquaLo

Join the Conversation

Enter your first name. It will only be used to display with your comment.
Enter your email. This will be used to validate you as a real user but will NOT be displayed with the comment.
By submitting this form, you accept the Mollom privacy policy.