Skip to content

Commit

Permalink
Add security considerations for Attribute Conditions (#393)
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo authored Feb 7, 2024
1 parent ec485ac commit ee1c1b6
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 32 deletions.
101 changes: 69 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,8 @@ These instructions use the [gcloud][gcloud] command-line tool.
1. Create a Workload Identity Pool:

```sh
# TODO: replace ${PROJECT_ID} with your value below.
gcloud iam workload-identity-pools create "github" \
--project="${PROJECT_ID}" \
--location="global" \
Expand All @@ -354,6 +356,8 @@ These instructions use the [gcloud][gcloud] command-line tool.
1. Get the full ID of the Workload Identity **Pool**:

```sh
# TODO: replace ${PROJECT_ID} with your value below.
gcloud iam workload-identity-pools describe "github" \
--project="${PROJECT_ID}" \
--location="global" \
Expand All @@ -368,29 +372,35 @@ These instructions use the [gcloud][gcloud] command-line tool.

1. Create a Workload Identity **Provider** in that pool:

**🛑 CAUTION!** Always add an Attribute Condition to restrict entry into the
Workload Identity Pool. You can further restrict access in IAM Bindings, but
always add a basic condition that restricts admission into the pool. A good
default option is to restrict admission based on your GitHub organization as
demonstrated below. Please see the [security
considerations][security-considerations] for more details.

```sh
# TODO: replace ${PROJECT_ID} and ${GITHUB_ORG} with your values below.
gcloud iam workload-identity-pools providers create-oidc "my-repo" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="github" \
--display-name="My GitHub repo Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
--attribute-condition="assertion.repository_owner == '${GITHUB_ORG}'" \
--issuer-uri="https://token.actions.githubusercontent.com"
```

The attribute mappings map claims in the GitHub Actions JWT to assertions
you can make about the request (like the repository or GitHub username of
the principal invoking the GitHub Action). These can be used to further
restrict the authentication using `--attribute-condition` flags.

> [!IMPORTANT]
>
> You must map any claims in the incoming token to attributes before you can
> assert on those attributes in a CEL expression or IAM policy!
> **❗️ IMPORTANT** You must map any claims in the incoming token to
> attributes before you can assert on those attributes in a CEL expression
> or IAM policy!

1. Extract the Workload Identity **Provider** resource name:

```sh
# TODO: replace ${PROJECT_ID} with your value below.
gcloud iam workload-identity-pools providers describe "my-repo" \
--project="${PROJECT_ID}" \
--location="global" \
Expand All @@ -408,12 +418,10 @@ These instructions use the [gcloud][gcloud] command-line tool.
workload_identity_provider: '...' # "projects/123456789/locations/global/workloadIdentityPools/github/providers/my-repo"
```

> [!IMPORTANT]
>
> The `project_id` input is optional, but may be required by downstream
> authentication systems such as the `gcloud` CLI. Unfortunately we cannot
> extract the project ID from the Workload Identity Provider, since it
> requires the project _number_.
> **❗️ IMPORTANT** The `project_id` input is optional, but may be required
> by downstream authentication systems such as the `gcloud` CLI.
> Unfortunately we cannot extract the project ID from the Workload Identity
> Provider, since it requires the project _number_.
>
> It is technically possible to convert a project _number_ into a project
> _ID_, but it requires permissions to call Cloud Resource Manager, and we
Expand All @@ -428,9 +436,14 @@ These instructions use the [gcloud][gcloud] command-line tool.
specific repository a secret in Google Secret Manager.

```sh
# TODO(developer): Update this value to your GitHub repository.
export REPO="username/name" # e.g. "google/chrome"
export WORKLOAD_IDENTITY_POOL_ID="value/from/above" # e.g. "projects/123456789/locations/global/workloadIdentityPools/github"
# TODO: replace ${PROJECT_ID}, ${WORKLOAD_IDENTITY_POOL_ID}, and ${REPO}
# with your values below.
#
# ${REPO} is the full repo name including the parent GitHub organization,
# such as "my-org/my-repo".
#
# ${WORKLOAD_IDENTITY_POOL_ID} is the full pool id, such as
# "projects/123456789/locations/global/workloadIdentityPools/github".
gcloud secrets add-iam-policy-binding "my-secret" \
--project="${PROJECT_ID}" \
Expand Down Expand Up @@ -464,13 +477,17 @@ These instructions use the [gcloud][gcloud] command-line tool.
Service Account, take note of the email address and skip this step.

```sh
# TODO: replace ${PROJECT_ID} with your value below.
gcloud iam service-accounts create "my-service-account" \
--project "${PROJECT_ID}"
```

1. Create a Workload Identity Pool:

```sh
# TODO: replace ${PROJECT_ID} with your value below.
gcloud iam workload-identity-pools create "github" \
--project="${PROJECT_ID}" \
--location="global" \
Expand All @@ -480,6 +497,8 @@ These instructions use the [gcloud][gcloud] command-line tool.
1. Get the full ID of the Workload Identity **Pool**:

```sh
# TODO: replace ${PROJECT_ID} with your value below.
gcloud iam workload-identity-pools describe "github" \
--project="${PROJECT_ID}" \
--location="global" \
Expand All @@ -494,33 +513,42 @@ These instructions use the [gcloud][gcloud] command-line tool.

1. Create a Workload Identity **Provider** in that pool:

**🛑 CAUTION!** Always add an Attribute Condition to restrict entry into the
Workload Identity Pool. You can further restrict access in IAM Bindings, but
always add a basic condition that restricts admission into the pool. A good
default option is to restrict admission based on your GitHub organization as
demonstrated below. Please see the [security
considerations][security-considerations] for more details.

```sh
# TODO: replace ${PROJECT_ID} and ${GITHUB_ORG} with your values below.
gcloud iam workload-identity-pools providers create-oidc "my-repo" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="github" \
--display-name="My GitHub repo Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
--attribute-condition="assertion.repository_owner == '${GITHUB_ORG}'" \
--issuer-uri="https://token.actions.githubusercontent.com"
```

The attribute mappings map claims in the GitHub Actions JWT to assertions
you can make about the request (like the repository or GitHub username of
the principal invoking the GitHub Action). These can be used to further
restrict the authentication using `--attribute-condition` flags.

> [!IMPORTANT]
>
> You must map any claims in the incoming token to attributes before you can
> assert on those attributes in a CEL expression or IAM policy!**
> **❗️ IMPORTANT** You must map any claims in the incoming token to
> attributes before you can assert on those attributes in a CEL expression
> or IAM policy!

1. Allow authentications from the Workload Identity Pool to your Google Cloud
Service Account.

```sh
# TODO(developer): Update this value to your GitHub repository.
export REPO="username/name" # e.g. "google/chrome"
export WORKLOAD_IDENTITY_POOL_ID="value/from/above" # e.g. "projects/123456789/locations/global/workloadIdentityPools/github"
# TODO: replace ${PROJECT_ID}, ${WORKLOAD_IDENTITY_POOL_ID}, and ${REPO}
# with your values below.
#
# ${REPO} is the full repo name including the parent GitHub organization,
# such as "my-org/my-repo".
#
# ${WORKLOAD_IDENTITY_POOL_ID} is the full pool id, such as
# "projects/123456789/locations/global/workloadIdentityPools/github".
gcloud iam service-accounts add-iam-policy-binding "my-service-account@${PROJECT_ID}.iam.gserviceaccount.com" \
--project="${PROJECT_ID}" \
Expand All @@ -535,6 +563,8 @@ These instructions use the [gcloud][gcloud] command-line tool.
1. Extract the Workload Identity **Provider** resource name:

```sh
# TODO: replace ${PROJECT_ID} with your value below.
gcloud iam workload-identity-pools providers describe "my-repo" \
--project="${PROJECT_ID}" \
--location="global" \
Expand All @@ -557,6 +587,8 @@ These instructions use the [gcloud][gcloud] command-line tool.
shows granting access to a secret in Google Secret Manager.

```sh
# TODO: replace ${PROJECT_ID} with your value below.
gcloud secrets add-iam-policy-binding "my-secret" \
--project="${PROJECT_ID}" \
--role="roles/secretmanager.secretAccessor" \
Expand Down Expand Up @@ -591,13 +623,17 @@ These instructions use the [gcloud][gcloud] command-line tool.
Service Account, take note of the email address and skip this step.

```sh
# TODO: replace ${PROJECT_ID} with your value below.
gcloud iam service-accounts create "my-service-account" \
--project "${PROJECT_ID}"
```

1. Create a Service Account Key JSON for the Service Account.

```sh
# TODO: replace ${PROJECT_ID} with your value below.
gcloud iam service-accounts keys create "key.json" \
--iam-account "my-service-account@${PROJECT_ID}.iam.gserviceaccount.com"
```
Expand All @@ -621,3 +657,4 @@ These instructions use the [gcloud][gcloud] command-line tool.
[github-perms]: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#permissions
[map-external]: https://cloud.google.com/iam/docs/access-resources-oidc#impersonate
[wif]: https://cloud.google.com/iam/docs/workload-identity-federation
[security-considerations]: docs/SECURITY_CONSIDERATIONS.md
47 changes: 47 additions & 0 deletions docs/SECURITY_CONSIDERATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Security Considerations

There are important risks to consider when mapping GitHub Actions OIDC token
claims.


## Use Unique Mapping Values

Many of the claims embedded in the GitHub Actions OIDC token are not guaranteed
to be unique, and tokens issued by other GitHub organizations or repositories
may contain the same values, allowing them to establish an identity. To protect
against this situation, always use an Attribute Condition to restrict access to
tokens issued by your GitHub organization.

```cel
assertion.repository_owner == 'my-github-org'
```

Never use a "*" in an IAM Binding unless you absolutely know what you are doing!


## Use GitHub's Numeric, Immutable Values

Using "name" fields in Attribute Conditions or IAM Bindings like `repository` and `repository_owner` increase the chances of [cybersquatting][] and [typosquatting][] attacks. If you delete your GitHub repository or GitHub organization, someone could claim that same name and establish an identity. To protect against this situation, use the numeric `*_id` fields instead, which GitHub guarantees to be unique and never re-used.

To get your numeric organization ID:

```sh
ORG="my-org" # TODO: replace with your org
curl -sfL -H "Accept: application/json" "https://api.github.com/orgs/${ORG}" | jq .id
```

To get your numeric repository ID:

```sh
REPO="my-org/my-repo" # TODO: replace with your full repo including the org
curl -sfL -H "Accept: application/json" "https://api.github.com/repos/${REPO}" | jq .id
```

These can be used in an Attribute Condition:

```cel
assertion.repository_owner_id == 1342004 && assertion.repository_id == 260064828
```

[cybersquatting]: https://en.wikipedia.org/wiki/Cybersquatting
[typosquatting]: https://en.wikipedia.org/wiki/Typosquatting

0 comments on commit ee1c1b6

Please sign in to comment.