influxdb/bolt/secret.go

192 lines
4.0 KiB
Go

package bolt
import (
"context"
"encoding/base64"
"fmt"
bolt "github.com/coreos/bbolt"
"github.com/influxdata/platform"
)
var (
secretBucket = []byte("secretsv1")
)
var _ platform.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 platform.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 platform.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 "", fmt.Errorf("secret not found")
}
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 platform.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 platform.ID) ([]string, error) {
cur := tx.Bucket(secretBucket).Cursor()
prefix, err := orgID.Encode()
if err != nil {
return nil, err
}
k, _ := cur.Seek(prefix)
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 platform.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 platform.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 platform.ID, k string) ([]byte, error) {
buf, err := orgID.Encode()
if err != nil {
return nil, err
}
key := make([]byte, 0, platform.IDLength+len(k))
key = append(key, buf...)
key = append(key, k...)
return key, nil
}
func decodeSecretKey(key []byte) (platform.ID, string, error) {
if len(key) < platform.IDLength {
// This should not happen.
return platform.InvalidID(), "", fmt.Errorf("Provided key is too short to contain an ID. Please report this error.")
}
var id platform.ID
if err := id.Decode(key[:platform.IDLength]); err != nil {
return platform.InvalidID(), "", err
}
k := string(key[platform.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
}