Files
wild-central-api/internal/secrets/secrets_test.go
2025-11-08 20:10:13 +00:00

967 lines
24 KiB
Go

package secrets
import (
"os"
"path/filepath"
"strings"
"sync"
"testing"
"github.com/wild-cloud/wild-central/daemon/internal/storage"
)
// Test: GenerateSecret generates valid secrets
func TestGenerateSecret(t *testing.T) {
tests := []struct {
name string
length int
want int
}{
{
name: "default length",
length: DefaultSecretLength,
want: DefaultSecretLength,
},
{
name: "custom length 64",
length: 64,
want: 64,
},
{
name: "custom length 128",
length: 128,
want: 128,
},
{
name: "zero length defaults to DefaultSecretLength",
length: 0,
want: DefaultSecretLength,
},
{
name: "negative length defaults to DefaultSecretLength",
length: -1,
want: DefaultSecretLength,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
secret, err := GenerateSecret(tt.length)
if err != nil {
t.Fatalf("GenerateSecret failed: %v", err)
}
if len(secret) != tt.want {
t.Errorf("got length %d, want %d", len(secret), tt.want)
}
// Verify only alphanumeric characters
for _, c := range secret {
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
t.Errorf("non-alphanumeric character found: %c", c)
}
}
})
}
}
// Test: GenerateSecret produces unique values
func TestGenerateSecret_Uniqueness(t *testing.T) {
const numSecrets = 100
secrets := make(map[string]bool, numSecrets)
for i := 0; i < numSecrets; i++ {
secret, err := GenerateSecret(32)
if err != nil {
t.Fatalf("GenerateSecret failed: %v", err)
}
if secrets[secret] {
t.Errorf("duplicate secret generated: %s", secret)
}
secrets[secret] = true
}
if len(secrets) != numSecrets {
t.Errorf("expected %d unique secrets, got %d", numSecrets, len(secrets))
}
}
// Test: NewManager creates manager successfully
func TestNewManager(t *testing.T) {
m := NewManager()
if m == nil {
t.Fatal("NewManager returned nil")
}
if m.yq == nil {
t.Error("Manager.yq is nil")
}
}
// Test: EnsureSecretsFile creates secrets file with proper structure and permissions
func TestEnsureSecretsFile(t *testing.T) {
tests := []struct {
name string
setupFunc func(t *testing.T, instancePath string)
wantErr bool
errContains string
}{
{
name: "creates secrets file when not exists",
setupFunc: nil,
wantErr: false,
},
{
name: "returns nil when secrets file exists",
setupFunc: func(t *testing.T, instancePath string) {
secretsPath := filepath.Join(instancePath, "secrets.yaml")
content := `# Wild Cloud Instance Secrets
cluster:
talosSecrets: ""
kubeconfig: ""
certManager:
cloudflare:
apiToken: ""
`
if err := storage.WriteFile(secretsPath, []byte(content), 0600); err != nil {
t.Fatalf("setup failed: %v", err)
}
},
wantErr: false,
},
{
name: "corrects permissions on existing file",
setupFunc: func(t *testing.T, instancePath string) {
secretsPath := filepath.Join(instancePath, "secrets.yaml")
content := `# Wild Cloud Instance Secrets
cluster:
talosSecrets: "existing-secret"
kubeconfig: ""
certManager:
cloudflare:
apiToken: ""
`
// Create with wrong permissions
if err := storage.WriteFile(secretsPath, []byte(content), 0644); err != nil {
t.Fatalf("setup failed: %v", err)
}
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
instancePath := t.TempDir()
m := NewManager()
if tt.setupFunc != nil {
tt.setupFunc(t, instancePath)
}
err := m.EnsureSecretsFile(instancePath)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
} else if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("error %q does not contain %q", err.Error(), tt.errContains)
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
// Verify secrets file exists
secretsPath := filepath.Join(instancePath, "secrets.yaml")
if !storage.FileExists(secretsPath) {
t.Error("secrets file not created")
}
// Verify permissions are 0600 (secure)
info, err := os.Stat(secretsPath)
if err != nil {
t.Fatalf("failed to stat secrets file: %v", err)
}
if info.Mode().Perm() != 0600 {
t.Errorf("expected permissions 0600, got %o", info.Mode().Perm())
}
// Verify file has expected structure
content, err := storage.ReadFile(secretsPath)
if err != nil {
t.Fatalf("failed to read secrets: %v", err)
}
contentStr := string(content)
requiredFields := []string{"cluster:", "certManager:"}
for _, field := range requiredFields {
if !strings.Contains(contentStr, field) {
t.Errorf("secrets missing required field: %s", field)
}
}
})
}
}
// Test: GetSecret retrieves secrets correctly
func TestGetSecret(t *testing.T) {
tests := []struct {
name string
secretsYAML string
key string
want string
wantErr bool
errContains string
}{
{
name: "get simple string value",
secretsYAML: `cluster:
talosSecrets: "my-secret-value"
`,
key: "cluster.talosSecrets",
want: "my-secret-value",
wantErr: false,
},
{
name: "get nested value with dot notation",
secretsYAML: `certManager:
cloudflare:
apiToken: "cf-token-12345"
`,
key: "certManager.cloudflare.apiToken",
want: "cf-token-12345",
wantErr: false,
},
{
name: "get non-existent key returns error",
secretsYAML: `cluster:
talosSecrets: "value"
`,
key: "nonexistent",
wantErr: true,
errContains: "secret not found",
},
{
name: "get empty string value returns error",
secretsYAML: `cluster:
talosSecrets: ""
`,
key: "cluster.talosSecrets",
wantErr: true,
errContains: "secret not found",
},
{
name: "get null value returns error",
secretsYAML: `cluster:
talosSecrets: null
`,
key: "cluster.talosSecrets",
wantErr: true,
errContains: "secret not found",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir := t.TempDir()
secretsPath := filepath.Join(tempDir, "secrets.yaml")
if err := storage.WriteFile(secretsPath, []byte(tt.secretsYAML), 0600); err != nil {
t.Fatalf("setup failed: %v", err)
}
m := NewManager()
got, err := m.GetSecret(secretsPath, tt.key)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
} else if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("error %q does not contain %q", err.Error(), tt.errContains)
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if got != tt.want {
t.Errorf("got %q, want %q", got, tt.want)
}
})
}
}
// Test: GetSecret error cases
func TestGetSecret_Errors(t *testing.T) {
tests := []struct {
name string
setupFunc func(t *testing.T) string
key string
errContains string
}{
{
name: "non-existent file",
setupFunc: func(t *testing.T) string {
return filepath.Join(t.TempDir(), "nonexistent.yaml")
},
key: "cluster.talosSecrets",
errContains: "secrets file not found",
},
{
name: "malformed yaml",
setupFunc: func(t *testing.T) string {
tempDir := t.TempDir()
secretsPath := filepath.Join(tempDir, "secrets.yaml")
content := `invalid: yaml: [[[`
if err := storage.WriteFile(secretsPath, []byte(content), 0600); err != nil {
t.Fatalf("setup failed: %v", err)
}
return secretsPath
},
key: "cluster.talosSecrets",
errContains: "getting secret",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
secretsPath := tt.setupFunc(t)
m := NewManager()
_, err := m.GetSecret(secretsPath, tt.key)
if err == nil {
t.Error("expected error, got nil")
} else if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("error %q does not contain %q", err.Error(), tt.errContains)
}
})
}
}
// Test: GetSecret does not leak secrets in error messages
func TestGetSecret_NoSecretLeakage(t *testing.T) {
tempDir := t.TempDir()
secretsPath := filepath.Join(tempDir, "secrets.yaml")
secretValue := "super-secret-password-12345"
secretsYAML := `cluster:
talosSecrets: "` + secretValue + `"
`
if err := storage.WriteFile(secretsPath, []byte(secretsYAML), 0600); err != nil {
t.Fatalf("setup failed: %v", err)
}
m := NewManager()
// Try to get a non-existent key - error should not contain actual secret values
_, err := m.GetSecret(secretsPath, "nonexistent.key")
if err == nil {
t.Fatal("expected error, got nil")
}
// Error message should not contain the secret value
if strings.Contains(err.Error(), secretValue) {
t.Errorf("error message leaked secret value: %v", err)
}
}
// Test: SetSecret sets secrets correctly
func TestSetSecret(t *testing.T) {
tests := []struct {
name string
initialYAML string
key string
value string
verifyFunc func(t *testing.T, secretsPath string)
}{
{
name: "set simple value",
initialYAML: `cluster:
talosSecrets: ""
`,
key: "cluster.talosSecrets",
value: "new-secret-value",
verifyFunc: func(t *testing.T, secretsPath string) {
m := NewManager()
got, err := m.GetSecret(secretsPath, "cluster.talosSecrets")
if err != nil {
t.Fatalf("verify failed: %v", err)
}
if got != "new-secret-value" {
t.Errorf("got %q, want %q", got, "new-secret-value")
}
},
},
{
name: "set nested value",
initialYAML: `certManager:
cloudflare:
apiToken: ""
`,
key: "certManager.cloudflare.apiToken",
value: "cf-token-xyz",
verifyFunc: func(t *testing.T, secretsPath string) {
m := NewManager()
got, err := m.GetSecret(secretsPath, "certManager.cloudflare.apiToken")
if err != nil {
t.Fatalf("verify failed: %v", err)
}
if got != "cf-token-xyz" {
t.Errorf("got %q, want %q", got, "cf-token-xyz")
}
},
},
{
name: "update existing value",
initialYAML: `cluster:
talosSecrets: "old-secret"
`,
key: "cluster.talosSecrets",
value: "new-secret",
verifyFunc: func(t *testing.T, secretsPath string) {
m := NewManager()
got, err := m.GetSecret(secretsPath, "cluster.talosSecrets")
if err != nil {
t.Fatalf("verify failed: %v", err)
}
if got != "new-secret" {
t.Errorf("got %q, want %q", got, "new-secret")
}
},
},
{
name: "create new nested path",
initialYAML: `cluster: {}
`,
key: "cluster.newSecret",
value: "newValue",
verifyFunc: func(t *testing.T, secretsPath string) {
m := NewManager()
got, err := m.GetSecret(secretsPath, "cluster.newSecret")
if err != nil {
t.Fatalf("verify failed: %v", err)
}
if got != "newValue" {
t.Errorf("got %q, want %q", got, "newValue")
}
},
},
{
name: "set value with special characters",
initialYAML: `cluster:
talosSecrets: ""
`,
key: "cluster.talosSecrets",
value: `special"quotes'and\backslashes`,
verifyFunc: func(t *testing.T, secretsPath string) {
m := NewManager()
got, err := m.GetSecret(secretsPath, "cluster.talosSecrets")
if err != nil {
t.Fatalf("verify failed: %v", err)
}
if got != `special"quotes'and\backslashes` {
t.Errorf("got %q, want %q", got, `special"quotes'and\backslashes`)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir := t.TempDir()
secretsPath := filepath.Join(tempDir, "secrets.yaml")
if err := storage.WriteFile(secretsPath, []byte(tt.initialYAML), 0600); err != nil {
t.Fatalf("setup failed: %v", err)
}
m := NewManager()
if err := m.SetSecret(secretsPath, tt.key, tt.value); err != nil {
t.Errorf("SetSecret failed: %v", err)
return
}
// Verify the value was set correctly
tt.verifyFunc(t, secretsPath)
// Verify permissions remain secure (0600)
info, err := os.Stat(secretsPath)
if err != nil {
t.Fatalf("failed to stat secrets file: %v", err)
}
if info.Mode().Perm() != 0600 {
t.Errorf("permissions changed after SetSecret: got %o, want 0600", info.Mode().Perm())
}
})
}
}
// Test: SetSecret error cases
func TestSetSecret_Errors(t *testing.T) {
tests := []struct {
name string
setupFunc func(t *testing.T) string
key string
value string
errContains string
}{
{
name: "non-existent file",
setupFunc: func(t *testing.T) string {
return filepath.Join(t.TempDir(), "nonexistent.yaml")
},
key: "cluster.talosSecrets",
value: "secret",
errContains: "secrets file not found",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
secretsPath := tt.setupFunc(t)
m := NewManager()
err := m.SetSecret(secretsPath, tt.key, tt.value)
if err == nil {
t.Error("expected error, got nil")
} else if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("error %q does not contain %q", err.Error(), tt.errContains)
}
})
}
}
// Test: SetSecret with concurrent access
func TestSetSecret_ConcurrentAccess(t *testing.T) {
tempDir := t.TempDir()
secretsPath := filepath.Join(tempDir, "secrets.yaml")
initialYAML := `counter: "0"
`
if err := storage.WriteFile(secretsPath, []byte(initialYAML), 0600); err != nil {
t.Fatalf("setup failed: %v", err)
}
m := NewManager()
const numGoroutines = 10
var wg sync.WaitGroup
errors := make(chan error, numGoroutines)
// Launch multiple goroutines trying to write different values
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(val int) {
defer wg.Done()
key := "counter"
value := string(rune('0' + val))
if err := m.SetSecret(secretsPath, key, value); err != nil {
errors <- err
}
}(i)
}
wg.Wait()
close(errors)
// Check if any errors occurred
for err := range errors {
t.Errorf("concurrent write error: %v", err)
}
// Verify permissions remain secure after concurrent access
info, err := os.Stat(secretsPath)
if err != nil {
t.Fatalf("failed to stat secrets file: %v", err)
}
if info.Mode().Perm() != 0600 {
t.Errorf("permissions changed after concurrent writes: got %o, want 0600", info.Mode().Perm())
}
// Verify we can read a value (should be one of the written values)
value, err := m.GetSecret(secretsPath, "counter")
if err != nil {
t.Errorf("failed to read value after concurrent writes: %v", err)
}
if value == "" || value == "null" {
t.Error("counter value is empty after concurrent writes")
}
}
// Test: EnsureSecret generates and sets secret only when not set
func TestEnsureSecret(t *testing.T) {
tests := []struct {
name string
initialYAML string
key string
length int
expectNew bool
}{
{
name: "generates secret when empty string",
initialYAML: `cluster:
talosSecrets: ""
`,
key: "cluster.talosSecrets",
length: 32,
expectNew: true,
},
{
name: "generates secret when null",
initialYAML: `cluster:
talosSecrets: null
`,
key: "cluster.talosSecrets",
length: 32,
expectNew: true,
},
{
name: "does not generate when secret exists",
initialYAML: `cluster:
talosSecrets: "existing-secret"
`,
key: "cluster.talosSecrets",
length: 32,
expectNew: false,
},
{
name: "generates secret for non-existent key",
initialYAML: `cluster: {}
`,
key: "cluster.newSecret",
length: 64,
expectNew: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir := t.TempDir()
secretsPath := filepath.Join(tempDir, "secrets.yaml")
if err := storage.WriteFile(secretsPath, []byte(tt.initialYAML), 0600); err != nil {
t.Fatalf("setup failed: %v", err)
}
m := NewManager()
// Get initial value if exists
initialVal, _ := m.GetSecret(secretsPath, tt.key)
// Call EnsureSecret
secret, err := m.EnsureSecret(secretsPath, tt.key, tt.length)
if err != nil {
t.Errorf("EnsureSecret failed: %v", err)
return
}
// Verify secret is returned
if secret == "" {
t.Error("EnsureSecret returned empty secret")
}
// Verify length
if tt.expectNew && len(secret) != tt.length {
t.Errorf("expected secret length %d, got %d", tt.length, len(secret))
}
// Get final value
finalVal, err := m.GetSecret(secretsPath, tt.key)
if err != nil {
t.Fatalf("GetSecret failed: %v", err)
}
if tt.expectNew {
// Should have generated new secret
if initialVal != "" && finalVal == initialVal {
t.Errorf("expected new secret, got same value: %q", finalVal)
}
} else {
// Should have kept existing secret
if finalVal != initialVal {
t.Errorf("expected to keep existing secret %q, got %q", initialVal, finalVal)
}
}
// Call EnsureSecret again - should be idempotent
secret2, err := m.EnsureSecret(secretsPath, tt.key, tt.length)
if err != nil {
t.Errorf("second EnsureSecret failed: %v", err)
return
}
// Secret should not change on second call
if secret2 != secret {
t.Errorf("secret changed on second ensure: %q -> %q", secret, secret2)
}
})
}
}
// Test: GenerateAndStoreSecret convenience function
func TestGenerateAndStoreSecret(t *testing.T) {
tempDir := t.TempDir()
secretsPath := filepath.Join(tempDir, "secrets.yaml")
initialYAML := `cluster:
talosSecrets: ""
`
if err := storage.WriteFile(secretsPath, []byte(initialYAML), 0600); err != nil {
t.Fatalf("setup failed: %v", err)
}
m := NewManager()
// Generate and store secret
secret, err := m.GenerateAndStoreSecret(secretsPath, "cluster.talosSecrets")
if err != nil {
t.Fatalf("GenerateAndStoreSecret failed: %v", err)
}
// Verify secret length matches default
if len(secret) != DefaultSecretLength {
t.Errorf("expected length %d, got %d", DefaultSecretLength, len(secret))
}
// Verify secret is stored
stored, err := m.GetSecret(secretsPath, "cluster.talosSecrets")
if err != nil {
t.Fatalf("GetSecret failed: %v", err)
}
if stored != secret {
t.Errorf("stored secret %q does not match returned secret %q", stored, secret)
}
}
// Test: DeleteSecret removes secrets correctly
func TestDeleteSecret(t *testing.T) {
tests := []struct {
name string
initialYAML string
key string
wantErr bool
}{
{
name: "delete existing secret",
initialYAML: `cluster:
talosSecrets: "secret-to-delete"
kubeconfig: "other-secret"
`,
key: "cluster.talosSecrets",
wantErr: false,
},
{
name: "delete nested secret",
initialYAML: `certManager:
cloudflare:
apiToken: "token-to-delete"
`,
key: "certManager.cloudflare.apiToken",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir := t.TempDir()
secretsPath := filepath.Join(tempDir, "secrets.yaml")
if err := storage.WriteFile(secretsPath, []byte(tt.initialYAML), 0600); err != nil {
t.Fatalf("setup failed: %v", err)
}
m := NewManager()
// Verify secret exists before deletion
_, err := m.GetSecret(secretsPath, tt.key)
if err != nil {
t.Fatalf("secret should exist before deletion: %v", err)
}
// Delete secret
err = m.DeleteSecret(secretsPath, tt.key)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
// Verify secret no longer exists
_, err = m.GetSecret(secretsPath, tt.key)
if err == nil {
t.Error("secret should not exist after deletion")
}
// Verify permissions remain secure (0600)
info, err := os.Stat(secretsPath)
if err != nil {
t.Fatalf("failed to stat secrets file: %v", err)
}
if info.Mode().Perm() != 0600 {
t.Errorf("permissions changed after DeleteSecret: got %o, want 0600", info.Mode().Perm())
}
})
}
}
// Test: DeleteSecret error cases
func TestDeleteSecret_Errors(t *testing.T) {
t.Run("non-existent file", func(t *testing.T) {
tempDir := t.TempDir()
secretsPath := filepath.Join(tempDir, "nonexistent.yaml")
m := NewManager()
err := m.DeleteSecret(secretsPath, "cluster.talosSecrets")
if err == nil {
t.Error("expected error, got nil")
} else if !strings.Contains(err.Error(), "secrets file not found") {
t.Errorf("error %q does not contain 'secrets file not found'", err.Error())
}
})
}
// Test: File permissions are always 0600
func TestEnsureSecretsFile_FilePermissions(t *testing.T) {
tempDir := t.TempDir()
m := NewManager()
if err := m.EnsureSecretsFile(tempDir); err != nil {
t.Fatalf("EnsureSecretsFile failed: %v", err)
}
secretsPath := filepath.Join(tempDir, "secrets.yaml")
info, err := os.Stat(secretsPath)
if err != nil {
t.Fatalf("failed to stat secrets file: %v", err)
}
// Verify file has 0600 permissions (read/write for owner only)
if info.Mode().Perm() != 0600 {
t.Errorf("expected permissions 0600, got %o", info.Mode().Perm())
}
}
// Test: Idempotent secrets creation
func TestEnsureSecretsFile_Idempotent(t *testing.T) {
tempDir := t.TempDir()
m := NewManager()
// First call creates secrets
if err := m.EnsureSecretsFile(tempDir); err != nil {
t.Fatalf("first EnsureSecretsFile failed: %v", err)
}
secretsPath := filepath.Join(tempDir, "secrets.yaml")
firstContent, err := storage.ReadFile(secretsPath)
if err != nil {
t.Fatalf("failed to read secrets: %v", err)
}
// Second call should not modify secrets
if err := m.EnsureSecretsFile(tempDir); err != nil {
t.Fatalf("second EnsureSecretsFile failed: %v", err)
}
secondContent, err := storage.ReadFile(secretsPath)
if err != nil {
t.Fatalf("failed to read secrets: %v", err)
}
if string(firstContent) != string(secondContent) {
t.Error("secrets content changed on second call")
}
}
// Test: Secrets structure contains required fields
func TestEnsureSecretsFile_RequiredFields(t *testing.T) {
tempDir := t.TempDir()
m := NewManager()
if err := m.EnsureSecretsFile(tempDir); err != nil {
t.Fatalf("EnsureSecretsFile failed: %v", err)
}
secretsPath := filepath.Join(tempDir, "secrets.yaml")
content, err := storage.ReadFile(secretsPath)
if err != nil {
t.Fatalf("failed to read secrets: %v", err)
}
contentStr := string(content)
requiredFields := []string{
"cluster:",
"talosSecrets:",
"kubeconfig:",
"certManager:",
"cloudflare:",
"apiToken:",
}
for _, field := range requiredFields {
if !strings.Contains(contentStr, field) {
t.Errorf("secrets missing required field: %s", field)
}
}
// Verify warning comment exists
if !strings.Contains(contentStr, "WARNING") || !strings.Contains(contentStr, "sensitive") {
t.Error("secrets file missing security warning comment")
}
}
// Test: Secrets are more restrictive than config
func TestSecretsPermissions_MoreRestrictiveThanConfig(t *testing.T) {
tempDir := t.TempDir()
secretsPath := filepath.Join(tempDir, "secrets.yaml")
configPath := filepath.Join(tempDir, "config.yaml")
// Create secrets file
m := NewManager()
if err := m.EnsureSecretsFile(tempDir); err != nil {
t.Fatalf("EnsureSecretsFile failed: %v", err)
}
// Create config file (typically 0644)
configContent := `baseDomain: "example.com"`
if err := storage.WriteFile(configPath, []byte(configContent), 0644); err != nil {
t.Fatalf("failed to write config: %v", err)
}
// Check permissions
secretsInfo, err := os.Stat(secretsPath)
if err != nil {
t.Fatalf("failed to stat secrets: %v", err)
}
configInfo, err := os.Stat(configPath)
if err != nil {
t.Fatalf("failed to stat config: %v", err)
}
secretsPerm := secretsInfo.Mode().Perm()
configPerm := configInfo.Mode().Perm()
// Secrets (0600) should be more restrictive than config (0644)
if secretsPerm >= configPerm {
t.Errorf("secrets permissions %o should be more restrictive than config %o", secretsPerm, configPerm)
}
// Secrets should not be group or world readable
if secretsPerm&0077 != 0 {
t.Errorf("secrets file should not have group/world permissions, got %o", secretsPerm)
}
}