From 738e7aefc89609a33d0139e1fc838825aac1f05e Mon Sep 17 00:00:00 2001 From: Jenna Goldstrich Date: Wed, 5 Jun 2024 14:01:23 -0700 Subject: [PATCH] feat: Add support for GitHub Actions OIDC via oidc_request_url and oidc_request_token fields (#421) * wip * Add unit tests * Fix docs, remove 2 hour powershell test * Remove OIDC test * Chroot shares the same client config as ARM unfortunately, so lets just pass through these values to be consistent, its feasible that someone would run the chroot builder in GHA to be fair, but I am not spending too much time testing it * Same with DTL as chroot * Address Lucas's feedback about auth docs and superfluous line in test * Add Microsoft's doc link for GitHub OIDC * I noticed DTL docs weren't updated and that's because they don't have the auth info included, adding that --- .web-docs/README.md | 11 +-- .web-docs/components/builder/arm/README.md | 7 ++ .web-docs/components/builder/chroot/README.md | 7 ++ .web-docs/components/builder/dtl/README.md | 52 ++++++++++++++ builder/azure/arm/builder.go | 2 + builder/azure/arm/config.hcl2spec.go | 4 ++ builder/azure/chroot/builder.hcl2spec.go | 4 ++ .../azure/common/client/azure_authorizer.go | 11 +++ .../azure/common/client/azure_client_set.go | 2 + builder/azure/common/client/config.go | 34 ++++++++-- builder/azure/common/client/config_test.go | 67 +++++++++++++++++++ builder/azure/dtl/builder.go | 2 + builder/azure/dtl/config.hcl2spec.go | 4 ++ .../common/client/Config-not-required.mdx | 7 ++ .../builder/azure/common/client/Config.mdx | 11 +-- docs/builders/dtl.mdx | 2 + example/github-oidc-example.pkr.hcl | 48 +++++++++++++ .../azure-dtlartifact/provisioner.hcl2spec.go | 4 ++ 18 files changed, 264 insertions(+), 15 deletions(-) create mode 100644 example/github-oidc-example.pkr.hcl diff --git a/.web-docs/README.md b/.web-docs/README.md index 7c78f50b..64c9ae2e 100644 --- a/.web-docs/README.md +++ b/.web-docs/README.md @@ -43,10 +43,13 @@ Packer can create Azure virtual machine images through variety of ways depending Config allows for various ways to authenticate Azure clients. When -`client_id` and `subscription_id` are specified in addition to one and only -one of the following: `client_secret`, `client_jwt`, `client_cert_path` -- -Packer will use the specified Azure Active Directory (AAD) Service Principal -(SP). +`client_id` and `subscription_id` are specified in addition to one of the following +* `client_secret` +* `client_jwt` +* `client_cert_path` +* `oidc_request_url` combined with `oidc_request_token` + +Packer will use the specified Azure Active Directory (AAD) Service Principal (SP). If none of these options are specified, Packer will attempt to use the Managed Identity and subscription of the VM that Packer is running on. This will only work if Packer is running on an Azure VM with either a System Assigned Managed diff --git a/.web-docs/components/builder/arm/README.md b/.web-docs/components/builder/arm/README.md index deeab8c8..31be2dc0 100644 --- a/.web-docs/components/builder/arm/README.md +++ b/.web-docs/components/builder/arm/README.md @@ -603,6 +603,13 @@ Providing `temp_resource_group_name` or `location` in combination with - `subscription_id` (string) - The subscription to use. +- `oidc_request_token` (string) - OIDC Request Token is used for GitHub Actions OIDC, this token is used with oidc_request_url to fetch access tokens to Azure + Value in GitHub Actions can be extracted from the `ACTIONS_ID_TOKEN_REQUEST_TOKEN` variable + Refer to [Configure a federated identity credential on an app](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#github-actions) for details on how setup GitHub Actions OIDC authentication + +- `oidc_request_url` (string) - OIDC Request URL is used for GitHub Actions OIDC, this token is used with oidc_request_url to fetch access tokens to Azure + Value in GitHub Actions can be extracted from the `ACTIONS_ID_TOKEN_REQUEST_URL` variable + - `use_azure_cli_auth` (bool) - Flag to use Azure CLI authentication. Defaults to false. CLI auth will use the information from an active `az login` session to connect to Azure and set the subscription id and tenant id associated to the signed in account. If enabled, it will use the authentication provided by the `az` CLI. diff --git a/.web-docs/components/builder/chroot/README.md b/.web-docs/components/builder/chroot/README.md index b7cdc405..6e9028d6 100644 --- a/.web-docs/components/builder/chroot/README.md +++ b/.web-docs/components/builder/chroot/README.md @@ -83,6 +83,13 @@ information. - `subscription_id` (string) - The subscription to use. +- `oidc_request_token` (string) - OIDC Request Token is used for GitHub Actions OIDC, this token is used with oidc_request_url to fetch access tokens to Azure + Value in GitHub Actions can be extracted from the `ACTIONS_ID_TOKEN_REQUEST_TOKEN` variable + Refer to [Configure a federated identity credential on an app](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#github-actions) for details on how setup GitHub Actions OIDC authentication + +- `oidc_request_url` (string) - OIDC Request URL is used for GitHub Actions OIDC, this token is used with oidc_request_url to fetch access tokens to Azure + Value in GitHub Actions can be extracted from the `ACTIONS_ID_TOKEN_REQUEST_URL` variable + - `use_azure_cli_auth` (bool) - Flag to use Azure CLI authentication. Defaults to false. CLI auth will use the information from an active `az login` session to connect to Azure and set the subscription id and tenant id associated to the signed in account. If enabled, it will use the authentication provided by the `az` CLI. diff --git a/.web-docs/components/builder/dtl/README.md b/.web-docs/components/builder/dtl/README.md index f6d08261..0f17fc2e 100644 --- a/.web-docs/components/builder/dtl/README.md +++ b/.web-docs/components/builder/dtl/README.md @@ -179,6 +179,58 @@ options. In addition to the options listed here, a [communicator](/packer/docs/t + + +- `cloud_environment_name` (string) - One of Public, China, or + USGovernment. Defaults to Public. Long forms such as + USGovernmentCloud and AzureUSGovernmentCloud are also supported. + +- `metadata_host` (string) - The Hostname of the Azure Metadata Service + (for example management.azure.com), used to obtain the Cloud Environment + when using a Custom Azure Environment. This can also be sourced from the + ARM_METADATA_HOST Environment Variable. + Note: CloudEnvironmentName must be set to the requested environment + name in the list of available environments held in the metadata_host. + +- `client_id` (string) - The application ID of the AAD Service Principal. + Requires either `client_secret`, `client_cert_path` or `client_jwt` to be set as well. + +- `client_secret` (string) - A password/secret registered for the AAD SP. + +- `client_cert_path` (string) - The path to a PKCS#12 bundle (.pfx file) to be used as the client certificate + that will be used to authenticate as the specified AAD SP. + +- `client_cert_password` (string) - The password for decrypting the client certificate bundle. + +- `client_jwt` (string) - A JWT bearer token for client auth (RFC 7523, Sec. 2.2) that will be used + to authenticate the AAD SP. Provides more control over token the expiration + when using certificate authentication than when using `client_cert_path`. + +- `object_id` (string) - The object ID for the AAD SP. Optional, will be derived from the oAuth token if left empty. + +- `tenant_id` (string) - The Active Directory tenant identifier with which your `client_id` and + `subscription_id` are associated. If not specified, `tenant_id` will be + looked up using `subscription_id`. + +- `subscription_id` (string) - The subscription to use. + +- `oidc_request_token` (string) - OIDC Request Token is used for GitHub Actions OIDC, this token is used with oidc_request_url to fetch access tokens to Azure + Value in GitHub Actions can be extracted from the `ACTIONS_ID_TOKEN_REQUEST_TOKEN` variable + Refer to [Configure a federated identity credential on an app](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#github-actions) for details on how setup GitHub Actions OIDC authentication + +- `oidc_request_url` (string) - OIDC Request URL is used for GitHub Actions OIDC, this token is used with oidc_request_url to fetch access tokens to Azure + Value in GitHub Actions can be extracted from the `ACTIONS_ID_TOKEN_REQUEST_URL` variable + +- `use_azure_cli_auth` (bool) - Flag to use Azure CLI authentication. Defaults to false. + CLI auth will use the information from an active `az login` session to connect to Azure and set the subscription id and tenant id associated to the signed in account. + If enabled, it will use the authentication provided by the `az` CLI. + Azure CLI authentication will use the credential marked as `isDefault` and can be verified using `az account show`. + Works with normal authentication (`az login`) and service principals (`az login --service-principal --username APP_ID --password PASSWORD --tenant TENANT_ID`). + Ignores all other configurations if enabled. + + + + - `skip_create_image` (bool) - Skip creating the image. diff --git a/builder/azure/arm/builder.go b/builder/azure/arm/builder.go index 5d3dbed8..1e455913 100644 --- a/builder/azure/arm/builder.go +++ b/builder/azure/arm/builder.go @@ -98,6 +98,8 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) ClientCertPassword: b.config.ClientConfig.ClientCertPassword, TenantID: b.config.ClientConfig.TenantID, SubscriptionID: b.config.ClientConfig.SubscriptionID, + OidcRequestUrl: b.config.ClientConfig.OidcRequestURL, + OidcRequestToken: b.config.ClientConfig.OidcRequestToken, } ui.Message("Creating Azure Resource Manager (ARM) client ...") diff --git a/builder/azure/arm/config.hcl2spec.go b/builder/azure/arm/config.hcl2spec.go index 16b32fe3..2dd94f90 100644 --- a/builder/azure/arm/config.hcl2spec.go +++ b/builder/azure/arm/config.hcl2spec.go @@ -31,6 +31,8 @@ type FlatConfig struct { ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"` TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"` SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"` + OidcRequestToken *string `mapstructure:"oidc_request_token" cty:"oidc_request_token" hcl:"oidc_request_token"` + OidcRequestURL *string `mapstructure:"oidc_request_url" cty:"oidc_request_url" hcl:"oidc_request_url"` UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"` UserAssignedManagedIdentities []string `mapstructure:"user_assigned_managed_identities" required:"false" cty:"user_assigned_managed_identities" hcl:"user_assigned_managed_identities"` CaptureNamePrefix *string `mapstructure:"capture_name_prefix" cty:"capture_name_prefix" hcl:"capture_name_prefix"` @@ -181,6 +183,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, "subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false}, + "oidc_request_token": &hcldec.AttrSpec{Name: "oidc_request_token", Type: cty.String, Required: false}, + "oidc_request_url": &hcldec.AttrSpec{Name: "oidc_request_url", Type: cty.String, Required: false}, "use_azure_cli_auth": &hcldec.AttrSpec{Name: "use_azure_cli_auth", Type: cty.Bool, Required: false}, "user_assigned_managed_identities": &hcldec.AttrSpec{Name: "user_assigned_managed_identities", Type: cty.List(cty.String), Required: false}, "capture_name_prefix": &hcldec.AttrSpec{Name: "capture_name_prefix", Type: cty.String, Required: false}, diff --git a/builder/azure/chroot/builder.hcl2spec.go b/builder/azure/chroot/builder.hcl2spec.go index 2e267d63..f90a9016 100644 --- a/builder/azure/chroot/builder.hcl2spec.go +++ b/builder/azure/chroot/builder.hcl2spec.go @@ -29,6 +29,8 @@ type FlatConfig struct { ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"` TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"` SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"` + OidcRequestToken *string `mapstructure:"oidc_request_token" cty:"oidc_request_token" hcl:"oidc_request_token"` + OidcRequestURL *string `mapstructure:"oidc_request_url" cty:"oidc_request_url" hcl:"oidc_request_url"` UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"` FromScratch *bool `mapstructure:"from_scratch" cty:"from_scratch" hcl:"from_scratch"` Source *string `mapstructure:"source" required:"true" cty:"source" hcl:"source"` @@ -86,6 +88,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, "subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false}, + "oidc_request_token": &hcldec.AttrSpec{Name: "oidc_request_token", Type: cty.String, Required: false}, + "oidc_request_url": &hcldec.AttrSpec{Name: "oidc_request_url", Type: cty.String, Required: false}, "use_azure_cli_auth": &hcldec.AttrSpec{Name: "use_azure_cli_auth", Type: cty.Bool, Required: false}, "from_scratch": &hcldec.AttrSpec{Name: "from_scratch", Type: cty.Bool, Required: false}, "source": &hcldec.AttrSpec{Name: "source", Type: cty.String, Required: false}, diff --git a/builder/azure/common/client/azure_authorizer.go b/builder/azure/common/client/azure_authorizer.go index 472123f6..1b418d29 100644 --- a/builder/azure/common/client/azure_authorizer.go +++ b/builder/azure/common/client/azure_authorizer.go @@ -20,6 +20,8 @@ type AzureAuthOptions struct { ClientJWT string ClientCertPath string ClientCertPassword string + OidcRequestUrl string + OidcRequestToken string TenantID string SubscriptionID string } @@ -79,6 +81,15 @@ func buildAuthorizer(ctx context.Context, authOpts AzureAuthOptions, env environ TenantID: authOpts.TenantID, OIDCAssertionToken: authOpts.ClientJWT, } + case AuthTypeOidcURL: + authConfig = auth.Credentials{ + Environment: env, + EnableAuthenticationUsingGitHubOIDC: true, + ClientID: authOpts.ClientID, + TenantID: authOpts.TenantID, + GitHubOIDCTokenRequestURL: authOpts.OidcRequestUrl, + GitHubOIDCTokenRequestToken: authOpts.OidcRequestToken, + } default: return nil, fmt.Errorf("Unexpected AuthType %s set when trying to create Azure Client", authOpts.AuthType) } diff --git a/builder/azure/common/client/azure_client_set.go b/builder/azure/common/client/azure_client_set.go index dc0ab419..5d067eb7 100644 --- a/builder/azure/common/client/azure_client_set.go +++ b/builder/azure/common/client/azure_client_set.go @@ -74,6 +74,8 @@ func new(c Config, say func(string)) (*azureClientSet, error) { ClientCertPath: c.ClientCertPath, ClientCertPassword: c.ClientCertPassword, TenantID: c.TenantID, + OidcRequestUrl: c.OidcRequestURL, + OidcRequestToken: c.OidcRequestToken, SubscriptionID: c.SubscriptionID, } cloudEnv := c.cloudEnvironment diff --git a/builder/azure/common/client/config.go b/builder/azure/common/client/config.go index c8d8a488..55827609 100644 --- a/builder/azure/common/client/config.go +++ b/builder/azure/common/client/config.go @@ -28,10 +28,13 @@ import ( var NullModelSDKErr = fmt.Errorf("Unexpected SDK response, please open an issue on the Azure plugin issue tracker") // Config allows for various ways to authenticate Azure clients. When -// `client_id` and `subscription_id` are specified in addition to one and only -// one of the following: `client_secret`, `client_jwt`, `client_cert_path` -- -// Packer will use the specified Azure Active Directory (AAD) Service Principal -// (SP). +// `client_id` and `subscription_id` are specified in addition to one of the following +// * `client_secret` +// * `client_jwt` +// * `client_cert_path` +// * `oidc_request_url` combined with `oidc_request_token` +// +// Packer will use the specified Azure Active Directory (AAD) Service Principal (SP). // If none of these options are specified, Packer will attempt to use the Managed Identity // and subscription of the VM that Packer is running on. This will only work if // Packer is running on an Azure VM with either a System Assigned Managed @@ -76,7 +79,14 @@ type Config struct { // The subscription to use. SubscriptionID string `mapstructure:"subscription_id"` - authType string + // OIDC Request Token is used for GitHub Actions OIDC, this token is used with oidc_request_url to fetch access tokens to Azure + // Value in GitHub Actions can be extracted from the `ACTIONS_ID_TOKEN_REQUEST_TOKEN` variable + // Refer to [Configure a federated identity credential on an app](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#github-actions) for details on how setup GitHub Actions OIDC authentication + OidcRequestToken string `mapstructure:"oidc_request_token"` + // OIDC Request URL is used for GitHub Actions OIDC, this token is used with oidc_request_url to fetch access tokens to Azure + // Value in GitHub Actions can be extracted from the `ACTIONS_ID_TOKEN_REQUEST_URL` variable + OidcRequestURL string `mapstructure:"oidc_request_url"` + authType string // Flag to use Azure CLI authentication. Defaults to false. // CLI auth will use the information from an active `az login` session to connect to Azure and set the subscription id and tenant id associated to the signed in account. @@ -95,6 +105,7 @@ const ( AuthTypeClientSecret = "ClientSecret" AuthTypeClientCert = "ClientCertificate" AuthTypeClientBearerJWT = "ClientBearerJWT" + AuthTypeOidcURL = "OIDCURL" AuthTypeAzureCLI = "AzureCLI" ) @@ -212,6 +223,10 @@ func (c Config) Validate(errs *packersdk.MultiError) { return } + if c.SubscriptionID != "" && c.ClientID != "" && c.OidcRequestToken != "" && c.OidcRequestURL != "" { + return + } + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("No valid set of authentication values specified:\n"+ " to use the Managed Identity of the current machine, do not specify any of the fields below:\n"+ " - client_secret\n"+ @@ -221,7 +236,8 @@ func (c Config) Validate(errs *packersdk.MultiError) { " to use an Azure Active Directory service principal, specify either:\n"+ " - subscription_id, client_id and client_secret\n"+ " - subscription_id, client_id and client_cert_path\n"+ - " - subscription_id, client_id and client_jwt.")) + " - subscription_id, client_id and client_jwt\n"+ + " - subscription_id, client_id, oidc_request_url, and oidc_request_token.")) } func (c Config) UseCLI() bool { @@ -233,7 +249,9 @@ func (c Config) UseMSI() bool { c.ClientSecret == "" && c.ClientJWT == "" && c.ClientCertPath == "" && - c.TenantID == "" + c.TenantID == "" && + c.OidcRequestToken == "" && + c.OidcRequestURL == "" } // FillParameters capture the user intent from the supplied parameter set in AuthType, retrieves the TenantID and CloudEnvironment if not specified. @@ -248,6 +266,8 @@ func (c *Config) FillParameters() error { c.authType = AuthTypeClientSecret } else if c.ClientCertPath != "" { c.authType = AuthTypeClientCert + } else if c.OidcRequestToken != "" { + c.authType = AuthTypeOidcURL } else { c.authType = AuthTypeClientBearerJWT } diff --git a/builder/azure/common/client/config_test.go b/builder/azure/common/client/config_test.go index 024783b1..3d102ca7 100644 --- a/builder/azure/common/client/config_test.go +++ b/builder/azure/common/client/config_test.go @@ -52,6 +52,13 @@ func Test_ClientConfig_RequiredParametersSet(t *testing.T) { }, wantErr: false, }, + { + name: "oidc request url, oidc request token, client id, and tenant sh", + config: Config{ + TenantID: "ok", + }, + wantErr: true, + }, { name: "client_secret without client_id should error", config: Config{ @@ -158,6 +165,66 @@ func Test_ClientConfig_AzureCli(t *testing.T) { } } +func Test_ClientConfig_GitHubOIDC(t *testing.T) { + retrievedTid := "my-tenant-id" + findTenantID = func(environments.Environment, string) (string, error) { return retrievedTid, nil } + cfg := Config{ + cloudEnvironment: environments.AzurePublic(), + OidcRequestToken: "whatever", + OidcRequestURL: "whatever", + ClientID: "whatever", + SubscriptionID: "whatever", + } + assertValid(t, cfg) + + err := cfg.FillParameters() + if err != nil { + t.Fatalf("Expected nil err, but got: %v", err) + } + + if cfg.AuthType() != AuthTypeOidcURL { + t.Fatalf("Expected authType to be %q, but got: %q", AuthTypeAzureCLI, cfg.AuthType()) + } +} + +func Test_ClientConfig_GitHubOIDC_Rejections(t *testing.T) { + // No Subscription + cfg := Config{ + cloudEnvironment: environments.AzurePublic(), + OidcRequestToken: "whatever", + OidcRequestURL: "whatever", + ClientID: "whatever", + } + assertInvalid(t, cfg) + + // No Request Token + cfg = Config{ + cloudEnvironment: environments.AzurePublic(), + SubscriptionID: "whatever", + OidcRequestURL: "whatever", + ClientID: "whatever", + } + assertInvalid(t, cfg) + + // No Request URL + cfg = Config{ + cloudEnvironment: environments.AzurePublic(), + OidcRequestToken: "whatever", + SubscriptionID: "whatever", + ClientID: "whatever", + } + assertInvalid(t, cfg) + + // No Client ID + cfg = Config{ + cloudEnvironment: environments.AzurePublic(), + OidcRequestToken: "whatever", + SubscriptionID: "whatever", + OidcRequestURL: "whatever", + } + assertInvalid(t, cfg) +} + func getEnvOrSkip(t *testing.T, envVar string) string { v := os.Getenv(envVar) if v == "" { diff --git a/builder/azure/dtl/builder.go b/builder/azure/dtl/builder.go index 5ca70624..61efa42a 100644 --- a/builder/azure/dtl/builder.go +++ b/builder/azure/dtl/builder.go @@ -81,6 +81,8 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) ClientCertPassword: b.config.ClientConfig.ClientCertPassword, TenantID: b.config.ClientConfig.TenantID, SubscriptionID: b.config.ClientConfig.SubscriptionID, + OidcRequestUrl: b.config.ClientConfig.OidcRequestURL, + OidcRequestToken: b.config.ClientConfig.OidcRequestToken, } ui.Message("Creating Azure DevTestLab (DTL) client ...") azureClient, err := NewAzureClient( diff --git a/builder/azure/dtl/config.hcl2spec.go b/builder/azure/dtl/config.hcl2spec.go index a5b263f4..697434fe 100644 --- a/builder/azure/dtl/config.hcl2spec.go +++ b/builder/azure/dtl/config.hcl2spec.go @@ -56,6 +56,8 @@ type FlatConfig struct { ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"` TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"` SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"` + OidcRequestToken *string `mapstructure:"oidc_request_token" cty:"oidc_request_token" hcl:"oidc_request_token"` + OidcRequestURL *string `mapstructure:"oidc_request_url" cty:"oidc_request_url" hcl:"oidc_request_url"` UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"` CaptureNamePrefix *string `mapstructure:"capture_name_prefix" cty:"capture_name_prefix" hcl:"capture_name_prefix"` CaptureContainerName *string `mapstructure:"capture_container_name" cty:"capture_container_name" hcl:"capture_container_name"` @@ -174,6 +176,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, "subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false}, + "oidc_request_token": &hcldec.AttrSpec{Name: "oidc_request_token", Type: cty.String, Required: false}, + "oidc_request_url": &hcldec.AttrSpec{Name: "oidc_request_url", Type: cty.String, Required: false}, "use_azure_cli_auth": &hcldec.AttrSpec{Name: "use_azure_cli_auth", Type: cty.Bool, Required: false}, "capture_name_prefix": &hcldec.AttrSpec{Name: "capture_name_prefix", Type: cty.String, Required: false}, "capture_container_name": &hcldec.AttrSpec{Name: "capture_container_name", Type: cty.String, Required: false}, diff --git a/docs-partials/builder/azure/common/client/Config-not-required.mdx b/docs-partials/builder/azure/common/client/Config-not-required.mdx index 2f373f90..e0b5cd24 100644 --- a/docs-partials/builder/azure/common/client/Config-not-required.mdx +++ b/docs-partials/builder/azure/common/client/Config-not-required.mdx @@ -33,6 +33,13 @@ - `subscription_id` (string) - The subscription to use. +- `oidc_request_token` (string) - OIDC Request Token is used for GitHub Actions OIDC, this token is used with oidc_request_url to fetch access tokens to Azure + Value in GitHub Actions can be extracted from the `ACTIONS_ID_TOKEN_REQUEST_TOKEN` variable + Refer to [Configure a federated identity credential on an app](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#github-actions) for details on how setup GitHub Actions OIDC authentication + +- `oidc_request_url` (string) - OIDC Request URL is used for GitHub Actions OIDC, this token is used with oidc_request_url to fetch access tokens to Azure + Value in GitHub Actions can be extracted from the `ACTIONS_ID_TOKEN_REQUEST_URL` variable + - `use_azure_cli_auth` (bool) - Flag to use Azure CLI authentication. Defaults to false. CLI auth will use the information from an active `az login` session to connect to Azure and set the subscription id and tenant id associated to the signed in account. If enabled, it will use the authentication provided by the `az` CLI. diff --git a/docs-partials/builder/azure/common/client/Config.mdx b/docs-partials/builder/azure/common/client/Config.mdx index f7aa97dd..a4d58822 100644 --- a/docs-partials/builder/azure/common/client/Config.mdx +++ b/docs-partials/builder/azure/common/client/Config.mdx @@ -1,10 +1,13 @@ Config allows for various ways to authenticate Azure clients. When -`client_id` and `subscription_id` are specified in addition to one and only -one of the following: `client_secret`, `client_jwt`, `client_cert_path` -- -Packer will use the specified Azure Active Directory (AAD) Service Principal -(SP). +`client_id` and `subscription_id` are specified in addition to one of the following +* `client_secret` +* `client_jwt` +* `client_cert_path` +* `oidc_request_url` combined with `oidc_request_token` + +Packer will use the specified Azure Active Directory (AAD) Service Principal (SP). If none of these options are specified, Packer will attempt to use the Managed Identity and subscription of the VM that Packer is running on. This will only work if Packer is running on an Azure VM with either a System Assigned Managed diff --git a/docs/builders/dtl.mdx b/docs/builders/dtl.mdx index 39e90e62..ab823dcd 100644 --- a/docs/builders/dtl.mdx +++ b/docs/builders/dtl.mdx @@ -28,6 +28,8 @@ options. In addition to the options listed here, a [communicator](/packer/docs/t @include 'builder/azure/dtl/Config-not-required.mdx' +@include 'builder/azure/common/client/Config-not-required.mdx' + @include 'builder/azure/common/Config-not-required.mdx' #### DtlArtifact diff --git a/example/github-oidc-example.pkr.hcl b/example/github-oidc-example.pkr.hcl new file mode 100644 index 00000000..b98a1745 --- /dev/null +++ b/example/github-oidc-example.pkr.hcl @@ -0,0 +1,48 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + + +variable "arm_client_id" { + type = string + default = "${env("ARM_CLIENT_ID")}" +} + +variable "oidc_request_token" { + type = string + default = "${env("ACTIONS_ID_TOKEN_REQUEST_TOKEN")}" +} + +variable "oidc_request_url" { + type = string + default = "${env("ACTIONS_ID_TOKEN_REQUEST_URL")}" +} + +variable "subscription_id" { + type = string + default = "${env("ARM_SUBSCRIPTION_ID")}" +} + +source "azure-arm" "autogenerated_1" { + client_id = "${var.arm_client_id}" + oidc_request_token = "${var.oidc_request_token}" + oidc_request_url = "${var.oidc_request_url}" + communicator = "winrm" + image_offer = "WindowsServer" + image_publisher = "MicrosoftWindowsServer" + image_sku = "2012-R2-Datacenter" + location = "South Central US" + managed_image_name = "oidc-example" + managed_image_resource_group_name = "packer-acceptance-test" + polling_duration_timeout = "3h" + os_type = "Windows" + subscription_id = "${var.subscription_id}" + vm_size = "Standard_DS2_v2" + winrm_insecure = "true" + winrm_timeout = "3m" + winrm_use_ssl = "true" + winrm_username = "packer" +} + +build { + sources = ["source.azure-arm.autogenerated_1"] +} diff --git a/provisioner/azure-dtlartifact/provisioner.hcl2spec.go b/provisioner/azure-dtlartifact/provisioner.hcl2spec.go index 3ae83f19..93f5bfe1 100644 --- a/provisioner/azure-dtlartifact/provisioner.hcl2spec.go +++ b/provisioner/azure-dtlartifact/provisioner.hcl2spec.go @@ -55,6 +55,8 @@ type FlatConfig struct { ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"` TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"` SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"` + OidcRequestToken *string `mapstructure:"oidc_request_token" cty:"oidc_request_token" hcl:"oidc_request_token"` + OidcRequestURL *string `mapstructure:"oidc_request_url" cty:"oidc_request_url" hcl:"oidc_request_url"` UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"` DtlArtifacts []FlatDtlArtifact `mapstructure:"dtl_artifacts" required:"true" cty:"dtl_artifacts" hcl:"dtl_artifacts"` LabName *string `mapstructure:"lab_name" required:"true" cty:"lab_name" hcl:"lab_name"` @@ -95,6 +97,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, "subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false}, + "oidc_request_token": &hcldec.AttrSpec{Name: "oidc_request_token", Type: cty.String, Required: false}, + "oidc_request_url": &hcldec.AttrSpec{Name: "oidc_request_url", Type: cty.String, Required: false}, "use_azure_cli_auth": &hcldec.AttrSpec{Name: "use_azure_cli_auth", Type: cty.Bool, Required: false}, "dtl_artifacts": &hcldec.BlockListSpec{TypeName: "dtl_artifacts", Nested: hcldec.ObjectSpec((*FlatDtlArtifact)(nil).HCL2Spec())}, "lab_name": &hcldec.AttrSpec{Name: "lab_name", Type: cty.String, Required: false},