All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
116 lines
3.1 KiB
Go
116 lines
3.1 KiB
Go
package handlers
|
|
|
|
// to the store + vault layers. Implements sandbox.ConnectionResolver.
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"armature/crypto"
|
|
"armature/models"
|
|
"armature/store"
|
|
)
|
|
|
|
// ConnectionResolverAdapter implements sandbox.ConnectionResolver.
|
|
type ConnectionResolverAdapter struct {
|
|
stores store.Stores
|
|
vault *crypto.KeyResolver
|
|
}
|
|
|
|
func NewConnectionResolverAdapter(s store.Stores, vault *crypto.KeyResolver) *ConnectionResolverAdapter {
|
|
return &ConnectionResolverAdapter{stores: s, vault: vault}
|
|
}
|
|
|
|
// Resolve returns a single connection with secrets decrypted.
|
|
func (r *ConnectionResolverAdapter) Resolve(ctx context.Context, userID, connType, name string) (*models.ExtConnection, error) {
|
|
conn, err := r.stores.Connections.Resolve(ctx, userID, connType, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.decryptInPlace(ctx, conn)
|
|
return conn, nil
|
|
}
|
|
|
|
// ResolveAll returns all accessible connections with secrets decrypted.
|
|
func (r *ConnectionResolverAdapter) ResolveAll(ctx context.Context, userID, connType string) ([]models.ExtConnection, error) {
|
|
conns, err := r.stores.Connections.ResolveAll(ctx, userID, connType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i := range conns {
|
|
r.decryptInPlace(ctx, &conns[i])
|
|
}
|
|
return conns, nil
|
|
}
|
|
|
|
// decryptInPlace decrypts secret fields in the connection's config.
|
|
func (r *ConnectionResolverAdapter) decryptInPlace(ctx context.Context, conn *models.ExtConnection) {
|
|
if conn == nil || conn.Config == nil || r.vault == nil {
|
|
return
|
|
}
|
|
secretFields := r.lookupSecretFields(ctx, conn.PackageID, conn.Type)
|
|
for k, v := range conn.Config {
|
|
if !secretFields[k] {
|
|
continue
|
|
}
|
|
m, ok := v.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
enc := toBytes(m["enc"])
|
|
nonce := toBytes(m["nonce"])
|
|
if len(enc) == 0 {
|
|
continue
|
|
}
|
|
plaintext := r.safeDecrypt(enc, nonce, conn.Scope, conn.OwnerID)
|
|
if plaintext != "" {
|
|
conn.Config[k] = plaintext
|
|
}
|
|
}
|
|
}
|
|
|
|
// safeDecrypt wraps vault.Decrypt to recover from GCM panics caused by
|
|
// invalid nonce lengths (e.g. stale vault keys after container restart).
|
|
func (r *ConnectionResolverAdapter) safeDecrypt(enc, nonce []byte, scope, ownerID string) (plaintext string) {
|
|
defer func() {
|
|
if rv := recover(); rv != nil {
|
|
plaintext = ""
|
|
}
|
|
}()
|
|
result, err := r.vault.Decrypt(enc, nonce, scope, ownerID)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return result
|
|
}
|
|
|
|
// lookupSecretFields reads the package manifest to identify secret fields.
|
|
func (r *ConnectionResolverAdapter) lookupSecretFields(ctx context.Context, packageID, connType string) map[string]bool {
|
|
result := map[string]bool{}
|
|
pkg, err := r.stores.Packages.Get(ctx, packageID)
|
|
if err != nil || pkg == nil {
|
|
return result
|
|
}
|
|
connsRaw, ok := pkg.Manifest["connections"]
|
|
if !ok {
|
|
return result
|
|
}
|
|
connsJSON, _ := json.Marshal(connsRaw)
|
|
var conns []struct {
|
|
Type string `json:"type"`
|
|
Fields map[string]map[string]string `json:"fields"`
|
|
}
|
|
json.Unmarshal(connsJSON, &conns)
|
|
for _, cd := range conns {
|
|
if cd.Type == connType {
|
|
for fieldName, fieldDef := range cd.Fields {
|
|
if fieldDef["type"] == "secret" {
|
|
result[fieldName] = true
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return result
|
|
}
|