chore: clean .gitignore cache
This commit is contained in:
@@ -1,427 +0,0 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Wikid82/charon/backend/internal/config"
|
||||
"github.com/Wikid82/charon/backend/internal/crypto"
|
||||
"github.com/Wikid82/charon/backend/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
_ "github.com/Wikid82/charon/backend/pkg/dnsprovider/builtin" // Auto-register DNS providers
|
||||
)
|
||||
|
||||
// encryptCredentials is a helper to encrypt credentials for test fixtures
|
||||
func encryptCredentials(t *testing.T, credentials map[string]string) string {
|
||||
t.Helper()
|
||||
|
||||
// Always use a valid 32-byte base64-encoded key (decodes to exactly 32 bytes)
|
||||
// base64.StdEncoding.EncodeToString([]byte("12345678901234567890123456789012"))
|
||||
// = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="
|
||||
encryptionKey := "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="
|
||||
os.Setenv("CHARON_ENCRYPTION_KEY", encryptionKey)
|
||||
|
||||
encryptor, err := crypto.NewEncryptionService(encryptionKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
credJSON, err := json.Marshal(credentials)
|
||||
require.NoError(t, err)
|
||||
|
||||
encrypted, err := encryptor.Encrypt(credJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// setupTestDB creates an in-memory database for testing
|
||||
func setupTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Auto-migrate all models including related ones
|
||||
err = db.AutoMigrate(
|
||||
&models.ProxyHost{},
|
||||
&models.Location{},
|
||||
&models.DNSProvider{},
|
||||
&models.DNSProviderCredential{},
|
||||
&models.SSLCertificate{},
|
||||
&models.Setting{},
|
||||
&models.SecurityConfig{},
|
||||
&models.AccessList{},
|
||||
&models.SecurityHeaderProfile{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
// TestApplyConfig_SingleCredential_BackwardCompatibility tests that single-credential
|
||||
// providers continue to work as before (backward compatibility)
|
||||
func TestApplyConfig_SingleCredential_BackwardCompatibility(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a single-credential provider
|
||||
provider := models.DNSProvider{
|
||||
ProviderType: "cloudflare",
|
||||
UseMultiCredentials: false,
|
||||
CredentialsEncrypted: encryptCredentials(t, map[string]string{
|
||||
"api_token": "test-single-token",
|
||||
}),
|
||||
PropagationTimeout: 60,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&provider).Error)
|
||||
|
||||
// Create a proxy host with wildcard domain
|
||||
host := models.ProxyHost{
|
||||
DomainNames: "*.example.com",
|
||||
DNSProviderID: &provider.ID,
|
||||
ForwardHost: "localhost",
|
||||
ForwardPort: 8080,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&host).Error)
|
||||
|
||||
// Create ACME email setting
|
||||
setting := models.Setting{
|
||||
Key: "caddy.acme_email",
|
||||
Value: "test@example.com",
|
||||
}
|
||||
require.NoError(t, db.Create(&setting).Error)
|
||||
|
||||
// Create manager with mock client
|
||||
mockClient := &MockClient{}
|
||||
manager := NewManager(mockClient, db, t.TempDir(), "", false, config.SecurityConfig{})
|
||||
|
||||
// Apply config
|
||||
err := manager.ApplyConfig(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the generated config has DNS challenge with single credential
|
||||
assert.True(t, mockClient.LoadCalled, "Load should have been called")
|
||||
assert.NotNil(t, mockClient.LastLoadedConfig, "Config should have been loaded")
|
||||
|
||||
// Verify TLS automation policies exist
|
||||
require.NotNil(t, mockClient.LastLoadedConfig.Apps.TLS)
|
||||
require.NotNil(t, mockClient.LastLoadedConfig.Apps.TLS.Automation)
|
||||
require.Greater(t, len(mockClient.LastLoadedConfig.Apps.TLS.Automation.Policies), 0)
|
||||
|
||||
// Find the DNS challenge policy
|
||||
var dnsPolicy *AutomationPolicy
|
||||
for _, policy := range mockClient.LastLoadedConfig.Apps.TLS.Automation.Policies {
|
||||
if len(policy.Subjects) > 0 && policy.Subjects[0] == "*.example.com" {
|
||||
dnsPolicy = policy
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, dnsPolicy, "DNS challenge policy should exist for *.example.com")
|
||||
|
||||
// Verify it uses the single credential
|
||||
require.Greater(t, len(dnsPolicy.IssuersRaw), 0)
|
||||
issuer := dnsPolicy.IssuersRaw[0].(map[string]any)
|
||||
require.NotNil(t, issuer["challenges"])
|
||||
challenges := issuer["challenges"].(map[string]any)
|
||||
require.NotNil(t, challenges["dns"])
|
||||
dnsChallenge := challenges["dns"].(map[string]any)
|
||||
require.NotNil(t, dnsChallenge["provider"])
|
||||
providerConfig := dnsChallenge["provider"].(map[string]any)
|
||||
|
||||
assert.Equal(t, "cloudflare", providerConfig["name"])
|
||||
assert.Equal(t, "test-single-token", providerConfig["api_token"])
|
||||
}
|
||||
|
||||
// TestApplyConfig_MultiCredential_ExactMatch tests that multi-credential providers
|
||||
// correctly match credentials by exact zone match
|
||||
func TestApplyConfig_MultiCredential_ExactMatch(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a multi-credential provider
|
||||
provider := models.DNSProvider{
|
||||
ProviderType: "cloudflare",
|
||||
UseMultiCredentials: true,
|
||||
PropagationTimeout: 60,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&provider).Error)
|
||||
|
||||
// Create zone-specific credentials
|
||||
exampleComCred := models.DNSProviderCredential{
|
||||
UUID: uuid.New().String(),
|
||||
DNSProviderID: provider.ID,
|
||||
Label: "Example.com Credential",
|
||||
ZoneFilter: "example.com",
|
||||
CredentialsEncrypted: encryptCredentials(t, map[string]string{
|
||||
"api_token": "token-example-com",
|
||||
}),
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&exampleComCred).Error)
|
||||
|
||||
exampleOrgCred := models.DNSProviderCredential{
|
||||
UUID: uuid.New().String(),
|
||||
DNSProviderID: provider.ID,
|
||||
Label: "Example.org Credential",
|
||||
ZoneFilter: "example.org",
|
||||
CredentialsEncrypted: encryptCredentials(t, map[string]string{
|
||||
"api_token": "token-example-org",
|
||||
}),
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&exampleOrgCred).Error)
|
||||
|
||||
// Create proxy hosts for different domains
|
||||
hostCom := models.ProxyHost{
|
||||
UUID: uuid.New().String(),
|
||||
DomainNames: "*.example.com",
|
||||
DNSProviderID: &provider.ID,
|
||||
ForwardHost: "localhost",
|
||||
ForwardPort: 8080,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&hostCom).Error)
|
||||
|
||||
hostOrg := models.ProxyHost{
|
||||
UUID: uuid.New().String(),
|
||||
DomainNames: "*.example.org",
|
||||
DNSProviderID: &provider.ID,
|
||||
ForwardHost: "localhost",
|
||||
ForwardPort: 8081,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&hostOrg).Error)
|
||||
|
||||
// Create ACME email setting
|
||||
setting := models.Setting{
|
||||
Key: "caddy.acme_email",
|
||||
Value: "test@example.com",
|
||||
}
|
||||
require.NoError(t, db.Create(&setting).Error)
|
||||
|
||||
// Create manager with mock client
|
||||
mockClient := &MockClient{}
|
||||
manager := NewManager(mockClient, db, t.TempDir(), "", false, config.SecurityConfig{})
|
||||
|
||||
// Apply config
|
||||
err := manager.ApplyConfig(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the generated config has separate DNS challenge policies
|
||||
assert.True(t, mockClient.LoadCalled)
|
||||
require.NotNil(t, mockClient.LastLoadedConfig.Apps.TLS)
|
||||
require.NotNil(t, mockClient.LastLoadedConfig.Apps.TLS.Automation)
|
||||
|
||||
policies := mockClient.LastLoadedConfig.Apps.TLS.Automation.Policies
|
||||
require.Greater(t, len(policies), 1, "Should have separate policies for each domain")
|
||||
|
||||
// Find policies for each domain
|
||||
var comPolicy, orgPolicy *AutomationPolicy
|
||||
for _, policy := range policies {
|
||||
if len(policy.Subjects) > 0 {
|
||||
switch policy.Subjects[0] {
|
||||
case "*.example.com":
|
||||
comPolicy = policy
|
||||
case "*.example.org":
|
||||
orgPolicy = policy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require.NotNil(t, comPolicy, "Policy for *.example.com should exist")
|
||||
require.NotNil(t, orgPolicy, "Policy for *.example.org should exist")
|
||||
|
||||
// Verify each policy uses the correct credential
|
||||
assertDNSChallengeCredential(t, comPolicy, "cloudflare", "token-example-com")
|
||||
assertDNSChallengeCredential(t, orgPolicy, "cloudflare", "token-example-org")
|
||||
}
|
||||
|
||||
// TestApplyConfig_MultiCredential_WildcardMatch tests wildcard zone matching
|
||||
func TestApplyConfig_MultiCredential_WildcardMatch(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a multi-credential provider
|
||||
provider := models.DNSProvider{
|
||||
ProviderType: "cloudflare",
|
||||
UseMultiCredentials: true,
|
||||
PropagationTimeout: 60,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&provider).Error)
|
||||
|
||||
// Create wildcard credential for *.example.com (matches app.example.com, api.example.com, etc.)
|
||||
wildcardCred := models.DNSProviderCredential{
|
||||
UUID: uuid.New().String(),
|
||||
DNSProviderID: provider.ID,
|
||||
Label: "Wildcard Example.com",
|
||||
ZoneFilter: "*.example.com",
|
||||
CredentialsEncrypted: encryptCredentials(t, map[string]string{
|
||||
"api_token": "token-wildcard",
|
||||
}),
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&wildcardCred).Error)
|
||||
|
||||
// Create proxy host for subdomain
|
||||
host := models.ProxyHost{
|
||||
UUID: uuid.New().String(),
|
||||
DomainNames: "*.app.example.com",
|
||||
DNSProviderID: &provider.ID,
|
||||
ForwardHost: "localhost",
|
||||
ForwardPort: 8080,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&host).Error)
|
||||
|
||||
// Create ACME email setting
|
||||
setting := models.Setting{
|
||||
Key: "caddy.acme_email",
|
||||
Value: "test@example.com",
|
||||
}
|
||||
require.NoError(t, db.Create(&setting).Error)
|
||||
|
||||
// Create manager with mock client
|
||||
mockClient := &MockClient{}
|
||||
manager := NewManager(mockClient, db, t.TempDir(), "", false, config.SecurityConfig{})
|
||||
|
||||
// Apply config
|
||||
err := manager.ApplyConfig(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify config was generated
|
||||
assert.True(t, mockClient.LoadCalled)
|
||||
require.NotNil(t, mockClient.LastLoadedConfig.Apps.TLS)
|
||||
require.NotNil(t, mockClient.LastLoadedConfig.Apps.TLS.Automation)
|
||||
|
||||
// Find the DNS challenge policy
|
||||
var dnsPolicy *AutomationPolicy
|
||||
for _, policy := range mockClient.LastLoadedConfig.Apps.TLS.Automation.Policies {
|
||||
if len(policy.Subjects) > 0 && policy.Subjects[0] == "*.app.example.com" {
|
||||
dnsPolicy = policy
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, dnsPolicy, "DNS challenge policy should exist")
|
||||
|
||||
// Verify it uses the wildcard credential
|
||||
assertDNSChallengeCredential(t, dnsPolicy, "cloudflare", "token-wildcard")
|
||||
}
|
||||
|
||||
// TestApplyConfig_MultiCredential_CatchAll tests catch-all credential (empty zone_filter)
|
||||
func TestApplyConfig_MultiCredential_CatchAll(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a multi-credential provider
|
||||
provider := models.DNSProvider{
|
||||
ProviderType: "cloudflare",
|
||||
UseMultiCredentials: true,
|
||||
PropagationTimeout: 60,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&provider).Error)
|
||||
|
||||
// Create catch-all credential (empty zone_filter)
|
||||
catchAllCred := models.DNSProviderCredential{
|
||||
UUID: uuid.New().String(),
|
||||
DNSProviderID: provider.ID,
|
||||
Label: "Catch-All",
|
||||
ZoneFilter: "",
|
||||
CredentialsEncrypted: encryptCredentials(t, map[string]string{
|
||||
"api_token": "token-catch-all",
|
||||
}),
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&catchAllCred).Error)
|
||||
|
||||
// Create proxy host for a domain with no specific credential
|
||||
host := models.ProxyHost{
|
||||
UUID: uuid.New().String(),
|
||||
DomainNames: "*.random.net",
|
||||
DNSProviderID: &provider.ID,
|
||||
ForwardHost: "localhost",
|
||||
ForwardPort: 8080,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&host).Error)
|
||||
|
||||
// Create ACME email setting
|
||||
setting := models.Setting{
|
||||
Key: "caddy.acme_email",
|
||||
Value: "test@example.com",
|
||||
}
|
||||
require.NoError(t, db.Create(&setting).Error)
|
||||
|
||||
// Create manager with mock client
|
||||
mockClient := &MockClient{}
|
||||
manager := NewManager(mockClient, db, t.TempDir(), "", false, config.SecurityConfig{})
|
||||
|
||||
// Apply config
|
||||
err := manager.ApplyConfig(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify config was generated
|
||||
assert.True(t, mockClient.LoadCalled)
|
||||
require.NotNil(t, mockClient.LastLoadedConfig.Apps.TLS)
|
||||
require.NotNil(t, mockClient.LastLoadedConfig.Apps.TLS.Automation)
|
||||
|
||||
// Find the DNS challenge policy
|
||||
var dnsPolicy *AutomationPolicy
|
||||
for _, policy := range mockClient.LastLoadedConfig.Apps.TLS.Automation.Policies {
|
||||
if len(policy.Subjects) > 0 && policy.Subjects[0] == "*.random.net" {
|
||||
dnsPolicy = policy
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, dnsPolicy, "DNS challenge policy should exist")
|
||||
|
||||
// Verify it uses the catch-all credential
|
||||
assertDNSChallengeCredential(t, dnsPolicy, "cloudflare", "token-catch-all")
|
||||
}
|
||||
|
||||
// assertDNSChallengeCredential is a helper to verify DNS challenge uses correct credentials
|
||||
func assertDNSChallengeCredential(t *testing.T, policy *AutomationPolicy, providerType, expectedToken string) {
|
||||
t.Helper()
|
||||
|
||||
require.Greater(t, len(policy.IssuersRaw), 0, "Policy should have issuers")
|
||||
issuer := policy.IssuersRaw[0].(map[string]any)
|
||||
require.NotNil(t, issuer["challenges"], "Issuer should have challenges")
|
||||
challenges := issuer["challenges"].(map[string]any)
|
||||
require.NotNil(t, challenges["dns"], "Challenges should have DNS")
|
||||
dnsChallenge := challenges["dns"].(map[string]any)
|
||||
require.NotNil(t, dnsChallenge["provider"], "DNS challenge should have provider")
|
||||
providerConfig := dnsChallenge["provider"].(map[string]any)
|
||||
|
||||
assert.Equal(t, providerType, providerConfig["name"], "Provider type should match")
|
||||
assert.Equal(t, expectedToken, providerConfig["api_token"], "API token should match")
|
||||
}
|
||||
|
||||
// MockClient is a mock Caddy client for testing
|
||||
type MockClient struct {
|
||||
LoadCalled bool
|
||||
LastLoadedConfig *Config
|
||||
PingError error
|
||||
LoadError error
|
||||
GetConfigResult *Config
|
||||
GetConfigError error
|
||||
}
|
||||
|
||||
func (m *MockClient) Load(ctx context.Context, config *Config) error {
|
||||
m.LoadCalled = true
|
||||
m.LastLoadedConfig = config
|
||||
return m.LoadError
|
||||
}
|
||||
|
||||
func (m *MockClient) Ping(ctx context.Context) error {
|
||||
return m.PingError
|
||||
}
|
||||
|
||||
func (m *MockClient) GetConfig(ctx context.Context) (*Config, error) {
|
||||
return m.GetConfigResult, m.GetConfigError
|
||||
}
|
||||
Reference in New Issue
Block a user