-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from buildkite-plugins/initial
Google Cloud Workload Identity Federation Buildkite Plugin
- Loading branch information
Showing
8 changed files
with
290 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
steps: | ||
- label: ":shell: Tests" | ||
plugins: | ||
- plugin-tester#v1.0.0: ~ | ||
|
||
- label: ":sparkles: Lint" | ||
plugins: | ||
- plugin-linter#v3.1.0: | ||
id: gcp-workload-identity-federation | ||
|
||
- label: ":shell: Shellcheck" | ||
plugins: | ||
- shellcheck#v1.3.0: | ||
files: hooks/** |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,91 @@ | ||
# gcp-workload-identity-federation-buildkite-plugin | ||
# Google Cloud Workload Identity Federation Buildkite Plugin | ||
|
||
A Buildkite plugin to assume a Google Cloud service account using [workload identity federation](https://cloud.google.com/iam/docs/workload-identity-federation). | ||
|
||
The plugin requests an OIDC token from Buildkite and uses it to a populate Google Cloud credentials file. | ||
|
||
The path to the file is populated in `GOOGLE_APPLICATION_CREDENTIALS` for SDKs that use [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials), and in `CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE` for the `gcloud` CLI. | ||
|
||
## Google Cloud configuration | ||
|
||
You should already have a Google Cloud project and a Service Account to assume. See [Google's documentation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers) for more detailed instructions for these steps. | ||
|
||
1. Create a [Workload Identity Pool](https://console.cloud.google.com/iam-admin/workload-identity-pools). | ||
|
||
We recommend creating a different pool for each security boundary. | ||
|
||
In this example we're using `buildkite-example-pipeline`. | ||
|
||
2. Add a provider to the pool. | ||
|
||
Use OpenID Connect, and give it a name like `buildkite`. | ||
|
||
Use `https://agent.buildkite.com` as the Issuer. | ||
|
||
Copy the value of the default audience or provide your own. | ||
|
||
3. Configure provider attributes. | ||
|
||
Because Google limits the length of attributes to 127 characters, we suggest the following mapping: | ||
|
||
| Google | OIDC | | ||
| --- | --- | | ||
| `google.subject` | `"organization:" + assertion.sub.split(":")[1] + ":pipeline:" + assertion.sub.split(":")[3]` | | ||
| `attribute.pipeline_slug` | `assertion.pipeline_slug` | | ||
| `attribute.build_branch` | `assertion.build_branch` | | ||
|
||
With this mapping you can use a [CEL](https://github.com/google/cel-spec) expression to restrict which pipelines can assume the service account: | ||
|
||
```cel | ||
google.subject == "organization:acme:pipeline:buildkite-example-pipeline" | ||
``` | ||
|
||
4. Grant access to the service account. | ||
|
||
5. Configure this plugin using the workload provider audience without the leading `https:`, and the service account email address. | ||
|
||
## Example | ||
|
||
Add the following to your `pipeline.yml`: | ||
|
||
```yml | ||
steps: | ||
- command: | | ||
echo "Credentials are located at \$GOOGLE_APPLICATION_CREDENTIALS" | ||
plugins: | ||
- gcp-workload-identity-federation#v1.0.0: | ||
audience: "//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/buildkite-example-pipeline/providers/buildkite" | ||
service-account: "[email protected]" | ||
``` | ||
## Configuration | ||
### `audience` (Required, string) | ||
|
||
- The default audience as shown on the Workload Identity Federation Provider page, without the `https:` prefix, or a custom audience that you configure. | ||
|
||
### `service-account` (Required, string) | ||
|
||
- The service account for which you want to acquire an access token. | ||
|
||
## Developing | ||
|
||
To run testing, shellchecks and plugin linting use use `bk run` with the [Buildkite CLI](https://github.com/buildkite/cli). | ||
|
||
```bash | ||
bk run | ||
``` | ||
|
||
Or if you want to run just the tests, you can use the docker [Plugin Tester](https://github.com/buildkite-plugins/buildkite-plugin-tester): | ||
|
||
```bash | ||
docker run --rm -ti -v "${PWD}":/plugin buildkite/plugin-tester:latest | ||
``` | ||
|
||
## Contributing | ||
|
||
1. Fork the repo | ||
2. Make the changes | ||
3. Run the tests | ||
4. Commit and push your changes | ||
5. Send a pull request |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
#!/bin/bash | ||
|
||
set -euo pipefail | ||
|
||
if [[ -z "${BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_AUDIENCE:-}" ]]; then | ||
echo "🚨 Missing 'audience' plugin configuration" | ||
exit 1 | ||
fi | ||
|
||
if [[ -z "${BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_SERVICE_ACCOUNT:-}" ]]; then | ||
echo "🚨 Missing 'service-account' plugin configuration" | ||
exit 1 | ||
fi | ||
|
||
# Create a temporary directory with both BSD and GNU mktemp | ||
TMPDIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'buildkiteXXXX') | ||
|
||
echo "~~~ :buildkite: Requesting OIDC token from Buildkite" | ||
|
||
buildkite-agent oidc request-token --audience "$BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_AUDIENCE" > "$TMPDIR"/token.json | ||
|
||
echo "~~~ :gcloud: Configuring Google Cloud credentials" | ||
|
||
cat << JSON > "$TMPDIR"/credentials.json | ||
{ | ||
"type": "external_account", | ||
"audience": "$BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_AUDIENCE", | ||
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt", | ||
"token_url": "https://sts.googleapis.com/v1/token", | ||
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/$BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_SERVICE_ACCOUNT:generateAccessToken", | ||
"credential_source": { | ||
"file": "$TMPDIR/token.json" | ||
} | ||
} | ||
JSON | ||
|
||
export BUILDKITE_OIDC_TMPDIR=$TMPDIR | ||
export GOOGLE_APPLICATION_CREDENTIALS=$TMPDIR/credentials.json | ||
export CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=$GOOGLE_APPLICATION_CREDENTIALS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#!/bin/bash | ||
|
||
set -euo pipefail | ||
|
||
if [[ -v BUILDKITE_OIDC_TMPDIR ]]; then | ||
rm -rf "$BUILDKITE_OIDC_TMPDIR" | ||
|
||
echo "Removed credentials from $BUILDKITE_OIDC_TMPDIR" | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
name: gcp-workload-identity-federation | ||
description: Grant pipelines access to Google Cloud resources using Workload Identity Federation | ||
author: https://github.com/buildkite-plugins | ||
public: true | ||
requirements: | ||
- bash | ||
- buildkite-agent | ||
configuration: | ||
properties: | ||
audience: | ||
type: string | ||
service-account: | ||
type: string | ||
required: | ||
- audience | ||
- service-account | ||
additionalProperties: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"extends": [ | ||
"config:base", | ||
":disableDependencyDashboard" | ||
], | ||
"docker-compose": { | ||
"digest": { | ||
"enabled": false | ||
} | ||
}, | ||
"labels": ["dependencies"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
#!/usr/bin/env bats | ||
|
||
# Uncomment to enable stub debug output: | ||
# export BUILDKITE_AGENT_STUB_DEBUG=/dev/tty | ||
# export MKTEMP_STUB_DEBUG=/dev/tty | ||
|
||
setup() { | ||
load "$BATS_PLUGIN_PATH/load.bash" | ||
} | ||
|
||
@test "fails when mktemp fails" { | ||
export BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_AUDIENCE="//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/buildkite-example-pipeline/providers/buildkite" | ||
export BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_SERVICE_ACCOUNT="[email protected]" | ||
|
||
stub mktemp "-d : exit 1" | ||
stub mktemp "-d -t 'buildkiteXXXX' : exit 1" | ||
|
||
run "$PWD/hooks/pre-command" | ||
|
||
assert_failure | ||
} | ||
|
||
@test "fails when audience is missing" { | ||
export BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_SERVICE_ACCOUNT="[email protected]" | ||
|
||
run "$PWD/hooks/pre-command" | ||
|
||
assert_failure | ||
} | ||
|
||
@test "fails when service account is missing" { | ||
export BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_AUDIENCE="//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/buildkite-example-pipeline/providers/buildkite" | ||
|
||
run "$PWD/hooks/pre-command" | ||
|
||
assert_failure | ||
} | ||
|
||
@test "succeeds when mktemp fails once" { | ||
export BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_AUDIENCE="//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/buildkite-example-pipeline/providers/buildkite" | ||
export BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_SERVICE_ACCOUNT="[email protected]" | ||
|
||
stub mktemp "-d : exit 1" | ||
stub mktemp "-d -t 'buildkiteXXXX' : echo $BATS_TEST_TMPDIR" | ||
stub buildkite-agent "echo dummy-jwt" | ||
|
||
run "$PWD/hooks/pre-command" | ||
|
||
assert_success | ||
|
||
unstub mktemp | ||
unstub buildkite-agent | ||
} | ||
|
||
@test "exports credentials" { | ||
export BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_AUDIENCE="//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/buildkite-example-pipeline/providers/buildkite" | ||
export BUILDKITE_PLUGIN_GCP_WORKLOAD_IDENTITY_FEDERATION_SERVICE_ACCOUNT="[email protected]" | ||
|
||
stub mktemp "-d : echo $BATS_TEST_TMPDIR" | ||
stub buildkite-agent "oidc request-token --audience //iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/buildkite-example-pipeline/providers/buildkite : echo dummy-jwt" | ||
|
||
run "$PWD/hooks/pre-command" | ||
|
||
assert_success | ||
|
||
assert_output --partial "Requesting OIDC token from Buildkite" | ||
assert_output --partial "Configuring Google Cloud credentials" | ||
|
||
diff $BATS_TEST_TMPDIR/token.json <(echo dummy-jwt) | ||
diff $BATS_TEST_TMPDIR/credentials.json <(cat << JSON | ||
{ | ||
"type": "external_account", | ||
"audience": "//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/buildkite-example-pipeline/providers/buildkite", | ||
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt", | ||
"token_url": "https://sts.googleapis.com/v1/token", | ||
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateAccessToken", | ||
"credential_source": { | ||
"file": "$BATS_TEST_TMPDIR/token.json" | ||
} | ||
} | ||
JSON) | ||
unstub mktemp | ||
unstub buildkite-agent | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/usr/bin/env bats | ||
|
||
setup() { | ||
load "$BATS_PLUGIN_PATH/load.bash" | ||
} | ||
|
||
@test "removes tmp directory" { | ||
export BUILDKITE_OIDC_TMPDIR=$BATS_TEST_TMPDIR | ||
|
||
run "$PWD/hooks/pre-exit" | ||
|
||
assert_success | ||
|
||
assert_output --partial "Removed credentials from $BATS_TEST_TMPDIR" | ||
} | ||
|
||
@test "does nothing if the directory is not set" { | ||
run "$PWD/hooks/pre-exit" | ||
|
||
assert_success | ||
|
||
assert_output "" | ||
} |