From 25439964125db2c74d592a18eb9f820fab561013 Mon Sep 17 00:00:00 2001 From: Daniel Baumgarten Date: Wed, 25 Jun 2025 14:09:36 +0000 Subject: [PATCH 01/10] First draft of documentation for idtoken var source Signed-off-by: Daniel Baumgarten --- lit/docs/vars.lit | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/lit/docs/vars.lit b/lit/docs/vars.lit index 742198b1..ae968aaa 100644 --- a/lit/docs/vars.lit +++ b/lit/docs/vars.lit @@ -369,6 +369,8 @@ configured multiple times with different parameters \reference{aws-ssm-credential-manager}{\code{ssm}} }{ \reference{aws-asm-credential-manager}{\code{secretmanager}} (since v7.7.0) + }{ + \code{idtoken} (since v7.14.0) } In the future we want to make use of something like the \link{Prototypes @@ -524,6 +526,51 @@ configured multiple times with different parameters } } } + }{ + \schema-group{\code{idtoken} var source}{idtoken-var-source}{ + \required-attribute{type}{`idtoken`}{ + The \code{idtoken} type issues JWTs which are signed by concourse and contain information about the currently running pipeline/job. + + These JWTs can be used to authenticate with external services. + } + + \required-attribute{config}{idtoken_config}{ + \schema{idtoken_config}{ + \required-attribute{audience}{[string]}{ + A list of audience-values to place into the token's aud-claim. + } + \optional-attribute{subject_scope}{string}{ + Determines what is put into the token's sub-claim. + + \list{ + \code{team}: sub="" + }{ + \code{pipeline}: sub="/" + }{ + \code{instance}: sub="//" + }{ + \code{job}: sub="///" + } + + Note: If a path element is empty (for example because you chose \code{job} on a pipeline with no instance-vars), the empty element is still added. This is done to avoid colissions between subjects of different pipelines. + + Default \code{pipeline}. + } + \optional-attribute{expires_in}{duration}{ + How long the token should be valid. + + Default \code{1h}. Can be at max \code{24h}. + } + \optional-attribute{algorithm}{string}{ + The signature algorithm to use for the token. + + Supported values: \code{RS256}, \code{ES256}. + + Default \code{RS256}. + } + } + } + } } } } From 966c3e92c7d212df401ea0e09f8a1c0459797c6b Mon Sep 17 00:00:00 2001 From: Daniel Baumgarten Date: Fri, 27 Jun 2025 12:26:13 +0000 Subject: [PATCH 02/10] Detailed documentation for idtoken credential provider Signed-off-by: Daniel Baumgarten --- lit/docs/operation/creds.lit | 1 + lit/docs/operation/creds/idtoken.lit | 179 +++++++++++++++++++++++++++ lit/docs/vars.lit | 18 +-- 3 files changed, 184 insertions(+), 14 deletions(-) create mode 100644 lit/docs/operation/creds/idtoken.lit diff --git a/lit/docs/operation/creds.lit b/lit/docs/operation/creds.lit index 87fa98b7..b082ff21 100644 --- a/lit/docs/operation/creds.lit +++ b/lit/docs/operation/creds.lit @@ -46,6 +46,7 @@ backend you want to use. \include-section{creds/aws-secretsmanager.lit} \include-section{creds/kubernetes.lit} \include-section{creds/cyberark-conjur.lit} +\include-section{creds/idtoken.lit} \include-section{creds/caching.lit} \include-section{creds/redacting.lit} \include-section{creds/retry.lit} diff --git a/lit/docs/operation/creds/idtoken.lit b/lit/docs/operation/creds/idtoken.lit new file mode 100644 index 00000000..2f282952 --- /dev/null +++ b/lit/docs/operation/creds/idtoken.lit @@ -0,0 +1,179 @@ +\title{\aux{The }IDToken credential manager}{idtoken-credential-manager} + +\use-plugin{concourse-docs} +\omit-children-from-table-of-contents + +This idtoken credential manager is a bit special. It does not load any credentials from an external source, but instead generates \link{JWTs}{https://datatracker.ietf.org/doc/html/rfc7519} which are signed by concourse and contain information about the pipeline/job +that is currently running. It can NOT be used as a cluster-wide credential manager, but must instead be used as a \reference{var-sources}{var source}. + +These JWTs can be used to authenticate with external services via "identity federation" with the identity of the pipeline. + +Examples for services that support authentication via JWTs are: + +\list{ + \link{Vault}{https://vaultproject.io} +}{ + \link{AWS}{https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers.html} +}{ + \link{Azure}{https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0} +} + + +External Services can verify if JWTs are actually issued by your Concourse, by checking the signatures on the JWTs against the public keys published by your Concourse. + +The public keys for verification are published as \link{JWKS}{https://datatracker.ietf.org/doc/html/rfc7517} at: +\codeblock{bash}{{{ +https://your-concourse-server.com/.well-known/jwks.json +}}} + +Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/specs/openid-connect-discovery-1_0.html}, which allows external services to auto-discover the JWKS-URL. + +\section{ + \title{Usage} + + You create a var-source of type \code{idtoken} with the configuration you want (see \reference{idtoken-credential-manager-config}{next chapter}) in your pipeline. + That var-source then exposes a single variable \code{token}, which contains the JWT and can be used in any step of your pipeline. + + You can also have multiple idtoken var-sources in the same pipeline, each with different audiences, lifetimes etc. + + \codeblock{bash}{{{ + var_sources: + - name: myidtoken + type: idtoken + config: + audience: ["sts.amazonaws.com"] + + jobs: + - name: print-creds + plan: + - task: print + config: + platform: linux + image_resource: + type: mock + source: {mirror_self: true} + run: + path: bash + args: + - -c + - | + echo myidtoken: ((myidtoken:token)) + }}} +} + +\section{ + \title{Configuration}{idtoken-credential-manager-config} + + You can pass several config options to the var-source to customize the generated JWTs. + You can for example configure the aud-claim, expiration or granularity of the sub-claim. + See \reference{idtoken-var-source}{here} for details. + + \section{ + \title{Subject Scope}{idtoken-subject-scope} + + Some external services (like for example AWS) only perform exact-matches on a token's sub-claim and ignore most other claims. + To enable use-cases like for example "all pipelines of a team should be able to assume an AWS-Role", Concourse offers the option to configure how fine-granular the sub-claim's value should be. + + This is configured via the \code{subject_scope} setting of the \reference{idtoken-var-source}{var-source}. + + Depending of the value of \code{subject_scope}, the content of the JWT's sub-claim will differ: + \list{ + \code{team}: sub="" + }{ + \code{pipeline}: sub="/" + }{ + \code{instance}: sub="//" + }{ + \code{job}: sub="///" + } + + \smaller{Note: If a path element is empty (for example because you chose \code{job} on a pipeline with no instance-vars), the empty element is still added.} + + This way all your pipelines can simply get a token with \code{subject_scope="team"} and use this token to assume an AWS-Role that matches on \code{sub="yourteamname"}. + + } + +} + +\section{ + \title{Example JWT} + + The generated tokens usually look something like this: + \code{ + eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3lvdXItY29uY291cnNlLmV4YW1wbGUuY29tIiwiZXhwIjoxNzUxMDE1NzM0LCJhdWQiOlsiYXBpOi8vQXp1cmVBRFRva2VuRXhjaGFuZ2UiXSwic3ViIjoibWFpbi9leGFtcGxlLXBpcGVsaW5lIiwidGVhbSI6Im1haW4iLCJwaXBlbGluZSI6ImV4YW1wbGUtcGlwZWxpbmUiLCJqb2IiOiJleGFtcGxlLWpvYiJ9.my7l44tH0wfz8vc6z3fMmzTMxZ8_orhjcsOti3BKSNo + } + + And after decoding like this: + \codeblock{bash}{{{ + { + "iss": "https://your-concourse.example.com", + "exp": 1751015734, + "aud": ["api://AzureADTokenExchange"], + "sub": "main/example-pipeline", + "team": "main", + "pipeline": "example-pipeline", + "instance_vars": { + "branch": "dev" + } + "job": "example-job" + } + }}} + + Here is a short explanation of the different claims: + + \list{ + \code{iss}: Who issued the token (always contains the external URL of your Concourse) + }{ + \code{exp}: When the token will expire + }{ + \code{aud}: Who the token is intended for. (In the above example it's for Azure's Identity Federation API) + }{ + \code{team}: The team of the pipeline this token was generated for + }{ + \code{pipeline}: The pipeline this token was generated for + }{ + \code{job}: The name of the job (inside the pipeline) this token was generated for + }{ + \code{instance_vars}: Any instance_vars for the pipeline (if it is an instanced pipeline). + }{ + \code{sub}: A combination of team + pipeline + instance_vars + job. Which parts are used here is configurable, see \reference{idtoken-subject-scope}{here}. + } +} + +\section{ + \title{Automatic Key Rotation}{idtoken-key-rotation} + + Concourse will automatically rotate the signing keys used for creating the JWTs. The default rotation period is \code{7 days}. The previously used keys are being kept around for a while (\code{24h}) + so that verification of currently existing JWTs doesn't fail. + + That behavior can be configured via the following ATC flags: + + \list{ + \code{signing-key.rotation-period}: How often to rotate the signing keys. Default: \code{7d}. 0 means don't rotate at all. + }{ + \code{signing-key.grace-period}: How long to keep previously used signing keys published in the JWKs after they have been superceeded. Default: \code{24h}. + }{ + \code{signing-key.check-interval}: How often to check if new keys are needed or if old ones should be removed. Default: \code{10m} + } + +} + +\right-side{Examples}{ + \example{Vault}{ + + TODO + + } + + \example{AWS}{ + + TODO + + } + + \example{Azure}{ + + TODO + + } +} diff --git a/lit/docs/vars.lit b/lit/docs/vars.lit index ae968aaa..8ebe9e2b 100644 --- a/lit/docs/vars.lit +++ b/lit/docs/vars.lit @@ -370,7 +370,7 @@ configured multiple times with different parameters }{ \reference{aws-asm-credential-manager}{\code{secretmanager}} (since v7.7.0) }{ - \code{idtoken} (since v7.14.0) + \reference{idtoken-credential-manager}{\code{idtoken}} (since v7.14.0) } In the future we want to make use of something like the \link{Prototypes @@ -540,19 +540,9 @@ configured multiple times with different parameters A list of audience-values to place into the token's aud-claim. } \optional-attribute{subject_scope}{string}{ - Determines what is put into the token's sub-claim. - - \list{ - \code{team}: sub="" - }{ - \code{pipeline}: sub="/" - }{ - \code{instance}: sub="//" - }{ - \code{job}: sub="///" - } - - Note: If a path element is empty (for example because you chose \code{job} on a pipeline with no instance-vars), the empty element is still added. This is done to avoid colissions between subjects of different pipelines. + Determines what is put into the token's sub-claim. See \reference{idtoken-subject-scope}{here} for e detailed explanation. + + Supported values: \code{team}, \code{pipeline}, \code{instance}, \code{job}. Default \code{pipeline}. } From 7cd43ef09e797cc152cb9392c82d1a94b1800caf Mon Sep 17 00:00:00 2001 From: Daniel Baumgarten Date: Fri, 27 Jun 2025 13:22:32 +0000 Subject: [PATCH 03/10] Add AWS example for idtoken credential provider Signed-off-by: Daniel Baumgarten --- lit/docs/operation/creds/idtoken.lit | 45 ++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/lit/docs/operation/creds/idtoken.lit b/lit/docs/operation/creds/idtoken.lit index 2f282952..fc7832a4 100644 --- a/lit/docs/operation/creds/idtoken.lit +++ b/lit/docs/operation/creds/idtoken.lit @@ -166,9 +166,48 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s } \example{AWS}{ - - TODO - + AWS supports \link{federation with external identity providers}{https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers.html}. + Using this you can allow identities managed by an external identity provider to perform actions in your AWS account. + + In this scenario the external identity provider is Concourse and the identities are teams/pipelines/jobs. + This means you are able to grant a specific pipeline or job permissions to perform actions in AWS (like deploying something). All without managing IAM-Users or dealing + with long-lived credentials. + + First you need to \link{create an OpenID Connect identity provider}{https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html} in your AWS Account. + As \code{Provider URL} specify the external URL of your Concourse server. + For \code{Audience} you can choose any string you like. Using something like \code{sts.amazonaws.com} is recommended. You have to use the same string later in the configuration of your var-source. + + Next you will need to \link{create an IAM-Role that can be assumed using your JWT}{https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html#idp_oidc_Create}. + As \code{Identity Provider} choose the one you created previously. + Add a condition on the sub-claim with type \code{StringEquals} and value \code{yourteam/yourpipeline}. This will allow ONLY that specific pipeline (and any instanced versions of it) to assume that IAM Role using a JWT. If you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this condition accordingly! + In the next step you will be able to choose which AWS permissions your role will get. + + Now you can use the AWS \link{AssumeRoleWithWebIdentity API operation}{https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html} to assume your role via a JWT issued by concourse. + The easiest way is to do this is via the \link{assume-role-with-web-identity AWS CLI command}{https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role-with-web-identity.html}: + \codeblock{bash}{{{ + var_sources: + - name: awstoken + type: idtoken + config: + audience: ["sts.amazonaws.com"] + + jobs: + - name: aws-deploy + plan: + - task: print + config: + platform: linux + image_resource: + type: registry-image + source: { repository: amazon/aws-cli } + run: + path: bash + args: + - -c + - | + aws sts assume-role-with-web-identity --role-arn arn:aws:iam:::role/ --web-identity-token ((awstoken:token)) > creds.json + echo "Now do something with the temporary credentials in creds.json" + }}} } \example{Azure}{ From de4087153bf46a6211172a1271500fb4e0c27f5f Mon Sep 17 00:00:00 2001 From: Daniel Baumgarten Date: Fri, 27 Jun 2025 13:49:52 +0000 Subject: [PATCH 04/10] Add Vault example for idtoken credential provider Signed-off-by: Daniel Baumgarten --- lit/docs/operation/creds/idtoken.lit | 81 +++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/lit/docs/operation/creds/idtoken.lit b/lit/docs/operation/creds/idtoken.lit index fc7832a4..24b83050 100644 --- a/lit/docs/operation/creds/idtoken.lit +++ b/lit/docs/operation/creds/idtoken.lit @@ -160,8 +160,85 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s \right-side{Examples}{ \example{Vault}{ + You can use JWTs to authenticate with \link{HashiCorp Vault}{https://developer.hashicorp.com/vault/docs/auth/jwt#jwt-authentication}. This way your pipelines can directly communicate with Vault and use all of it's features, beyond what Concourse's native vault-integration offers. - TODO + First enable the JWT auth method in your Vault Server: + \codeblock{bash}{{{ + vault auth enable jwt + }}} + + Now configure the JWT auth method to accept JWTs issued by your Concourse: + \codeblock{bash}{{{ + vault write auth/jwt/config \ + oidc_discovery_url="https://" \ + default_role="demo" + }}} + + Lastly configure a "role" for JWT auth. Make sure to use the same value in your pipeline that you used for \code{bound_audiences}. \code{bound_subject} must be the sub-claim value of your JWT, if you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this accordingly! + \codeblock{bash}{{{ + vault write auth/jwt/role/demo \ + bound_subject="main/your-pipeline" \ + bound_audiences="my-vault-server.com" \ + policies=webapps \ + ttl=1h + }}} + + This role will allow the holder of a JWT with \code{aud=my-vault-server.com} and \code{sub=main/your-pipeline} to get a vault-token with the vault-policy "webapps". + + Pipelines can now do the following: + \codeblock{bash}{{{ + var_sources: + - name: vaulttoken + type: idtoken + config: + audience: ["my-vault-server.com"] + + jobs: + - name: aws-deploy + plan: + - task: print + config: + platform: linux + image_resource: + type: registry-image + source: { repository: hashicorp/vault } + run: + path: bash + args: + - -c + - | + vault write auth/jwt/login role=demo jwt=((vaulttoken:token)) > vault-response.json + echo "Now do something with the token in vault-response.json" + }}} + + You don't have to create a role and a policy for every single of your pipelines! + You can use claims from the JWT with Vault's \link{policy templating}{https://developer.hashicorp.com/vault/tutorials/policies/policy-templating} feature. + This way you can define a policy that allows a pipeline access to all secrets it would usually have access to using Concourse's native vault-integration: + + \codeblock{bash}{{{ + path "secret/metadata/concourse/{{ identity.entity.aliases..metadata.team }}" { + capabilities = ["list"] + } + + path "secret/data/concourse/{{ identity.entity.aliases..metadata.team }}/+" { + capabilities = ["read"] + } + + path "secret/metadata/concourse/{{ identity.entity.aliases..metadata.team }}/{{ identity.entity.aliases..metadata.pipeline }}" { + capabilities = ["list"] + } + + path "secret/metadata/concourse/{{ identity.entity.aliases..metadata.team }}/{{ identity.entity.aliases..metadata.pipeline }}/*" { + capabilities = ["read", "list"] + } + + path "secret/data/concourse/{{ identity.entity.aliases..metadata.team }}/{{ identity.entity.aliases..metadata.pipeline }}/*" { + capabilities = ["read", "list"] + } + }}} + \smaller{Make sure to set \code{} to the actual mount-accessor value of your JWT Auth method!} + + With a policy like this you don't need to configure \code{bound_subject} for your JWT auth role. Every single pipeline can simply use the same role and the policy will take care that they can only access secrets ment for them. } @@ -207,7 +284,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s - | aws sts assume-role-with-web-identity --role-arn arn:aws:iam:::role/ --web-identity-token ((awstoken:token)) > creds.json echo "Now do something with the temporary credentials in creds.json" - }}} + }}} } \example{Azure}{ From e95c033c19aa5783c13f2de2b507fc6c91a5ef7b Mon Sep 17 00:00:00 2001 From: Daniel Baumgarten Date: Mon, 30 Jun 2025 08:26:12 +0000 Subject: [PATCH 05/10] Add Azure example for idtoken credential provider Signed-off-by: Daniel Baumgarten --- lit/docs/operation/creds/idtoken.lit | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/lit/docs/operation/creds/idtoken.lit b/lit/docs/operation/creds/idtoken.lit index 24b83050..bbcbe1a9 100644 --- a/lit/docs/operation/creds/idtoken.lit +++ b/lit/docs/operation/creds/idtoken.lit @@ -288,8 +288,40 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s } \example{Azure}{ + Azure also supports a way to grant the holder of a JWT permissions in the Cloud. This is done via a feature calles \link{Federated Credentials}{https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0}. - TODO + First, \link{create a user-assigned managed identity}{https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities?pivots=identity-mi-methods-azp#create-a-user-assigned-managed-identity}. This identity will later be used by your pipeline. + + Now \link{create a federated credential}{https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity?pivots=identity-wif-mi-methods-azp#configure-a-federated-identity-credential-on-a-user-assigned-managed-identity} for the identity you just created. + As Scenario select "Other". As "Issuer URL" paste the URL of your Concourse server. As "Subject Identifier" use \code{/} of the pipeline that should be able to use the identity. If you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this setting here accordingly! + + You can now assign IAM-Permissions to the identity, which define what the identity is allowed to do in your Azure subscription. You most probaly already know how to do this. + + Your pipeline can now use the az cli to login to Azure using a JWT generated by Concourse: + \codeblock{bash}{{{ + var_sources: + - name: azuretoken + type: idtoken + config: + audience: ["api://AzureADTokenExchange"] + + jobs: + - name: azure-deploy + plan: + - task: print + config: + platform: linux + image_resource: + type: registry-image + source: { repository: bitnami/azure-cli } + run: + path: bash + args: + - -c + - | + az login --identity --client-id --federated-token abcd ((azuretoken:token)) + echo "You are now authenticated with Azure. Do something with it!" + }}} } } From e1e2a7f1d221e3a70552efc1248d2162e7957144 Mon Sep 17 00:00:00 2001 From: Daniel Baumgarten Date: Mon, 30 Jun 2025 11:15:38 +0000 Subject: [PATCH 06/10] Checked and improved vault example for idtoken credential provider Signed-off-by: Daniel Baumgarten --- lit/docs/operation/creds/idtoken.lit | 62 +++++++++++++++++----------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/lit/docs/operation/creds/idtoken.lit b/lit/docs/operation/creds/idtoken.lit index bbcbe1a9..6f7a9b35 100644 --- a/lit/docs/operation/creds/idtoken.lit +++ b/lit/docs/operation/creds/idtoken.lit @@ -106,16 +106,14 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s And after decoding like this: \codeblock{bash}{{{ { - "iss": "https://your-concourse.example.com", - "exp": 1751015734, - "aud": ["api://AzureADTokenExchange"], - "sub": "main/example-pipeline", - "team": "main", - "pipeline": "example-pipeline", - "instance_vars": { - "branch": "dev" - } - "job": "example-job" + "aud": "sts.amazonaws.com", + "exp": 1751282764, + "iat": 1751279164, + "iss": "https://your-concourse-server.com", + "job": "print-creds", + "pipeline": "mypipeline", + "sub": "main/mypipeline", + "team": "main" } }}} @@ -176,14 +174,17 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s Lastly configure a "role" for JWT auth. Make sure to use the same value in your pipeline that you used for \code{bound_audiences}. \code{bound_subject} must be the sub-claim value of your JWT, if you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this accordingly! \codeblock{bash}{{{ - vault write auth/jwt/role/demo \ + vault write auth/jwt/role/demo \ + role_type="jwt"\ + user_claim="sub" \ bound_subject="main/your-pipeline" \ bound_audiences="my-vault-server.com" \ policies=webapps \ ttl=1h }}} - This role will allow the holder of a JWT with \code{aud=my-vault-server.com} and \code{sub=main/your-pipeline} to get a vault-token with the vault-policy "webapps". + This role will allow the holder of a JWT with \code{aud=my-vault-server.com} and \code{sub=main/your-pipeline} to get a vault-token with the vault-policy "webapps". If the policy you want to assign has a different name, simply change it in the above example. + Make sure to adapt the value for \code{bound_subject} according to your team and pipeline name. Pipelines can now do the following: \codeblock{bash}{{{ @@ -194,51 +195,64 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s audience: ["my-vault-server.com"] jobs: - - name: aws-deploy + - name: vault-login plan: - - task: print + - task: login config: platform: linux image_resource: type: registry-image source: { repository: hashicorp/vault } run: - path: bash + path: sh args: - -c - | - vault write auth/jwt/login role=demo jwt=((vaulttoken:token)) > vault-response.json + export VAULT_ADDR=https://my-vault-server.com + vault write auth/jwt/login role=demo jwt=((vaulttoken:token)) --format=json > vault-response.json echo "Now do something with the token in vault-response.json" }}} You don't have to create a role and a policy for every single of your pipelines! You can use claims from the JWT with Vault's \link{policy templating}{https://developer.hashicorp.com/vault/tutorials/policies/policy-templating} feature. - This way you can define a policy that allows a pipeline access to all secrets it would usually have access to using Concourse's native vault-integration: + This way you can define a policy that allows a pipeline read to all secrets it would usually have access to using Concourse's native vault-integration: \codeblock{bash}{{{ - path "secret/metadata/concourse/{{ identity.entity.aliases..metadata.team }}" { + path "concourse/metadata/{{ identity.entity.aliases..metadata.team }}" { capabilities = ["list"] } - path "secret/data/concourse/{{ identity.entity.aliases..metadata.team }}/+" { + path "concourse/data/{{ identity.entity.aliases..metadata.team }}/+" { capabilities = ["read"] } - path "secret/metadata/concourse/{{ identity.entity.aliases..metadata.team }}/{{ identity.entity.aliases..metadata.pipeline }}" { + path "concourse/metadata/{{ identity.entity.aliases..metadata.team }}/{{ identity.entity.aliases..metadata.pipeline }}" { capabilities = ["list"] } - path "secret/metadata/concourse/{{ identity.entity.aliases..metadata.team }}/{{ identity.entity.aliases..metadata.pipeline }}/*" { + path "concourse/metadata/{{ identity.entity.aliases..metadata.team }}/{{ identity.entity.aliases..metadata.pipeline }}/*" { capabilities = ["read", "list"] } - path "secret/data/concourse/{{ identity.entity.aliases..metadata.team }}/{{ identity.entity.aliases..metadata.pipeline }}/*" { + path "concourse/data/{{ identity.entity.aliases..metadata.team }}/{{ identity.entity.aliases..metadata.pipeline }}/*" { capabilities = ["read", "list"] } }}} - \smaller{Make sure to set \code{} to the actual mount-accessor value of your JWT Auth method!} + \smaller{Make sure to set \code{} to the actual mount-accessor value of your JWT Auth method! You can use \code{vault auth list --format=json | jq -r '."jwt/".accessor'} to get the accessor for your jwt auth method.} - With a policy like this you don't need to configure \code{bound_subject} for your JWT auth role. Every single pipeline can simply use the same role and the policy will take care that they can only access secrets ment for them. + With a policy like this you don't need to configure \code{bound_subject} for your JWT auth role. Every single pipeline can simply use the same role and the policy will take care that they can only access secrets meant for them. However you need to explicitly configure claim to metadata mapping: + + \codeblock{bash}{{{ + vault write auth/jwt/role/demo \ + role_type="jwt"\ + user_claim="sub" \ + bound_subject= \ + bound_audiences="my-vault-server.com" \ + policies=pipeline-new \ + claim_mappings='team=team' \ + claim_mappings='pipeline=pipeline' \ + ttl=1h + }}} } From aae89f423686fbdae5f4118c9bd1b301fcbe4de3 Mon Sep 17 00:00:00 2001 From: Daniel Baumgarten Date: Mon, 30 Jun 2025 12:50:21 +0000 Subject: [PATCH 07/10] Improved azure example for idtoken credential provider Signed-off-by: Daniel Baumgarten --- lit/docs/operation/creds/idtoken.lit | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lit/docs/operation/creds/idtoken.lit b/lit/docs/operation/creds/idtoken.lit index 6f7a9b35..f94a9a8b 100644 --- a/lit/docs/operation/creds/idtoken.lit +++ b/lit/docs/operation/creds/idtoken.lit @@ -206,6 +206,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s run: path: sh args: + - -e - -c - | export VAULT_ADDR=https://my-vault-server.com @@ -294,6 +295,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s run: path: bash args: + - -e - -c - | aws sts assume-role-with-web-identity --role-arn arn:aws:iam:::role/ --web-identity-token ((awstoken:token)) > creds.json @@ -304,10 +306,10 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s \example{Azure}{ Azure also supports a way to grant the holder of a JWT permissions in the Cloud. This is done via a feature calles \link{Federated Credentials}{https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0}. - First, \link{create a user-assigned managed identity}{https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities?pivots=identity-mi-methods-azp#create-a-user-assigned-managed-identity}. This identity will later be used by your pipeline. + First, \link{create an EntraID App Registration}{https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app}. This app registration will be the service principal used by your pipeline. - Now \link{create a federated credential}{https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity?pivots=identity-wif-mi-methods-azp#configure-a-federated-identity-credential-on-a-user-assigned-managed-identity} for the identity you just created. - As Scenario select "Other". As "Issuer URL" paste the URL of your Concourse server. As "Subject Identifier" use \code{/} of the pipeline that should be able to use the identity. If you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this setting here accordingly! + Now \link{create a federated credential}{https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#other-identity-providers} for the app registration you just created. + As Scenario select "Other". As "Issuer" paste the URL of your Concourse server. As "Type" select "Explicit subject identifier" and as "Value" use \code{/} of the pipeline that should be able to use the identity. If you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this setting here accordingly! You can now assign IAM-Permissions to the identity, which define what the identity is allowed to do in your Azure subscription. You most probaly already know how to do this. @@ -322,18 +324,20 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s jobs: - name: azure-deploy plan: - - task: print + - task: login config: platform: linux image_resource: type: registry-image - source: { repository: bitnami/azure-cli } + source: { repository: mcr.microsoft.com/azure-cli } run: path: bash args: + - -e - -c - | - az login --identity --client-id --federated-token abcd ((azuretoken:token)) + echo ((azuretoken:token)) + az login --service-principal -u --tenant --federated-token ((azuretoken:token)) echo "You are now authenticated with Azure. Do something with it!" }}} From 737cd1f5883467ab1cd4d244a1cd916e2b153db4 Mon Sep 17 00:00:00 2001 From: Daniel Baumgarten Date: Mon, 30 Jun 2025 14:44:34 +0000 Subject: [PATCH 08/10] Improved AWS example for idtoken credential provider Signed-off-by: Daniel Baumgarten --- lit/docs/operation/creds/idtoken.lit | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lit/docs/operation/creds/idtoken.lit b/lit/docs/operation/creds/idtoken.lit index f94a9a8b..3631149e 100644 --- a/lit/docs/operation/creds/idtoken.lit +++ b/lit/docs/operation/creds/idtoken.lit @@ -284,7 +284,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s audience: ["sts.amazonaws.com"] jobs: - - name: aws-deploy + - name: aws-login plan: - task: print config: @@ -298,7 +298,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s - -e - -c - | - aws sts assume-role-with-web-identity --role-arn arn:aws:iam:::role/ --web-identity-token ((awstoken:token)) > creds.json + aws sts assume-role-with-web-identity --role-session-name Concourse --role-arn arn:aws:iam:::role/ --web-identity-token ((awstoken:token)) > creds.json echo "Now do something with the temporary credentials in creds.json" }}} } From 2b9e25d673c296135bf089bd3bf1c7f79354c390 Mon Sep 17 00:00:00 2001 From: Daniel Baumgarten Date: Wed, 2 Jul 2025 07:29:26 +0000 Subject: [PATCH 09/10] Correct typos in idtoken credential provider docs Signed-off-by: Daniel Baumgarten --- lit/docs/operation/creds/idtoken.lit | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lit/docs/operation/creds/idtoken.lit b/lit/docs/operation/creds/idtoken.lit index 3631149e..c00c6e3c 100644 --- a/lit/docs/operation/creds/idtoken.lit +++ b/lit/docs/operation/creds/idtoken.lit @@ -3,7 +3,7 @@ \use-plugin{concourse-docs} \omit-children-from-table-of-contents -This idtoken credential manager is a bit special. It does not load any credentials from an external source, but instead generates \link{JWTs}{https://datatracker.ietf.org/doc/html/rfc7519} which are signed by concourse and contain information about the pipeline/job +This idtoken credential manager is a bit special. It doesn't load any credentials from an external source but instead generates \link{JWTs}{https://datatracker.ietf.org/doc/html/rfc7519} which are signed by concourse and contain information about the pipeline/job that is currently running. It can NOT be used as a cluster-wide credential manager, but must instead be used as a \reference{var-sources}{var source}. These JWTs can be used to authenticate with external services via "identity federation" with the identity of the pipeline. @@ -19,14 +19,14 @@ Examples for services that support authentication via JWTs are: } -External Services can verify if JWTs are actually issued by your Concourse, by checking the signatures on the JWTs against the public keys published by your Concourse. +External services can verify if JWTs are actually issued by your Concourse, by checking the signatures on the JWTs against the public keys published by your Concourse. The public keys for verification are published as \link{JWKS}{https://datatracker.ietf.org/doc/html/rfc7517} at: \codeblock{bash}{{{ https://your-concourse-server.com/.well-known/jwks.json }}} -Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/specs/openid-connect-discovery-1_0.html}, which allows external services to auto-discover the JWKS-URL. +Concourse also offers a \link{OIDC Discovery Endpoint}{https://openid.net/specs/openid-connect-discovery-1_0.html}, which allows external services to auto-discover the JWKS-URL. \section{ \title{Usage} @@ -141,15 +141,15 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s \section{ \title{Automatic Key Rotation}{idtoken-key-rotation} - Concourse will automatically rotate the signing keys used for creating the JWTs. The default rotation period is \code{7 days}. The previously used keys are being kept around for a while (\code{24h}) - so that verification of currently existing JWTs doesn't fail. + Concourse will automatically rotate the signing keys used for creating the JWTs. The default rotation period is \code{7 days}. The previously used keys are being kept around for a while (by default \code{24h}) + so that verification of currently existing JWTs doesn't fail during key rotation. That behavior can be configured via the following ATC flags: \list{ \code{signing-key.rotation-period}: How often to rotate the signing keys. Default: \code{7d}. 0 means don't rotate at all. }{ - \code{signing-key.grace-period}: How long to keep previously used signing keys published in the JWKs after they have been superceeded. Default: \code{24h}. + \code{signing-key.grace-period}: How long to keep previously used signing keys published in the JWKs after they have been superseded. Default: \code{24h}. }{ \code{signing-key.check-interval}: How often to check if new keys are needed or if old ones should be removed. Default: \code{10m} } @@ -158,7 +158,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s \right-side{Examples}{ \example{Vault}{ - You can use JWTs to authenticate with \link{HashiCorp Vault}{https://developer.hashicorp.com/vault/docs/auth/jwt#jwt-authentication}. This way your pipelines can directly communicate with Vault and use all of it's features, beyond what Concourse's native vault-integration offers. + You can use JWTs to authenticate with \link{HashiCorp Vault}{https://developer.hashicorp.com/vault/docs/auth/jwt#jwt-authentication}. This way your pipelines can directly communicate with Vault and use all of it's features, beyond what Concourse's native Vault-integration offers. First enable the JWT auth method in your Vault Server: \codeblock{bash}{{{ @@ -172,7 +172,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s default_role="demo" }}} - Lastly configure a "role" for JWT auth. Make sure to use the same value in your pipeline that you used for \code{bound_audiences}. \code{bound_subject} must be the sub-claim value of your JWT, if you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this accordingly! + Lastly configure a role for JWT auth. Make sure to use the same value in your pipeline that you used for \code{bound_audiences} (the best would be the URL of your Vault). \code{bound_subject} must be the sub-claim value of your JWT, if you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this accordingly! \codeblock{bash}{{{ vault write auth/jwt/role/demo \ role_type="jwt"\ @@ -183,7 +183,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s ttl=1h }}} - This role will allow the holder of a JWT with \code{aud=my-vault-server.com} and \code{sub=main/your-pipeline} to get a vault-token with the vault-policy "webapps". If the policy you want to assign has a different name, simply change it in the above example. + This role will allow the holder of a JWT with \code{aud=my-vault-server.com} and \code{sub=main/your-pipeline} to get a Vault token with the Vault-policy "webapps". If the policy you want to assign has a different name, simply change it in the above example. Make sure to adapt the value for \code{bound_subject} according to your team and pipeline name. Pipelines can now do the following: @@ -216,7 +216,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s You don't have to create a role and a policy for every single of your pipelines! You can use claims from the JWT with Vault's \link{policy templating}{https://developer.hashicorp.com/vault/tutorials/policies/policy-templating} feature. - This way you can define a policy that allows a pipeline read to all secrets it would usually have access to using Concourse's native vault-integration: + This way you can define a policy that allows a pipeline read to all the secrets it would usually have access to using Concourse's native Vault-integration: \codeblock{bash}{{{ path "concourse/metadata/{{ identity.entity.aliases..metadata.team }}" { @@ -241,7 +241,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s }}} \smaller{Make sure to set \code{} to the actual mount-accessor value of your JWT Auth method! You can use \code{vault auth list --format=json | jq -r '."jwt/".accessor'} to get the accessor for your jwt auth method.} - With a policy like this you don't need to configure \code{bound_subject} for your JWT auth role. Every single pipeline can simply use the same role and the policy will take care that they can only access secrets meant for them. However you need to explicitly configure claim to metadata mapping: + With a policy like this you don't need to configure \code{bound_subject} in your JWT auth role. Every single pipeline can simply use the same role and the policy will take care that they can only access secrets meant for them. However you need to explicitly configure claim to metadata mapping: \codeblock{bash}{{{ vault write auth/jwt/role/demo \ @@ -274,7 +274,7 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s Add a condition on the sub-claim with type \code{StringEquals} and value \code{yourteam/yourpipeline}. This will allow ONLY that specific pipeline (and any instanced versions of it) to assume that IAM Role using a JWT. If you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this condition accordingly! In the next step you will be able to choose which AWS permissions your role will get. - Now you can use the AWS \link{AssumeRoleWithWebIdentity API operation}{https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html} to assume your role via a JWT issued by concourse. + Now you can use the AWS \link{AssumeRoleWithWebIdentity API operation}{https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html} to assume your role via a JWT issued by Concourse. The easiest way is to do this is via the \link{assume-role-with-web-identity AWS CLI command}{https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role-with-web-identity.html}: \codeblock{bash}{{{ var_sources: @@ -304,14 +304,14 @@ Concourse also has a minimal \link{OIDC Discovery Endpoint}{https://openid.net/s } \example{Azure}{ - Azure also supports a way to grant the holder of a JWT permissions in the Cloud. This is done via a feature calles \link{Federated Credentials}{https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0}. + Azure also supports a way to grant the holder of a JWT permissions in the Cloud. This is done via a feature called \link{Federated Credentials}{https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0}. First, \link{create an EntraID App Registration}{https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app}. This app registration will be the service principal used by your pipeline. Now \link{create a federated credential}{https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#other-identity-providers} for the app registration you just created. - As Scenario select "Other". As "Issuer" paste the URL of your Concourse server. As "Type" select "Explicit subject identifier" and as "Value" use \code{/} of the pipeline that should be able to use the identity. If you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this setting here accordingly! + As "Scenario" select "Other". As "Issuer" paste the URL of your Concourse server. As "Type" select "Explicit subject identifier" and as "Value" use \code{/} of the pipeline that should be able to use the identity. If you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this setting here accordingly! - You can now assign IAM-Permissions to the identity, which define what the identity is allowed to do in your Azure subscription. You most probaly already know how to do this. + You can now assign IAM permissions to the identity of the app registration, which define what the identity is allowed to do in your Azure subscription. You most probably already know how to do this. Your pipeline can now use the az cli to login to Azure using a JWT generated by Concourse: \codeblock{bash}{{{ From 59d531d751e6d6e25bfdcb8f9f12cf263a47c2b5 Mon Sep 17 00:00:00 2001 From: Daniel Baumgarten Date: Wed, 2 Jul 2025 07:41:11 +0000 Subject: [PATCH 10/10] Improved formatting of examples for idtoken credential provider Signed-off-by: Daniel Baumgarten --- lit/docs/operation/creds/idtoken.lit | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lit/docs/operation/creds/idtoken.lit b/lit/docs/operation/creds/idtoken.lit index c00c6e3c..9c3d6b93 100644 --- a/lit/docs/operation/creds/idtoken.lit +++ b/lit/docs/operation/creds/idtoken.lit @@ -172,7 +172,7 @@ Concourse also offers a \link{OIDC Discovery Endpoint}{https://openid.net/specs/ default_role="demo" }}} - Lastly configure a role for JWT auth. Make sure to use the same value in your pipeline that you used for \code{bound_audiences} (the best would be the URL of your Vault). \code{bound_subject} must be the sub-claim value of your JWT, if you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this accordingly! + Lastly configure a role for JWT auth. Make sure to use the same value in your pipeline that you used for \italic{bound_audiences} (the best would be the URL of your Vault). \italic{bound_subject} must be the sub-claim value of your JWT, if you use the \italic{subject_scope} setting to change the contents of your sub-claim, adapt this accordingly! \codeblock{bash}{{{ vault write auth/jwt/role/demo \ role_type="jwt"\ @@ -183,8 +183,8 @@ Concourse also offers a \link{OIDC Discovery Endpoint}{https://openid.net/specs/ ttl=1h }}} - This role will allow the holder of a JWT with \code{aud=my-vault-server.com} and \code{sub=main/your-pipeline} to get a Vault token with the Vault-policy "webapps". If the policy you want to assign has a different name, simply change it in the above example. - Make sure to adapt the value for \code{bound_subject} according to your team and pipeline name. + This role will allow the holder of a JWT with \italic{aud=my-vault-server.com} and \italic{sub=main/your-pipeline} to get a Vault token with the Vault-policy "webapps". If the policy you want to assign has a different name, simply change it in the above example. + Make sure to adapt the value for \italic{bound_subject} according to your team and pipeline name. Pipelines can now do the following: \codeblock{bash}{{{ @@ -239,9 +239,9 @@ Concourse also offers a \link{OIDC Discovery Endpoint}{https://openid.net/specs/ capabilities = ["read", "list"] } }}} - \smaller{Make sure to set \code{} to the actual mount-accessor value of your JWT Auth method! You can use \code{vault auth list --format=json | jq -r '."jwt/".accessor'} to get the accessor for your jwt auth method.} + \smaller{Make sure to set \italic{} to the actual mount-accessor value of your JWT Auth method! You can use \italic{vault auth list --format=json | jq -r '."jwt/".accessor'} to get the accessor for your jwt auth method.} - With a policy like this you don't need to configure \code{bound_subject} in your JWT auth role. Every single pipeline can simply use the same role and the policy will take care that they can only access secrets meant for them. However you need to explicitly configure claim to metadata mapping: + With a policy like this you don't need to configure \italic{bound_subject} in your JWT auth role. Every single pipeline can simply use the same role and the policy will take care that they can only access secrets meant for them. However you need to explicitly configure claim to metadata mapping: \codeblock{bash}{{{ vault write auth/jwt/role/demo \ @@ -266,12 +266,12 @@ Concourse also offers a \link{OIDC Discovery Endpoint}{https://openid.net/specs/ with long-lived credentials. First you need to \link{create an OpenID Connect identity provider}{https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html} in your AWS Account. - As \code{Provider URL} specify the external URL of your Concourse server. - For \code{Audience} you can choose any string you like. Using something like \code{sts.amazonaws.com} is recommended. You have to use the same string later in the configuration of your var-source. + As \italic{Provider URL} specify the external URL of your Concourse server. + For \italic{Audience} you can choose any string you like. Using something like \italic{sts.amazonaws.com} is recommended. You have to use the same string later in the configuration of your var-source. Next you will need to \link{create an IAM-Role that can be assumed using your JWT}{https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html#idp_oidc_Create}. - As \code{Identity Provider} choose the one you created previously. - Add a condition on the sub-claim with type \code{StringEquals} and value \code{yourteam/yourpipeline}. This will allow ONLY that specific pipeline (and any instanced versions of it) to assume that IAM Role using a JWT. If you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this condition accordingly! + As \italic{Identity Provider} choose the one you created previously. + Add a condition on the sub-claim with type \italic{StringEquals} and value \italic{yourteam/yourpipeline}. This will allow ONLY that specific pipeline (and any instanced versions of it) to assume that IAM Role using a JWT. If you use the \italic{subject_scope} setting to change the contents of your sub-claim, adapt this condition accordingly! In the next step you will be able to choose which AWS permissions your role will get. Now you can use the AWS \link{AssumeRoleWithWebIdentity API operation}{https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html} to assume your role via a JWT issued by Concourse. @@ -309,7 +309,7 @@ Concourse also offers a \link{OIDC Discovery Endpoint}{https://openid.net/specs/ First, \link{create an EntraID App Registration}{https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app}. This app registration will be the service principal used by your pipeline. Now \link{create a federated credential}{https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#other-identity-providers} for the app registration you just created. - As "Scenario" select "Other". As "Issuer" paste the URL of your Concourse server. As "Type" select "Explicit subject identifier" and as "Value" use \code{/} of the pipeline that should be able to use the identity. If you use the \code{subject_scope} setting to change the contents of your sub-claim, adapt this setting here accordingly! + As \italic{Scenario} select "Other". As \italic{Issuer} paste the URL of your Concourse server. As \italic{Type} select "Explicit subject identifier" and as \italic{Value} use \italic{/} of the pipeline that should be able to use the identity. If you use the \italic{subject_scope} setting to change the contents of your sub-claim, adapt this setting here accordingly! You can now assign IAM permissions to the identity of the app registration, which define what the identity is allowed to do in your Azure subscription. You most probably already know how to do this.