Removed backup files and unused legacy scanner function. All code verified as unreferenced.
263 lines
6.7 KiB
Go
263 lines
6.7 KiB
Go
package services
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
)
|
|
|
|
// SecretsManager handles Docker secrets creation and management
|
|
type SecretsManager struct {
|
|
secretsPath string
|
|
encryptionKey string
|
|
}
|
|
|
|
// NewSecretsManager creates a new secrets manager
|
|
func NewSecretsManager() *SecretsManager {
|
|
secretsPath := getSecretsPath()
|
|
return &SecretsManager{
|
|
secretsPath: secretsPath,
|
|
}
|
|
}
|
|
|
|
// CreateDockerSecrets creates Docker secrets from the provided secrets map
|
|
func (sm *SecretsManager) CreateDockerSecrets(secrets map[string]string) error {
|
|
if len(secrets) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Ensure secrets directory exists
|
|
if err := os.MkdirAll(sm.secretsPath, 0755); err != nil {
|
|
return fmt.Errorf("failed to create secrets directory: %w", err)
|
|
}
|
|
|
|
// Generate encryption key if not provided
|
|
if sm.encryptionKey == "" {
|
|
key, err := sm.GenerateEncryptionKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate encryption key: %w", err)
|
|
}
|
|
sm.encryptionKey = key
|
|
}
|
|
|
|
// Create each secret
|
|
for name, value := range secrets {
|
|
if err := sm.createSecret(name, value); err != nil {
|
|
return fmt.Errorf("failed to create secret %s: %w", name, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createSecret creates a single Docker secret
|
|
func (sm *SecretsManager) createSecret(name, value string) error {
|
|
secretPath := filepath.Join(sm.secretsPath, name)
|
|
|
|
// Encrypt sensitive values
|
|
encryptedValue, err := sm.encryptSecret(value)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encrypt secret: %w", err)
|
|
}
|
|
|
|
// Write secret file with restricted permissions
|
|
if err := os.WriteFile(secretPath, encryptedValue, 0400); err != nil {
|
|
return fmt.Errorf("failed to write secret file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// encryptSecret encrypts a secret value using AES-256-GCM
|
|
func (sm *SecretsManager) encryptSecret(value string) ([]byte, error) {
|
|
// Generate key from master key
|
|
keyBytes, err := hex.DecodeString(sm.encryptionKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid encryption key format: %w", err)
|
|
}
|
|
|
|
// Create cipher
|
|
block, err := aes.NewCipher(keyBytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create cipher: %w", err)
|
|
}
|
|
|
|
// Create GCM
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create GCM: %w", err)
|
|
}
|
|
|
|
// Generate nonce
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return nil, fmt.Errorf("failed to generate nonce: %w", err)
|
|
}
|
|
|
|
// Encrypt
|
|
ciphertext := gcm.Seal(nonce, nonce, []byte(value), nil)
|
|
|
|
// Prepend nonce to ciphertext
|
|
result := append(nonce, ciphertext...)
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// decryptSecret decrypts a secret value using AES-256-GCM
|
|
func (sm *SecretsManager) decryptSecret(encryptedValue []byte) (string, error) {
|
|
if len(encryptedValue) < 12 { // GCM nonce size
|
|
return "", fmt.Errorf("invalid encrypted value length")
|
|
}
|
|
|
|
// Generate key from master key
|
|
keyBytes, err := hex.DecodeString(sm.encryptionKey)
|
|
if err != nil {
|
|
return "", fmt.Errorf("invalid encryption key format: %w", err)
|
|
}
|
|
|
|
// Create cipher
|
|
block, err := aes.NewCipher(keyBytes)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create cipher: %w", err)
|
|
}
|
|
|
|
// Create GCM
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create GCM: %w", err)
|
|
}
|
|
|
|
// Extract nonce and ciphertext
|
|
nonce := encryptedValue[:gcm.NonceSize()]
|
|
ciphertext := encryptedValue[gcm.NonceSize():]
|
|
|
|
// Decrypt
|
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to decrypt secret: %w", err)
|
|
}
|
|
|
|
return string(plaintext), nil
|
|
}
|
|
|
|
// GenerateEncryptionKey generates a new encryption key
|
|
func (sm *SecretsManager) GenerateEncryptionKey() (string, error) {
|
|
bytes := make([]byte, 32)
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
return "", fmt.Errorf("failed to generate encryption key: %w", err)
|
|
}
|
|
return hex.EncodeToString(bytes), nil
|
|
}
|
|
|
|
// SetEncryptionKey sets the master encryption key
|
|
func (sm *SecretsManager) SetEncryptionKey(key string) {
|
|
sm.encryptionKey = key
|
|
}
|
|
|
|
// GetEncryptionKey returns the current encryption key
|
|
func (sm *SecretsManager) GetEncryptionKey() string {
|
|
return sm.encryptionKey
|
|
}
|
|
|
|
// GetSecretsPath returns the current secrets path
|
|
func (sm *SecretsManager) GetSecretsPath() string {
|
|
return sm.secretsPath
|
|
}
|
|
|
|
// ValidateSecrets validates that all required secrets exist
|
|
func (sm *SecretsManager) ValidateSecrets(requiredSecrets []string) error {
|
|
for _, secretName := range requiredSecrets {
|
|
secretPath := filepath.Join(sm.secretsPath, secretName)
|
|
if _, err := os.Stat(secretPath); os.IsNotExist(err) {
|
|
return fmt.Errorf("required secret not found: %s", secretName)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListSecrets returns a list of all created secrets
|
|
func (sm *SecretsManager) ListSecrets() ([]string, error) {
|
|
entries, err := os.ReadDir(sm.secretsPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return []string{}, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to read secrets directory: %w", err)
|
|
}
|
|
|
|
var secrets []string
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
secrets = append(secrets, entry.Name())
|
|
}
|
|
}
|
|
|
|
return secrets, nil
|
|
}
|
|
|
|
// RemoveSecret removes a Docker secret
|
|
func (sm *SecretsManager) RemoveSecret(name string) error {
|
|
secretPath := filepath.Join(sm.secretsPath, name)
|
|
return os.Remove(secretPath)
|
|
}
|
|
|
|
// Cleanup removes all secrets and the secrets directory
|
|
func (sm *SecretsManager) Cleanup() error {
|
|
if _, err := os.Stat(sm.secretsPath); os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
// Remove all files in the directory
|
|
entries, err := os.ReadDir(sm.secretsPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read secrets directory: %w", err)
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
if err := os.Remove(filepath.Join(sm.secretsPath, entry.Name())); err != nil {
|
|
return fmt.Errorf("failed to remove secret %s: %w", entry.Name(), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the directory itself
|
|
return os.Remove(sm.secretsPath)
|
|
}
|
|
|
|
// getSecretsPath returns the platform-specific secrets path
|
|
func getSecretsPath() string {
|
|
if runtime.GOOS == "windows" {
|
|
return `C:\ProgramData\Docker\secrets`
|
|
}
|
|
return "/run/secrets"
|
|
}
|
|
|
|
// IsDockerEnvironment checks if running in Docker
|
|
func IsDockerEnvironment() bool {
|
|
// Check for .dockerenv file
|
|
if _, err := os.Stat("/.dockerenv"); err == nil {
|
|
return true
|
|
}
|
|
|
|
// Check for Docker in cgroup
|
|
if data, err := os.ReadFile("/proc/1/cgroup"); err == nil {
|
|
if containsString(string(data), "docker") {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// containsString checks if a string contains a substring
|
|
func containsString(s, substr string) bool {
|
|
return len(s) >= len(substr) && (s == substr ||
|
|
(len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr)))
|
|
} |