From c9791d2ea3d7c4006bfc10da7801021a7cdc08d6 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Wed, 18 Feb 2026 21:44:50 +0100 Subject: [PATCH 1/2] feat: Support Azure DevOps OIDC provider Signed-off-by: Jorge Turrado --- docs/guides/workload_identity_federation.md | 35 +++++++++++++++---- docs/index.md | 2 +- go.mod | 2 ++ go.sum | 4 +-- stackit/provider.go | 13 ++++++- .../workload_identity_federation.md.tmpl | 35 +++++++++++++++---- templates/index.md.tmpl | 2 +- 7 files changed, 76 insertions(+), 17 deletions(-) diff --git a/docs/guides/workload_identity_federation.md b/docs/guides/workload_identity_federation.md index 30d82f558..6a7fb6446 100644 --- a/docs/guides/workload_identity_federation.md +++ b/docs/guides/workload_identity_federation.md @@ -1,12 +1,12 @@ --- -page_title: "Workload Identity Federation with GitHub Actions" +page_title: "Workload Identity Federation" --- # Workload Identity Federation with GitHub Actions Workload Identity Federation (WIF) allows you to authenticate the STACKIT Terraform provider without using long-lived Service Account keys. This is particularly useful in CI/CD environments like **GitHub Actions**, **GitLab CI**, or **Azure DevOps**, where you can use short-lived -OIDC tokens. This guide focuses on using WIF with GitHub Actions, but the principles may apply to other CI/CD platforms that support OIDC. +OIDC tokens. This guide focuses on using WIF with GitHub Actions and Azure DevOps, but the principles may apply to other CI/CD platforms that support OIDC. ## Prerequisites @@ -16,8 +16,13 @@ Before using Workload Identity Federation flow, you need to: ## Setup Workload Identity Federation WIF can be configured to trust any public OIDC provider following the [docs page](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/how-tos/manage-service-account-federations/#create-a-federated-identity-provider) -but for the purpose of this guide we will focus on GitHub Actions as OIDC provider. GitHub Actions supports OIDC authentication using -the public issuer "https://token.actions.githubusercontent.com" (for GH Enterprise you should check your issuer URL) and setting repository and action information +but for the purpose of this guide we will focus on GitHub Actions and AzureDevOps as OIDC providers. + +> Important: The most closed assertions including all the data that you cant from the OIDC token should be used to avoid potential security risks of trusting tokens that are not issued in the context of your CI/CD pipeline. + +### GitHub Actions assertions + +GitHub Actions supports OIDC authentication using the public issuer "https://token.actions.githubusercontent.com" (for GH Enterprise you should check your issuer URL) and setting repository and action information as part of the OIDC token claims. [More info here](https://docs.github.com/es/actions/concepts/security/openid-connect). Using this provider [repository](https://github.com/stackitcloud/terraform-provider-stackit) (stackitcloud/terraform-provider-stackit) as example and assuming that we want to @@ -28,7 +33,23 @@ execute terraform on the main branch, we will configure the service account "Fed - **sub**->equals->repo:stackitcloud/terraform-provider-stackit:ref:refs/heads/main # This is the repository and branch where the action will run - **aud**->equals->sts.accounts.stackit.cloud # Mandatory value -> Note: You can use more fine-grained assertions just adding them. More info about OIDC token claims in [GitHub](https://docs.github.com/en/actions/reference/security/oidc) +> Note: You can use more fine-grained assertions by adding them. More info about OIDC token claims in [GitHub](https://docs.github.com/en/actions/reference/security/oidc) + +### Azure DevOps assertions + +Azure DevOps supports OIDC authentication using the public issuer "https://vstoken.azure.com" (for Azure DevOps Server you should check your issuer URL) and setting information like organization, project, and pipeline +as part of the OIDC token claims. + +Using a hypothetical pipeline named `terraform-ado-oidc` inside the prohect 'https://myorg.azure.com/project-abc` as example and assuming that we want to +execute terraform on the main branch, we will configure the service account "Federated identity Provider" with the following configuration: +- **Provider Name**: AzureDevOps # This is just an example, you can choose any name you want +- **Issuer URL**: https://vstoken.azure.com/{ORGANIZATION_ID} # This is the public issuer for Azure DevOps OIDC tokens +- **Assertions**: + - **aud**->equals->api://AzureADTokenExchange # Mandatory value + - **sub**->equals->p://myorg/project-abc/terraform-ado-oidc # This is the pipeline where the process is running + - **rpo_ref**->equals->refs/heads/main # This is the branch where the pipeline will run, can be used instead of sub claim + +> Note: This is just an example, you can use more or less fine-grained assertions. ## Provider Configuration @@ -50,7 +71,9 @@ provider "stackit" { In most CI/CD scenarios, the cleanest way is to set the `STACKIT_SERVICE_ACCOUNT_EMAIL` environment variable as well as `STACKIT_USE_OIDC="1"` to enable the WIF flow. This way you don't need to change your provider configuration and the provider will automatically fetch the OIDC token and exchange it for a short-lived access token. -## Example GitHub Actions Workflow +## Examples + +### GitHub Actions Workflow > Note: To request OIDC tokens, you need to [grant this permission to the GitHub Actions workflow](https://docs.github.com/en/actions/reference/security/oidc#required-permission). diff --git a/docs/index.md b/docs/index.md index 27290dfe4..7a29fe3b8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -74,7 +74,7 @@ When using Workload Identity Federation (WIF), you don't need a static service a WIF can be configured to trust any public OIDC provider following the [official documentation](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/how-tos/manage-service-account-federations/#create-a-federated-identity-provider). -To use WIF, set the `use_oidc` flag to `true` and provide an OIDC token for the exchange. While you can provide the token directly via `service_account_federated_token`, this is **not recommended for GitHub Actions**, as the provider will automatically fetch the token from the environment. For a complete setup, see our [Workload Identity Federation guide](./guides/workload_identity_federation.md). +To use WIF, set the `use_oidc` flag to `true` and provide an OIDC token for the exchange. While you can provide the token directly via `service_account_federated_token`, this is **not recommended for GitHub Actions or Azure DevOps**, as the provider will automatically fetch the token from the environment. For a complete setup, see our [Workload Identity Federation guide](./docs/guides/workload_identity_federation.md). In addition to this, you must set the `service_account_email` to specify which service account to impersonate. diff --git a/go.mod b/go.mod index 048e496ba..1f1b5a817 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/stackitcloud/terraform-provider-stackit go 1.24.0 +replace github.com/stackitcloud/stackit-sdk-go/core => github.com/jorturfer/stackit-sdk-go/core v0.0.0-20260218191605-c1e0812ff15f + require ( github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index f2ff5e6e4..3d6f41cc1 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/jorturfer/stackit-sdk-go/core v0.0.0-20260218191605-c1e0812ff15f h1:fTNZZta47QPQs8rG48i47wFQChjoYwUeoMGikLj6iSk= +github.com/jorturfer/stackit-sdk-go/core v0.0.0-20260218191605-c1e0812ff15f/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -149,8 +151,6 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= -github.com/stackitcloud/stackit-sdk-go/core v0.21.1 h1:Y/PcAgM7DPYMNqum0MLv4n1mF9ieuevzcCIZYQfm3Ts= -github.com/stackitcloud/stackit-sdk-go/core v0.21.1/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI= github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0 h1:HxPgBu04j5tj6nfZ2r0l6v4VXC0/tYOGe4sA5Addra8= github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0/go.mod h1:uYI9pHAA2g84jJN25ejFUxa0/JtfpPZqMDkctQ1BzJk= github.com/stackitcloud/stackit-sdk-go/services/cdn v1.10.0 h1:YALzjYAApyQMKyt4C2LKhPRZHa6brmbFeKuuwl+KOTs= diff --git a/stackit/provider.go b/stackit/provider.go index 85abf49ba..4747afe19 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -549,7 +549,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, sdkConfig.ServiceAccountFederatedTokenFunc = oidcadapters.ReadJWTFromFileSystem(oidc_token_path) } - // Workload Identity Federation via provided OIDC Token from GitHub Actions + // Workload Identity Federation via provided OIDC Token from GitHub Actions or Azure Dev Ops if sdkConfig.ServiceAccountFederatedTokenFunc == nil && utils.GetEnvBoolIfValueAbsent(providerConfig.UseOIDC, "STACKIT_USE_OIDC") { sdkConfig.WorkloadIdentityFederation = true // https://docs.github.com/en/actions/reference/security/oidc#methods-for-requesting-the-oidc-token @@ -558,6 +558,17 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, if oidcReqURL != "" && oidcReqToken != "" { sdkConfig.ServiceAccountFederatedTokenFunc = oidcadapters.RequestGHOIDCToken(oidcReqURL, oidcReqToken) } + + // https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/oidctoken/create?view=azure-devops-rest-7.1#taskhuboidctoken + // https://learn.microsoft.com/es-es/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml + oidcReqURL = utils.GetEnvStringOrDefault(providerConfig.OIDCTokenRequestURL, "SYSTEM_OIDCREQUESTURI", "") + oidcReqToken = utils.GetEnvStringOrDefault(providerConfig.OIDCTokenRequestToken, "SYSTEM_ACCESSTOKEN", "") + // This can be set to the ID of the service connection to restrict the token exchange to that connection, not supported by default to avoid additional configuration + // for users that don't need it, can be added as an additional provider config parameter in the future if there is demand + serviceConnectionID := "" + if oidcReqURL != "" && oidcReqToken != "" { + sdkConfig.ServiceAccountFederatedTokenFunc = oidcadapters.RequestAzureDevOpsOIDCToken(oidcReqURL, oidcReqToken, serviceConnectionID) + } } roundTripper, err := sdkauth.SetupAuth(sdkConfig) diff --git a/templates/guides/workload_identity_federation.md.tmpl b/templates/guides/workload_identity_federation.md.tmpl index 30d82f558..6a7fb6446 100644 --- a/templates/guides/workload_identity_federation.md.tmpl +++ b/templates/guides/workload_identity_federation.md.tmpl @@ -1,12 +1,12 @@ --- -page_title: "Workload Identity Federation with GitHub Actions" +page_title: "Workload Identity Federation" --- # Workload Identity Federation with GitHub Actions Workload Identity Federation (WIF) allows you to authenticate the STACKIT Terraform provider without using long-lived Service Account keys. This is particularly useful in CI/CD environments like **GitHub Actions**, **GitLab CI**, or **Azure DevOps**, where you can use short-lived -OIDC tokens. This guide focuses on using WIF with GitHub Actions, but the principles may apply to other CI/CD platforms that support OIDC. +OIDC tokens. This guide focuses on using WIF with GitHub Actions and Azure DevOps, but the principles may apply to other CI/CD platforms that support OIDC. ## Prerequisites @@ -16,8 +16,13 @@ Before using Workload Identity Federation flow, you need to: ## Setup Workload Identity Federation WIF can be configured to trust any public OIDC provider following the [docs page](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/how-tos/manage-service-account-federations/#create-a-federated-identity-provider) -but for the purpose of this guide we will focus on GitHub Actions as OIDC provider. GitHub Actions supports OIDC authentication using -the public issuer "https://token.actions.githubusercontent.com" (for GH Enterprise you should check your issuer URL) and setting repository and action information +but for the purpose of this guide we will focus on GitHub Actions and AzureDevOps as OIDC providers. + +> Important: The most closed assertions including all the data that you cant from the OIDC token should be used to avoid potential security risks of trusting tokens that are not issued in the context of your CI/CD pipeline. + +### GitHub Actions assertions + +GitHub Actions supports OIDC authentication using the public issuer "https://token.actions.githubusercontent.com" (for GH Enterprise you should check your issuer URL) and setting repository and action information as part of the OIDC token claims. [More info here](https://docs.github.com/es/actions/concepts/security/openid-connect). Using this provider [repository](https://github.com/stackitcloud/terraform-provider-stackit) (stackitcloud/terraform-provider-stackit) as example and assuming that we want to @@ -28,7 +33,23 @@ execute terraform on the main branch, we will configure the service account "Fed - **sub**->equals->repo:stackitcloud/terraform-provider-stackit:ref:refs/heads/main # This is the repository and branch where the action will run - **aud**->equals->sts.accounts.stackit.cloud # Mandatory value -> Note: You can use more fine-grained assertions just adding them. More info about OIDC token claims in [GitHub](https://docs.github.com/en/actions/reference/security/oidc) +> Note: You can use more fine-grained assertions by adding them. More info about OIDC token claims in [GitHub](https://docs.github.com/en/actions/reference/security/oidc) + +### Azure DevOps assertions + +Azure DevOps supports OIDC authentication using the public issuer "https://vstoken.azure.com" (for Azure DevOps Server you should check your issuer URL) and setting information like organization, project, and pipeline +as part of the OIDC token claims. + +Using a hypothetical pipeline named `terraform-ado-oidc` inside the prohect 'https://myorg.azure.com/project-abc` as example and assuming that we want to +execute terraform on the main branch, we will configure the service account "Federated identity Provider" with the following configuration: +- **Provider Name**: AzureDevOps # This is just an example, you can choose any name you want +- **Issuer URL**: https://vstoken.azure.com/{ORGANIZATION_ID} # This is the public issuer for Azure DevOps OIDC tokens +- **Assertions**: + - **aud**->equals->api://AzureADTokenExchange # Mandatory value + - **sub**->equals->p://myorg/project-abc/terraform-ado-oidc # This is the pipeline where the process is running + - **rpo_ref**->equals->refs/heads/main # This is the branch where the pipeline will run, can be used instead of sub claim + +> Note: This is just an example, you can use more or less fine-grained assertions. ## Provider Configuration @@ -50,7 +71,9 @@ provider "stackit" { In most CI/CD scenarios, the cleanest way is to set the `STACKIT_SERVICE_ACCOUNT_EMAIL` environment variable as well as `STACKIT_USE_OIDC="1"` to enable the WIF flow. This way you don't need to change your provider configuration and the provider will automatically fetch the OIDC token and exchange it for a short-lived access token. -## Example GitHub Actions Workflow +## Examples + +### GitHub Actions Workflow > Note: To request OIDC tokens, you need to [grant this permission to the GitHub Actions workflow](https://docs.github.com/en/actions/reference/security/oidc#required-permission). diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index f30f2133a..09c85782b 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -38,7 +38,7 @@ When using Workload Identity Federation (WIF), you don't need a static service a WIF can be configured to trust any public OIDC provider following the [official documentation](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/how-tos/manage-service-account-federations/#create-a-federated-identity-provider). -To use WIF, set the `use_oidc` flag to `true` and provide an OIDC token for the exchange. While you can provide the token directly via `service_account_federated_token`, this is **not recommended for GitHub Actions**, as the provider will automatically fetch the token from the environment. For a complete setup, see our [Workload Identity Federation guide](./guides/workload_identity_federation.md). +To use WIF, set the `use_oidc` flag to `true` and provide an OIDC token for the exchange. While you can provide the token directly via `service_account_federated_token`, this is **not recommended for GitHub Actions or Azure DevOps**, as the provider will automatically fetch the token from the environment. For a complete setup, see our [Workload Identity Federation guide](./docs/guides/workload_identity_federation.md). In addition to this, you must set the `service_account_email` to specify which service account to impersonate. From ddb14e7b0e09eaa1632c8fc16c74e1fd120e8ff2 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Wed, 18 Feb 2026 22:15:51 +0100 Subject: [PATCH 2/2] feat: Support Azure DevOps OIDC provider Signed-off-by: Jorge Turrado --- docs/guides/workload_identity_federation.md | 10 +++++++--- templates/guides/workload_identity_federation.md.tmpl | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/guides/workload_identity_federation.md b/docs/guides/workload_identity_federation.md index 6a7fb6446..bea8d3897 100644 --- a/docs/guides/workload_identity_federation.md +++ b/docs/guides/workload_identity_federation.md @@ -38,16 +38,20 @@ execute terraform on the main branch, we will configure the service account "Fed ### Azure DevOps assertions Azure DevOps supports OIDC authentication using the public issuer "https://vstoken.azure.com" (for Azure DevOps Server you should check your issuer URL) and setting information like organization, project, and pipeline -as part of the OIDC token claims. +as part of the OIDC token claims. Using a hypothetical pipeline named `terraform-ado-oidc` inside the prohect 'https://myorg.azure.com/project-abc` as example and assuming that we want to execute terraform on the main branch, we will configure the service account "Federated identity Provider" with the following configuration: - **Provider Name**: AzureDevOps # This is just an example, you can choose any name you want -- **Issuer URL**: https://vstoken.azure.com/{ORGANIZATION_ID} # This is the public issuer for Azure DevOps OIDC tokens +- **Issuer URL**: https://vstoken.dev.azure.com/{ORGANIZATION_ID} # This is the public issuer for Azure DevOps OIDC tokens + - For most organizations, the URL uses `vstoken.dev.azure.com`, but some legacy organizations might use 'vstoken.azure.com' To be 100% sure, you can inspect the `iss` claim in a decoded OIDC token from your pipeline, + - How to find your ORGANIZATION_ID? + - Via Browser: Go to https://dev.azure.com/{YOUR_ORG_NAME}/_apis/connectionData and copy the value of instanceId. + - Via Pipeline: Add a script step echo $(System.CollectionId) to print it during a run. - **Assertions**: - **aud**->equals->api://AzureADTokenExchange # Mandatory value - **sub**->equals->p://myorg/project-abc/terraform-ado-oidc # This is the pipeline where the process is running - - **rpo_ref**->equals->refs/heads/main # This is the branch where the pipeline will run, can be used instead of sub claim + - **rpo_ref**->equals->refs/heads/main # This is the branch where the pipeline will run > Note: This is just an example, you can use more or less fine-grained assertions. diff --git a/templates/guides/workload_identity_federation.md.tmpl b/templates/guides/workload_identity_federation.md.tmpl index 6a7fb6446..bea8d3897 100644 --- a/templates/guides/workload_identity_federation.md.tmpl +++ b/templates/guides/workload_identity_federation.md.tmpl @@ -38,16 +38,20 @@ execute terraform on the main branch, we will configure the service account "Fed ### Azure DevOps assertions Azure DevOps supports OIDC authentication using the public issuer "https://vstoken.azure.com" (for Azure DevOps Server you should check your issuer URL) and setting information like organization, project, and pipeline -as part of the OIDC token claims. +as part of the OIDC token claims. Using a hypothetical pipeline named `terraform-ado-oidc` inside the prohect 'https://myorg.azure.com/project-abc` as example and assuming that we want to execute terraform on the main branch, we will configure the service account "Federated identity Provider" with the following configuration: - **Provider Name**: AzureDevOps # This is just an example, you can choose any name you want -- **Issuer URL**: https://vstoken.azure.com/{ORGANIZATION_ID} # This is the public issuer for Azure DevOps OIDC tokens +- **Issuer URL**: https://vstoken.dev.azure.com/{ORGANIZATION_ID} # This is the public issuer for Azure DevOps OIDC tokens + - For most organizations, the URL uses `vstoken.dev.azure.com`, but some legacy organizations might use 'vstoken.azure.com' To be 100% sure, you can inspect the `iss` claim in a decoded OIDC token from your pipeline, + - How to find your ORGANIZATION_ID? + - Via Browser: Go to https://dev.azure.com/{YOUR_ORG_NAME}/_apis/connectionData and copy the value of instanceId. + - Via Pipeline: Add a script step echo $(System.CollectionId) to print it during a run. - **Assertions**: - **aud**->equals->api://AzureADTokenExchange # Mandatory value - **sub**->equals->p://myorg/project-abc/terraform-ado-oidc # This is the pipeline where the process is running - - **rpo_ref**->equals->refs/heads/main # This is the branch where the pipeline will run, can be used instead of sub claim + - **rpo_ref**->equals->refs/heads/main # This is the branch where the pipeline will run > Note: This is just an example, you can use more or less fine-grained assertions.