Skip to content

Commit 8fbfd88

Browse files
authored
Merge pull request #25 from vimeo/add-gsm-support
Adds support for Google Secrets Manager
2 parents d14f3da + fcc692b commit 8fbfd88

File tree

12 files changed

+519
-181
lines changed

12 files changed

+519
-181
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
strategy:
88
matrix:
99
os: [macOS-latest, ubuntu-latest]
10-
goversion: [1.19, "1.20"]
10+
goversion: ["1.24"]
1111
steps:
1212
- name: Set up Go ${{matrix.goversion}} on ${{matrix.os}}
1313
uses: actions/setup-go@v3

Dockerfile

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
FROM golang:1.18.3-alpine as builder
1+
FROM golang:1.24-alpine AS builder
22

33
RUN apk add --no-cache ca-certificates libc-dev git make gcc
44
RUN adduser -D pentagon
55
USER pentagon
66

7-
# Enable go modules
8-
ENV GO111MODULE on
9-
107
# The golang docker images configure GOPATH=/go
118
RUN mkdir -p /go/src/github.com/vimeo/pentagon /go/pkg/
129
COPY --chown=pentagon . /go/src/github.com/vimeo/pentagon/

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
[![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)
33

44
# Pentagon
5-
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.
6-
7-
Use at your own risk...
5+
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.
86

97
## Why not just query Vault?
108
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.
119

1210
## Configuration
13-
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:
11+
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:
1412

1513
```yaml
1614
vault:
@@ -28,6 +26,10 @@ mappings:
2826
secretName: k8s-secretname
2927
vaultEngineType: # optionally "kv" or "kv-v2" to override the defaultEngineType specified above
3028
secretType: Opaque # optionally - default "Opaque" e.g.: "kubernetes.io/tls"
29+
# mappings from google secrets manager paths to kubernetes secret names
30+
- sourceType: gsm
31+
path: projects/my-project/secrets/my-secret/versions/latest
32+
secretName: my-secret
3133
```
3234
3335
### Labels and Reconciliation
@@ -92,6 +94,7 @@ The application will return 0 on success (when all keys were copied/updated succ
9294
| 22 | Configuration error. |
9395
| 30 | Unable to instantiate vault client. |
9496
| 31 | Unable to instantiate kubernetes client. |
97+
| 32 | Unable to instantiate Google Secrets Manager client. |
9598
| 40 | Error copying keys. |
9699

97100
## Kubernetes Configuration

config.go

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,32 @@ package pentagon
22

33
import (
44
"fmt"
5+
"log"
56

67
"github.com/hashicorp/vault/api"
78
"github.com/vimeo/pentagon/vault"
89

910
corev1 "k8s.io/api/core/v1"
1011
)
1112

12-
// DefaultNamespace is the default kubernetes namespace.
13-
const DefaultNamespace = "default"
13+
const (
14+
// DefaultNamespace is the default kubernetes namespace.
15+
DefaultNamespace = "default"
1416

15-
// DefaultLabelValue is the default label value that will be applied to secrets
16-
// created by pentagon.
17-
const DefaultLabelValue = "default"
17+
// DefaultLabelValue is the default label value that will be applied to secrets
18+
// created by pentagon.
19+
DefaultLabelValue = "default"
1820

19-
// Config describes the configuration for vaultofsecrets
21+
// VaultSourceType indicates a mapping sourced from Hashicorp Vault.
22+
VaultSourceType = "vault"
23+
24+
// GSMSourceType indicates a mapping sourced from Google Secrets Manager.
25+
GSMSourceType = "gsm"
26+
)
27+
28+
// Config describes the configuration for Pentagon.
2029
type Config struct {
21-
// VaultURL is the URL used to connect to vault.
30+
// Vault is the configuration used to connect to vault.
2231
Vault VaultConfig `yaml:"vault"`
2332

2433
// Namespace is the k8s namespace that the secrets will be created in.
@@ -51,6 +60,17 @@ func (c *Config) SetDefaults() {
5160
// set all the underlying mapping engine types to their default
5261
// if unspecified
5362
for i, m := range c.Mappings {
63+
// default to vault source type for backward compatibility
64+
if m.SourceType == "" {
65+
c.Mappings[i].SourceType = VaultSourceType
66+
}
67+
68+
// copy VaultPath to Path for backward compatibility
69+
if m.Path == "" && m.VaultPath != "" {
70+
log.Println("WARNING: Use mapping.Path instead of mapping.VaultPath (deprecated)")
71+
c.Mappings[i].Path = m.VaultPath
72+
}
73+
5474
if m.VaultEngineType == "" {
5575
c.Mappings[i].VaultEngineType = c.Vault.DefaultEngineType
5676
}
@@ -66,6 +86,21 @@ func (c *Config) Validate() error {
6686
return fmt.Errorf("no mappings provided")
6787
}
6888

89+
validSourceTypes := map[string]struct{}{
90+
"": {},
91+
VaultSourceType: {},
92+
GSMSourceType: {},
93+
}
94+
95+
for _, m := range c.Mappings {
96+
if _, ok := validSourceTypes[m.SourceType]; !ok {
97+
return fmt.Errorf("invalid source type: %+v", m.SourceType)
98+
}
99+
if m.Path == "" {
100+
return fmt.Errorf("path should not be empty: %+v", m)
101+
}
102+
}
103+
69104
return nil
70105
}
71106

@@ -99,7 +134,16 @@ type VaultConfig struct {
99134

100135
// Mapping is a single mapping for a vault secret to a k8s secret.
101136
type Mapping struct {
102-
// VaultPath is the path to the vault secret.
137+
// SourceType is the source of a secret: Vault or GSM. Defaults to Vault.
138+
SourceType string `yaml:"sourceType"`
139+
140+
// Path is the path to a Vault or GSM secret.
141+
// GSM secrets can use one of the following forms;
142+
// - projects/*/secrets/*/versions/*
143+
// - projects/*/locations/*/secrets/*/versions/*
144+
Path string `yaml:"path"`
145+
146+
// [DEPRECATED] VaultPath is the path to a vault secret. Use Path instead.
103147
VaultPath string `yaml:"vaultPath"`
104148

105149
// SecretName is the name of the k8s secret that the vault contents should

config_test.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ func TestSetDefaults(t *testing.T) {
1313
},
1414
Mappings: []Mapping{
1515
{
16-
VaultPath: "vaultPath",
16+
Path: "path",
17+
SecretName: "theSecret",
18+
},
19+
{
20+
VaultPath: "path",
1721
SecretName: "theSecret",
1822
},
1923
},
@@ -33,6 +37,12 @@ func TestSetDefaults(t *testing.T) {
3337
}
3438

3539
for _, m := range c.Mappings {
40+
if m.SourceType != VaultSourceType {
41+
t.Fatalf("source type should have defaulted to vault: %+v", m)
42+
}
43+
if m.Path == "" {
44+
t.Fatalf("empty path for vault secret: %+v", m)
45+
}
3646
if m.VaultEngineType == "" {
3747
t.Fatalf("empty vault engine type for mapping: %+v", m)
3848
}
@@ -80,7 +90,7 @@ func TestValidate(t *testing.T) {
8090
c = &Config{
8191
Mappings: []Mapping{
8292
{
83-
VaultPath: "foo",
93+
Path: "foo",
8494
SecretName: "bar",
8595
},
8696
},
@@ -91,3 +101,27 @@ func TestValidate(t *testing.T) {
91101
t.Fatalf("configuration should have been valid: %s", err)
92102
}
93103
}
104+
105+
func TestValidSourceTypes(t *testing.T) {
106+
c := &Config{
107+
Mappings: []Mapping{
108+
{SourceType: "", Path: "foo"},
109+
{SourceType: VaultSourceType, Path: "foo"},
110+
{SourceType: GSMSourceType, Path: "foo"},
111+
},
112+
}
113+
if err := c.Validate(); err != nil {
114+
t.Fatalf("mappings should have been valid: %s", err)
115+
}
116+
}
117+
118+
func TestInvalidSourceType(t *testing.T) {
119+
c := &Config{
120+
Mappings: []Mapping{
121+
{SourceType: "foo"},
122+
},
123+
}
124+
if err := c.Validate(); err == nil {
125+
t.Fatalf("failed to detect invalid mapping source type")
126+
}
127+
}

go.mod

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
module github.com/vimeo/pentagon
22

3-
go 1.18
3+
go 1.23.0
4+
5+
toolchain go1.24.2
46

57
require (
6-
cloud.google.com/go/compute/metadata v0.2.3
8+
cloud.google.com/go/compute/metadata v0.7.0
9+
cloud.google.com/go/secretmanager v1.15.0
10+
github.com/googleapis/gax-go/v2 v2.14.2
711
github.com/hashicorp/vault/api v1.9.0
812
gopkg.in/yaml.v2 v2.4.0
913
k8s.io/api v0.26.3
@@ -12,22 +16,28 @@ require (
1216
)
1317

1418
require (
15-
cloud.google.com/go/compute v1.19.0 // indirect
19+
cloud.google.com/go/auth v0.16.2 // indirect
20+
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
21+
cloud.google.com/go/iam v1.5.2 // indirect
1622
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
1723
github.com/davecgh/go-spew v1.1.1 // indirect
1824
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
1925
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
2026
github.com/fatih/color v1.15.0 // indirect
21-
github.com/go-logr/logr v1.2.3 // indirect
27+
github.com/felixge/httpsnoop v1.0.4 // indirect
28+
github.com/go-logr/logr v1.4.2 // indirect
29+
github.com/go-logr/stdr v1.2.2 // indirect
2230
github.com/go-openapi/jsonpointer v0.19.6 // indirect
2331
github.com/go-openapi/jsonreference v0.20.2 // indirect
2432
github.com/go-openapi/swag v0.22.3 // indirect
2533
github.com/gogo/protobuf v1.3.2 // indirect
26-
github.com/golang/protobuf v1.5.3 // indirect
34+
github.com/golang/protobuf v1.5.4 // indirect
2735
github.com/google/gnostic v0.6.9 // indirect
28-
github.com/google/go-cmp v0.5.9 // indirect
36+
github.com/google/go-cmp v0.7.0 // indirect
2937
github.com/google/gofuzz v1.2.0 // indirect
30-
github.com/google/uuid v1.3.0 // indirect
38+
github.com/google/s2a-go v0.1.9 // indirect
39+
github.com/google/uuid v1.6.0 // indirect
40+
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
3141
github.com/hashicorp/errwrap v1.1.0 // indirect
3242
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
3343
github.com/hashicorp/go-hclog v1.5.0 // indirect
@@ -50,15 +60,26 @@ require (
5060
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
5161
github.com/pkg/errors v0.9.1 // indirect
5262
github.com/ryanuber/go-glob v1.0.0 // indirect
53-
golang.org/x/crypto v0.7.0 // indirect
54-
golang.org/x/net v0.8.0 // indirect
55-
golang.org/x/oauth2 v0.6.0 // indirect
56-
golang.org/x/sys v0.6.0 // indirect
57-
golang.org/x/term v0.6.0 // indirect
58-
golang.org/x/text v0.8.0 // indirect
59-
golang.org/x/time v0.3.0 // indirect
60-
google.golang.org/appengine v1.6.7 // indirect
61-
google.golang.org/protobuf v1.30.0 // indirect
63+
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
64+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
65+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
66+
go.opentelemetry.io/otel v1.36.0 // indirect
67+
go.opentelemetry.io/otel/metric v1.36.0 // indirect
68+
go.opentelemetry.io/otel/trace v1.36.0 // indirect
69+
golang.org/x/crypto v0.39.0 // indirect
70+
golang.org/x/net v0.41.0 // indirect
71+
golang.org/x/oauth2 v0.30.0 // indirect
72+
golang.org/x/sync v0.15.0 // indirect
73+
golang.org/x/sys v0.33.0 // indirect
74+
golang.org/x/term v0.32.0 // indirect
75+
golang.org/x/text v0.26.0 // indirect
76+
golang.org/x/time v0.12.0 // indirect
77+
google.golang.org/api v0.237.0 // indirect
78+
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
79+
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
80+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
81+
google.golang.org/grpc v1.73.0 // indirect
82+
google.golang.org/protobuf v1.36.6 // indirect
6283
gopkg.in/inf.v0 v0.9.1 // indirect
6384
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
6485
gopkg.in/yaml.v3 v3.0.1 // indirect

0 commit comments

Comments
 (0)