From 273a7204405f99172b02dee56501ad7b0c6cbca3 Mon Sep 17 00:00:00 2001 From: Brian Farrell Date: Tue, 2 Mar 2021 04:51:18 -0600 Subject: [PATCH] Add client_cert_token_timeout to address GH-9465 (#10528) --- builder/azure/arm/config.hcl2spec.go | 2 + builder/azure/chroot/builder.hcl2spec.go | 2 + builder/azure/common/client/config.go | 11 +++- builder/azure/common/client/config_test.go | 10 ++++ .../azure/common/client/tokenprovider_cert.go | 4 +- builder/azure/dtl/config.hcl2spec.go | 2 + .../azure-dtlartifact/provisioner.hcl2spec.go | 50 ++++++++++--------- website/content/docs/builders/azure/arm.mdx | 3 ++ website/content/docs/builders/azure/index.mdx | 2 + .../common/client/Config-not-required.mdx | 2 + 10 files changed, 61 insertions(+), 27 deletions(-) diff --git a/builder/azure/arm/config.hcl2spec.go b/builder/azure/arm/config.hcl2spec.go index f097d662b..354535128 100644 --- a/builder/azure/arm/config.hcl2spec.go +++ b/builder/azure/arm/config.hcl2spec.go @@ -23,6 +23,7 @@ type FlatConfig struct { ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"` ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"` ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"` + ClientCertExpireTimeout *string `mapstructure:"client_cert_token_timeout" required:"false" cty:"client_cert_token_timeout" hcl:"client_cert_token_timeout"` ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"` ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"` TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"` @@ -151,6 +152,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false}, "client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false}, "client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false}, + "client_cert_token_timeout": &hcldec.AttrSpec{Name: "client_cert_token_timeout", Type: cty.String, Required: false}, "client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false}, "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, diff --git a/builder/azure/chroot/builder.hcl2spec.go b/builder/azure/chroot/builder.hcl2spec.go index 6e233f2b1..97a372622 100644 --- a/builder/azure/chroot/builder.hcl2spec.go +++ b/builder/azure/chroot/builder.hcl2spec.go @@ -22,6 +22,7 @@ type FlatConfig struct { ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"` ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"` ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"` + ClientCertExpireTimeout *string `mapstructure:"client_cert_token_timeout" required:"false" cty:"client_cert_token_timeout" hcl:"client_cert_token_timeout"` ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"` ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"` TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"` @@ -76,6 +77,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false}, "client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false}, "client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false}, + "client_cert_token_timeout": &hcldec.AttrSpec{Name: "client_cert_token_timeout", Type: cty.String, Required: false}, "client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false}, "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, diff --git a/builder/azure/common/client/config.go b/builder/azure/common/client/config.go index 11c31d64f..041197222 100644 --- a/builder/azure/common/client/config.go +++ b/builder/azure/common/client/config.go @@ -39,6 +39,8 @@ type Config struct { // The path to a pem-encoded certificate that will be used to authenticate // as the specified AAD SP. ClientCertPath string `mapstructure:"client_cert_path"` + // The timeout for the JWT Token when using a [client certificate](#client_cert_path). Defaults to 1 hour. + ClientCertExpireTimeout time.Duration `mapstructure:"client_cert_token_timeout" required:"false"` // 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`. @@ -163,6 +165,9 @@ func (c Config) Validate(errs *packersdk.MultiError) { if _, err := os.Stat(c.ClientCertPath); err != nil { errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("client_cert_path is not an accessible file: %v", err)) } + if c.ClientCertExpireTimeout < 5*time.Minute { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("client_cert_token_timeout will expire within 5 minutes, please set a value greater than 5 minutes")) + } return } @@ -259,7 +264,7 @@ func (c Config) GetServicePrincipalToken( auth = NewSecretOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientSecret, c.TenantID) case authTypeClientCert: say("Getting tokens using client certificate") - auth, err = NewCertOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientCertPath, c.TenantID) + auth, err = NewCertOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientCertPath, c.TenantID, c.ClientCertExpireTimeout) if err != nil { return nil, err } @@ -336,6 +341,10 @@ func (c *Config) FillParameters() error { c.TenantID = tenantID } + if c.ClientCertExpireTimeout == 0 { + c.ClientCertExpireTimeout = time.Hour + } + return nil } diff --git a/builder/azure/common/client/config_test.go b/builder/azure/common/client/config_test.go index 74484d883..c2bc05c5b 100644 --- a/builder/azure/common/client/config_test.go +++ b/builder/azure/common/client/config_test.go @@ -95,6 +95,16 @@ func Test_ClientConfig_RequiredParametersSet(t *testing.T) { }, wantErr: true, }, + { + name: "client_cert_token_timeout should be 5 minutes or more", + config: Config{ + SubscriptionID: "ok", + ClientID: "ok", + ClientCertPath: "/dev/null", + ClientCertExpireTimeout: 1 * time.Minute, + }, + wantErr: true, + }, { name: "too many client_* values", config: Config{ diff --git a/builder/azure/common/client/tokenprovider_cert.go b/builder/azure/common/client/tokenprovider_cert.go index b03df2059..64decad46 100644 --- a/builder/azure/common/client/tokenprovider_cert.go +++ b/builder/azure/common/client/tokenprovider_cert.go @@ -18,14 +18,14 @@ import ( "github.com/hashicorp/packer/builder/azure/pkcs12" ) -func NewCertOAuthTokenProvider(env azure.Environment, clientID, clientCertPath, tenantID string) (oAuthTokenProvider, error) { +func NewCertOAuthTokenProvider(env azure.Environment, clientID, clientCertPath, tenantID string, certExpireTimeout time.Duration) (oAuthTokenProvider, error) { cert, key, err := readCert(clientCertPath) if err != nil { return nil, fmt.Errorf("Error reading certificate: %v", err) } audience := fmt.Sprintf("%s%s/oauth2/token", env.ActiveDirectoryEndpoint, tenantID) - jwt, err := makeJWT(clientID, audience, cert, key, time.Hour, true) + jwt, err := makeJWT(clientID, audience, cert, key, certExpireTimeout, true) if err != nil { return nil, fmt.Errorf("Error generating JWT: %v", err) } diff --git a/builder/azure/dtl/config.hcl2spec.go b/builder/azure/dtl/config.hcl2spec.go index c7bc974a7..b0684e265 100644 --- a/builder/azure/dtl/config.hcl2spec.go +++ b/builder/azure/dtl/config.hcl2spec.go @@ -49,6 +49,7 @@ type FlatConfig struct { ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"` ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"` ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"` + ClientCertExpireTimeout *string `mapstructure:"client_cert_token_timeout" required:"false" cty:"client_cert_token_timeout" hcl:"client_cert_token_timeout"` ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"` ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"` TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"` @@ -163,6 +164,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false}, "client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false}, "client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false}, + "client_cert_token_timeout": &hcldec.AttrSpec{Name: "client_cert_token_timeout", Type: cty.String, Required: false}, "client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false}, "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, diff --git a/provisioner/azure-dtlartifact/provisioner.hcl2spec.go b/provisioner/azure-dtlartifact/provisioner.hcl2spec.go index 95601f797..dc914de5c 100644 --- a/provisioner/azure-dtlartifact/provisioner.hcl2spec.go +++ b/provisioner/azure-dtlartifact/provisioner.hcl2spec.go @@ -37,30 +37,31 @@ func (*FlatArtifactParameter) HCL2Spec() map[string]hcldec.Spec { // FlatConfig is an auto-generated flat version of Config. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatConfig struct { - PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` - PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` - PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` - PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` - PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` - PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` - PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` - PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` - CloudEnvironmentName *string `mapstructure:"cloud_environment_name" required:"false" cty:"cloud_environment_name" hcl:"cloud_environment_name"` - ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"` - ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"` - ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"` - ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"` - 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"` - UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"` - DtlArtifacts []FlatDtlArtifact `mapstructure:"dtl_artifacts" cty:"dtl_artifacts" hcl:"dtl_artifacts"` - LabName *string `mapstructure:"lab_name" cty:"lab_name" hcl:"lab_name"` - ResourceGroupName *string `mapstructure:"lab_resource_group_name" cty:"lab_resource_group_name" hcl:"lab_resource_group_name"` - VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` - PollingDurationTimeout *string `mapstructure:"polling_duration_timeout" required:"false" cty:"polling_duration_timeout" hcl:"polling_duration_timeout"` - AzureTags map[string]*string `mapstructure:"azure_tags" cty:"azure_tags" hcl:"azure_tags"` - Json map[string]interface{} `cty:"json" hcl:"json"` + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + CloudEnvironmentName *string `mapstructure:"cloud_environment_name" required:"false" cty:"cloud_environment_name" hcl:"cloud_environment_name"` + ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"` + ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"` + ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"` + ClientCertExpireTimeout *string `mapstructure:"client_cert_token_timeout" required:"false" cty:"client_cert_token_timeout" hcl:"client_cert_token_timeout"` + ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"` + 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"` + UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"` + DtlArtifacts []FlatDtlArtifact `mapstructure:"dtl_artifacts" cty:"dtl_artifacts" hcl:"dtl_artifacts"` + LabName *string `mapstructure:"lab_name" cty:"lab_name" hcl:"lab_name"` + ResourceGroupName *string `mapstructure:"lab_resource_group_name" cty:"lab_resource_group_name" hcl:"lab_resource_group_name"` + VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` + PollingDurationTimeout *string `mapstructure:"polling_duration_timeout" required:"false" cty:"polling_duration_timeout" hcl:"polling_duration_timeout"` + AzureTags map[string]*string `mapstructure:"azure_tags" cty:"azure_tags" hcl:"azure_tags"` + Json map[string]interface{} `cty:"json" hcl:"json"` } // FlatMapstructure returns a new FlatConfig. @@ -87,6 +88,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false}, "client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false}, "client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false}, + "client_cert_token_timeout": &hcldec.AttrSpec{Name: "client_cert_token_timeout", Type: cty.String, Required: false}, "client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false}, "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, diff --git a/website/content/docs/builders/azure/arm.mdx b/website/content/docs/builders/azure/arm.mdx index ff4b2b41d..8dd43c386 100644 --- a/website/content/docs/builders/azure/arm.mdx +++ b/website/content/docs/builders/azure/arm.mdx @@ -61,6 +61,9 @@ you should specify `subscription_id`, `client_id` and one of `client_secret`, - `client_cert_path` (string) - The location of a PEM file containing a certificate and private key for service principal. +- `client_cert_token_timeout` (duration string | ex: "1h30m12s") - How long to set the expire time on the token created when using + `client_cert_path`. + - `client_jwt` (string) - The bearer JWT assertion signed using a certificate associated with your service principal principal. See [Azure Active Directory docs](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials) diff --git a/website/content/docs/builders/azure/index.mdx b/website/content/docs/builders/azure/index.mdx index a1b3c0254..a961910e2 100644 --- a/website/content/docs/builders/azure/index.mdx +++ b/website/content/docs/builders/azure/index.mdx @@ -94,6 +94,8 @@ way to authenticate the SP to AAD: for the AAD SP. - `client_cert_path` - allows usage of a certificate to be used to authenticate as the specified AAD SP. +- `client_cert_token_timeout` - How long to set the expire time on the token created when using + `client_cert_path`. - `client_jwt` - For advanced scenario's where the used cannot provide Packer the full certificate, they can provide a JWT bearer token for client auth (RFC 7523, Sec. 2.2). These bearer tokens are created and signed using a diff --git a/website/content/partials/builder/azure/common/client/Config-not-required.mdx b/website/content/partials/builder/azure/common/client/Config-not-required.mdx index 46d553c81..c45b07915 100644 --- a/website/content/partials/builder/azure/common/client/Config-not-required.mdx +++ b/website/content/partials/builder/azure/common/client/Config-not-required.mdx @@ -12,6 +12,8 @@ - `client_cert_path` (string) - The path to a pem-encoded certificate that will be used to authenticate as the specified AAD SP. +- `client_cert_token_timeout` (duration string | ex: "1h5m2s") - The timeout for the JWT Token when using a [client certificate](#client_cert_path). Defaults to 1 hour. + - `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`.