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  }