Skip to content

Commit 817d96c

Browse files
authored
feat: Support Workload Identity Federation flow (#1109)
- add support for workload identity federation flow as auth method in the Terraform provider - add guide how to use workload identity federation flow in Github actions
1 parent 3703051 commit 817d96c

File tree

12 files changed

+744
-143
lines changed

12 files changed

+744
-143
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
---
2+
page_title: "Workload Identity Federation with GitHub Actions"
3+
---
4+
5+
# Workload Identity Federation with GitHub Actions
6+
7+
Workload Identity Federation (WIF) allows you to authenticate the STACKIT Terraform provider without using long-lived Service Account keys.
8+
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.
10+
11+
## Prerequisites
12+
13+
Before using Workload Identity Federation flow, you need to:
14+
1. [Create](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/how-tos/manage-service-accounts/) a **Service Account** on STACKIT.
15+
16+
## Setup Workload Identity Federation
17+
18+
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
21+
as part of the OIDC token claims. [More info here](https://docs.github.com/es/actions/concepts/security/openid-connect).
22+
23+
Using this provider [repository](https://github.com/stackitcloud/terraform-provider-stackit) (stackitcloud/terraform-provider-stackit) as example and assuming that we want to
24+
execute terraform on the main branch, we will configure the service account "Federated identity Provider" with the following configuration:
25+
- **Provider Name**: GitHub # This is just an example, you can choose any name you want
26+
- **Issuer URL**: https://token.actions.githubusercontent.com # This is the public issuer for GitHub Actions OIDC tokens
27+
- **Assertions**:
28+
- **sub**->equals->repo:stackitcloud/terraform-provider-stackit:ref:refs/heads/main # This is the repository and branch where the action will run
29+
- **aud**->equals->sts.accounts.stackit.cloud # Mandatory value
30+
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)
32+
33+
## Provider Configuration
34+
35+
To use WIF, you must set an `use_oidc` flag to `true` as well as provide an OIDC token for the exchange. While you can provide the token directly in the configuration
36+
through `service_account_federated_token`, this is not recommended for GitHub Actions as the provider will automatically fetch the token from the GitHub OIDC.
37+
38+
In addition to this, you need to set the `service_account_email` to specify which service account you want to use. This is mandatory as the provider needs to know which service account to exchange the token for.
39+
40+
```hcl
41+
provider "stackit" {
42+
service_account_email = "terraform-example@sa.stackit.cloud"
43+
use_oidc = true
44+
... # Other provider configuration
45+
}
46+
```
47+
48+
### Using Environment Variables (Recommended)
49+
50+
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
51+
change your provider configuration and the provider will automatically fetch the OIDC token and exchange it for a short-lived access token.
52+
53+
## Example GitHub Actions Workflow
54+
55+
> 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).
56+
57+
```yaml
58+
name: Workload Identity Federation with STACKIT
59+
60+
on:
61+
push:
62+
branches:
63+
- '**'
64+
65+
jobs:
66+
demo-job:
67+
name: Workload Identity Federation with STACKIT
68+
runs-on: ubuntu-latest
69+
permissions:
70+
contents: read
71+
id-token: write
72+
73+
steps:
74+
- name: Checkout Code
75+
uses: actions/checkout@v4
76+
77+
- name: Setup Terraform
78+
uses: hashicorp/setup-terraform@v3
79+
with:
80+
terraform_wrapper: false
81+
82+
- name: Create Test Configuration
83+
run: |
84+
cat <<EOF > main.tf
85+
terraform {
86+
required_providers {
87+
stackit = {
88+
source = "stackitcloud/stackit"
89+
}
90+
}
91+
}
92+
93+
provider "stackit" {
94+
default_region = "eu01"
95+
}
96+
97+
resource "stackit_service_account" "sa" {
98+
project_id = "e1925fbf-5272-497a-8298-1586760670de"
99+
name = "terraform-example-ci"
100+
}
101+
EOF
102+
103+
- name: Terraform Init
104+
run: |
105+
terraform init
106+
env:
107+
STACKIT_USE_OIDC: "1"
108+
STACKIT_SERVICE_ACCOUNT_EMAIL: "terraform-example@sa.stackit.cloud"
109+
110+
- name: Terraform Plan
111+
run: |
112+
terraform plan -out=tfplan
113+
env:
114+
STACKIT_USE_OIDC: "1"
115+
STACKIT_SERVICE_ACCOUNT_EMAIL: "terraform-example@sa.stackit.cloud"
116+
117+
- name: Terraform Apply
118+
run: terraform apply -auto-approve tfplan
119+
env:
120+
STACKIT_USE_OIDC: "1"
121+
STACKIT_SERVICE_ACCOUNT_EMAIL: "terraform-example@sa.stackit.cloud"
122+
```

docs/index.md

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,20 @@ provider "stackit" {
1111
1212
# Authentication
1313
14-
# Token flow (scheduled for deprecation and will be removed on December 17, 2025)
14+
# Workload Identity Federation flow
1515
provider "stackit" {
16-
default_region = "eu01"
17-
service_account_token = var.service_account_token
16+
default_region = "eu01"
17+
service_account_email = var.service_account_email
18+
service_account_federated_token = var.service_account_federated_token
19+
use_oidc = true
20+
}
21+
22+
# Workload Identity Federation flow (using path)
23+
provider "stackit" {
24+
default_region = "eu01"
25+
service_account_email = var.service_account_email
26+
service_account_federated_token_path = var.service_account_federated_token_path
27+
use_oidc = true
1828
}
1929
2030
# Key flow
@@ -36,13 +46,13 @@ provider "stackit" {
3646

3747
To authenticate, you will need a [service account](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/). Create it in the [STACKIT Portal](https://portal.stackit.cloud/) and assign the necessary permissions to it, e.g. `project.owner`. There are multiple ways to authenticate:
3848

39-
- Key flow (recommended)
40-
- Token flow (is scheduled for deprecation and will be removed on December 17, 2025)
49+
- Workload Identity Federation
50+
- Key flow
4151

42-
When setting up authentication, the provider will always try to use the key flow first and search for credentials in several locations, following a specific order:
52+
When setting up authentication, the provider will always try to use the workload identity federation flow first and search for credentials in several locations, following a specific order:
4353

44-
1. Explicit configuration, e.g. by setting the field `service_account_key_path` in the provider block (see example below)
45-
2. Environment variable, e.g. by setting `STACKIT_SERVICE_ACCOUNT_KEY_PATH`
54+
1. Explicit configuration, e.g. by setting the field `use_oidc` in the provider block (see example below)
55+
2. Environment variable, e.g. by setting `STACKIT_USE_OIDC`
4656
3. Credentials file
4757

4858
The provider will check the credentials file located in the path defined by the `STACKIT_CREDENTIALS_PATH` env var, if specified,
@@ -51,12 +61,23 @@ When setting up authentication, the provider will always try to use the key flow
5161

5262
```json
5363
{
54-
"STACKIT_SERVICE_ACCOUNT_TOKEN": "foo_token",
5564
"STACKIT_SERVICE_ACCOUNT_KEY_PATH": "path/to/sa_key.json",
5665
"STACKIT_PRIVATE_KEY_PATH": "path/to/private_key.pem"
5766
}
5867
```
5968

69+
### Workload Identity Federation
70+
71+
The following instructions assume that you have created a service account and assigned the necessary permissions to it, e.g. `project.owner`.
72+
73+
When using Workload Identity Federation (WIF), you don't need a static service account secret or key. Instead, the provider exchanges a short-lived OIDC token (from GitHub Actions, GitLab CI, etc.) for a STACKIT access token. This is the most secure way to authenticate in CI/CD environments as it eliminates the need for long-lived secrets.
74+
75+
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).
76+
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).
78+
79+
In addition to this, you must set the `service_account_email` to specify which service account to impersonate.
80+
6081
### Key flow
6182

6283
The following instructions assume that you have created a service account and assigned the necessary permissions to it, e.g. `project.owner`.
@@ -67,7 +88,7 @@ When creating the service account key, a new pair can be created automatically,
6788

6889
**Optionally**, you can provide your own private key when creating the service account key, which will then require you to also provide it explicitly to the [STACKIT Terraform Provider](https://github.com/stackitcloud/terraform-provider-stackit), additionally to the service account key. Check the STACKIT Docs for an [example of how to create your own key-pair](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/how-tos/manage-service-account-keys/).
6990

70-
To configure the key flow, follow this steps:
91+
To configure the key flow, follow these steps:
7192

7293
1. Create a service account key:
7394

@@ -109,17 +130,6 @@ To configure the key flow, follow this steps:
109130
> - setting the environment variable: `STACKIT_PRIVATE_KEY_PATH`
110131
> - setting `STACKIT_PRIVATE_KEY_PATH` in the credentials file (see above)
111132
112-
113-
### Token flow
114-
115-
> Is scheduled for deprecation and will be removed on December 17, 2025.
116-
117-
Using this flow is less secure since the token is long-lived. You can provide the token in several ways:
118-
119-
1. Setting the field `service_account_token` in the provider
120-
2. Setting the environment variable `STACKIT_SERVICE_ACCOUNT_TOKEN`
121-
3. Setting it in the credentials file (see above)
122-
123133
# Backend configuration
124134

125135
To keep track of your terraform state, you can configure an [S3 backend](https://developer.hashicorp.com/terraform/language/settings/backends/s3) using [STACKIT Object Storage](https://docs.stackit.cloud/products/storage/object-storage).
@@ -172,6 +182,8 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
172182
- `mongodbflex_custom_endpoint` (String) Custom endpoint for the MongoDB Flex service
173183
- `objectstorage_custom_endpoint` (String) Custom endpoint for the Object Storage service
174184
- `observability_custom_endpoint` (String) Custom endpoint for the Observability service
185+
- `oidc_request_token` (String) The bearer token for the request to the OIDC provider. For use when authenticating as a Service Account using OpenID Connect.
186+
- `oidc_request_url` (String) The URL for the OIDC provider from which to request an ID token. For use when authenticating as a Service Account using OpenID Connect.
175187
- `opensearch_custom_endpoint` (String) Custom endpoint for the OpenSearch service
176188
- `postgresflex_custom_endpoint` (String) Custom endpoint for the PostgresFlex service
177189
- `private_key` (String) Private RSA key used for authentication, relevant for the key flow. It takes precedence over the private key that is included in the service account key.
@@ -185,7 +197,9 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
185197
- `server_backup_custom_endpoint` (String) Custom endpoint for the Server Backup service
186198
- `server_update_custom_endpoint` (String) Custom endpoint for the Server Update service
187199
- `service_account_custom_endpoint` (String) Custom endpoint for the Service Account service
188-
- `service_account_email` (String, Deprecated) Service account email. It can also be set using the environment variable STACKIT_SERVICE_ACCOUNT_EMAIL. It is required if you want to use the resource manager project resource.
200+
- `service_account_email` (String) Service account email. It can also be set using the environment variable STACKIT_SERVICE_ACCOUNT_EMAIL. It is required if you want to use the resource manager project resource. This value is required using OpenID Connect authentication.
201+
- `service_account_federated_token` (String) The OIDC ID token for use when authenticating as a Service Account using OpenID Connect.
202+
- `service_account_federated_token_path` (String) Path for workload identity assertion. It can also be set using the environment variable STACKIT_FEDERATED_TOKEN_FILE.
189203
- `service_account_key` (String) Service account key used for authentication. If set, the key flow will be used to authenticate all operations.
190204
- `service_account_key_path` (String) Path for the service account key used for authentication. If set, the key flow will be used to authenticate all operations.
191205
- `service_account_token` (String, Deprecated) Token used for authentication. If set, the token flow will be used to authenticate all operations.
@@ -194,3 +208,4 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
194208
- `ske_custom_endpoint` (String) Custom endpoint for the Kubernetes Engine (SKE) service
195209
- `sqlserverflex_custom_endpoint` (String) Custom endpoint for the SQL Server Flex service
196210
- `token_custom_endpoint` (String) Custom endpoint for the token API, which is used to request access tokens when using the key flow
211+
- `use_oidc` (Boolean) Enables OIDC for Authentication. This can also be sourced from the `STACKIT_USE_OIDC` Environment Variable. Defaults to `false`.

examples/provider/provider.tf

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,20 @@ provider "stackit" {
44

55
# Authentication
66

7-
# Token flow (scheduled for deprecation and will be removed on December 17, 2025)
7+
# Workload Identity Federation flow
88
provider "stackit" {
9-
default_region = "eu01"
10-
service_account_token = var.service_account_token
9+
default_region = "eu01"
10+
service_account_email = var.service_account_email
11+
service_account_federated_token = var.service_account_federated_token
12+
use_oidc = true
13+
}
14+
15+
# Workload Identity Federation flow (using path)
16+
provider "stackit" {
17+
default_region = "eu01"
18+
service_account_email = var.service_account_email
19+
service_account_federated_token_path = var.service_account_federated_token_path
20+
use_oidc = true
1121
}
1222

1323
# Key flow

stackit/internal/conversion/conversion_test.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package conversion
22

33
import (
44
"context"
5+
"crypto/tls"
6+
"net/http"
57
"reflect"
68
"testing"
79

@@ -306,6 +308,9 @@ func TestParseProviderData(t *testing.T) {
306308
}
307309

308310
func TestParseEphemeralProviderData(t *testing.T) {
311+
var randomRoundTripper http.RoundTripper = &http.Transport{
312+
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS13},
313+
}
309314
type args struct {
310315
providerData any
311316
}
@@ -354,21 +359,17 @@ func TestParseEphemeralProviderData(t *testing.T) {
354359
name: "valid provider data 2",
355360
args: args{
356361
providerData: core.EphemeralProviderData{
357-
PrivateKey: "",
358-
PrivateKeyPath: "/home/dev/foo/private-key.json",
359-
ServiceAccountKey: "",
360-
ServiceAccountKeyPath: "/home/dev/foo/key.json",
361-
TokenCustomEndpoint: "",
362+
ProviderData: core.ProviderData{
363+
RoundTripper: randomRoundTripper,
364+
},
362365
},
363366
},
364367
want: want{
365368
ok: true,
366369
providerData: core.EphemeralProviderData{
367-
PrivateKey: "",
368-
PrivateKeyPath: "/home/dev/foo/private-key.json",
369-
ServiceAccountKey: "",
370-
ServiceAccountKeyPath: "/home/dev/foo/key.json",
371-
TokenCustomEndpoint: "",
370+
ProviderData: core.ProviderData{
371+
RoundTripper: randomRoundTripper,
372+
},
372373
},
373374
},
374375
wantErr: false,

stackit/internal/core/core.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,11 @@ const (
2929

3030
type EphemeralProviderData struct {
3131
ProviderData
32-
33-
PrivateKey string
34-
PrivateKeyPath string
35-
ServiceAccountKey string
36-
ServiceAccountKeyPath string
37-
TokenCustomEndpoint string
3832
}
3933

4034
type ProviderData struct {
4135
RoundTripper http.RoundTripper
42-
ServiceAccountEmail string // Deprecated: ServiceAccountEmail is not required and will be removed after 12th June 2025.
36+
ServiceAccountEmail string
4337
// Deprecated: Use DefaultRegion instead
4438
Region string
4539
DefaultRegion string

0 commit comments

Comments
 (0)