mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-06-16 23:54:13 +00:00
fix(serverless): get_secret round-trip via secrets.Encrypt + string scan (#837)
The base64 wrapper wasn't enough: DBSecretsManager scanned encrypted_value into []byte, so the rqlite client applied base64 binary semantics on read and the ciphertext never round-tripped — get_secret stayed empty. Mirror the proven push-credentials store exactly: encrypt to a 'enc:'-prefixed base64 string via pkg/secrets and scan the column into a STRING for Decrypt. Text round-trips cleanly through rqlite regardless of the BLOB column.
This commit is contained in:
parent
a59017350b
commit
123ca90b65
@ -2,15 +2,13 @@ package hostfunctions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/DeBrosOfficial/network/pkg/rqlite"
|
"github.com/DeBrosOfficial/network/pkg/rqlite"
|
||||||
|
"github.com/DeBrosOfficial/network/pkg/secrets"
|
||||||
"github.com/DeBrosOfficial/network/pkg/serverless"
|
"github.com/DeBrosOfficial/network/pkg/serverless"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -68,18 +66,18 @@ func NewDBSecretsManager(db rqlite.Client, encryptionKeyHex string, allowEphemer
|
|||||||
|
|
||||||
// Set stores an encrypted secret.
|
// Set stores an encrypted secret.
|
||||||
func (s *DBSecretsManager) Set(ctx context.Context, namespace, name, value string) error {
|
func (s *DBSecretsManager) Set(ctx context.Context, namespace, name, value string) error {
|
||||||
encrypted, err := s.encrypt([]byte(value))
|
// Encrypt to a "enc:"-prefixed base64 STRING and store/read it as a string —
|
||||||
|
// the proven pattern used by the push-credentials store. bugboard #837: the
|
||||||
|
// previous code stored the raw AES-GCM bytes as a []byte param and read them
|
||||||
|
// back into []byte, but the rqlite client applies base64 binary semantics to
|
||||||
|
// []byte on both legs and the round-trip never reproduced the ciphertext, so
|
||||||
|
// decrypt() always failed and get_secret returned empty. A text string round-
|
||||||
|
// trips cleanly.
|
||||||
|
encrypted, err := secrets.Encrypt(value, s.encryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to encrypt secret: %w", err)
|
return fmt.Errorf("failed to encrypt secret: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the ciphertext as an EXPLICIT base64 string (bugboard #837): the
|
|
||||||
// rqlite client serializes a raw []byte parameter as base64 and reads it
|
|
||||||
// back as that base64 TEXT — not the original bytes — so a raw-blob write
|
|
||||||
// round-tripped into base64 that decrypt() could never open. Encoding here
|
|
||||||
// (and decoding in Get) makes the round-trip deterministic and symmetric.
|
|
||||||
encoded := base64.StdEncoding.EncodeToString(encrypted)
|
|
||||||
|
|
||||||
// Upsert the secret
|
// Upsert the secret
|
||||||
query := `
|
query := `
|
||||||
INSERT INTO function_secrets (id, namespace, name, encrypted_value, created_at, updated_at)
|
INSERT INTO function_secrets (id, namespace, name, encrypted_value, created_at, updated_at)
|
||||||
@ -91,7 +89,7 @@ func (s *DBSecretsManager) Set(ctx context.Context, namespace, name, value strin
|
|||||||
|
|
||||||
id := fmt.Sprintf("%s:%s", namespace, name)
|
id := fmt.Sprintf("%s:%s", namespace, name)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if _, err := s.db.Exec(ctx, query, id, namespace, name, encoded, now, now); err != nil {
|
if _, err := s.db.Exec(ctx, query, id, namespace, name, encrypted, now, now); err != nil {
|
||||||
return fmt.Errorf("failed to save secret: %w", err)
|
return fmt.Errorf("failed to save secret: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,8 +100,10 @@ func (s *DBSecretsManager) Set(ctx context.Context, namespace, name, value strin
|
|||||||
func (s *DBSecretsManager) Get(ctx context.Context, namespace, name string) (string, error) {
|
func (s *DBSecretsManager) Get(ctx context.Context, namespace, name string) (string, error) {
|
||||||
query := `SELECT encrypted_value FROM function_secrets WHERE namespace = ? AND name = ?`
|
query := `SELECT encrypted_value FROM function_secrets WHERE namespace = ? AND name = ?`
|
||||||
|
|
||||||
|
// Scan into a STRING (not []byte) so the rqlite client returns the stored
|
||||||
|
// text verbatim instead of applying base64 binary semantics (bugboard #837).
|
||||||
var rows []struct {
|
var rows []struct {
|
||||||
EncryptedValue []byte `db:"encrypted_value"`
|
EncryptedValue string `db:"encrypted_value"`
|
||||||
}
|
}
|
||||||
if err := s.db.Query(ctx, &rows, query, namespace, name); err != nil {
|
if err := s.db.Query(ctx, &rows, query, namespace, name); err != nil {
|
||||||
return "", fmt.Errorf("failed to query secret: %w", err)
|
return "", fmt.Errorf("failed to query secret: %w", err)
|
||||||
@ -113,20 +113,12 @@ func (s *DBSecretsManager) Get(ctx context.Context, namespace, name string) (str
|
|||||||
return "", serverless.ErrSecretNotFound
|
return "", serverless.ErrSecretNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the base64 wrapper written by Set. Fall back to the raw bytes for
|
decrypted, err := secrets.Decrypt(rows[0].EncryptedValue, s.encryptionKey)
|
||||||
// any value that isn't valid base64 (defensive — should not occur once all
|
|
||||||
// writes go through the encode path above). bugboard #837.
|
|
||||||
ciphertext, decErr := base64.StdEncoding.DecodeString(string(rows[0].EncryptedValue))
|
|
||||||
if decErr != nil {
|
|
||||||
ciphertext = rows[0].EncryptedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
decrypted, err := s.decrypt(ciphertext)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to decrypt secret: %w", err)
|
return "", fmt.Errorf("failed to decrypt secret: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(decrypted), nil
|
return decrypted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns all secret names for a namespace.
|
// List returns all secret names for a namespace.
|
||||||
@ -164,44 +156,3 @@ func (s *DBSecretsManager) Delete(ctx context.Context, namespace, name string) e
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// encrypt encrypts data using AES-256-GCM.
|
|
||||||
func (s *DBSecretsManager) encrypt(plaintext []byte) ([]byte, error) {
|
|
||||||
block, err := aes.NewCipher(s.encryptionKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
gcm, err := cipher.NewGCM(block)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce := make([]byte, gcm.NonceSize())
|
|
||||||
if _, err := rand.Read(nonce); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gcm.Seal(nonce, nonce, plaintext, nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt decrypts data using AES-256-GCM.
|
|
||||||
func (s *DBSecretsManager) decrypt(ciphertext []byte) ([]byte, error) {
|
|
||||||
block, err := aes.NewCipher(s.encryptionKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
gcm, err := cipher.NewGCM(block)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nonceSize := gcm.NonceSize()
|
|
||||||
if len(ciphertext) < nonceSize {
|
|
||||||
return nil, fmt.Errorf("ciphertext too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
|
||||||
return gcm.Open(nil, nonce, ciphertext, nil)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -66,15 +66,15 @@ func (f *fakeSecretsDB) Query(ctx context.Context, dest any, query string, args
|
|||||||
namespace, _ := args[0].(string)
|
namespace, _ := args[0].(string)
|
||||||
name, _ := args[1].(string)
|
name, _ := args[1].(string)
|
||||||
rows, ok := dest.(*[]struct {
|
rows, ok := dest.(*[]struct {
|
||||||
EncryptedValue []byte `db:"encrypted_value"`
|
EncryptedValue string `db:"encrypted_value"`
|
||||||
})
|
})
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("unexpected dest type")
|
return errors.New("unexpected dest type")
|
||||||
}
|
}
|
||||||
if enc, found := f.store[storeKey(namespace, name)]; found {
|
if enc, found := f.store[storeKey(namespace, name)]; found {
|
||||||
*rows = append(*rows, struct {
|
*rows = append(*rows, struct {
|
||||||
EncryptedValue []byte `db:"encrypted_value"`
|
EncryptedValue string `db:"encrypted_value"`
|
||||||
}{EncryptedValue: enc})
|
}{EncryptedValue: string(enc)})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user