From 8ad9077475a33a3a6bcfc642034b2e8e5e41728d Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Thu, 26 Jun 2025 11:04:09 -0400 Subject: [PATCH 01/14] Extends config system to support differentiation of secret source types --- config.go | 58 ++++++++++++++++++++++++++++++++++++++++++++------ config_test.go | 27 +++++++++++++++++++++++ 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/config.go b/config.go index 6a23540..f4bc06c 100644 --- a/config.go +++ b/config.go @@ -9,18 +9,29 @@ import ( corev1 "k8s.io/api/core/v1" ) -// DefaultNamespace is the default kubernetes namespace. -const DefaultNamespace = "default" +const ( + // DefaultNamespace is the default kubernetes namespace. + DefaultNamespace = "default" -// DefaultLabelValue is the default label value that will be applied to secrets -// created by pentagon. -const DefaultLabelValue = "default" + // DefaultLabelValue is the default label value that will be applied to secrets + // created by pentagon. + DefaultLabelValue = "default" + + // VaultSourceType indicates a mapping sourced from Hashicorp Vault. + VaultSourceType = "vault" + + // GSMSourceType indicates a mapping sourced from Google Secrets Manager. + GSMSourceType = "gsm" +) // Config describes the configuration for vaultofsecrets type Config struct { - // VaultURL is the URL used to connect to vault. + // Vault is the configuration used to connect to vault. Vault VaultConfig `yaml:"vault"` + // GSM is the configuration used to connect to Google Secrets Manager. + GSM GSMConfig `yaml:"gsm"` + // Namespace is the k8s namespace that the secrets will be created in. Namespace string `yaml:"namespace"` @@ -51,6 +62,11 @@ func (c *Config) SetDefaults() { // set all the underlying mapping engine types to their default // if unspecified for i, m := range c.Mappings { + // default to vault source type for backward compatibility + if m.SourceType == "" { + c.Mappings[i].SourceType = VaultSourceType + } + if m.VaultEngineType == "" { c.Mappings[i].VaultEngineType = c.Vault.DefaultEngineType } @@ -66,6 +82,18 @@ func (c *Config) Validate() error { return fmt.Errorf("no mappings provided") } + validSourceTypes := map[string]struct{}{ + "": {}, + VaultSourceType: {}, + GSMSourceType: {}, + } + + for _, m := range c.Mappings { + if _, ok := validSourceTypes[m.SourceType]; !ok { + return fmt.Errorf("invalid source type: %+v", m.SourceType) + } + } + return nil } @@ -97,11 +125,27 @@ type VaultConfig struct { TLSConfig *api.TLSConfig `yaml:"tls"` // for other vault TLS options } +// GSMConfig is the Google Secrets Manager configuration. +type GSMConfig struct { + // Will add fields as needed. +} + // Mapping is a single mapping for a vault secret to a k8s secret. type Mapping struct { - // VaultPath is the path to the vault secret. + // SourceType is the source of a secret: Vault or GSM. Defaults to Vault. + SourceType string `yaml:"sourceType"` + + // VaultPath is the path to a vault secret. VaultPath string `yaml:"vaultPath"` + // GSMPath is the full-qualified path of a GSM secret, including its name and version. + // For example: + // - projects/*/secrets/*/versions/* + // - projects/*/secrets/*/versions/latest + // - projects/*/locations/*/secrets/*/versions/* + // - projects/*/locations/*/secrets/*/versions/latest + GSMPath string `yaml:"gsmPath"` + // SecretName is the name of the k8s secret that the vault contents should // be written to. Note that this must be a DNS-1123-compatible name and // match the regex [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* diff --git a/config_test.go b/config_test.go index f39e815..a58d7bf 100644 --- a/config_test.go +++ b/config_test.go @@ -33,6 +33,9 @@ func TestSetDefaults(t *testing.T) { } for _, m := range c.Mappings { + if m.SourceType != VaultSourceType { + t.Fatalf("source type should have defaulted to vault: %+v", m) + } if m.VaultEngineType == "" { t.Fatalf("empty vault engine type for mapping: %+v", m) } @@ -91,3 +94,27 @@ func TestValidate(t *testing.T) { t.Fatalf("configuration should have been valid: %s", err) } } + +func TestValidSourceTypes(t *testing.T) { + c := &Config{ + Mappings: []Mapping{ + {SourceType: ""}, + {SourceType: VaultSourceType}, + {SourceType: GSMSourceType}, + }, + } + if err := c.Validate(); err != nil { + t.Fatalf("mappings should have been valid: %s", err) + } +} + +func TestInvalidSourceType(t *testing.T) { + c := &Config{ + Mappings: []Mapping{ + {SourceType: "foo"}, + }, + } + if err := c.Validate(); err == nil { + t.Fatalf("failed to detect invalid mapping source type") + } +} From d2aee312dd9656f0e75b9d3926f29fc1f02983f9 Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Thu, 26 Jun 2025 11:39:34 -0400 Subject: [PATCH 02/14] Separates concerns by compartmentalizing Vault and K8s functionality --- reflector.go | 193 +++++++++++++++++++++++++-------------------------- 1 file changed, 94 insertions(+), 99 deletions(-) diff --git a/reflector.go b/reflector.go index c7cbb64..b43f7ab 100644 --- a/reflector.go +++ b/reflector.go @@ -5,20 +5,20 @@ import ( "fmt" "log" - v1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" + typedv1 "k8s.io/client-go/kubernetes/typed/core/v1" "github.com/vimeo/pentagon/vault" ) -// LabelKey is the name of label that will be attached to every secret created -// by pentagon. +// LabelKey is the name of label that will be attached to every secret created by pentagon. const LabelKey = "pentagon" -// NewReflector returns a new relfector +// NewReflector returns a new reflector func NewReflector( vaultClient vault.Logical, k8sClient kubernetes.Interface, @@ -26,151 +26,146 @@ func NewReflector( labelValue string, ) *Reflector { return &Reflector{ - vaultClient: vaultClient, - k8sClient: k8sClient, - k8sNamespace: k8sNamespace, - labelValue: labelValue, + vaultClient: vaultClient, + secretsClient: k8sClient.CoreV1().Secrets(k8sNamespace), + k8sNamespace: k8sNamespace, + labelValue: labelValue, } } // Reflector moves things from vault to kubernetes type Reflector struct { - vaultClient vault.Logical - k8sClient kubernetes.Interface - k8sNamespace string - labelValue string + vaultClient vault.Logical + secretsClient typedv1.SecretInterface + k8sNamespace string + labelValue string + secretsSet map[string]struct{} } -// Reflect actually syncs the values between vault and k8s secrets based on -// the mappings passed. +// Reflect syncs the values between vault and k8s secrets based on the mappings passed. func (r *Reflector) Reflect(ctx context.Context, mappings []Mapping) error { - secrets := r.k8sClient.CoreV1().Secrets(r.k8sNamespace) - - // only select secrets that we created - listOptions := metav1.ListOptions{ + // create a set of existing k8s secrets which were created by pentagon + secretsList, err := r.secretsClient.List(ctx, metav1.ListOptions{ LabelSelector: labels.Set{LabelKey: r.labelValue}.String(), - } - - secretsList, err := secrets.List(ctx, listOptions) + }) if err != nil { return fmt.Errorf("error listing secrets: %s", err) } - - // make a set of the secrets keyed by name so we can easily access them. - secretsSet := make(map[string]struct{}, secretsList.Size()) + r.secretsSet = make(map[string]struct{}, secretsList.Size()) for _, secret := range secretsList.Items { - secretsSet[secret.ObjectMeta.Name] = struct{}{} + r.secretsSet[secret.ObjectMeta.Name] = struct{}{} } - // make a set of the secrets that we're actually updating so we can - // reconcile later. + // make a set of the secrets that we're updating so we can reconcile later. touchedSecrets := map[string]struct{}{} for _, mapping := range mappings { - secretData, err := r.vaultClient.Read(mapping.VaultPath) + k8sSecretData, err := r.getVaultSecret(mapping) if err != nil { - return fmt.Errorf( - "error reading vault key '%s': %s", - mapping.VaultPath, - err, - ) + return err } - if secretData == nil { - return fmt.Errorf("secret %s not found", mapping.VaultPath) + if err := r.createK8sSecret(ctx, mapping, k8sSecretData); err != nil { + return err } - var k8sSecretData map[string][]byte + log.Printf( + "reflected vault secret %s to kubernetes %s type (%s)", + mapping.VaultPath, + mapping.SecretName, + mapping.SecretType, + ) - // convert map[string]interface{} to map[string][]byte - switch mapping.VaultEngineType { - case vault.EngineTypeKeyValueV1: - k8sSecretData, err = r.castData(secretData.Data) - if err != nil { - return fmt.Errorf("error casting data: %s", err) - } - case vault.EngineTypeKeyValueV2: - // there's an extra level of wrapping with the v2 kv secrets engine - if unwrapped, ok := secretData.Data["data"].(map[string]interface{}); ok { - k8sSecretData, err = r.castData(unwrapped) - } else { - return fmt.Errorf("key/value v2 interface did not have " + - "expected extra wrapping") - } - default: - return fmt.Errorf( - "unknown vault engine type: %q", - mapping.VaultEngineType, - ) - } + // record the fact that we updated it + touchedSecrets[mapping.SecretName] = struct{}{} + } - // create the new Secret - newSecret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: mapping.SecretName, - Namespace: r.k8sNamespace, - Labels: map[string]string{ - LabelKey: r.labelValue, - }, - }, - Data: k8sSecretData, - Type: mapping.SecretType, + // if we're not using the default label value, delete any secrets that are no longer in our + // mappings, but might still exist from previous runs in kubernetes + if r.labelValue != DefaultLabelValue { + if err := r.reconcile(ctx, r.secretsSet, touchedSecrets); err != nil { + return fmt.Errorf("error reconciling: %s", err) } + } + + return nil +} + +func (r *Reflector) getVaultSecret(mapping Mapping) (map[string][]byte, error) { + secretData, err := r.vaultClient.Read(mapping.VaultPath) + if err != nil { + return nil, fmt.Errorf("error reading vault key '%s': %s", mapping.VaultPath, err) + } + + if secretData == nil { + return nil, fmt.Errorf("secret %s not found", mapping.VaultPath) + } - if _, ok := secretsSet[mapping.SecretName]; ok { - // secret already exists, so we should update it - _, err = secrets.Update(ctx, newSecret, metav1.UpdateOptions{}) + // convert map[string]interface{} to map[string][]byte + var k8sSecretData map[string][]byte + switch mapping.VaultEngineType { + case vault.EngineTypeKeyValueV1: + k8sSecretData, err = r.castData(secretData.Data) + if err != nil { + return nil, fmt.Errorf("error casting data: %s", err) + } + case vault.EngineTypeKeyValueV2: + // there's an extra level of wrapping with the v2 kv secrets engine + if unwrapped, ok := secretData.Data["data"].(map[string]interface{}); ok { + k8sSecretData, err = r.castData(unwrapped) if err != nil { - return fmt.Errorf("error updating secret: %s", err) + return nil, fmt.Errorf("error casting data: %s", err) } } else { - // secret doesn't exist, so create it - _, err = secrets.Create(ctx, newSecret, metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("error creating secret: %s", err) - } + return nil, fmt.Errorf("key/value v2 interface did not have expected extra wrapping") } + default: + return nil, fmt.Errorf("unknown vault engine type: %q", mapping.VaultEngineType) + } - log.Printf( - "reflected vault secret %s to kubernetes %s type (%s)", - mapping.VaultPath, - mapping.SecretName, - mapping.SecretType, - ) + return k8sSecretData, nil +} - // record the fact that we actually updated it - touchedSecrets[newSecret.Name] = struct{}{} +func (r *Reflector) createK8sSecret(ctx context.Context, mapping Mapping, data map[string][]byte) error { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: mapping.SecretName, + Namespace: r.k8sNamespace, + Labels: map[string]string{LabelKey: r.labelValue}, + }, + Data: data, + Type: mapping.SecretType, } - // if we're not using the default label value, reconcile any secrets - // that are no longer in vault, but might still exist from previous runs - // in kubernetes - if r.labelValue != DefaultLabelValue { - err = r.reconcile(ctx, secretsSet, touchedSecrets) + if _, ok := r.secretsSet[mapping.SecretName]; ok { + // secret already exists, so we should update it + _, err := r.secretsClient.Update(ctx, secret, metav1.UpdateOptions{}) if err != nil { - return fmt.Errorf("error reconciling: %s", err) + return fmt.Errorf("error updating secret: %s", err) + } + } else { + // secret doesn't exist, so create it + _, err := r.secretsClient.Create(ctx, secret, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("error creating secret: %s", err) } } - return nil } -// reconcile delete any secrets that were not part of the mapping (but still -// present in the secrets with the same label) +// reconcile deletes any secrets that were not part of the mapping (but still present in the secrets +// with the same label) func (r *Reflector) reconcile( ctx context.Context, allSecrets map[string]struct{}, touchedSecrets map[string]struct{}, ) error { - secretsAPI := r.k8sClient.CoreV1().Secrets(r.k8sNamespace) - for secret := range allSecrets { if _, found := touchedSecrets[secret]; !found { // it was in the list, but we didn't update it (or create it) - err := secretsAPI.Delete(ctx, secret, metav1.DeleteOptions{}) + err := r.secretsClient.Delete(ctx, secret, metav1.DeleteOptions{}) - // not found is ok because we're deleting, so only return the - // error if it's NOT not found... + // not found is ok, since we're deleting the secret if err != nil && !errors.IsNotFound(err) { return err } From bd9f2feac20e03016c6d5699311d6f42d0432c4a Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Thu, 26 Jun 2025 13:46:00 -0400 Subject: [PATCH 03/14] Adds handler for GSM secrets --- pentagon/main.go | 9 +++++++++ reflector.go | 39 ++++++++++++++++++++++++++++++++++----- reflector_test.go | 13 ++++++++++++- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/pentagon/main.go b/pentagon/main.go index fb18bf6..7ad68f5 100644 --- a/pentagon/main.go +++ b/pentagon/main.go @@ -12,6 +12,7 @@ import ( "syscall" "cloud.google.com/go/compute/metadata" + secretmanager "cloud.google.com/go/secretmanager/apiv1" "github.com/hashicorp/vault/api" yaml "gopkg.in/yaml.v2" "k8s.io/client-go/kubernetes" @@ -76,8 +77,16 @@ func main() { os.Exit(31) } + gsmClient, err := secretmanager.NewClient(ctx) + if err != nil { + log.Printf("unable to get GSM client: %s", err) + os.Exit(32) + } + defer gsmClient.Close() + reflector := pentagon.NewReflector( vaultClient.Logical(), + gsmClient, k8sClient, config.Namespace, config.Label, diff --git a/reflector.go b/reflector.go index b43f7ab..2a08ba1 100644 --- a/reflector.go +++ b/reflector.go @@ -5,6 +5,8 @@ import ( "fmt" "log" + secretmanager "cloud.google.com/go/secretmanager/apiv1" + "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,28 +23,31 @@ const LabelKey = "pentagon" // NewReflector returns a new reflector func NewReflector( vaultClient vault.Logical, + gsmClient *secretmanager.Client, k8sClient kubernetes.Interface, k8sNamespace string, labelValue string, ) *Reflector { return &Reflector{ vaultClient: vaultClient, + gsmClient: gsmClient, secretsClient: k8sClient.CoreV1().Secrets(k8sNamespace), k8sNamespace: k8sNamespace, labelValue: labelValue, } } -// Reflector moves things from vault to kubernetes +// Reflector moves secrets from Vault/GSM to Kubernetes type Reflector struct { vaultClient vault.Logical + gsmClient *secretmanager.Client secretsClient typedv1.SecretInterface k8sNamespace string labelValue string secretsSet map[string]struct{} } -// Reflect syncs the values between vault and k8s secrets based on the mappings passed. +// Reflect syncs the values between Vault/GSM and k8s secrets based on the mappings passed. func (r *Reflector) Reflect(ctx context.Context, mappings []Mapping) error { // create a set of existing k8s secrets which were created by pentagon secretsList, err := r.secretsClient.List(ctx, metav1.ListOptions{ @@ -60,9 +65,22 @@ func (r *Reflector) Reflect(ctx context.Context, mappings []Mapping) error { touchedSecrets := map[string]struct{}{} for _, mapping := range mappings { - k8sSecretData, err := r.getVaultSecret(mapping) - if err != nil { - return err + var k8sSecretData map[string][]byte + switch mapping.SourceType { + case GSMSourceType: + var err error + k8sSecretData, err = r.getGSMSecret(ctx, mapping) + if err != nil { + return err + } + case VaultSourceType: + var err error + k8sSecretData, err = r.getVaultSecret(mapping) + if err != nil { + return err + } + default: + return fmt.Errorf("unknown secret source type: %s", mapping.SourceType) } if err := r.createK8sSecret(ctx, mapping, k8sSecretData); err != nil { @@ -126,6 +144,17 @@ func (r *Reflector) getVaultSecret(mapping Mapping) (map[string][]byte, error) { return k8sSecretData, nil } +func (r *Reflector) getGSMSecret(ctx context.Context, mapping Mapping) (map[string][]byte, error) { + resp, err := r.gsmClient.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{ + Name: mapping.GSMPath, + }) + if err != nil { + return nil, err + } + + return map[string][]byte{mapping.SecretName: resp.Payload.Data}, nil +} + func (r *Reflector) createK8sSecret(ctx context.Context, mapping Mapping, data map[string][]byte) error { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ diff --git a/reflector_test.go b/reflector_test.go index a4653f1..3d0eb1a 100644 --- a/reflector_test.go +++ b/reflector_test.go @@ -40,12 +40,14 @@ func TestRefactorSimple(t *testing.T) { r := NewReflector( vaultClient, + nil, // TODO: Mock GSM. k8sClient, DefaultNamespace, DefaultLabelValue, ) err := r.Reflect(ctx, []Mapping{ { + SourceType: "vault", VaultPath: "secrets/data/foo", SecretName: "foo", VaultEngineType: engineType, @@ -101,6 +103,7 @@ func TestReflectorNoReconcile(t *testing.T) { r := NewReflector( vaultClient, + nil, // TODO: Mock GSM. k8sClient, DefaultNamespace, DefaultLabelValue, @@ -109,11 +112,13 @@ func TestReflectorNoReconcile(t *testing.T) { // reflect both secrets err := r.Reflect(ctx, []Mapping{ { + SourceType: "vault", VaultPath: "secrets/data/foo1", SecretName: "foo1", VaultEngineType: engineType, }, { + SourceType: "vault", VaultPath: "secrets/data/foo2", SecretName: "foo2", VaultEngineType: engineType, @@ -140,6 +145,7 @@ func TestReflectorNoReconcile(t *testing.T) { // and not get reconciled because we're using the default label value. err = r.Reflect(ctx, []Mapping{ { + SourceType: "vault", VaultPath: "secrets/data/foo1", SecretName: "foo1", VaultEngineType: engineType, @@ -199,15 +205,17 @@ func TestReflectorWithReconcile(t *testing.T) { t.Fatalf("unable to create other-reflect secret: %s", err) } - r := NewReflector(vaultClient, k8sClient, DefaultNamespace, "test") + r := NewReflector(vaultClient, nil, k8sClient, DefaultNamespace, "test") // TODO: Mock GSM. err = r.Reflect(ctx, []Mapping{ { + SourceType: "vault", VaultPath: "secrets/data/foo1", SecretName: "foo1", VaultEngineType: engineType, }, { + SourceType: "vault", VaultPath: "secrets/data/foo2", SecretName: "foo2", VaultEngineType: engineType, @@ -238,6 +246,7 @@ func TestReflectorWithReconcile(t *testing.T) { // because we're using a non-default label value. err = r.Reflect(ctx, []Mapping{ { + SourceType: "vault", VaultPath: "secrets/data/foo1", SecretName: "foo1", VaultEngineType: engineType, @@ -283,12 +292,14 @@ func TestUnsupportedEngineType(t *testing.T) { r := NewReflector( vaultClient, + nil, // TODO: Mock GSM. k8sClient, DefaultNamespace, DefaultLabelValue, ) err := r.Reflect(ctx, []Mapping{ { + SourceType: "vault", VaultPath: "secrets/data/foo", SecretName: "foo", VaultEngineType: vault.EngineType("unsupported"), From 584709832b018b4b528a3eed97d325d69cf18bbd Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Thu, 26 Jun 2025 16:21:01 -0400 Subject: [PATCH 04/14] Adds mock GSM client --- gsm/mock_gsm.go | 54 ++++++++++++++++++++++++++++++++++++++++++++ gsm/mock_gsm_test.go | 37 ++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 gsm/mock_gsm.go create mode 100644 gsm/mock_gsm_test.go diff --git a/gsm/mock_gsm.go b/gsm/mock_gsm.go new file mode 100644 index 0000000..a28ca67 --- /dev/null +++ b/gsm/mock_gsm.go @@ -0,0 +1,54 @@ +package gsm + +import ( + "context" + "errors" + "fmt" + "strings" + + "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" + "github.com/googleapis/gax-go/v2" +) + +// SecretAccessor exposes the AccessSecretVersion method from the SecretManager Client. +type SecretAccessor interface { + AccessSecretVersion( + context.Context, + *secretmanagerpb.AccessSecretVersionRequest, + ...gax.CallOption, + ) (*secretmanagerpb.AccessSecretVersionResponse, error) +} + +type MockGSM struct{} + +func NewMockGSM() *MockGSM { + return &MockGSM{} +} + +func (m *MockGSM) AccessSecretVersion( + ctx context.Context, + req *secretmanagerpb.AccessSecretVersionRequest, + opts ...gax.CallOption, +) (*secretmanagerpb.AccessSecretVersionResponse, error) { + fields := strings.Split(req.Name, "/") + var data string + if strings.Contains(req.Name, "locations") { + if len(fields) != 8 { + return nil, errors.New("invalid regional secret path") + } + // project, location, secret, version + data = fmt.Sprintf("%s_%s_%s_%s", fields[1], fields[3], fields[5], fields[7]) + } else { + if len(fields) != 6 { + return nil, errors.New("invalid secret path") + } + // project, secret, version + data = fmt.Sprintf("%s_%s_%s", fields[1], fields[3], fields[5]) + } + + return &secretmanagerpb.AccessSecretVersionResponse{ + Payload: &secretmanagerpb.SecretPayload{ + Data: []byte(data), + }, + }, nil +} diff --git a/gsm/mock_gsm_test.go b/gsm/mock_gsm_test.go new file mode 100644 index 0000000..da5faaa --- /dev/null +++ b/gsm/mock_gsm_test.go @@ -0,0 +1,37 @@ +package gsm + +import ( + "context" + "testing" + + "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" +) + +func TestMockGSM(t *testing.T) { + m := NewMockGSM() + ctx := context.Background() + + // Test regional secrets. + req1 := &secretmanagerpb.AccessSecretVersionRequest{ + Name: "projects/foo/locations/bar/secrets/baz/versions/3", + } + resp1, err := m.AccessSecretVersion(ctx, req1) + if err != nil { + t.Fatal(err) + } + if string(resp1.Payload.Data) != "foo_bar_baz_3" { + t.Fatal(err) + } + + // Test non-regional secrets. + req2 := &secretmanagerpb.AccessSecretVersionRequest{ + Name: "projects/foo/secrets/bar/versions/latest", + } + resp2, err := m.AccessSecretVersion(ctx, req2) + if err != nil { + t.Fatal(err) + } + if string(resp2.Payload.Data) != "foo_bar_latest" { + t.Fatal(err) + } +} From b32696fd1b129b79b89b8961bb5c4a533819be47 Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Thu, 26 Jun 2025 16:21:34 -0400 Subject: [PATCH 05/14] Use GSM interface in Reflector --- reflector.go | 27 ++++++++++++++-------- reflector_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/reflector.go b/reflector.go index 2a08ba1..60fd2bc 100644 --- a/reflector.go +++ b/reflector.go @@ -5,7 +5,6 @@ import ( "fmt" "log" - secretmanager "cloud.google.com/go/secretmanager/apiv1" "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -14,6 +13,7 @@ import ( "k8s.io/client-go/kubernetes" typedv1 "k8s.io/client-go/kubernetes/typed/core/v1" + "github.com/vimeo/pentagon/gsm" "github.com/vimeo/pentagon/vault" ) @@ -23,7 +23,7 @@ const LabelKey = "pentagon" // NewReflector returns a new reflector func NewReflector( vaultClient vault.Logical, - gsmClient *secretmanager.Client, + gsmClient gsm.SecretAccessor, k8sClient kubernetes.Interface, k8sNamespace string, labelValue string, @@ -40,7 +40,7 @@ func NewReflector( // Reflector moves secrets from Vault/GSM to Kubernetes type Reflector struct { vaultClient vault.Logical - gsmClient *secretmanager.Client + gsmClient gsm.SecretAccessor secretsClient typedv1.SecretInterface k8sNamespace string labelValue string @@ -66,6 +66,7 @@ func (r *Reflector) Reflect(ctx context.Context, mappings []Mapping) error { for _, mapping := range mappings { var k8sSecretData map[string][]byte + var msg string switch mapping.SourceType { case GSMSourceType: var err error @@ -73,12 +74,24 @@ func (r *Reflector) Reflect(ctx context.Context, mappings []Mapping) error { if err != nil { return err } + msg = fmt.Sprintf( + "reflected GSM secret %s to kubernetes secret %s (type %s)", + mapping.GSMPath, + mapping.SecretName, + mapping.SecretType, + ) case VaultSourceType: var err error k8sSecretData, err = r.getVaultSecret(mapping) if err != nil { return err } + msg = fmt.Sprintf( + "reflected vault secret %s to kubernetes secret %s (type %s)", + mapping.VaultPath, + mapping.SecretName, + mapping.SecretType, + ) default: return fmt.Errorf("unknown secret source type: %s", mapping.SourceType) } @@ -87,14 +100,8 @@ func (r *Reflector) Reflect(ctx context.Context, mappings []Mapping) error { return err } - log.Printf( - "reflected vault secret %s to kubernetes %s type (%s)", - mapping.VaultPath, - mapping.SecretName, - mapping.SecretType, - ) - // record the fact that we updated it + log.Println(msg) touchedSecrets[mapping.SecretName] = struct{}{} } diff --git a/reflector_test.go b/reflector_test.go index 3d0eb1a..ae8bbd6 100644 --- a/reflector_test.go +++ b/reflector_test.go @@ -2,6 +2,7 @@ package pentagon import ( "context" + "log" "testing" v1 "k8s.io/api/core/v1" @@ -9,6 +10,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sfake "k8s.io/client-go/kubernetes/fake" + "github.com/vimeo/pentagon/gsm" "github.com/vimeo/pentagon/vault" ) @@ -40,7 +42,7 @@ func TestRefactorSimple(t *testing.T) { r := NewReflector( vaultClient, - nil, // TODO: Mock GSM. + gsm.NewMockGSM(), k8sClient, DefaultNamespace, DefaultLabelValue, ) @@ -74,15 +76,59 @@ func TestRefactorSimple(t *testing.T) { } if string(secret.Data["foo"]) != "bar" { - t.Fatalf("foo does not equal bar: %s", string(secret.Data["foo"])) + t.Fatalf("secret value does not equal bar: %s", string(secret.Data["foo"])) } if string(secret.Data["bar"]) != "baz" { - t.Fatalf("bar does not equal baz: %s", string(secret.Data["bar"])) + t.Fatalf("secret value does not equal baz: %s", string(secret.Data["bar"])) } }) } +func TestRefactorGSM(t *testing.T) { + ctx := context.Background() + k8sClient := k8sfake.NewSimpleClientset() + + r := NewReflector( + nil, + gsm.NewMockGSM(), + k8sClient, DefaultNamespace, + DefaultLabelValue, + ) + + err := r.Reflect(ctx, []Mapping{ + { + SourceType: "gsm", + GSMPath: "projects/foo/secrets/bar/versions/latest", + SecretName: "foo", + }, + }) + if err != nil { + t.Fatalf("reflect didn't work: %s", err) + } + + // now get the secret out of k8s + secrets := k8sClient.CoreV1().Secrets(DefaultNamespace) + + secret, err := secrets.Get(ctx, "foo", metav1.GetOptions{}) + if err != nil { + t.Fatalf("secret should be there: %s", err) + } + + if secret.Labels[LabelKey] != DefaultLabelValue { + t.Fatalf( + "secret pentagon label should be %s is %s", + DefaultLabelValue, + secret.Labels[LabelKey], + ) + } + + log.Println(string(secret.Data["foo"])) + if string(secret.Data["foo"]) != "foo_bar_latest" { + t.Fatalf("secret value does not equal foo_bar_latest: %s", string(secret.Data["foo"])) + } +} + func TestReflectorNoReconcile(t *testing.T) { allEngineTest(t, func(t testing.TB, engineType vault.EngineType) { ctx := context.Background() @@ -103,7 +149,7 @@ func TestReflectorNoReconcile(t *testing.T) { r := NewReflector( vaultClient, - nil, // TODO: Mock GSM. + gsm.NewMockGSM(), k8sClient, DefaultNamespace, DefaultLabelValue, @@ -205,7 +251,7 @@ func TestReflectorWithReconcile(t *testing.T) { t.Fatalf("unable to create other-reflect secret: %s", err) } - r := NewReflector(vaultClient, nil, k8sClient, DefaultNamespace, "test") // TODO: Mock GSM. + r := NewReflector(vaultClient, gsm.NewMockGSM(), k8sClient, DefaultNamespace, "test") err = r.Reflect(ctx, []Mapping{ { @@ -292,7 +338,7 @@ func TestUnsupportedEngineType(t *testing.T) { r := NewReflector( vaultClient, - nil, // TODO: Mock GSM. + gsm.NewMockGSM(), k8sClient, DefaultNamespace, DefaultLabelValue, ) From 8089e1e4349539ac90e26d7c19d660672a663c21 Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Thu, 26 Jun 2025 14:06:48 -0400 Subject: [PATCH 06/14] Adds GSM dependencies --- go.mod | 53 +++++++++++++++++-------- go.sum | 119 ++++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 122 insertions(+), 50 deletions(-) diff --git a/go.mod b/go.mod index 3919ef1..60e44fa 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,13 @@ module github.com/vimeo/pentagon -go 1.18 +go 1.23.0 + +toolchain go1.24.2 require ( - cloud.google.com/go/compute/metadata v0.2.3 + cloud.google.com/go/compute/metadata v0.7.0 + cloud.google.com/go/secretmanager v1.15.0 + github.com/googleapis/gax-go/v2 v2.14.2 github.com/hashicorp/vault/api v1.9.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.3 @@ -12,22 +16,28 @@ require ( ) require ( - cloud.google.com/go/compute v1.19.0 // indirect + cloud.google.com/go/auth v0.16.2 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/iam v1.5.2 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/fatih/color v1.15.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect @@ -50,15 +60,26 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/time v0.12.0 // indirect + google.golang.org/api v0.237.0 // indirect + google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/grpc v1.73.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index f8ebff8..1f70424 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,17 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA= +cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= +cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4= +cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/secretmanager v1.15.0 h1:RtkCMgTpaBMbzozcRUGfZe46jb9a3qh5EdEtVRUATF8= +cloud.google.com/go/secretmanager v1.15.0/go.mod h1:1hQSAhKK7FldiYw//wbR/XPfPc08eQ81oBsnRUHEvUc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -37,11 +45,16 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= @@ -49,12 +62,12 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -67,8 +80,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -77,14 +90,20 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= +github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= @@ -119,8 +138,9 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -153,7 +173,9 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -161,11 +183,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -177,19 +202,36 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -202,25 +244,26 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -237,20 +280,20 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -263,15 +306,21 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.237.0 h1:MP7XVsGZesOsx3Q8WVa4sUdbrsTvDSOERd3Vh4xj/wc= +google.golang.org/api v0.237.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -279,6 +328,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -291,8 +342,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From f409a55c2b22159e93a913851f8196dfddb0ad18 Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Wed, 2 Jul 2025 09:48:41 -0400 Subject: [PATCH 07/14] Upgrades Go to v1.24 --- .github/workflows/go.yml | 2 +- Dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 69820b4..58c17d4 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: os: [macOS-latest, ubuntu-latest] - goversion: [1.19, "1.20"] + goversion: ["1.24"] steps: - name: Set up Go ${{matrix.goversion}} on ${{matrix.os}} uses: actions/setup-go@v3 diff --git a/Dockerfile b/Dockerfile index 301049f..02d6d17 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM golang:1.18.3-alpine as builder +FROM golang:1.24-alpine AS builder RUN apk add --no-cache ca-certificates libc-dev git make gcc RUN adduser -D pentagon USER pentagon # Enable go modules -ENV GO111MODULE on +ENV GO111MODULE=on # The golang docker images configure GOPATH=/go RUN mkdir -p /go/src/github.com/vimeo/pentagon /go/pkg/ From 9122be9aead25d6992bd24964d2d355d49d4e137 Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Wed, 2 Jul 2025 09:50:32 -0400 Subject: [PATCH 08/14] Updates README.md --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 83c335a..9fdaf8b 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,13 @@ [![GoDoc](https://godoc.org/github.com/vimeo/pentagon?status.svg)](https://godoc.org/github.com/vimeo/pentagon) [![Go Report Card](https://goreportcard.com/badge/github.com/vimeo/pentagon)](https://goreportcard.com/report/github.com/vimeo/pentagon) # Pentagon -Pentagon is a small application designed to run as a Kubernetes CronJob to periodically copy secrets stored in [Vault](https://www.vaultproject.io) into equivalent [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), keeping them synchronized. Naturally, this should be used with care as "standard" Kubernetes Secrets are simply obfuscated as base64-encoded strings. However, one can and should use more secure methods of securing secrets including Google's [KMS](https://cloud.google.com/kubernetes-engine/docs/how-to/encrypting-secrets) and restricting roles and service accounts appropriately. - -Use at your own risk... +Pentagon is a small application designed to run as a Kubernetes CronJob to periodically copy secrets stored in [Vault](https://www.vaultproject.io) or Google Secrets Manager into equivalent [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), keeping them synchronized. Naturally, this should be used with care as "standard" Kubernetes Secrets are simply obfuscated as base64-encoded strings. However, one can and should use more secure methods of securing secrets including Google's [KMS](https://cloud.google.com/kubernetes-engine/docs/how-to/encrypting-secrets) and restricting roles and service accounts appropriately. ## Why not just query Vault? That's a good question. If you have a highly-available Vault setup that is stable and performant and you're able to modify your applications to query Vault, that's a completely reasonable approach to take. If you don't have such a setup, Pentagon provides a way to cache things securely in Kubernetes secrets which can then be provided to applications without directly introducing a Vault dependency. ## Configuration -Pentagon requires a simple YAML configuration file, the path to which should be passed as the first and only argument to the application. It is recommended that you store this configuration in a [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/) and reference it in the CronJob specification. A sample configuration follows: +Pentagon requires a YAML configuration file, the path to which should be passed as the first and only argument to the application. It is recommended that you store this configuration in a [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/) and reference it in the CronJob specification. A sample configuration follows: ```yaml vault: @@ -28,6 +26,10 @@ mappings: secretName: k8s-secretname vaultEngineType: # optionally "kv" or "kv-v2" to override the defaultEngineType specified above secretType: Opaque # optionally - default "Opaque" e.g.: "kubernetes.io/tls" + # mappings from google secrets manager paths to kubernetes secret names + - sourceType: gsm + gsmPath: projects/my-project/secrets/my-secret/versions/latest + secretName: my-secret ``` ### Labels and Reconciliation From faf1a4f47caac8160d64fe4b8fb0d5d4d3056b3f Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Thu, 3 Jul 2025 09:37:48 -0400 Subject: [PATCH 09/14] Updates log message --- pentagon/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pentagon/main.go b/pentagon/main.go index 7ad68f5..ae7629c 100644 --- a/pentagon/main.go +++ b/pentagon/main.go @@ -94,7 +94,7 @@ func main() { err = reflector.Reflect(ctx, config.Mappings) if err != nil { - log.Printf("error reflecting vault values into kubernetes: %s", err) + log.Printf("error reflecting secrets into kubernetes: %s", err) os.Exit(40) } } From 90dcaf28d67b173127ad265e59345ab49a7ec8de Mon Sep 17 00:00:00 2001 From: Will Date: Mon, 7 Jul 2025 16:48:37 -0400 Subject: [PATCH 10/14] Update reflector.go Co-authored-by: Sergio Salvatore --- reflector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflector.go b/reflector.go index 60fd2bc..a92a994 100644 --- a/reflector.go +++ b/reflector.go @@ -156,7 +156,7 @@ func (r *Reflector) getGSMSecret(ctx context.Context, mapping Mapping) (map[stri Name: mapping.GSMPath, }) if err != nil { - return nil, err + return nil, fmt.Errorf("error accessing GSM secret %q: %w", mapping.GSMPath, err) } return map[string][]byte{mapping.SecretName: resp.Payload.Data}, nil From 890a57d06b7e9dea6280740da6f5d1a25db9045d Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Mon, 7 Jul 2025 17:07:30 -0400 Subject: [PATCH 11/14] Adds suggested changes --- Dockerfile | 3 --- README.md | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 02d6d17..f9de17a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,9 +4,6 @@ RUN apk add --no-cache ca-certificates libc-dev git make gcc RUN adduser -D pentagon USER pentagon -# Enable go modules -ENV GO111MODULE=on - # The golang docker images configure GOPATH=/go RUN mkdir -p /go/src/github.com/vimeo/pentagon /go/pkg/ COPY --chown=pentagon . /go/src/github.com/vimeo/pentagon/ diff --git a/README.md b/README.md index 9fdaf8b..47fc560 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ The application will return 0 on success (when all keys were copied/updated succ | 22 | Configuration error. | | 30 | Unable to instantiate vault client. | | 31 | Unable to instantiate kubernetes client. | +| 32 | Unable to instantiate Google Secrets Manager client. | | 40 | Error copying keys. | ## Kubernetes Configuration From 4cc6d61f1c9d34d5aa5b4d003916055c79057b76 Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Tue, 8 Jul 2025 13:28:58 -0400 Subject: [PATCH 12/14] Use mapping.Path for both Vault and GSM secrets --- config.go | 31 ++++++++++++++----------------- config_test.go | 11 +++++++++-- reflector.go | 14 +++++++------- reflector_test.go | 18 +++++++++--------- 4 files changed, 39 insertions(+), 35 deletions(-) diff --git a/config.go b/config.go index f4bc06c..a4ee231 100644 --- a/config.go +++ b/config.go @@ -2,6 +2,7 @@ package pentagon import ( "fmt" + "log" "github.com/hashicorp/vault/api" "github.com/vimeo/pentagon/vault" @@ -24,14 +25,11 @@ const ( GSMSourceType = "gsm" ) -// Config describes the configuration for vaultofsecrets +// Config describes the configuration for Pentagon. type Config struct { // Vault is the configuration used to connect to vault. Vault VaultConfig `yaml:"vault"` - // GSM is the configuration used to connect to Google Secrets Manager. - GSM GSMConfig `yaml:"gsm"` - // Namespace is the k8s namespace that the secrets will be created in. Namespace string `yaml:"namespace"` @@ -67,6 +65,12 @@ func (c *Config) SetDefaults() { c.Mappings[i].SourceType = VaultSourceType } + // copy VaultPath to Path for backward compatibility + if m.Path == "" && m.VaultPath != "" { + log.Println("WARNING: Use mapping.Path instead of mapping.VaultPath (deprecated)") + c.Mappings[i].Path = m.VaultPath + } + if m.VaultEngineType == "" { c.Mappings[i].VaultEngineType = c.Vault.DefaultEngineType } @@ -125,26 +129,19 @@ type VaultConfig struct { TLSConfig *api.TLSConfig `yaml:"tls"` // for other vault TLS options } -// GSMConfig is the Google Secrets Manager configuration. -type GSMConfig struct { - // Will add fields as needed. -} - // Mapping is a single mapping for a vault secret to a k8s secret. type Mapping struct { // SourceType is the source of a secret: Vault or GSM. Defaults to Vault. SourceType string `yaml:"sourceType"` - // VaultPath is the path to a vault secret. - VaultPath string `yaml:"vaultPath"` - - // GSMPath is the full-qualified path of a GSM secret, including its name and version. - // For example: + // Path is the path to a Vault or GSM secret. + // GSM secrets can use one of the following forms; // - projects/*/secrets/*/versions/* - // - projects/*/secrets/*/versions/latest // - projects/*/locations/*/secrets/*/versions/* - // - projects/*/locations/*/secrets/*/versions/latest - GSMPath string `yaml:"gsmPath"` + Path string `yaml:"path"` + + // [DEPRECATED] VaultPath is the path to a vault secret. Use Path instead. + VaultPath string `yaml:"vaultPath"` // SecretName is the name of the k8s secret that the vault contents should // be written to. Note that this must be a DNS-1123-compatible name and diff --git a/config_test.go b/config_test.go index a58d7bf..f848ffd 100644 --- a/config_test.go +++ b/config_test.go @@ -13,7 +13,11 @@ func TestSetDefaults(t *testing.T) { }, Mappings: []Mapping{ { - VaultPath: "vaultPath", + Path: "path", + SecretName: "theSecret", + }, + { + VaultPath: "path", SecretName: "theSecret", }, }, @@ -36,6 +40,9 @@ func TestSetDefaults(t *testing.T) { if m.SourceType != VaultSourceType { t.Fatalf("source type should have defaulted to vault: %+v", m) } + if m.Path == "" { + t.Fatalf("empty path for vault secret: %+v", m) + } if m.VaultEngineType == "" { t.Fatalf("empty vault engine type for mapping: %+v", m) } @@ -83,7 +90,7 @@ func TestValidate(t *testing.T) { c = &Config{ Mappings: []Mapping{ { - VaultPath: "foo", + Path: "foo", SecretName: "bar", }, }, diff --git a/reflector.go b/reflector.go index a92a994..1c4902b 100644 --- a/reflector.go +++ b/reflector.go @@ -76,7 +76,7 @@ func (r *Reflector) Reflect(ctx context.Context, mappings []Mapping) error { } msg = fmt.Sprintf( "reflected GSM secret %s to kubernetes secret %s (type %s)", - mapping.GSMPath, + mapping.Path, mapping.SecretName, mapping.SecretType, ) @@ -88,7 +88,7 @@ func (r *Reflector) Reflect(ctx context.Context, mappings []Mapping) error { } msg = fmt.Sprintf( "reflected vault secret %s to kubernetes secret %s (type %s)", - mapping.VaultPath, + mapping.Path, mapping.SecretName, mapping.SecretType, ) @@ -117,13 +117,13 @@ func (r *Reflector) Reflect(ctx context.Context, mappings []Mapping) error { } func (r *Reflector) getVaultSecret(mapping Mapping) (map[string][]byte, error) { - secretData, err := r.vaultClient.Read(mapping.VaultPath) + secretData, err := r.vaultClient.Read(mapping.Path) if err != nil { - return nil, fmt.Errorf("error reading vault key '%s': %s", mapping.VaultPath, err) + return nil, fmt.Errorf("error reading vault key '%s': %s", mapping.Path, err) } if secretData == nil { - return nil, fmt.Errorf("secret %s not found", mapping.VaultPath) + return nil, fmt.Errorf("secret %s not found", mapping.Path) } // convert map[string]interface{} to map[string][]byte @@ -153,10 +153,10 @@ func (r *Reflector) getVaultSecret(mapping Mapping) (map[string][]byte, error) { func (r *Reflector) getGSMSecret(ctx context.Context, mapping Mapping) (map[string][]byte, error) { resp, err := r.gsmClient.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{ - Name: mapping.GSMPath, + Name: mapping.Path, }) if err != nil { - return nil, fmt.Errorf("error accessing GSM secret %q: %w", mapping.GSMPath, err) + return nil, fmt.Errorf("error accessing GSM secret %q: %w", mapping.Path, err) } return map[string][]byte{mapping.SecretName: resp.Payload.Data}, nil diff --git a/reflector_test.go b/reflector_test.go index ae8bbd6..48f6103 100644 --- a/reflector_test.go +++ b/reflector_test.go @@ -50,7 +50,7 @@ func TestRefactorSimple(t *testing.T) { err := r.Reflect(ctx, []Mapping{ { SourceType: "vault", - VaultPath: "secrets/data/foo", + Path: "secrets/data/foo", SecretName: "foo", VaultEngineType: engineType, }, @@ -99,7 +99,7 @@ func TestRefactorGSM(t *testing.T) { err := r.Reflect(ctx, []Mapping{ { SourceType: "gsm", - GSMPath: "projects/foo/secrets/bar/versions/latest", + Path: "projects/foo/secrets/bar/versions/latest", SecretName: "foo", }, }) @@ -159,13 +159,13 @@ func TestReflectorNoReconcile(t *testing.T) { err := r.Reflect(ctx, []Mapping{ { SourceType: "vault", - VaultPath: "secrets/data/foo1", + Path: "secrets/data/foo1", SecretName: "foo1", VaultEngineType: engineType, }, { SourceType: "vault", - VaultPath: "secrets/data/foo2", + Path: "secrets/data/foo2", SecretName: "foo2", VaultEngineType: engineType, }, @@ -192,7 +192,7 @@ func TestReflectorNoReconcile(t *testing.T) { err = r.Reflect(ctx, []Mapping{ { SourceType: "vault", - VaultPath: "secrets/data/foo1", + Path: "secrets/data/foo1", SecretName: "foo1", VaultEngineType: engineType, }, @@ -256,13 +256,13 @@ func TestReflectorWithReconcile(t *testing.T) { err = r.Reflect(ctx, []Mapping{ { SourceType: "vault", - VaultPath: "secrets/data/foo1", + Path: "secrets/data/foo1", SecretName: "foo1", VaultEngineType: engineType, }, { SourceType: "vault", - VaultPath: "secrets/data/foo2", + Path: "secrets/data/foo2", SecretName: "foo2", VaultEngineType: engineType, }, @@ -293,7 +293,7 @@ func TestReflectorWithReconcile(t *testing.T) { err = r.Reflect(ctx, []Mapping{ { SourceType: "vault", - VaultPath: "secrets/data/foo1", + Path: "secrets/data/foo1", SecretName: "foo1", VaultEngineType: engineType, }, @@ -346,7 +346,7 @@ func TestUnsupportedEngineType(t *testing.T) { err := r.Reflect(ctx, []Mapping{ { SourceType: "vault", - VaultPath: "secrets/data/foo", + Path: "secrets/data/foo", SecretName: "foo", VaultEngineType: vault.EngineType("unsupported"), }, From bb6618ffc13cbcedafb5c46f8566feca0ebabccd Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Tue, 8 Jul 2025 13:55:10 -0400 Subject: [PATCH 13/14] Check for empty path in config validation --- config.go | 3 +++ config_test.go | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/config.go b/config.go index a4ee231..88e1a7f 100644 --- a/config.go +++ b/config.go @@ -96,6 +96,9 @@ func (c *Config) Validate() error { if _, ok := validSourceTypes[m.SourceType]; !ok { return fmt.Errorf("invalid source type: %+v", m.SourceType) } + if m.Path == "" { + return fmt.Errorf("path should not be empty: %+v", m) + } } return nil diff --git a/config_test.go b/config_test.go index f848ffd..886ff4a 100644 --- a/config_test.go +++ b/config_test.go @@ -105,9 +105,9 @@ func TestValidate(t *testing.T) { func TestValidSourceTypes(t *testing.T) { c := &Config{ Mappings: []Mapping{ - {SourceType: ""}, - {SourceType: VaultSourceType}, - {SourceType: GSMSourceType}, + {SourceType: "", Path: "foo"}, + {SourceType: VaultSourceType, Path: "foo"}, + {SourceType: GSMSourceType, Path: "foo"}, }, } if err := c.Validate(); err != nil { From fcc692baf60ae32fe76c74e698a4f48c045c4096 Mon Sep 17 00:00:00 2001 From: Will Roberts Date: Tue, 8 Jul 2025 14:01:10 -0400 Subject: [PATCH 14/14] Fixes path reference in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 47fc560..ab44fdf 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ mappings: secretType: Opaque # optionally - default "Opaque" e.g.: "kubernetes.io/tls" # mappings from google secrets manager paths to kubernetes secret names - sourceType: gsm - gsmPath: projects/my-project/secrets/my-secret/versions/latest + path: projects/my-project/secrets/my-secret/versions/latest secretName: my-secret ```