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

Add additional validation to secret_suffix in Kubernetes backend #35666

Merged
merged 11 commits into from
Oct 4, 2024
Merged
27 changes: 26 additions & 1 deletion internal/backend/remote-state/kubernetes/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"log"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/mitchellh/go-homedir"
"github.com/zclconf/go-cty/cty"
Expand Down Expand Up @@ -54,7 +56,7 @@ func New() backend.Backend {
"secret_suffix": {
Type: cty.String,
Required: true,
Description: "Suffix used when creating the secret. The secret will be named in the format: `tfstate-{workspace}-{secret_suffix}`.",
Description: "Suffix used when creating the secret. The secret will be named in the format: `tfstate-{workspace}-{secret_suffix}`. Note that the backend may append its own numeric index to the secret name when chunking large state files into multiple secrets. In this case, there will be multiple secrets named in the format: `tfstate-{workspace}-{secret_suffix}-{index}`.",
},
"labels": {
Type: cty.Map(cty.String),
Expand Down Expand Up @@ -322,7 +324,17 @@ func (b *Backend) Configure(configVal cty.Value) tfdiags.Diagnostics {

ns := data.String("namespace")
b.namespace = ns

b.nameSuffix = data.String("secret_suffix")
if hasNumericSuffix(b.nameSuffix, "-") {
// If the last segment is a number, it's considered invalid.
// The backend automatically appends its own numeric suffix when chunking large state files into multiple secrets.
// Allowing a user-defined numeric suffix could cause conflicts with this mechanism.
return backendbase.ErrorAsDiagnostics(
fmt.Errorf("secret_suffix must not end with '-<number>', got %q", b.nameSuffix),
)
}

b.config = cfg

return nil
Expand Down Expand Up @@ -464,3 +476,16 @@ func decodeListOfString(v cty.Value) []string {
}
return ret
}

func hasNumericSuffix(value, substr string) bool {
// Find the last occurrence of '-' and get the part after it
if idx := strings.LastIndex(value, substr); idx != -1 {
lastPart := value[idx+1:]
// Try to convert the last part to an integer.
if _, err := strconv.Atoi(lastPart); err == nil {
return true
}
}
// Return false if no '-' is found or if the last part isn't numeric
return false
}
21 changes: 21 additions & 0 deletions internal/backend/remote-state/kubernetes/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,24 @@ func cleanupK8sResources(t *testing.T) {
t.Fatal(errs)
}
}

func Test_hasNumericSuffix(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"my-secret-123", true},
{"my-secret-abcd", false},
{"nodashhere", false},
{"another-secret-45abc", false},
{"some-thing-1", true},
{"some-thing-1-23", true},
}

for _, tt := range tests {
isNumeric := hasNumericSuffix(tt.input, "-")
if isNumeric != tt.expected {
t.Errorf("expected %v, got %v for input %s", tt.expected, isNumeric, tt.input)
}
}
}
2 changes: 2 additions & 0 deletions internal/backend/remote-state/kubernetes/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ func (c *RemoteClient) getSecrets() ([]unstructured.Unstructured, error) {
for _, item := range res.Items {
name := item.GetName()
nameParts := strings.Split(name, "-")
// Because large Terraform state files are split into multiple secrets,
// we parse the index from the secret name.
index, err := strconv.Atoi(nameParts[len(nameParts)-1])
if err != nil {
index = 0
Expand Down
2 changes: 1 addition & 1 deletion website/docs/language/backend/kubernetes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ data "terraform_remote_state" "foo" {

The following configuration options are supported:

* `secret_suffix` - (Required) Suffix used when creating secrets. Secrets will be named in the format: `tfstate-{workspace}-{secret_suffix}`.
* `secret_suffix` - (Required) Suffix used when creating the secret. The secret will be named in the format: `tfstate-{workspace}-{secret_suffix}`. Note that the backend may append its own numeric index to the secret name when chunking large state files into multiple secrets. In this case, there will be multiple secrets named in the format: `tfstate-{workspace}-{secret_suffix}-{index}`.
* `labels` - (Optional) Map of additional labels to be applied to the secret and lease.
* `namespace` - (Optional) Namespace to store the secret and lease in. Can be sourced from `KUBE_NAMESPACE`.
* `in_cluster_config` - (Optional) Used to authenticate to the cluster from inside a pod. Can be sourced from `KUBE_IN_CLUSTER_CONFIG`.
Expand Down
Loading