Skip to content

ProtectionBypassForAutomationSecret Update only happens when ProtectionBypassForAutomation is toggled #294

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

Open
DavidS-ovm opened this issue Mar 28, 2025 · 2 comments · May be fixed by #295

Comments

@DavidS-ovm
Copy link

When setting protection_bypass_for_automation and protection_bypass_for_automation_secret through vercel_project, the value is correctly updated in the terraform state, but not in the actual project as confirmed through the UI.

code:

# Vercel Protection Bypass for Automation requires the secret to be exactly 32
# characters long, and only if we inject the value can we also use it elsewhere,
# e.g. to put it into 1password for Github Actions.
resource "random_password" "vercel_automation_bypass" {
  count   = var.terraform_env_name == "dogfood" ? 1 : 0
  length  = 32
  special = false # vercel does not like special characters
}

# Configure Protection Bypass for Automation on the project. This is only done
# in dogfood, as we primarily use vercel for code review deployments on PRs.
resource "vercel_project" "frontend" {
  count   = var.terraform_env_name == "dogfood" ? 1 : 0
  name    = "frontend"
  team_id = var.vercel_team_id

  enable_affected_projects_deployments = true
  framework                            = "nextjs"
  git_repository = {
    type              = "github"
    repo              = "overmindtech/workspace"
    production_branch = "main"
  }
  protection_bypass_for_automation        = true
  protection_bypass_for_automation_secret = random_password.vercel_automation_bypass[0].result
  root_directory                          = "frontend"
  skew_protection                         = "12 hours"
}

resource "onepassword_item" "vercel_generated" {
  count = var.terraform_env_name == "dogfood" ? 1 : 0
  vault = data.onepassword_vault.global_vault.uuid

  title      = "vercel"
  category   = "secure_note"
  note_value = "generated items from vercel; provisioned through the dogfood terraform environment"

  section {
    label = "generated"
    field {
      label = "info: VERCEL_AUTOMATION_BYPASS_SECRET"
      type  = "STRING"
      value = "https://vercel.com/overmindtech/frontend/settings/deployment-protection; this is provisioned from terraform"
    }
    field {
      label = "VERCEL_AUTOMATION_BYPASS_SECRET"
      type  = "CONCEALED"
      value = random_password.vercel_automation_bypass[0].result
    }
  }
}

plan:

[...]
vercel_project.frontend[0]: Refreshing state... [id=prj_pobSoGaGRB9xUIAp3ckLPNRvWMsM]
[...]
  # random_password.vercel_automation_bypass[0] is tainted, so must be replaced
-/+ resource "random_password" "vercel_automation_bypass" {
      ~ bcrypt_hash = (sensitive value)
      ~ id          = "none" -> (known after apply)
      ~ result      = (sensitive value)
        # (10 unchanged attributes hidden)
    }
[...]
  # vercel_project.frontend[0] will be updated in-place
  ~ resource "vercel_project" "frontend" {
        id                                                = "prj_pobSoGaGRB9xUIAp3ckLPNRvWMsM"
        name                                              = "frontend"
      ~ protection_bypass_for_automation_secret           = (sensitive value)
        # (20 unchanged attributes hidden)
    }
[...]
  # onepassword_item.vercel_generated[0] will be updated in-place
  ~ resource "onepassword_item" "vercel_generated" {
        id         = "vaults/smyzrf7ekx2hlq7oq6kntecca4/items/qektdrtygrybtjecadu7777hjy"
      + password   = (sensitive value)
        # (5 unchanged attributes hidden)

      ~ section {
            id    = "74443c7f-eb6b-4ecf-83ab-52e2c279eb83"
            # (1 unchanged attribute hidden)

          ~ field {
                id    = "xwjdroeux4bqkaz2hgnta5vaqu"
              ~ value = (sensitive value)
                # (2 unchanged attributes hidden)
            }

            # (1 unchanged block hidden)
        }
    }

apply:

random_password.vercel_automation_bypass[0]: Destroying... [id=none]
random_password.vercel_automation_bypass[0]: Destruction complete after 0s
random_password.vercel_automation_bypass[0]: Creating...
random_password.vercel_automation_bypass[0]: Creation complete after 0s [id=none]
onepassword_item.vercel_generated[0]: Modifying... [id=vaults/smyzrf7ekx2hlq7oq6kntecca4/items/qektdrtygrybtjecadu7777hjy]
vercel_project.frontend[0]: Modifying... [id=prj_pobSoGaGRB9xUIAp3ckLPNRvWMsM]
onepassword_item.vercel_generated[0]: Modifications complete after 1s [id=vaults/smyzrf7ekx2hlq7oq6kntecca4/items/qektdrtygrybtjecadu7777hjy]
vercel_project.frontend[0]: Modifications complete after 1s [id=prj_pobSoGaGRB9xUIAp3ckLPNRvWMsM]
[...]

Apply complete! Resources: 26 added, 8 changed, 2 destroyed.

Checking the new state:

terraform plan -out=tfplan
terraform show -json tfplan 2>&1 | jq | less

Searching for random_password.vercel_automation_bypass and vercel_project.frontend in the JSON output shows the same value as stored in 1password (proving that the value has been correctly applied outside) on both random_password.vercel_automation_bypass.result (the generated value) as well as on vercel_project.frontend[0].protection_bypass_for_automation_secret (proving that the value has been correctly passed to the vercel_project provider).

Reading through the client code at

func getUpdateBypassProtectionRequestBody(newValue bool, secret string) string {
if newValue {
if secret == "" {
return "{}"
}
return string(mustMarshal(struct {
Revoke generateBypassProtectionRequest `json:"generate"`
}{
Revoke: generateBypassProtectionRequest{
Secret: secret,
},
}))
}
return string(mustMarshal(struct {
Revoke revokeBypassProtectionRequest `json:"revoke"`
}{
Revoke: revokeBypassProtectionRequest{
Regenerate: false,
Secret: secret,
},
}))
}
I'm getting the feeling that this should read

func getUpdateBypassProtectionRequestBody(newValue bool, secret string) string {
	if newValue {
		if secret == "" {
			return "{}"
		}
		return string(mustMarshal(struct {
			Generate generateBypassProtectionRequest `json:"generate"`
		}{
			Generate: generateBypassProtectionRequest{
				Secret: secret,
			},
		}))
	}


	return string(mustMarshal(struct {
		Revoke revokeBypassProtectionRequest `json:"revoke"`
	}{
		Revoke: revokeBypassProtectionRequest{
			Regenerate: false,
			Secret:     secret,
		},
	}))
}

instead, but that's just a red herring that had me chasing my tail for 10 minutes.

The true error is that

if state.ProtectionBypassForAutomation != plan.ProtectionBypassForAutomation {
is only checking whether the ProtectionBypassForAutomation flag has been toggled, but not if the secret is still the same.

DavidS-ovm added a commit to DavidS-ovm/terraform-provider-vercel that referenced this issue Mar 28, 2025
… plan

This fixes vercel#294.

Before this change, the vercel_project resource did not pass on changes
to the protection_bypass_for_automation_secret attribute on to the API.

This changes the `projectResource.Update` function to create a
`UpdateProtectionBypassForAutomation` call if the secret has changed.
@DavidS-ovm
Copy link
Author

Opened support case with Vercel: https://vercel.com/overmindtech/~/support/cases/440431

DavidS-ovm added a commit to DavidS-ovm/terraform-provider-vercel that referenced this issue Apr 10, 2025
… plan

This fixes vercel#294.

Before this change, the vercel_project resource did not pass on changes
to the protection_bypass_for_automation_secret attribute on to the API.

This commit contains the following changes:
* `client/project_protection_bypass_for_automation_update.go`: Change the anonymous struct member to accurately reflect what is marshalled
* `projectResource.Create()`: always save the user-provided secret in the state, even if it is empty. This is required so that the update logic below doesn't try to reset the secret when it is generated by the vercel API.
* `projectResource.Update()`: correctly handle the different update scenarios


## Limitations

This change stops API-generated secrets to be returned into the state. This
will break existing setups that rely on this undocumented fact.
@DavidS-ovm
Copy link
Author

Updated PR with feedback from maintainers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant