|
| 1 | +# Repo and Project Permissions |
| 2 | + |
| 3 | +Sometimes it may be necessary to limit who can run which commands, such as |
| 4 | +restricting who can apply changes to production, while allowing more |
| 5 | +freedom for dev and test environments. |
| 6 | + |
| 7 | +## Authorization Workflow |
| 8 | + |
| 9 | +Atlantis performs two authorization checks to verify a user has the necessary |
| 10 | +permissions to run a command: |
| 11 | + |
| 12 | +1. After a command has been validated, before var files, repo metadata, or |
| 13 | + pull request statuses are checked and validated. |
| 14 | +2. After pre workflow hooks have run, repo configuration processed, and |
| 15 | + affected projects determined. |
| 16 | + |
| 17 | +::: tip Note |
| 18 | +The first check should be considered as validating the user for a repository |
| 19 | +as a whole, while the second check is for validating a user for a specific |
| 20 | +project in that repo. |
| 21 | +::: |
| 22 | + |
| 23 | +### Why check permissions twice? |
| 24 | +The way Atlantis is currently designed, not all relevant information may be |
| 25 | +available when the first check happens. In particular, affected projects |
| 26 | +are not known because pre workflow hooks haven't run yet, so repositories |
| 27 | +that use hooks to generate or modify repo configurations won't know which |
| 28 | +projects to check permissions for. |
| 29 | + |
| 30 | +## Configuring permissions |
| 31 | + |
| 32 | +Atlantis has two options for allowing instance administrators to configure |
| 33 | +permissions. |
| 34 | + |
| 35 | +### Server option [`--gh-team-allowlist`](server-configuration.md#gh-team-allowlist) |
| 36 | + |
| 37 | +The `--gh-team-allowlist` option allows administrators to configure a global |
| 38 | +set of permissions that apply to all repositories. For most use cases, this |
| 39 | +should be sufficient. |
| 40 | + |
| 41 | +### External command |
| 42 | + |
| 43 | +For administrators that require more granular and specific permission |
| 44 | +definitions, an external command can be defined in the [server side repo |
| 45 | +configuration](server-side-repo-config.md#teamauthz). This command will receive |
| 46 | +information about the command, repo, project, and GitHub teams the user is a |
| 47 | +member of, allowing administrators to integrate the permissions validation |
| 48 | +with other systems or business requirements. An example would be allowing |
| 49 | +users to apply changes to lower environments like dev and test environments |
| 50 | +while restricting changes to production or other sensitive environments. |
| 51 | + |
| 52 | +::: warning |
| 53 | +These options are mutually exclusive. If an external command is defined, |
| 54 | +the `--gh-team-allowlist` option is ignored. |
| 55 | +::: |
| 56 | + |
| 57 | +## Example |
| 58 | + |
| 59 | +### Restrict production changes |
| 60 | +This example shows a simple example of how a script could be used to restrict |
| 61 | +production changes to a specific team, while allowing anyone to work on other |
| 62 | +environments. For brevity, this example assumes each user is a member of a |
| 63 | +single team. |
| 64 | + |
| 65 | +`server-side-repo-config.yaml` |
| 66 | +```yaml |
| 67 | +team_authz: |
| 68 | + command: "/scripts/example.sh" |
| 69 | +``` |
| 70 | +
|
| 71 | +`example.sh` |
| 72 | +```shell |
| 73 | +#!/bin/bash |
| 74 | +
|
| 75 | +# Define name of team allowed to make production changes |
| 76 | +PROD_TEAM="example-org/prod-deployers" |
| 77 | +
|
| 78 | +# Set variables from command-line arguments for convenience |
| 79 | +COMMAND="$1" |
| 80 | +REPO="$2" |
| 81 | +TEAM="$3" |
| 82 | +
|
| 83 | +# Check if we are running the 'apply' command on prod |
| 84 | +if [ "${COMMAND}" == "apply" -a "${PROJECT_NAME}" == "prod" ] |
| 85 | +then |
| 86 | + # Only the prod team can make this change |
| 87 | + if [ "${TEAM}" == "${PROD_TEAM}" ] |
| 88 | + then |
| 89 | + echo "pass" |
| 90 | + exit 0 |
| 91 | + fi |
| 92 | + |
| 93 | + # Print reason for failing and exit |
| 94 | + echo "user \"${USER_NAME}\" must be a member of \"${PROD_TEAM}\" to apply changes to production." |
| 95 | + exit 0 |
| 96 | +fi |
| 97 | +
|
| 98 | +# Any other command and environment is okay |
| 99 | +echo "pass" |
| 100 | +exit 0 |
| 101 | +``` |
| 102 | +## Reference |
| 103 | + |
| 104 | +### External Command Execution |
| 105 | + |
| 106 | +External commands are executed on every authorization check with arguments and |
| 107 | +environment variables containing context about the command being checked. The |
| 108 | +command is executed using the following format: |
| 109 | + |
| 110 | +```shell |
| 111 | +external_command [external_args...] atlantis_command repo [teams...] |
| 112 | +``` |
| 113 | + |
| 114 | +| Key | Optional | Description | |
| 115 | +|--------------------|----------|-------------------------------------------------------------------------------------------| |
| 116 | +| `external_command` | no | Command defined in [server side repo configuration](server-side-repo-config.md) | |
| 117 | +| `external_args` | yes | Command arguments defined in [server side repo configuration](server-side-repo-config.md) | |
| 118 | +| `atlantis_command` | no | The atlantis command being run (`plan`, `apply`, etc) | |
| 119 | +| `repo` | no | The full name of the repo being executed (format: `owner/repo_name`) | |
| 120 | +| `teams` | yes | A list of zero or more teams of the user executing the command | |
| 121 | + |
| 122 | + |
| 123 | +The following environment variables are passed to the command on every execution: |
| 124 | + |
| 125 | +| Key | Description | |
| 126 | +|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
| 127 | +| `BASE_REPO_NAME` | Name of the repository that the pull request will be merged into, ex. `atlantis`. | |
| 128 | +| `BASE_REPO_OWNER` | Owner of the repository that the pull request will be merged into, ex. `runatlantis`. | |
| 129 | +| `COMMAND_NAME` | The name of the command that is being executed, i.e. `plan`, `apply` etc. | |
| 130 | +| `USER_NAME` | Username of the VCS user running command, ex. `acme-user`. During an autoplan, the user will be the Atlantis API user, ex. `atlantis`. | |
| 131 | + |
| 132 | + |
| 133 | +The following environment variables are also passed to the command when checking project authorization: |
| 134 | + |
| 135 | +| Key | Description | |
| 136 | +|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
| 137 | +| `BASE_BRANCH_NAME` | Name of the base branch of the pull request (the branch that the pull request is getting merged into) | |
| 138 | +| `COMMENT_ARGS` | Any additional flags passed in the comment on the pull request. Flags are separated by commas and every character is escaped, ex. `atlantis plan -- arg1 arg2` will result in `COMMENT_ARGS=\a\r\g\1,\a\r\g\2`. | |
| 139 | +| `HEAD_REPO_NAME` | Name of the repository that is getting merged into the base repository, ex. `atlantis`. | |
| 140 | +| `HEAD_REPO_OWNER` | Owner of the repository that is getting merged into the base repository, ex. `acme-corp`. | |
| 141 | +| `HEAD_BRANCH_NAME` | Name of the head branch of the pull request (the branch that is getting merged into the base) | |
| 142 | +| `HEAD_COMMIT` | The sha256 that points to the head of the branch that is being pull requested into the base. If the pull request is from Bitbucket Cloud the string will only be 12 characters long because Bitbucket Cloud truncates its commit IDs. | |
| 143 | +| `PROJECT_NAME` | Name of the project the command is being executed on | |
| 144 | +| `PULL_NUM` | Pull request number or ID, ex. `2`. | |
| 145 | +| `PULL_URL` | Pull request URL, ex. `https://github.com/runatlantis/atlantis/pull/2`. | |
| 146 | +| `PULL_AUTHOR` | Username of the pull request author, ex. `acme-user`. | |
| 147 | +| `REPO_ROOT` | The absolute path to the root of the cloned repository. | |
| 148 | +| `REPO_REL_PATH` | Path to the project relative to `REPO_ROOT` | |
| 149 | + |
| 150 | +### External Command Result Handling |
| 151 | + |
| 152 | +Atlantis determines if a user is authorized to run the requested command by |
| 153 | +checking if the external command exited with code `0` and if the last line |
| 154 | +of output is `pass`. |
| 155 | + |
| 156 | +``` |
| 157 | +# Psuedo-code of Atlantis evaluation of external commands |
| 158 | + |
| 159 | +user_authorized = |
| 160 | + external_command.exit_code == 0 |
| 161 | + && external_command.output.last_line == 'pass' |
| 162 | +``` |
| 163 | +
|
| 164 | +::: tip |
| 165 | +* A non-zero exit code means the command failed to evaluate the request for |
| 166 | +some reason (bad configuration, missing dependencies, solar flares, etc). |
| 167 | +* If the command was able to run successfully, but determined the user is not |
| 168 | +authorized, it should still exit with code `0`. |
| 169 | + * The command output could contain the reasoning for the authorization failure. |
| 170 | +::: |
0 commit comments