Only allow Bitbucket users of a specific group to approve a job
Any user in the organization can approve a type: approval
job, however it is possible to control the execution of any downstream job that requires
the type: approval
job.
To do so, we generally recommend using restricted context, with the method outlined in our documentation. However, due to a limitation of the Bitbucket API, it is not possible to restrict CircleCI contexts for Bitbucket projects.
This workaround will allow you to specify a Bitbucket group of users for whom a workflow can continue after they approved a type: approval
job.
How it works
- Retrieves the ID, then the full name of the user who approved the job
- Queries the Bitbucket API endpoint
/1.0/groups
to check if that user is a member of the specified group- If the user is a member of the group, the workflow continues.
- If the user isn’t a member, the whole workflow is cancelled before any other job can be executed.
Requirements
- To make calls to the Bitbucket API you will need to create an App password for an Admin user of the related Bitbucket workspace.
- To successfully send authenticated requests to CircleCI API v2, you must use a personal API token (project tokens are currently not supported on API v2)
Step 1 – Add all required environment variables to your project settings
Go to your project settings and add the following environment variables:
Variable Name | Description |
---|---|
CCI_Token | This will be a personal API token. |
BB_admin_username | A user with admin permissions on the Bitbucket workspace. |
BB_admin_App_Password | An App password for the above user |
APPROVERS_GRP | The Bitbucket group containing users who are allowed to approve the job |
Â
Note:
- The user for which the CircleCI personal API token is generated doesn’t have to (but can) be the same user as
BB_admin_username
. - If you prefer not to use a CircleCI personal API token or a Bitbucket App password generated for your own account (or any real user’s account), you can create a machine-user with admin access to the Bitbucket workspace, and generate both the CircleCI personal API token the Bitbucket App password for that machine-user
Step 2 – Add the check_approve.sh
script to the project repository
Create a check_approve.sh
file at the root of your repository with the below content:
Note that you can create the file anywhere in your repository. However, if you don’t add it to the root of your repository you’ll need to specify the path to the script when referencing it in your config.yml
(see Step 3)
#!/bin/bash
### Retrieve the CircleCI user ID of the job approver
WHO_APPROVED_ID=$(curl -X GET https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}/job -H "Circle-Token: ${CCI_Token}" | jq -r '.items[]|select(.type == "approval")|.approved_by')
### Use the ID to get the job approver's full name
WHO_APPROVED_FULL_NAME=$(curl -X GET https://circleci.com/api/v2/user/${WHO_APPROVED_ID} -H "Circle-Token: ${CCI_Token}" | jq -r .name)
ACCESS_GRANTED=0
### Check if the job approver is in the Bitbucket group
GRP_MBRS=$(curl -u ${BB_admin_username}:${BB_admin_App_Password} -G "https://api.bitbucket.org/1.0/groups/${CIRCLE_PROJECT_USERNAME}/${APPROVERS_GRP}/members"|jq -r '.[].display_name')
IFS=$'\n'
for OUTPUT in $GRP_MBRS; do
if [[ $OUTPUT == "${WHO_APPROVED_FULL_NAME}" ]]; then
ACCESS_GRANTED=1
fi
done
### If the job approver IS NOT in the Bitbucket group, cancel the whole workflow.
if [[ $ACCESS_GRANTED -eq 0 ]]; then
echo "You are not authorized to approve this job. Cancelling workflow..."
curl -X POST https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}/cancel -H "Circle-Token: ${CCI_Token}"
### If the job approver IS in the Bitbucket group, continue the workflow execution.
else
echo "You are authorized to approve this job. Continuing workflow..."
fi
### Allow sufficient time to complete the cancel operation (if applicable)
sleep 3
Step 3 – Include check_approve.sh
in your project’s config.yml
You can then use the check_approve.sh
script as in the below example:
version: 2.1
jobs:
first:
docker:
- image: circleci/node
steps:
- run:
command: |
echo job1
second:
docker:
- image: circleci/node
steps:
- run:
command: |
echo job2
approval-access-check:
docker:
- image: circleci/node
steps:
- checkout
- run:
command: |
chmod +x ./check_approve.sh
./check_approve.sh
workflows:
version: 2
papa:
jobs:
- first
- wait-for-approval:
type: approval
requires:
- first
- approval-access-check:
requires:
- wait-for-approval
- second:
requires:
- approval-access-check
Â
Alternatively, you could leverage the pre-steps
functionality instead of using an additional script:
version: 2.1
jobs:
first:
docker:
- image: circleci/node
steps:
- run:
command: |
echo job1
second:
docker:
- image: circleci/node
steps:
- run:
command: |
echo job2
workflows:
version: 2
example:
jobs:
- first
- wait-for-approval:
type: approval
requires:
- first
- second:
pre-steps: # steps to run before steps defined in the job bar
- run:
command: |
WHO_APPROVED_ID=$(curl -X GET https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}/job -H "Circle-Token: ${CCI_Token}" | jq -r '.items[]|select(.type == "approval")|.approved_by')
WHO_APPROVED_FULL_NAME=$(curl -X GET https://circleci.com/api/v2/user/${WHO_APPROVED_ID} -H "Circle-Token: ${CCI_Token}" | jq -r .name)
ACCESS_GRANTED=false
GRP_MBRS=$(curl -s -u ${BB_admin_username}:${BB_admin_App_Password} --request GET "https://api.bitbucket.org/1.0/groups/truckman3/${APPROVERS_GRP}/members"|jq -r '.[].display_name')
IFS=$'\n'
for OUTPUT in $GRP_MBRS; do
if [[ $OUTPUT == "${WHO_APPROVED_FULL_NAME}" ]]; then
ACCESS_GRANTED=1
fi
done
if [[ $ACCESS_GRANTED -eq 0 ]]; then
echo "You are not authorized to approve this job. Cancelling workflow..."
curl -X POST https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}/cancel -H "Circle-Token: ${CCI_Token}"
else
echo "You are authorized to approve this job. Continuing workflow..."
fi
sleep 3
requires:
- wait-for-approval
Caveats
- The purpose of this workaround is to prevent “accidental” job approvals; it does not provide any security functionality.
- From a security point of view, this method is not a substitute for restricted contexts; Any user with sufficient access to rerun the build with SSH would be able to print out the value of all the above environment variables.
Resources
Please consider adding your vote to this open feature request regarding context security for Bitbucket projects.