From 3d41097103c61c61bf58e5b033d9ef0409e40525 Mon Sep 17 00:00:00 2001 From: Kit Foster Date: Mon, 9 Dec 2024 21:29:00 +0100 Subject: [PATCH 1/9] Add support for customisable automation bypass secret --- ...protection_bypass_for_automation_update.go | 15 +- docs/data-sources/project.md | 3 +- docs/resources/project.md | 4 +- vercel/data_source_project.go | 150 ++++++++++-------- vercel/resource_project.go | 20 ++- vercel/resource_project_test.go | 3 +- vercel/validator_automation_bypass_secret.go | 45 ++++++ 7 files changed, 167 insertions(+), 73 deletions(-) create mode 100644 vercel/validator_automation_bypass_secret.go diff --git a/client/project_protection_bypass_for_automation_update.go b/client/project_protection_bypass_for_automation_update.go index d721539f..0bddd9b9 100644 --- a/client/project_protection_bypass_for_automation_update.go +++ b/client/project_protection_bypass_for_automation_update.go @@ -19,9 +19,22 @@ type revokeBypassProtectionRequest struct { Secret string `json:"secret"` } +type generateBypassProtectionRequest struct { + Secret string `json:"secret"` +} + func getUpdateBypassProtectionRequestBody(newValue bool, secret string) string { if newValue { - return "{}" + if secret == "" { + return "{}" + } + return string(mustMarshal(struct { + Revoke generateBypassProtectionRequest `json:"generate"` + }{ + Revoke: generateBypassProtectionRequest{ + Secret: secret, + }, + })) } return string(mustMarshal(struct { diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md index 7fd4096d..5033fed2 100644 --- a/docs/data-sources/project.md +++ b/docs/data-sources/project.md @@ -59,7 +59,8 @@ data "vercel_project" "example" { - `password_protection` (Attributes) Ensures visitors of your Preview Deployments must enter a password in order to gain access. (see [below for nested schema](#nestedatt--password_protection)) - `preview_comments` (Boolean) Whether comments are enabled on your Preview Deployments. - `prioritise_production_builds` (Boolean) If enabled, builds for the Production environment will be prioritized over Preview environments. -- `protection_bypass_for_automation` (Boolean) Allows automation services to bypass Vercel Authentication and Password Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass`. +- `protection_bypass_for_automation` (Boolean) Allows automation services to bypass Deployment Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass`. +- `protection_bypass_for_automation_secret` (String, Sensitive) If `protection_bypass_for_automation` is enabled, use this value in the `x-vercel-protection-bypass` header to bypass Deployment Protection for both Preview and Production Deployments. - `public_source` (Boolean) Specifies whether the source code and logs of the deployments for this project should be public or not. - `resource_config` (Attributes) Resource Configuration for the project. (see [below for nested schema](#nestedatt--resource_config)) - `root_directory` (String) The name of a directory or relative path to the source code of your project. When null is used it will default to the project root. diff --git a/docs/resources/project.md b/docs/resources/project.md index 67966aa8..5c0051b2 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -76,7 +76,8 @@ resource "vercel_project" "example" { - `password_protection` (Attributes) Ensures visitors of your Preview Deployments must enter a password in order to gain access. (see [below for nested schema](#nestedatt--password_protection)) - `preview_comments` (Boolean) Whether to enable comments on your Preview Deployments. If omitted, comments are controlled at the team level (default behaviour). - `prioritise_production_builds` (Boolean) If enabled, builds for the Production environment will be prioritized over Preview environments. -- `protection_bypass_for_automation` (Boolean) Allow automation services to bypass Vercel Authentication and Password Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass` with a value of the `password_protection_for_automation_secret` field. +- `protection_bypass_for_automation` (Boolean) Allow automation services to bypass Deployment Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass` with a value of the `password_protection_for_automation_secret` field. +- `protection_bypass_for_automation_secret` (String, Sensitive) If `protection_bypass_for_automation` is enabled, use this value in the `x-vercel-protection-bypass` header to bypass Deployment Protection for both Preview and Production Deployments. - `public_source` (Boolean) By default, visitors to the `/_logs` and `/_src` paths of your Production and Preview Deployments must log in with Vercel (requires being a member of your team) to see the Source, Logs and Deployment Status of your project. Setting `public_source` to `true` disables this behaviour, meaning the Source, Logs and Deployment Status can be publicly viewed. - `resource_config` (Attributes) Resource Configuration for the project. (see [below for nested schema](#nestedatt--resource_config)) - `root_directory` (String) The name of a directory or relative path to the source code of your project. If omitted, it will default to the project root. @@ -89,7 +90,6 @@ resource "vercel_project" "example" { ### Read-Only - `id` (String) The ID of this resource. -- `protection_bypass_for_automation_secret` (String) If `protection_bypass_for_automation` is enabled, use this value in the `x-vercel-protection-bypass` header to bypass Vercel Authentication and Password Protection for both Preview and Production Deployments. ### Nested Schema for `environment` diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go index 855ec8bd..4bcd9bde 100644 --- a/vercel/data_source_project.go +++ b/vercel/data_source_project.go @@ -279,7 +279,19 @@ For more detailed information, please see the [Vercel documentation](https://ver }, "protection_bypass_for_automation": schema.BoolAttribute{ Computed: true, - Description: "Allows automation services to bypass Vercel Authentication and Password Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass`.", + Description: "Allows automation services to bypass Deployment Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass`.", + }, + "protection_bypass_for_automation_secret": schema.StringAttribute{ + Sensitive: true, + Computed: true, + Description: "If `protection_bypass_for_automation` is enabled, use this value in the `x-vercel-protection-bypass` header to bypass Deployment Protection for both Preview and Production Deployments.", + Validators: []validator.String{ + stringvalidator.RegexMatches( + regexp.MustCompile(`^[a-zA-Z0-9]{32}$`), + "Specify `generate` to have the value generated automatically, or specify a 32 character secret.", + ), + validateAutomationBypassSecret(), + }, }, "automatically_expose_system_environment_variables": schema.BoolAttribute{ Computed: true, @@ -356,38 +368,39 @@ For more detailed information, please see the [Vercel documentation](https://ver // Project reflects the state terraform stores internally for a project. type ProjectDataSource struct { - BuildCommand types.String `tfsdk:"build_command"` - DevCommand types.String `tfsdk:"dev_command"` - Environment types.Set `tfsdk:"environment"` - Framework types.String `tfsdk:"framework"` - GitRepository *GitRepository `tfsdk:"git_repository"` - ID types.String `tfsdk:"id"` - IgnoreCommand types.String `tfsdk:"ignore_command"` - InstallCommand types.String `tfsdk:"install_command"` - Name types.String `tfsdk:"name"` - OutputDirectory types.String `tfsdk:"output_directory"` - PublicSource types.Bool `tfsdk:"public_source"` - RootDirectory types.String `tfsdk:"root_directory"` - ServerlessFunctionRegion types.String `tfsdk:"serverless_function_region"` - TeamID types.String `tfsdk:"team_id"` - VercelAuthentication *VercelAuthentication `tfsdk:"vercel_authentication"` - PasswordProtection *PasswordProtection `tfsdk:"password_protection"` - TrustedIps *TrustedIps `tfsdk:"trusted_ips"` - OIDCTokenConfig *OIDCTokenConfig `tfsdk:"oidc_token_config"` - OptionsAllowlist *OptionsAllowlist `tfsdk:"options_allowlist"` - ProtectionBypassForAutomation types.Bool `tfsdk:"protection_bypass_for_automation"` - AutoExposeSystemEnvVars types.Bool `tfsdk:"automatically_expose_system_environment_variables"` - GitComments types.Object `tfsdk:"git_comments"` - PreviewComments types.Bool `tfsdk:"preview_comments"` - AutoAssignCustomDomains types.Bool `tfsdk:"auto_assign_custom_domains"` - GitLFS types.Bool `tfsdk:"git_lfs"` - FunctionFailover types.Bool `tfsdk:"function_failover"` - CustomerSuccessCodeVisibility types.Bool `tfsdk:"customer_success_code_visibility"` - GitForkProtection types.Bool `tfsdk:"git_fork_protection"` - PrioritiseProductionBuilds types.Bool `tfsdk:"prioritise_production_builds"` - DirectoryListing types.Bool `tfsdk:"directory_listing"` - SkewProtection types.String `tfsdk:"skew_protection"` - ResourceConfig *ResourceConfig `tfsdk:"resource_config"` + BuildCommand types.String `tfsdk:"build_command"` + DevCommand types.String `tfsdk:"dev_command"` + Environment types.Set `tfsdk:"environment"` + Framework types.String `tfsdk:"framework"` + GitRepository *GitRepository `tfsdk:"git_repository"` + ID types.String `tfsdk:"id"` + IgnoreCommand types.String `tfsdk:"ignore_command"` + InstallCommand types.String `tfsdk:"install_command"` + Name types.String `tfsdk:"name"` + OutputDirectory types.String `tfsdk:"output_directory"` + PublicSource types.Bool `tfsdk:"public_source"` + RootDirectory types.String `tfsdk:"root_directory"` + ServerlessFunctionRegion types.String `tfsdk:"serverless_function_region"` + TeamID types.String `tfsdk:"team_id"` + VercelAuthentication *VercelAuthentication `tfsdk:"vercel_authentication"` + PasswordProtection *PasswordProtection `tfsdk:"password_protection"` + TrustedIps *TrustedIps `tfsdk:"trusted_ips"` + OIDCTokenConfig *OIDCTokenConfig `tfsdk:"oidc_token_config"` + OptionsAllowlist *OptionsAllowlist `tfsdk:"options_allowlist"` + ProtectionBypassForAutomation types.Bool `tfsdk:"protection_bypass_for_automation"` + ProtectionBypassForAutomationSecret types.String `tfsdk:"protection_bypass_for_automation_secret"` + AutoExposeSystemEnvVars types.Bool `tfsdk:"automatically_expose_system_environment_variables"` + GitComments types.Object `tfsdk:"git_comments"` + PreviewComments types.Bool `tfsdk:"preview_comments"` + AutoAssignCustomDomains types.Bool `tfsdk:"auto_assign_custom_domains"` + GitLFS types.Bool `tfsdk:"git_lfs"` + FunctionFailover types.Bool `tfsdk:"function_failover"` + CustomerSuccessCodeVisibility types.Bool `tfsdk:"customer_success_code_visibility"` + GitForkProtection types.Bool `tfsdk:"git_fork_protection"` + PrioritiseProductionBuilds types.Bool `tfsdk:"prioritise_production_builds"` + DirectoryListing types.Bool `tfsdk:"directory_listing"` + SkewProtection types.String `tfsdk:"skew_protection"` + ResourceConfig *ResourceConfig `tfsdk:"resource_config"` } func convertResponseToProjectDataSource(ctx context.Context, response client.ProjectResponse, plan Project, environmentVariables []client.EnvironmentVariable) (ProjectDataSource, error) { @@ -421,39 +434,46 @@ func convertResponseToProjectDataSource(ctx context.Context, response client.Pro DeploymentType: project.PasswordProtection.DeploymentType, } } + + var protectionBypassForAutomationSecret = types.StringNull() + if project.ProtectionBypassForAutomation.ValueBool() { + protectionBypassForAutomationSecret = types.StringValue(project.ProtectionBypassForAutomationSecret.ValueString()) + } + return ProjectDataSource{ - BuildCommand: project.BuildCommand, - DevCommand: project.DevCommand, - Environment: project.Environment, - Framework: project.Framework, - GitRepository: project.GitRepository, - ID: project.ID, - IgnoreCommand: project.IgnoreCommand, - InstallCommand: project.InstallCommand, - Name: project.Name, - OutputDirectory: project.OutputDirectory, - PublicSource: project.PublicSource, - RootDirectory: project.RootDirectory, - ServerlessFunctionRegion: project.ServerlessFunctionRegion, - TeamID: project.TeamID, - VercelAuthentication: project.VercelAuthentication, - PasswordProtection: pp, - TrustedIps: project.TrustedIps, - OIDCTokenConfig: project.OIDCTokenConfig, - OptionsAllowlist: project.OptionsAllowlist, - AutoExposeSystemEnvVars: types.BoolPointerValue(response.AutoExposeSystemEnvVars), - ProtectionBypassForAutomation: project.ProtectionBypassForAutomation, - GitComments: project.GitComments, - PreviewComments: project.PreviewComments, - AutoAssignCustomDomains: project.AutoAssignCustomDomains, - GitLFS: project.GitLFS, - FunctionFailover: project.FunctionFailover, - CustomerSuccessCodeVisibility: project.CustomerSuccessCodeVisibility, - GitForkProtection: project.GitForkProtection, - PrioritiseProductionBuilds: project.PrioritiseProductionBuilds, - DirectoryListing: project.DirectoryListing, - SkewProtection: project.SkewProtection, - ResourceConfig: project.ResourceConfig, + BuildCommand: project.BuildCommand, + DevCommand: project.DevCommand, + Environment: project.Environment, + Framework: project.Framework, + GitRepository: project.GitRepository, + ID: project.ID, + IgnoreCommand: project.IgnoreCommand, + InstallCommand: project.InstallCommand, + Name: project.Name, + OutputDirectory: project.OutputDirectory, + PublicSource: project.PublicSource, + RootDirectory: project.RootDirectory, + ServerlessFunctionRegion: project.ServerlessFunctionRegion, + TeamID: project.TeamID, + VercelAuthentication: project.VercelAuthentication, + PasswordProtection: pp, + TrustedIps: project.TrustedIps, + OIDCTokenConfig: project.OIDCTokenConfig, + OptionsAllowlist: project.OptionsAllowlist, + AutoExposeSystemEnvVars: types.BoolPointerValue(response.AutoExposeSystemEnvVars), + ProtectionBypassForAutomation: project.ProtectionBypassForAutomation, + ProtectionBypassForAutomationSecret: protectionBypassForAutomationSecret, + GitComments: project.GitComments, + PreviewComments: project.PreviewComments, + AutoAssignCustomDomains: project.AutoAssignCustomDomains, + GitLFS: project.GitLFS, + FunctionFailover: project.FunctionFailover, + CustomerSuccessCodeVisibility: project.CustomerSuccessCodeVisibility, + GitForkProtection: project.GitForkProtection, + PrioritiseProductionBuilds: project.PrioritiseProductionBuilds, + DirectoryListing: project.DirectoryListing, + SkewProtection: project.SkewProtection, + ResourceConfig: project.ResourceConfig, }, nil } diff --git a/vercel/resource_project.go b/vercel/resource_project.go index 4dda97cc..bc82d407 100644 --- a/vercel/resource_project.go +++ b/vercel/resource_project.go @@ -387,11 +387,20 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ }, "protection_bypass_for_automation": schema.BoolAttribute{ Optional: true, - Description: "Allow automation services to bypass Vercel Authentication and Password Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass` with a value of the `password_protection_for_automation_secret` field.", + Description: "Allow automation services to bypass Deployment Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass` with a value of the `password_protection_for_automation_secret` field.", }, "protection_bypass_for_automation_secret": schema.StringAttribute{ Computed: true, - Description: "If `protection_bypass_for_automation` is enabled, use this value in the `x-vercel-protection-bypass` header to bypass Vercel Authentication and Password Protection for both Preview and Production Deployments.", + Optional: true, + Sensitive: true, + Description: "If `protection_bypass_for_automation` is enabled, use this value in the `x-vercel-protection-bypass` header to bypass Deployment Protection for both Preview and Production Deployments.", + Validators: []validator.String{ + stringvalidator.RegexMatches( + regexp.MustCompile(`^[a-zA-Z0-9]{32}$`), + "Specify `generate` to have the value generated automatically, or specify a 32 character secret.", + ), + validateAutomationBypassSecret(), + }, }, "automatically_expose_system_environment_variables": schema.BoolAttribute{ Optional: true, @@ -1499,6 +1508,7 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest ProjectID: result.ID.ValueString(), TeamID: result.TeamID.ValueString(), NewValue: true, + Secret: plan.ProtectionBypassForAutomationSecret.ValueString(), }) if err != nil { resp.Diagnostics.AddError( @@ -1778,11 +1788,15 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest } if state.ProtectionBypassForAutomation != plan.ProtectionBypassForAutomation { + secret := state.ProtectionBypassForAutomationSecret.String() + if !plan.ProtectionBypassForAutomationSecret.IsNull() { + secret = plan.ProtectionBypassForAutomationSecret.ValueString() + } _, err := r.client.UpdateProtectionBypassForAutomation(ctx, client.UpdateProtectionBypassForAutomationRequest{ ProjectID: plan.ID.ValueString(), TeamID: plan.TeamID.ValueString(), NewValue: plan.ProtectionBypassForAutomation.ValueBool(), - Secret: state.ProtectionBypassForAutomationSecret.ValueString(), + Secret: secret, }) if err != nil { resp.Diagnostics.AddError( diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index 2cbad9ce..b4f20db9 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -260,7 +260,7 @@ func TestAcc_ProjectWithVercelAuthAndPasswordProtectionAndTrustedIps(t *testing. resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "options_allowlist.paths.#", "1"), resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "options_allowlist.paths.0.value", "/foo"), resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "protection_bypass_for_automation", "true"), - resource.TestCheckResourceAttrSet("vercel_project.enabled_to_start", "protection_bypass_for_automation_secret"), + resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "protection_bypass_for_automation_secret", "12345678912345678912345678912345"), testAccProjectExists("vercel_project.disabled_to_start", testTeam()), resource.TestCheckResourceAttr("vercel_project.disabled_to_start", "vercel_authentication.deployment_type", "standard_protection"), resource.TestCheckNoResourceAttr("vercel_project.disabled_to_start", "password_protection"), @@ -516,6 +516,7 @@ resource "vercel_project" "enabled_to_start" { ] } protection_bypass_for_automation = true + protection_bypass_for_automation_secret = "12345678912345678912345678912345" } resource "vercel_project" "disabled_to_start" { diff --git a/vercel/validator_automation_bypass_secret.go b/vercel/validator_automation_bypass_secret.go new file mode 100644 index 00000000..9d75f2ca --- /dev/null +++ b/vercel/validator_automation_bypass_secret.go @@ -0,0 +1,45 @@ +package vercel + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ validator.String = validateAutomationBypassSecretConditional{} + +func validateAutomationBypassSecret() validateAutomationBypassSecretConditional { + return validateAutomationBypassSecretConditional{} +} + +type validateAutomationBypassSecretConditional struct{} + +func (v validateAutomationBypassSecretConditional) Description(ctx context.Context) string { + return "protection_bypass_for_automation_secret is only allowed when protection_bypass_for_automation is true" +} + +func (v validateAutomationBypassSecretConditional) MarkdownDescription(ctx context.Context) string { + return "protection_bypass_for_automation_secret is only allowed when protection_bypass_for_automation is true" +} + +func (v validateAutomationBypassSecretConditional) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { + return + } + + enabled := types.Bool{} + if diags := req.Config.GetAttribute(ctx, path.Root("protection_bypass_for_automation"), &enabled); diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + if !enabled.ValueBool() { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Configuration", + "protection_bypass_for_automation_secret is not allowed unless protection_bypass_for_automation is true.", + ) + } +} From 7043f30637c51ca2a31bc513fbb3a1ee45f2df6b Mon Sep 17 00:00:00 2001 From: Kit Foster Date: Mon, 9 Dec 2024 22:06:43 +0100 Subject: [PATCH 2/9] Support changing from a generated secret to a customised one --- vercel/data_source_project.go | 7 +------ vercel/resource_project.go | 5 ++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go index 4bcd9bde..cc8de430 100644 --- a/vercel/data_source_project.go +++ b/vercel/data_source_project.go @@ -435,11 +435,6 @@ func convertResponseToProjectDataSource(ctx context.Context, response client.Pro } } - var protectionBypassForAutomationSecret = types.StringNull() - if project.ProtectionBypassForAutomation.ValueBool() { - protectionBypassForAutomationSecret = types.StringValue(project.ProtectionBypassForAutomationSecret.ValueString()) - } - return ProjectDataSource{ BuildCommand: project.BuildCommand, DevCommand: project.DevCommand, @@ -462,7 +457,7 @@ func convertResponseToProjectDataSource(ctx context.Context, response client.Pro OptionsAllowlist: project.OptionsAllowlist, AutoExposeSystemEnvVars: types.BoolPointerValue(response.AutoExposeSystemEnvVars), ProtectionBypassForAutomation: project.ProtectionBypassForAutomation, - ProtectionBypassForAutomationSecret: protectionBypassForAutomationSecret, + ProtectionBypassForAutomationSecret: project.ProtectionBypassForAutomationSecret, GitComments: project.GitComments, PreviewComments: project.PreviewComments, AutoAssignCustomDomains: project.AutoAssignCustomDomains, diff --git a/vercel/resource_project.go b/vercel/resource_project.go index bc82d407..f6d77a78 100644 --- a/vercel/resource_project.go +++ b/vercel/resource_project.go @@ -1253,6 +1253,9 @@ func convertResponseToProject(ctx context.Context, response client.ProjectRespon if !plan.ProtectionBypassForAutomation.IsNull() && !plan.ProtectionBypassForAutomation.ValueBool() { protectionBypass = types.BoolValue(false) } + if plan.ProtectionBypassForAutomationSecret.ValueString() != "" { + protectionBypassSecret = types.StringValue(plan.ProtectionBypassForAutomationSecret.ValueString()) + } environmentEntry := types.SetValueMust(envVariableElemType, env) if plan.Environment.IsNull() { @@ -1789,7 +1792,7 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest if state.ProtectionBypassForAutomation != plan.ProtectionBypassForAutomation { secret := state.ProtectionBypassForAutomationSecret.String() - if !plan.ProtectionBypassForAutomationSecret.IsNull() { + if plan.ProtectionBypassForAutomationSecret.ValueString() != "" { secret = plan.ProtectionBypassForAutomationSecret.ValueString() } _, err := r.client.UpdateProtectionBypassForAutomation(ctx, client.UpdateProtectionBypassForAutomationRequest{ From dcb1ce53ed9678923e000b3f3cf6aaaaaabdff21 Mon Sep 17 00:00:00 2001 From: Kit Foster Date: Mon, 9 Dec 2024 22:18:01 +0100 Subject: [PATCH 3/9] add unit tests --- vercel/resource_project_test.go | 114 +++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index b4f20db9..bf237419 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -260,7 +260,7 @@ func TestAcc_ProjectWithVercelAuthAndPasswordProtectionAndTrustedIps(t *testing. resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "options_allowlist.paths.#", "1"), resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "options_allowlist.paths.0.value", "/foo"), resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "protection_bypass_for_automation", "true"), - resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "protection_bypass_for_automation_secret", "12345678912345678912345678912345"), + resource.TestCheckResourceAttrSet("vercel_project.enabled_to_start", "protection_bypass_for_automation_secret"), testAccProjectExists("vercel_project.disabled_to_start", testTeam()), resource.TestCheckResourceAttr("vercel_project.disabled_to_start", "vercel_authentication.deployment_type", "standard_protection"), resource.TestCheckNoResourceAttr("vercel_project.disabled_to_start", "password_protection"), @@ -328,6 +328,48 @@ func TestAcc_ProjectWithVercelAuthAndPasswordProtectionAndTrustedIps(t *testing. }) } +func TestAcc_ProjectWithAutomationBypass(t *testing.T) { + projectSuffix := acctest.RandString(16) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + CheckDestroy: testAccProjectDestroy("vercel_project.enabled_to_start", testTeam()), + Steps: []resource.TestStep{ + { + Config: testAccProjectConfigAutomationBypass(projectSuffix, teamIDConfig()), + Check: resource.ComposeAggregateTestCheckFunc( + testAccProjectExists("vercel_project.disabled_to_enabled_generated_secret", testTeam()), + resource.TestCheckResourceAttr("vercel_project.disabled_to_enabled_generated_secret", "protection_bypass_for_automation", "false"), + testAccProjectExists("vercel_project.disabled_to_enabled_custom_secret", testTeam()), + resource.TestCheckResourceAttr("vercel_project.disabled_to_enabled_custom_secret", "protection_bypass_for_automation", "false"), + testAccProjectExists("vercel_project.enabled_generated_secret_to_enabled_custom_secret", testTeam()), + resource.TestCheckResourceAttr("vercel_project.enabled_generated_secret_to_enabled_custom_secret", "protection_bypass_for_automation", "true"), + resource.TestCheckResourceAttrSet("vercel_project.enabled_generated_secret_to_enabled_custom_secret", "protection_bypass_for_automation_secret"), + testAccProjectExists("vercel_project.enabled_generated_secret_to_disabled", testTeam()), + resource.TestCheckResourceAttr("vercel_project.enabled_generated_secret_to_disabled", "protection_bypass_for_automation", "true"), + resource.TestCheckResourceAttrSet("vercel_project.enabled_generated_secret_to_disabled", "protection_bypass_for_automation_secret"), + testAccProjectExists("vercel_project.enabled_custom_secret_to_disabled", testTeam()), + resource.TestCheckResourceAttr("vercel_project.enabled_custom_secret_to_disabled", "protection_bypass_for_automation", "true"), + resource.TestCheckResourceAttr("vercel_project.enabled_custom_secret_to_disabled", "protection_bypass_for_automation_secret", "12345678912345678912345678912345"), + ), + }, + { + Config: testAccProjectConfigAutomationBypassUpdate(projectSuffix, teamIDConfig()), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("vercel_project.disabled_to_enabled_generated_secret", "protection_bypass_for_automation", "true"), + resource.TestCheckResourceAttrSet("vercel_project.disabled_to_enabled_generated_secret", "protection_bypass_for_automation_secret"), + resource.TestCheckResourceAttr("vercel_project.disabled_to_enabled_custom_secret", "protection_bypass_for_automation", "true"), + resource.TestCheckResourceAttr("vercel_project.disabled_to_enabled_custom_secret", "protection_bypass_for_automation_secret", "12345678912345678912345678912345"), + resource.TestCheckResourceAttr("vercel_project.enabled_generated_secret_to_enabled_custom_secret", "protection_bypass_for_automation", "true"), + resource.TestCheckResourceAttr("vercel_project.enabled_generated_secret_to_enabled_custom_secret", "protection_bypass_for_automation_secret", "12345678912345678912345678912345"), + resource.TestCheckResourceAttr("vercel_project.enabled_generated_secret_to_disabled", "protection_bypass_for_automation", "false"), + resource.TestCheckResourceAttr("vercel_project.enabled_custom_secret_to_disabled", "protection_bypass_for_automation", "false"), + ), + }, + }, + }) +} + func getProjectImportID(n string) resource.ImportStateIdFunc { return func(s *terraform.State) (string, error) { rs, ok := s.RootModule().Resources[n] @@ -516,7 +558,6 @@ resource "vercel_project" "enabled_to_start" { ] } protection_bypass_for_automation = true - protection_bypass_for_automation_secret = "12345678912345678912345678912345" } resource "vercel_project" "disabled_to_start" { @@ -627,6 +668,75 @@ resource "vercel_project" "enabled_to_update" { `, projectSuffix, teamID) } +func testAccProjectConfigAutomationBypass(projectSuffix, teamID string) string { + return fmt.Sprintf(` +resource "vercel_project" "disabled_to_enabled_generated_secret" { + name = "test-acc-protection-one-%[1]s" + %[2]s +} + +resource "vercel_project" "disabled_to_enabled_custom_secret" { + name = "test-acc-protection-two-%[1]s" + %[2]s +} + +resource "vercel_project" "enabled_generated_secret_to_enabled_custom_secret" { + name = "test-acc-protection-three-%[1]s" + %[2]s + protection_bypass_for_automation = true +} + +resource "vercel_project" "enabled_generated_secret_to_disabled" { + name = "test-acc-protection-three-%[1]s" + %[2]s + protection_bypass_for_automation = true +} + +resource "vercel_project" "enabled_custom_secret_to_disabled" { + name = "test-acc-protection-three-%[1]s" + %[2]s + protection_bypass_for_automation = true + protection_bypass_for_automation_secret = "12345678912345678912345678912345" +} + `, projectSuffix, teamID) +} + +func testAccProjectConfigAutomationBypassUpdate(projectSuffix, teamID string) string { + return fmt.Sprintf(` +resource "vercel_project" "disabled_to_enabled_generated_secret" { + name = "test-acc-protection-one-%[1]s" + %[2]s + protection_bypass_for_automation = true +} + +resource "vercel_project" "disabled_to_enabled_custom_secret" { + name = "test-acc-protection-two-%[1]s" + %[2]s + protection_bypass_for_automation = true + protection_bypass_for_automation_secret = "12345678912345678912345678912345" +} + +resource "vercel_project" "enabled_generated_secret_to_enabled_custom_secret" { + name = "test-acc-protection-three-%[1]s" + %[2]s + protection_bypass_for_automation = true + protection_bypass_for_automation_secret = "12345678912345678912345678912345" +} + +resource "vercel_project" "enabled_generated_secret_to_disabled" { + name = "test-acc-protection-three-%[1]s" + %[2]s + protection_bypass_for_automation = false +} + +resource "vercel_project" "enabled_custom_secret_to_disabled" { + name = "test-acc-protection-three-%[1]s" + %[2]s + protection_bypass_for_automation = false +} + `, projectSuffix, teamID) +} + func testAccProjectConfigWithGitRepo(projectSuffix, teamID string) string { return fmt.Sprintf(` resource "vercel_project" "test_git" { From c015ef75b3ff18b85e2945c1443e82f61b91395e Mon Sep 17 00:00:00 2001 From: Kit Foster Date: Tue, 10 Dec 2024 12:39:20 +0100 Subject: [PATCH 4/9] update tests --- vercel/resource_project_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index bf237419..32d52776 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -671,29 +671,29 @@ resource "vercel_project" "enabled_to_update" { func testAccProjectConfigAutomationBypass(projectSuffix, teamID string) string { return fmt.Sprintf(` resource "vercel_project" "disabled_to_enabled_generated_secret" { - name = "test-acc-protection-one-%[1]s" + name = "test-acc-automation-bypass-one-%[1]s" %[2]s } resource "vercel_project" "disabled_to_enabled_custom_secret" { - name = "test-acc-protection-two-%[1]s" + name = "test-acc-automation-bypass-two-%[1]s" %[2]s } resource "vercel_project" "enabled_generated_secret_to_enabled_custom_secret" { - name = "test-acc-protection-three-%[1]s" + name = "test-acc-automation-bypass-three-%[1]s" %[2]s protection_bypass_for_automation = true } resource "vercel_project" "enabled_generated_secret_to_disabled" { - name = "test-acc-protection-three-%[1]s" + name = "test-acc-automation-bypass-four-%[1]s" %[2]s protection_bypass_for_automation = true } resource "vercel_project" "enabled_custom_secret_to_disabled" { - name = "test-acc-protection-three-%[1]s" + name = "test-acc-automation-bypass-five-%[1]s" %[2]s protection_bypass_for_automation = true protection_bypass_for_automation_secret = "12345678912345678912345678912345" @@ -704,33 +704,33 @@ resource "vercel_project" "enabled_custom_secret_to_disabled" { func testAccProjectConfigAutomationBypassUpdate(projectSuffix, teamID string) string { return fmt.Sprintf(` resource "vercel_project" "disabled_to_enabled_generated_secret" { - name = "test-acc-protection-one-%[1]s" + name = "test-acc-automation-bypass-one-%[1]s" %[2]s protection_bypass_for_automation = true } resource "vercel_project" "disabled_to_enabled_custom_secret" { - name = "test-acc-protection-two-%[1]s" + name = "test-acc-automation-bypass-two-%[1]s" %[2]s protection_bypass_for_automation = true protection_bypass_for_automation_secret = "12345678912345678912345678912345" } resource "vercel_project" "enabled_generated_secret_to_enabled_custom_secret" { - name = "test-acc-protection-three-%[1]s" + name = "test-acc-automation-bypass-three-%[1]s" %[2]s protection_bypass_for_automation = true protection_bypass_for_automation_secret = "12345678912345678912345678912345" } resource "vercel_project" "enabled_generated_secret_to_disabled" { - name = "test-acc-protection-three-%[1]s" + name = "test-acc-automation-bypass-four-%[1]s" %[2]s protection_bypass_for_automation = false } resource "vercel_project" "enabled_custom_secret_to_disabled" { - name = "test-acc-protection-three-%[1]s" + name = "test-acc-automation-bypass-five-%[1]s" %[2]s protection_bypass_for_automation = false } From d247530f969fc1496fb46cfcff80a27942260749 Mon Sep 17 00:00:00 2001 From: Kit Foster Date: Tue, 10 Dec 2024 12:53:09 +0100 Subject: [PATCH 5/9] update docs description --- docs/data-sources/project.md | 4 ++-- docs/resources/project.md | 4 ++-- vercel/data_source_project.go | 4 ++-- vercel/resource_project.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md index 5033fed2..78c3a4a2 100644 --- a/docs/data-sources/project.md +++ b/docs/data-sources/project.md @@ -59,8 +59,8 @@ data "vercel_project" "example" { - `password_protection` (Attributes) Ensures visitors of your Preview Deployments must enter a password in order to gain access. (see [below for nested schema](#nestedatt--password_protection)) - `preview_comments` (Boolean) Whether comments are enabled on your Preview Deployments. - `prioritise_production_builds` (Boolean) If enabled, builds for the Production environment will be prioritized over Preview environments. -- `protection_bypass_for_automation` (Boolean) Allows automation services to bypass Deployment Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass`. -- `protection_bypass_for_automation_secret` (String, Sensitive) If `protection_bypass_for_automation` is enabled, use this value in the `x-vercel-protection-bypass` header to bypass Deployment Protection for both Preview and Production Deployments. +- `protection_bypass_for_automation` (Boolean) Allows automation services to bypass Deployment Protection on this project when using an HTTP header named `x-vercel-protection-bypass` with the value from `protection_bypass_for_automation_secret`. +- `protection_bypass_for_automation_secret` (String, Sensitive) If `protection_bypass_for_automation` is enabled, optionally set this value to specify a 32 character secret, otherwise a secret will be generated. - `public_source` (Boolean) Specifies whether the source code and logs of the deployments for this project should be public or not. - `resource_config` (Attributes) Resource Configuration for the project. (see [below for nested schema](#nestedatt--resource_config)) - `root_directory` (String) The name of a directory or relative path to the source code of your project. When null is used it will default to the project root. diff --git a/docs/resources/project.md b/docs/resources/project.md index 5c0051b2..242c288a 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -76,8 +76,8 @@ resource "vercel_project" "example" { - `password_protection` (Attributes) Ensures visitors of your Preview Deployments must enter a password in order to gain access. (see [below for nested schema](#nestedatt--password_protection)) - `preview_comments` (Boolean) Whether to enable comments on your Preview Deployments. If omitted, comments are controlled at the team level (default behaviour). - `prioritise_production_builds` (Boolean) If enabled, builds for the Production environment will be prioritized over Preview environments. -- `protection_bypass_for_automation` (Boolean) Allow automation services to bypass Deployment Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass` with a value of the `password_protection_for_automation_secret` field. -- `protection_bypass_for_automation_secret` (String, Sensitive) If `protection_bypass_for_automation` is enabled, use this value in the `x-vercel-protection-bypass` header to bypass Deployment Protection for both Preview and Production Deployments. +- `protection_bypass_for_automation` (Boolean) Allow automation services to bypass Deployment Protection on this project when using an HTTP header named `x-vercel-protection-bypass` with a value of the `protection_bypass_for_automation_secret` field. +- `protection_bypass_for_automation_secret` (String, Sensitive) If `protection_bypass_for_automation` is enabled, optionally set this value to specify a 32 character secret, otherwise a secret will be generated. - `public_source` (Boolean) By default, visitors to the `/_logs` and `/_src` paths of your Production and Preview Deployments must log in with Vercel (requires being a member of your team) to see the Source, Logs and Deployment Status of your project. Setting `public_source` to `true` disables this behaviour, meaning the Source, Logs and Deployment Status can be publicly viewed. - `resource_config` (Attributes) Resource Configuration for the project. (see [below for nested schema](#nestedatt--resource_config)) - `root_directory` (String) The name of a directory or relative path to the source code of your project. If omitted, it will default to the project root. diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go index cc8de430..c5ece7ee 100644 --- a/vercel/data_source_project.go +++ b/vercel/data_source_project.go @@ -279,12 +279,12 @@ For more detailed information, please see the [Vercel documentation](https://ver }, "protection_bypass_for_automation": schema.BoolAttribute{ Computed: true, - Description: "Allows automation services to bypass Deployment Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass`.", + Description: "Allows automation services to bypass Deployment Protection on this project when using an HTTP header named `x-vercel-protection-bypass` with the value from `protection_bypass_for_automation_secret`.", }, "protection_bypass_for_automation_secret": schema.StringAttribute{ Sensitive: true, Computed: true, - Description: "If `protection_bypass_for_automation` is enabled, use this value in the `x-vercel-protection-bypass` header to bypass Deployment Protection for both Preview and Production Deployments.", + Description: "If `protection_bypass_for_automation` is enabled, optionally set this value to specify a 32 character secret, otherwise a secret will be generated.", Validators: []validator.String{ stringvalidator.RegexMatches( regexp.MustCompile(`^[a-zA-Z0-9]{32}$`), diff --git a/vercel/resource_project.go b/vercel/resource_project.go index f6d77a78..7cc8d544 100644 --- a/vercel/resource_project.go +++ b/vercel/resource_project.go @@ -387,13 +387,13 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ }, "protection_bypass_for_automation": schema.BoolAttribute{ Optional: true, - Description: "Allow automation services to bypass Deployment Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass` with a value of the `password_protection_for_automation_secret` field.", + Description: "Allow automation services to bypass Deployment Protection on this project when using an HTTP header named `x-vercel-protection-bypass` with a value of the `protection_bypass_for_automation_secret` field.", }, "protection_bypass_for_automation_secret": schema.StringAttribute{ Computed: true, Optional: true, Sensitive: true, - Description: "If `protection_bypass_for_automation` is enabled, use this value in the `x-vercel-protection-bypass` header to bypass Deployment Protection for both Preview and Production Deployments.", + Description: "If `protection_bypass_for_automation` is enabled, optionally set this value to specify a 32 character secret, otherwise a secret will be generated.", Validators: []validator.String{ stringvalidator.RegexMatches( regexp.MustCompile(`^[a-zA-Z0-9]{32}$`), From d160e32e8cfdee8c78ff768169058d3926e7521f Mon Sep 17 00:00:00 2001 From: Kit Foster Date: Tue, 10 Dec 2024 13:28:40 +0100 Subject: [PATCH 6/9] use ValueString --- client/project_protection_bypass_for_automation_update.go | 2 +- vercel/resource_project.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/project_protection_bypass_for_automation_update.go b/client/project_protection_bypass_for_automation_update.go index 0bddd9b9..667c8b56 100644 --- a/client/project_protection_bypass_for_automation_update.go +++ b/client/project_protection_bypass_for_automation_update.go @@ -70,7 +70,7 @@ func (c *Client) UpdateProtectionBypassForAutomation(ctx context.Context, reques }, &response) if err != nil { - return s, fmt.Errorf("unable to add protection bypass for automation: %w", err) + return s, fmt.Errorf("unable to update protection bypass for automation: %w", err) } if !request.NewValue { diff --git a/vercel/resource_project.go b/vercel/resource_project.go index 7cc8d544..60cf2523 100644 --- a/vercel/resource_project.go +++ b/vercel/resource_project.go @@ -1791,7 +1791,7 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest } if state.ProtectionBypassForAutomation != plan.ProtectionBypassForAutomation { - secret := state.ProtectionBypassForAutomationSecret.String() + secret := state.ProtectionBypassForAutomationSecret.ValueString() if plan.ProtectionBypassForAutomationSecret.ValueString() != "" { secret = plan.ProtectionBypassForAutomationSecret.ValueString() } From 5e87086847b56f67e9ea1d5fcbd33b5b019d708c Mon Sep 17 00:00:00 2001 From: Kit Foster Date: Tue, 10 Dec 2024 13:46:22 +0100 Subject: [PATCH 7/9] test case --- vercel/resource_project_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index 32d52776..51b95403 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -341,7 +341,7 @@ func TestAcc_ProjectWithAutomationBypass(t *testing.T) { testAccProjectExists("vercel_project.disabled_to_enabled_generated_secret", testTeam()), resource.TestCheckResourceAttr("vercel_project.disabled_to_enabled_generated_secret", "protection_bypass_for_automation", "false"), testAccProjectExists("vercel_project.disabled_to_enabled_custom_secret", testTeam()), - resource.TestCheckResourceAttr("vercel_project.disabled_to_enabled_custom_secret", "protection_bypass_for_automation", "false"), + resource.TestCheckNoResourceAttr("vercel_project.disabled_to_enabled_custom_secret", "protection_bypass_for_automation"), testAccProjectExists("vercel_project.enabled_generated_secret_to_enabled_custom_secret", testTeam()), resource.TestCheckResourceAttr("vercel_project.enabled_generated_secret_to_enabled_custom_secret", "protection_bypass_for_automation", "true"), resource.TestCheckResourceAttrSet("vercel_project.enabled_generated_secret_to_enabled_custom_secret", "protection_bypass_for_automation_secret"), From 721f67377d5f1048a04b2285757db141466271c5 Mon Sep 17 00:00:00 2001 From: Kit Foster Date: Tue, 10 Dec 2024 14:02:17 +0100 Subject: [PATCH 8/9] test case --- vercel/resource_project_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index 51b95403..df4442fe 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -339,7 +339,7 @@ func TestAcc_ProjectWithAutomationBypass(t *testing.T) { Config: testAccProjectConfigAutomationBypass(projectSuffix, teamIDConfig()), Check: resource.ComposeAggregateTestCheckFunc( testAccProjectExists("vercel_project.disabled_to_enabled_generated_secret", testTeam()), - resource.TestCheckResourceAttr("vercel_project.disabled_to_enabled_generated_secret", "protection_bypass_for_automation", "false"), + resource.TestCheckNoResourceAttr("vercel_project.disabled_to_enabled_generated_secret", "protection_bypass_for_automation"), testAccProjectExists("vercel_project.disabled_to_enabled_custom_secret", testTeam()), resource.TestCheckNoResourceAttr("vercel_project.disabled_to_enabled_custom_secret", "protection_bypass_for_automation"), testAccProjectExists("vercel_project.enabled_generated_secret_to_enabled_custom_secret", testTeam()), From a7e33d9b4580ce958052433f79e012744d426a80 Mon Sep 17 00:00:00 2001 From: Kit Foster Date: Tue, 10 Dec 2024 14:11:43 +0100 Subject: [PATCH 9/9] check destroy --- vercel/resource_project_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index df4442fe..b135bd3a 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -333,7 +333,7 @@ func TestAcc_ProjectWithAutomationBypass(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - CheckDestroy: testAccProjectDestroy("vercel_project.enabled_to_start", testTeam()), + CheckDestroy: testAccProjectDestroy("vercel_project.disabled_to_enabled_generated_secret", testTeam()), Steps: []resource.TestStep{ { Config: testAccProjectConfigAutomationBypass(projectSuffix, teamIDConfig()),