package secret import ( "context" "encoding/base64" "errors" "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/kit/platform" errors2 "github.com/influxdata/influxdb/v2/kit/platform/errors" "github.com/influxdata/influxdb/v2/kv" ) var secretBucket = []byte("secretsv1") // Storage is a store translation layer between the data storage unit and the // service layer. type Storage struct { store kv.Store } // NewStore creates a new storage system func NewStore(s kv.Store) (*Storage, error) { return &Storage{s}, nil } func (s *Storage) View(ctx context.Context, fn func(kv.Tx) error) error { return s.store.View(ctx, fn) } func (s *Storage) Update(ctx context.Context, fn func(kv.Tx) error) error { return s.store.Update(ctx, fn) } // GetSecret Returns the value of a secret func (s *Storage) GetSecret(ctx context.Context, tx kv.Tx, orgID platform.ID, k string) (string, error) { key, err := encodeSecretKey(orgID, k) if err != nil { return "", err } b, err := tx.Bucket(secretBucket) if err != nil { return "", err } val, err := b.Get(key) if kv.IsNotFound(err) { return "", &errors2.Error{ Code: errors2.ENotFound, Msg: influxdb.ErrSecretNotFound, } } if err != nil { return "", err } v, err := decodeSecretValue(val) if err != nil { return "", err } return v, nil } // ListSecrets returns a list of secret keys func (s *Storage) ListSecret(ctx context.Context, tx kv.Tx, orgID platform.ID) ([]string, error) { b, err := tx.Bucket(secretBucket) if err != nil { return nil, err } prefix, err := orgID.Encode() if err != nil { return nil, err } cur, err := b.ForwardCursor(prefix, kv.WithCursorPrefix(prefix)) if err != nil { return nil, err } keys := []string{} err = kv.WalkCursor(ctx, cur, func(k, v []byte) (bool, error) { id, key, err := decodeSecretKey(k) if err != nil { return false, err } if id != orgID { // We've reached the end of the keyspace for the provided orgID return false, nil } keys = append(keys, key) return true, nil }) if err != nil { return nil, err } return keys, nil } // PutSecret sets a secret in the db. func (s *Storage) PutSecret(ctx context.Context, tx kv.Tx, orgID platform.ID, k, v string) error { key, err := encodeSecretKey(orgID, k) if err != nil { return err } val := encodeSecretValue(v) b, err := tx.Bucket(secretBucket) if err != nil { return err } if err := b.Put(key, val); err != nil { return err } return nil } // DeleteSecret removes a secret for the db func (s *Storage) DeleteSecret(ctx context.Context, tx kv.Tx, orgID platform.ID, k string) error { key, err := encodeSecretKey(orgID, k) if err != nil { return err } b, err := tx.Bucket(secretBucket) if err != nil { return err } return b.Delete(key) } 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(), "", errors.New("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, err := base64.StdEncoding.DecodeString(string(val)) if 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 }