package bolt import ( "context" "encoding/base64" "errors" "fmt" bolt "github.com/coreos/bbolt" influxdb "github.com/influxdata/influxdb" ) var ( secretBucket = []byte("secretsv1") ) var _ influxdb.SecretService = (*Client)(nil) func (c *Client) initializeSecretService(ctx context.Context, tx *bolt.Tx) error { if _, err := tx.CreateBucketIfNotExists([]byte(secretBucket)); err != nil { return err } return nil } // LoadSecret retrieves the secret value v found at key k for organization orgID. func (c *Client) LoadSecret(ctx context.Context, orgID influxdb.ID, k string) (string, error) { var v string err := c.db.View(func(tx *bolt.Tx) error { val, err := c.loadSecret(ctx, tx, orgID, k) if err != nil { return err } v = val return nil }) if err != nil { return "", err } return v, nil } func (c *Client) loadSecret(ctx context.Context, tx *bolt.Tx, orgID influxdb.ID, k string) (string, error) { key, err := encodeSecretKey(orgID, k) if err != nil { return "", err } val := tx.Bucket(secretBucket).Get(key) if len(val) == 0 { return "", &influxdb.Error{ Code: influxdb.ENotFound, Msg: influxdb.ErrSecretNotFound, } } v, err := decodeSecretValue(val) if err != nil { return "", err } return v, nil } // GetSecretKeys retrieves all secret keys that are stored for the organization orgID. func (c *Client) GetSecretKeys(ctx context.Context, orgID influxdb.ID) ([]string, error) { var vs []string err := c.db.View(func(tx *bolt.Tx) error { vals, err := c.getSecretKeys(ctx, tx, orgID) if err != nil { return err } vs = vals return nil }) if err != nil { return nil, err } return vs, nil } func (c *Client) getSecretKeys(ctx context.Context, tx *bolt.Tx, orgID influxdb.ID) ([]string, error) { cur := tx.Bucket(secretBucket).Cursor() prefix, err := orgID.Encode() if err != nil { return nil, err } k, _ := cur.Seek(prefix) if len(k) == 0 { return []string{}, nil } id, key, err := decodeSecretKey(k) if err != nil { return nil, err } if id != orgID { return nil, fmt.Errorf("organization has no secret keys") } keys := []string{key} for { k, _ = cur.Next() if len(k) == 0 { // We've reached the end of the keys so we're done break } id, key, err = decodeSecretKey(k) if err != nil { return nil, err } if id != orgID { // We've reached the end of the keyspace for the provided orgID break } keys = append(keys, key) } return keys, nil } // PutSecret stores the secret pair (k,v) for the organization orgID. func (c *Client) PutSecret(ctx context.Context, orgID influxdb.ID, k, v string) error { return c.db.Update(func(tx *bolt.Tx) error { return c.putSecret(ctx, tx, orgID, k, v) }) } func (c *Client) putSecret(ctx context.Context, tx *bolt.Tx, orgID influxdb.ID, k, v string) error { key, err := encodeSecretKey(orgID, k) if err != nil { return err } val := encodeSecretValue(v) if err := tx.Bucket(secretBucket).Put(key, val); err != nil { return err } return nil } func encodeSecretKey(orgID influxdb.ID, k string) ([]byte, error) { buf, err := orgID.Encode() if err != nil { return nil, err } key := make([]byte, 0, influxdb.IDLength+len(k)) key = append(key, buf...) key = append(key, k...) return key, nil } func decodeSecretKey(key []byte) (influxdb.ID, string, error) { if len(key) < influxdb.IDLength { // This should not happen. return influxdb.InvalidID(), "", errors.New("provided key is too short to contain an ID (please report this error)") } var id influxdb.ID if err := id.Decode(key[:influxdb.IDLength]); err != nil { return influxdb.InvalidID(), "", err } k := string(key[influxdb.IDLength:]) return id, k, nil } func decodeSecretValue(val []byte) (string, error) { // store the secret value base64 encoded so that it's marginally better than plaintext v := make([]byte, base64.StdEncoding.DecodedLen(len(val))) if _, err := base64.StdEncoding.Decode(v, val); err != nil { return "", err } return string(v), nil } func encodeSecretValue(v string) []byte { val := make([]byte, base64.StdEncoding.EncodedLen(len(v))) base64.StdEncoding.Encode(val, []byte(v)) return val } // PutSecrets puts all provided secrets and overwrites any previous values. func (c *Client) PutSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error { return c.db.Update(func(tx *bolt.Tx) error { keys, err := c.getSecretKeys(ctx, tx, orgID) if err != nil { return err } for k, v := range m { if err := c.putSecret(ctx, tx, orgID, k, v); err != nil { return err } } for _, k := range keys { if _, ok := m[k]; !ok { if err := c.deleteSecret(ctx, tx, orgID, k); err != nil { return err } } } return nil }) } // PatchSecrets patches all provided secrets and updates any previous values. func (c *Client) PatchSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error { return c.db.Update(func(tx *bolt.Tx) error { for k, v := range m { if err := c.putSecret(ctx, tx, orgID, k, v); err != nil { return err } } return nil }) } // DeleteSecret removes secrets from the secret store. func (c *Client) DeleteSecret(ctx context.Context, orgID influxdb.ID, ks ...string) error { return c.db.Update(func(tx *bolt.Tx) error { for _, k := range ks { if err := c.deleteSecret(ctx, tx, orgID, k); err != nil { return err } } return nil }) } func (c *Client) deleteSecret(ctx context.Context, tx *bolt.Tx, orgID influxdb.ID, k string) error { key, err := encodeSecretKey(orgID, k) if err != nil { return err } return tx.Bucket(secretBucket).Delete(key) }