114 lines
3.3 KiB
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
|
|
}
|