This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/handlers/connection_resolver.go
Jeffrey Smith f0dd43144e rebrand: Switchboard Core → Armature
- Rename Go module switchboard-core → armature (155+ files)
- Rename Docker image → gobha/armature
- Rename K8s resources, secrets, deployments
- Rename Prometheus metrics switchboard_* → armature_*
- Rename env vars SWITCHBOARD_ADMIN_* → ARMATURE_ADMIN_*
- Rename DB names switchboard_core* → armature*
- Update all frontend branding, notification templates, docs
- Update CI scripts, e2e tests, Keycloak realm, nginx conf
- Rename scripts/switchboard-ca.sh → scripts/armature-ca.sh
- Rename k8s/switchboard.yaml → k8s/armature.yaml
- Rename chart alerting/dashboard files
- Fix: DockerHub push uses env: binding for secret injection
- Helm chart updated (name, labels, template functions, dashboard, alerting)
- Replace favicon/icon assets with Armature brand

No functional changes. Pure mechanical rename + CI fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:39:58 +00:00

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
}