Skip to content

Commit

Permalink
feat: Add support for GitHub Actions OIDC via oidc_request_url and oi…
Browse files Browse the repository at this point in the history
…dc_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
  • Loading branch information
JenGoldstrich authored Jun 5, 2024
1 parent 1dc3ae1 commit 738e7ae
Show file tree
Hide file tree
Showing 18 changed files with 264 additions and 15 deletions.
11 changes: 7 additions & 4 deletions .web-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ Packer can create Azure virtual machine images through variety of ways depending
<!-- Code generated from the comments of the Config struct in builder/azure/common/client/config.go; DO NOT EDIT MANUALLY -->

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
Expand Down
7 changes: 7 additions & 0 deletions .web-docs/components/builder/arm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions .web-docs/components/builder/chroot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
52 changes: 52 additions & 0 deletions .web-docs/components/builder/dtl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,58 @@ options. In addition to the options listed here, a [communicator](/packer/docs/t
<!-- End of code generated from the comments of the Config struct in builder/azure/dtl/config.go; -->


<!-- Code generated from the comments of the Config struct in builder/azure/common/client/config.go; DO NOT EDIT MANUALLY -->

- `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.

<!-- End of code generated from the comments of the Config struct in builder/azure/common/client/config.go; -->


<!-- Code generated from the comments of the Config struct in builder/azure/common/config.go; DO NOT EDIT MANUALLY -->

- `skip_create_image` (bool) - Skip creating the image.
Expand Down
2 changes: 2 additions & 0 deletions builder/azure/arm/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ...")
Expand Down
4 changes: 4 additions & 0 deletions builder/azure/arm/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions builder/azure/chroot/builder.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions builder/azure/common/client/azure_authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type AzureAuthOptions struct {
ClientJWT string
ClientCertPath string
ClientCertPassword string
OidcRequestUrl string
OidcRequestToken string
TenantID string
SubscriptionID string
}
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 2 additions & 0 deletions builder/azure/common/client/azure_client_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 27 additions & 7 deletions builder/azure/common/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -95,6 +105,7 @@ const (
AuthTypeClientSecret = "ClientSecret"
AuthTypeClientCert = "ClientCertificate"
AuthTypeClientBearerJWT = "ClientBearerJWT"
AuthTypeOidcURL = "OIDCURL"
AuthTypeAzureCLI = "AzureCLI"
)

Expand Down Expand Up @@ -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"+
Expand All @@ -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 {
Expand All @@ -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.
Expand All @@ -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
}
Expand Down
67 changes: 67 additions & 0 deletions builder/azure/common/client/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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 == "" {
Expand Down
Loading

0 comments on commit 738e7ae

Please sign in to comment.