Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for GitHub Actions OIDC via oidc_request_url and oidc_request_token fields #421

Merged
merged 9 commits into from
Jun 5, 2024
4 changes: 2 additions & 2 deletions .web-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ 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` --
`client_id` and `subscription_id` are specified in addition to one of the following
`client_secret`, `client_jwt`, `client_cert_path`, or `oidc_request_url combined with oidc_request_token` --
JenGoldstrich marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
6 changes: 6 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,12 @@ 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

- `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
6 changes: 6 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,12 @@ 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

- `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
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, why aren't we using the azure_client_set.go's New function for this? It seems those options are reified in this place

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

azure_client_set is only used by chroot, each builder needs to pass the options into the AuthOptions block

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slightly confusing given the fact that it was created in the common package, I imagine the idea was to eventually migrate to that but I kind of like having the clients separate personally

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess given both builders are using the same SDK, if the code is the same, might as well use the common implementation. Or if there's a good reason to keep them separate, drop the common one as it's not used

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me, like we talked about off GitHub I'll follow up with this change on another PR, thanks for this suggestion, we'll unify everything to use the common azure client

}

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,
JenGoldstrich marked this conversation as resolved.
Show resolved Hide resolved
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
26 changes: 21 additions & 5 deletions builder/azure/common/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ 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` --
// `client_id` and `subscription_id` are specified in addition to one of the following
// `client_secret`, `client_jwt`, `client_cert_path`, or `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
Expand Down Expand Up @@ -76,7 +76,13 @@ 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
JenGoldstrich marked this conversation as resolved.
Show resolved Hide resolved
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 +101,7 @@ const (
AuthTypeClientSecret = "ClientSecret"
AuthTypeClientCert = "ClientCertificate"
AuthTypeClientBearerJWT = "ClientBearerJWT"
AuthTypeOidcURL = "OIDCURL"
AuthTypeAzureCLI = "AzureCLI"
)

Expand Down Expand Up @@ -212,6 +219,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 +232,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 +245,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 +262,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
68 changes: 68 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,67 @@ 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)

JenGoldstrich marked this conversation as resolved.
Show resolved Hide resolved
}

func getEnvOrSkip(t *testing.T, envVar string) string {
v := os.Getenv(envVar)
if v == "" {
Expand Down
2 changes: 2 additions & 0 deletions builder/azure/dtl/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 4 additions & 0 deletions builder/azure/dtl/config.hcl2spec.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@

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

- `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
4 changes: 2 additions & 2 deletions docs-partials/builder/azure/common/client/Config.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<!-- 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` --
`client_id` and `subscription_id` are specified in addition to one of the following
`client_secret`, `client_jwt`, `client_cert_path`, or `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
Expand Down
Loading