github.com/m-lab/locate@v0.17.6/secrets/load.go (about) 1 // Package secrets loads secrets from the Google Cloud Secret Manager. 2 package secrets 3 4 import ( 5 "context" 6 "fmt" 7 "log" 8 "path" 9 10 secretmanager "cloud.google.com/go/secretmanager/apiv1" 11 "github.com/googleapis/gax-go" 12 "github.com/m-lab/access/token" 13 "github.com/m-lab/locate/prometheus" 14 "github.com/prometheus/common/config" 15 "google.golang.org/api/iterator" 16 secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1" 17 ) 18 19 // Constants used by the secrets loader. 20 const ( 21 latestVersion = "/versions/latest" 22 ) 23 24 // SecretClient wraps the AccessSecretVersion function provided by the 25 // secretmanager.Client. 26 type SecretClient interface { 27 AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) 28 ListSecretVersions(ctx context.Context, req *secretmanagerpb.ListSecretVersionsRequest, opts ...gax.CallOption) *secretmanager.SecretVersionIterator 29 } 30 31 // iter warps the Next() method of a *secretmanager.SecretVersionIterator. 32 type iter interface { 33 Next(it *secretmanager.SecretVersionIterator) (*secretmanagerpb.SecretVersion, error) 34 } 35 36 // stdIter implements the iter interfaces, and is used to invoke the 37 // iterator.Next() method. 38 type stdIter struct{} 39 40 // Next invokes the Next() method of a *secretmanager.SecretVersionIterator. 41 func (s *stdIter) Next(it *secretmanager.SecretVersionIterator) (*secretmanagerpb.SecretVersion, error) { 42 return it.Next() 43 } 44 45 // Config contains settings for secrets. 46 type Config struct { 47 iter iter 48 Project string 49 client SecretClient 50 } 51 52 // NewConfig creates a new secret config. 53 func NewConfig(project string, client SecretClient) *Config { 54 return &Config{ 55 iter: &stdIter{}, 56 Project: project, 57 client: client, 58 } 59 } 60 61 // getSecret fetches the version of a secret specified by 'path' from the Secret 62 // Manager API. 63 func (c *Config) getSecret(ctx context.Context, path string) ([]byte, error) { 64 req := &secretmanagerpb.AccessSecretVersionRequest{ 65 Name: path, 66 } 67 68 result, err := c.client.AccessSecretVersion(ctx, req) 69 if err != nil { 70 return nil, err 71 } 72 73 return result.Payload.Data, nil 74 } 75 76 // getSecretVersions returns a slice of all *enabled* versions for a secret. It 77 // will ignore disabled for destroyed versions of a secret. 78 func (c *Config) getSecretVersions(ctx context.Context, name string) ([]string, error) { 79 req := &secretmanagerpb.ListSecretVersionsRequest{ 80 Parent: c.path(name), 81 PageSize: 1000, 82 } 83 84 it := c.client.ListSecretVersions(ctx, req) 85 versions := []string{} 86 for { 87 resp, err := c.iter.Next(it) 88 if err == iterator.Done { 89 break 90 } 91 if err != nil { 92 return nil, err 93 } 94 if resp.State != secretmanagerpb.SecretVersion_ENABLED { 95 continue 96 } 97 versions = append(versions, resp.Name) 98 } 99 100 if len(versions) < 1 { 101 return nil, fmt.Errorf("no versions found for secret: %s", name) 102 } 103 104 return versions, nil 105 } 106 107 // LoadSigner fetches the oldest enabled version of the named secret containing 108 // the JWT signer key from the Secret Manager API and returns a *token.Signer. 109 func (c *Config) LoadSigner(ctx context.Context, name string) (*token.Signer, error) { 110 versions, err := c.getSecretVersions(ctx, name) 111 if err != nil { 112 return nil, err 113 } 114 log.Printf("Loading JWT private signer key %q", versions[len(versions)-1]) 115 key, err := c.getSecret(ctx, versions[len(versions)-1]) 116 if err != nil { 117 return nil, err 118 } 119 return token.NewSigner(key) 120 } 121 122 // LoadVerifier fetches all enabled versions of the named secret containing the 123 // JWT verifier keys and returns a * token.Verifier. 124 func (c *Config) LoadVerifier(ctx context.Context, name string) (*token.Verifier, error) { 125 versions, err := c.getSecretVersions(ctx, name) 126 if err != nil { 127 return nil, err 128 } 129 keys := [][]byte{} 130 for _, version := range versions { 131 key, err := c.getSecret(ctx, version) 132 if err != nil { 133 return nil, err 134 } 135 keys = append(keys, key) 136 } 137 return token.NewVerifier(keys...) 138 } 139 140 // LoadPrometheus fetches the latest version of the named secrets containing the 141 // Prometheus username and password. It returns a *prometheus.Credentials object. 142 func (c *Config) LoadPrometheus(ctx context.Context, user, pass string) (*prometheus.Credentials, error) { 143 userPath := path.Join(c.path(user), latestVersion) 144 u, err := c.getSecret(ctx, userPath) 145 if err != nil { 146 return nil, err 147 } 148 149 passPath := path.Join(c.path(pass), latestVersion) 150 p, err := c.getSecret(ctx, passPath) 151 if err != nil { 152 return nil, err 153 } 154 155 return &prometheus.Credentials{ 156 Username: string(u), 157 Password: config.Secret(p), 158 }, nil 159 } 160 161 func (c *Config) path(name string) string { 162 return "projects/" + c.Project + "/secrets/" + name 163 }