Skip to content

Commit 44a0f18

Browse files
feat: Support Azure DevOps OIDC provider (#1240)
* feat: Support Azure DevOps OIDC provider Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * feat: Support Azure DevOps OIDC provider Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * add pipeline Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * add pipeline Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * add pipeline Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * add pipeline Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * add pipeline Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * add pipeline Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * add pipeline Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * add pipeline Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * add pipeline Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * add pipeline Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * chore: Add Azure DevOps adapter Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * chore: Add Azure DevOps adapter Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * chore: Add Azure DevOps adapter Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * chore: Add Azure DevOps adapter Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * remove testing file Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * support azure devops Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * support azure devops Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * support azure devops Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * remove replace Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * feedback Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> * fix grammar Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> --------- Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz> Co-authored-by: cgoetz-inovex <carlo.goetz@inovex.de>
1 parent 87fc09a commit 44a0f18

File tree

7 files changed

+229
-18
lines changed

7 files changed

+229
-18
lines changed

docs/guides/workload_identity_federation.md

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
---
2-
page_title: "Workload Identity Federation with GitHub Actions"
2+
page_title: "Workload Identity Federation"
33
---
44

55
# Workload Identity Federation with GitHub Actions
66

77
Workload Identity Federation (WIF) allows you to authenticate the STACKIT Terraform provider without using long-lived Service Account keys.
88
This is particularly useful in CI/CD environments like **GitHub Actions**, **GitLab CI**, or **Azure DevOps**, where you can use short-lived
9-
OIDC tokens. This guide focuses on using WIF with GitHub Actions, but the principles may apply to other CI/CD platforms that support OIDC.
9+
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.
1010

1111
## Prerequisites
1212

@@ -16,8 +16,13 @@ Before using Workload Identity Federation flow, you need to:
1616
## Setup Workload Identity Federation
1717

1818
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)
19-
but for the purpose of this guide we will focus on GitHub Actions as OIDC provider. GitHub Actions supports OIDC authentication using
20-
the public issuer "https://token.actions.githubusercontent.com" (for GH Enterprise you should check your issuer URL) and setting repository and action information
19+
but for the purpose of this guide we will focus on GitHub Actions and AzureDevOps as OIDC providers.
20+
21+
> Important: You should use the most restrictive assertions possible by validating all available data from the OIDC token. This prevents security risks associated with trusting tokens issued outside the specific context of your CI/CD pipeline.
22+
23+
### GitHub Actions assertions
24+
25+
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
2126
as part of the OIDC token claims. [More info here](https://docs.github.com/es/actions/concepts/security/openid-connect).
2227

2328
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,27 @@ execute terraform on the main branch, we will configure the service account "Fed
2833
- **sub**->equals->repo:stackitcloud/terraform-provider-stackit:ref:refs/heads/main # This is the repository and branch where the action will run
2934
- **aud**->equals->sts.accounts.stackit.cloud # Mandatory value
3035

31-
> 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)
36+
> 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)
37+
38+
### Azure DevOps assertions
39+
40+
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
41+
as part of the OIDC token claims.
42+
43+
Using a hypothetical pipeline named `terraform-ado-oidc` inside the project 'https://myorg.azure.com/project-abc` as example and assuming that we want to
44+
execute terraform on the main branch, we will configure the service account "Federated identity Provider" with the following configuration:
45+
- **Provider Name**: AzureDevOps # This is just an example, you can choose any name you want
46+
- **Issuer URL**: https://vstoken.dev.azure.com/{ORGANIZATION_ID} # This is the public issuer for Azure DevOps OIDC tokens
47+
- 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.
48+
- How to find your ORGANIZATION_ID?
49+
- Via Browser: Go to https://dev.azure.com/{YOUR_ORG_NAME}/_apis/connectionData and copy the value of instanceId.
50+
- Via Pipeline: Add a script step echo $(System.CollectionId) to print it during a run.
51+
- **Assertions**:
52+
- **aud**->equals->api://AzureADTokenExchange # Mandatory value
53+
- **sub**->equals->p://myorg/project-abc/terraform-ado-oidc # This is the pipeline where the process is running
54+
- **rpo_ref**->equals->refs/heads/main # This is the branch where the pipeline will run
55+
56+
> Note: This is just an example, you can use more or less fine-grained assertions.
3257
3358
## Provider Configuration
3459

@@ -50,7 +75,9 @@ provider "stackit" {
5075
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
5176
change your provider configuration and the provider will automatically fetch the OIDC token and exchange it for a short-lived access token.
5277

53-
## Example GitHub Actions Workflow
78+
## Examples
79+
80+
### GitHub Actions Workflow
5481

5582
> 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).
5683
@@ -120,3 +147,75 @@ jobs:
120147
STACKIT_USE_OIDC: "1"
121148
STACKIT_SERVICE_ACCOUNT_EMAIL: "terraform-example@sa.stackit.cloud"
122149
```
150+
151+
### Azure Pipeline
152+
153+
```yaml
154+
trigger:
155+
branches:
156+
include:
157+
- main
158+
159+
pool:
160+
vmImage: "ubuntu-latest"
161+
162+
variables:
163+
STACKIT_USE_OIDC: "1"
164+
STACKIT_SERVICE_ACCOUNT_EMAIL: "terraform-example@sa.stackit.cloud"
165+
166+
jobs:
167+
- job: demo-job
168+
displayName: "Workload Identity Federation with STACKIT"
169+
steps:
170+
- checkout: self
171+
172+
- task: TerraformInstaller@1
173+
inputs:
174+
terraformVersion: "latest"
175+
displayName: "Setup Terraform"
176+
177+
- script: |
178+
cat <<EOF > main.tf
179+
terraform {
180+
required_providers {
181+
stackit = {
182+
source = "stackitcloud/stackit"
183+
}
184+
}
185+
}
186+
187+
provider "stackit" {
188+
default_region = "eu01"
189+
}
190+
191+
resource "stackit_service_account" "sa" {
192+
project_id = "e1925fbf-5272-497a-8298-1586760670de"
193+
name = "terraform-example-ci"
194+
}
195+
EOF
196+
displayName: "Create Test Configuration"
197+
198+
- script: |
199+
terraform init
200+
displayName: "Terraform Init"
201+
env:
202+
STACKIT_USE_OIDC: $(STACKIT_USE_OIDC)
203+
STACKIT_SERVICE_ACCOUNT_EMAIL: $(STACKIT_SERVICE_ACCOUNT_EMAIL)
204+
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
205+
206+
- script: |
207+
terraform plan -out=tfplan
208+
displayName: "Terraform Plan"
209+
env:
210+
STACKIT_USE_OIDC: $(STACKIT_USE_OIDC)
211+
STACKIT_SERVICE_ACCOUNT_EMAIL: $(STACKIT_SERVICE_ACCOUNT_EMAIL)
212+
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
213+
214+
- script: |
215+
terraform apply -auto-approve tfplan
216+
displayName: "Terraform Apply"
217+
env:
218+
STACKIT_USE_OIDC: $(STACKIT_USE_OIDC)
219+
STACKIT_SERVICE_ACCOUNT_EMAIL: $(STACKIT_SERVICE_ACCOUNT_EMAIL)
220+
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
221+
```

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ When using Workload Identity Federation (WIF), you don't need a static service a
7474

7575
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).
7676

77-
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).
77+
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).
7878

7979
In addition to this, you must set the `service_account_email` to specify which service account to impersonate.
8080

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ require (
1111
github.com/hashicorp/terraform-plugin-go v0.29.0
1212
github.com/hashicorp/terraform-plugin-log v0.10.0
1313
github.com/hashicorp/terraform-plugin-testing v1.14.0
14-
github.com/stackitcloud/stackit-sdk-go/core v0.21.1
14+
github.com/stackitcloud/stackit-sdk-go/core v0.22.0
1515
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.10.0
1616
github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.6
1717
github.com/stackitcloud/stackit-sdk-go/services/edge v0.4.3

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
109109
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
110110
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
111111
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
112+
github.com/jorturfer/stackit-sdk-go/core v0.0.0-20260218233050-3f219dc9de13 h1:SUA7JGasPoMs6ubO0xH0Lp+a6wQ+hoJF+KLVf5dDCis=
113+
github.com/jorturfer/stackit-sdk-go/core v0.0.0-20260218233050-3f219dc9de13/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI=
112114
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
113115
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
114116
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -149,8 +151,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
149151
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
150152
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
151153
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
152-
github.com/stackitcloud/stackit-sdk-go/core v0.21.1 h1:Y/PcAgM7DPYMNqum0MLv4n1mF9ieuevzcCIZYQfm3Ts=
153-
github.com/stackitcloud/stackit-sdk-go/core v0.21.1/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI=
154+
github.com/stackitcloud/stackit-sdk-go/core v0.22.0 h1:6rViz7GnNwXSh51Lur5xuDzO8EWSZfN9J0HvEkBKq6c=
155+
github.com/stackitcloud/stackit-sdk-go/core v0.22.0/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI=
154156
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0 h1:HxPgBu04j5tj6nfZ2r0l6v4VXC0/tYOGe4sA5Addra8=
155157
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0/go.mod h1:uYI9pHAA2g84jJN25ejFUxa0/JtfpPZqMDkctQ1BzJk=
156158
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.10.0 h1:YALzjYAApyQMKyt4C2LKhPRZHa6brmbFeKuuwl+KOTs=

stackit/provider.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
549549
sdkConfig.ServiceAccountFederatedTokenFunc = oidcadapters.ReadJWTFromFileSystem(oidc_token_path)
550550
}
551551

552-
// Workload Identity Federation via provided OIDC Token from GitHub Actions
552+
// Workload Identity Federation via provided OIDC Token from GitHub Actions or Azure DevOps
553553
if sdkConfig.ServiceAccountFederatedTokenFunc == nil && utils.GetEnvBoolIfValueAbsent(providerConfig.UseOIDC, "STACKIT_USE_OIDC") {
554554
sdkConfig.WorkloadIdentityFederation = true
555555
// 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,
558558
if oidcReqURL != "" && oidcReqToken != "" {
559559
sdkConfig.ServiceAccountFederatedTokenFunc = oidcadapters.RequestGHOIDCToken(oidcReqURL, oidcReqToken)
560560
}
561+
562+
// https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/oidctoken/create?view=azure-devops-rest-7.1#taskhuboidctoken
563+
// https://learn.microsoft.com/es-es/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml
564+
oidcReqURL = utils.GetEnvStringOrDefault(providerConfig.OIDCTokenRequestURL, "SYSTEM_OIDCREQUESTURI", "")
565+
oidcReqToken = utils.GetEnvStringOrDefault(providerConfig.OIDCTokenRequestToken, "SYSTEM_ACCESSTOKEN", "")
566+
// 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
567+
// for users that don't need it, can be added as an additional provider config parameter in the future if there is demand
568+
serviceConnectionID := ""
569+
if oidcReqURL != "" && oidcReqToken != "" {
570+
sdkConfig.ServiceAccountFederatedTokenFunc = oidcadapters.RequestAzureDevOpsOIDCToken(oidcReqURL, oidcReqToken, serviceConnectionID)
571+
}
561572
}
562573

563574
roundTripper, err := sdkauth.SetupAuth(sdkConfig)

0 commit comments

Comments
 (0)