- Implemented `getCrowdsecKeyStatus` API call to retrieve the current status of the CrowdSec API key. - Created `CrowdSecKeyWarning` component to display warnings when the API key is rejected. - Integrated `CrowdSecKeyWarning` into the Security page, ensuring it only shows when relevant. - Updated i18n initialization in main.tsx to prevent race conditions during rendering. - Enhanced authentication setup in tests to handle various response statuses more robustly. - Adjusted security tests to accept broader error responses for import validation.
900 lines
23 KiB
Go
900 lines
23 KiB
Go
package caddy
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/Wikid82/charon/backend/pkg/dnsprovider"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
_ "github.com/Wikid82/charon/backend/pkg/dnsprovider/builtin" // Auto-register DNS providers
|
|
)
|
|
|
|
type multiCredTestProvider struct{}
|
|
|
|
func (p *multiCredTestProvider) Type() string { return "testmulti" }
|
|
func (p *multiCredTestProvider) Metadata() dnsprovider.ProviderMetadata {
|
|
return dnsprovider.ProviderMetadata{Type: p.Type(), Name: "Test Multi", IsBuiltIn: true}
|
|
}
|
|
func (p *multiCredTestProvider) Init() error { return nil }
|
|
func (p *multiCredTestProvider) Cleanup() error { return nil }
|
|
func (p *multiCredTestProvider) RequiredCredentialFields() []dnsprovider.CredentialFieldSpec {
|
|
return nil
|
|
}
|
|
func (p *multiCredTestProvider) OptionalCredentialFields() []dnsprovider.CredentialFieldSpec {
|
|
return nil
|
|
}
|
|
func (p *multiCredTestProvider) ValidateCredentials(creds map[string]string) error { return nil }
|
|
func (p *multiCredTestProvider) TestCredentials(creds map[string]string) error { return nil }
|
|
func (p *multiCredTestProvider) SupportsMultiCredential() bool { return true }
|
|
func (p *multiCredTestProvider) BuildCaddyConfig(creds map[string]string) map[string]any {
|
|
return map[string]any{"name": p.Type(), "token": creds["token"]}
|
|
}
|
|
func (p *multiCredTestProvider) BuildCaddyConfigForZone(baseDomain string, creds map[string]string) map[string]any {
|
|
return map[string]any{"name": p.Type(), "zone": baseDomain, "token": creds["token"]}
|
|
}
|
|
func (p *multiCredTestProvider) PropagationTimeout() time.Duration { return 2 * time.Second }
|
|
func (p *multiCredTestProvider) PollingInterval() time.Duration { return 1 * time.Second }
|
|
|
|
func mustProviderID(v uint) *uint { return &v }
|
|
|
|
func TestGenerateConfig_DNSChallenge_LetsEncrypt_StagingCAAndPropagationTimeout(t *testing.T) {
|
|
providerID := uint(1)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.example.com,example.com",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "cloudflare"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"letsencrypt",
|
|
true,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "cloudflare",
|
|
PropagationTimeout: 120,
|
|
Credentials: map[string]string{"api_token": "tok"},
|
|
}},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
require.NotNil(t, conf.Apps.TLS)
|
|
require.NotNil(t, conf.Apps.TLS.Automation)
|
|
require.NotEmpty(t, conf.Apps.TLS.Automation.Policies)
|
|
|
|
// Find a policy that includes the wildcard subject
|
|
var foundIssuer map[string]any
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, s := range p.Subjects {
|
|
if s != "*.example.com" {
|
|
continue
|
|
}
|
|
require.NotEmpty(t, p.IssuersRaw)
|
|
for _, it := range p.IssuersRaw {
|
|
if m, ok := it.(map[string]any); ok {
|
|
if m["module"] == "acme" {
|
|
foundIssuer = m
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if foundIssuer != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
require.NotNil(t, foundIssuer)
|
|
require.Equal(t, "https://acme-staging-v02.api.letsencrypt.org/directory", foundIssuer["ca"])
|
|
|
|
challenges, ok := foundIssuer["challenges"].(map[string]any)
|
|
require.True(t, ok)
|
|
dns, ok := challenges["dns"].(map[string]any)
|
|
require.True(t, ok)
|
|
require.Equal(t, int64(120)*1_000_000_000, dns["propagation_timeout"])
|
|
}
|
|
|
|
func TestGenerateConfig_DNSChallenge_ZeroSSL_IssuerShape(t *testing.T) {
|
|
providerID := uint(2)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.example.net",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "cloudflare"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"zerossl",
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "cloudflare",
|
|
PropagationTimeout: 5,
|
|
Credentials: map[string]string{"api_token": "tok"},
|
|
}},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
require.NotNil(t, conf.Apps.TLS)
|
|
require.NotEmpty(t, conf.Apps.TLS.Automation.Policies)
|
|
|
|
// Expect at least one issuer with module zerossl
|
|
found := false
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, it := range p.IssuersRaw {
|
|
if m, ok := it.(map[string]any); ok {
|
|
if m["module"] == "zerossl" {
|
|
found = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
require.True(t, found)
|
|
}
|
|
|
|
func TestGenerateConfig_DNSChallenge_SkipsPolicyWhenProviderConfigMissing(t *testing.T) {
|
|
providerID := uint(3)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.example.org",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "cloudflare"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"letsencrypt",
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
nil, // no provider configs available
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
require.NotNil(t, conf.Apps.TLS)
|
|
require.NotEmpty(t, conf.Apps.TLS.Automation.Policies)
|
|
|
|
// No policy should include the wildcard subject since provider config was missing
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, s := range p.Subjects {
|
|
require.NotEqual(t, "*.example.org", s)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerateConfig_HTTPChallenge_ExcludesIPDomains(t *testing.T) {
|
|
host := models.ProxyHost{Enabled: true, DomainNames: "example.com,192.168.1.1"}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"letsencrypt",
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
require.NotNil(t, conf.Apps.TLS)
|
|
require.NotEmpty(t, conf.Apps.TLS.Automation.Policies)
|
|
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, s := range p.Subjects {
|
|
require.NotEqual(t, "192.168.1.1", s)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetCrowdSecAPIKey_EnvPriority(t *testing.T) {
|
|
// Skip if bouncer_key file exists (file takes priority over env vars per Phase 1 of LAPI auth fix)
|
|
const bouncerKeyFile = "/app/data/crowdsec/bouncer_key"
|
|
if _, err := os.Stat(bouncerKeyFile); err == nil {
|
|
t.Skip("Skipping env priority test - bouncer_key file exists (file takes priority over env vars)")
|
|
}
|
|
|
|
_ = os.Unsetenv("CROWDSEC_API_KEY")
|
|
_ = os.Unsetenv("CROWDSEC_BOUNCER_API_KEY")
|
|
|
|
t.Setenv("CROWDSEC_BOUNCER_API_KEY", "bouncer")
|
|
t.Setenv("CROWDSEC_API_KEY", "primary")
|
|
// CHARON_SECURITY_CROWDSEC_API_KEY has highest priority among env vars
|
|
require.Equal(t, "primary", getCrowdSecAPIKey())
|
|
|
|
_ = os.Unsetenv("CROWDSEC_API_KEY")
|
|
require.Equal(t, "bouncer", getCrowdSecAPIKey())
|
|
}
|
|
|
|
func TestHasWildcard_TrueFalse(t *testing.T) {
|
|
require.True(t, hasWildcard([]string{"*.example.com"}))
|
|
require.False(t, hasWildcard([]string{"example.com"}))
|
|
}
|
|
|
|
// TestGenerateConfig_MultiCredential_ZoneSpecificPolicies verifies that multi-credential DNS providers
|
|
// create separate TLS automation policies per zone with zone-specific credentials.
|
|
func TestGenerateConfig_MultiCredential_ZoneSpecificPolicies(t *testing.T) {
|
|
providerID := uint(10)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.zone1.com,zone1.com,*.zone2.com,zone2.com",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "cloudflare"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"letsencrypt",
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "cloudflare",
|
|
UseMultiCredentials: true,
|
|
ZoneCredentials: map[string]map[string]string{
|
|
"zone1.com": {"api_token": "token-zone1"},
|
|
"zone2.com": {"api_token": "token-zone2"},
|
|
},
|
|
Credentials: map[string]string{"api_token": "fallback-token"},
|
|
}},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
require.NotNil(t, conf.Apps.TLS)
|
|
require.NotEmpty(t, conf.Apps.TLS.Automation.Policies)
|
|
|
|
// Should have at least 2 policies for the 2 zones
|
|
policyCount := 0
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, s := range p.Subjects {
|
|
if s == "*.zone1.com" || s == "zone1.com" || s == "*.zone2.com" || s == "zone2.com" {
|
|
policyCount++
|
|
break
|
|
}
|
|
}
|
|
}
|
|
require.GreaterOrEqual(t, policyCount, 2, "expected at least 2 policies for multi-credential zones")
|
|
}
|
|
|
|
// TestGenerateConfig_MultiCredential_ZeroSSL_Issuer verifies multi-credential with ZeroSSL issuer.
|
|
func TestGenerateConfig_MultiCredential_ZeroSSL_Issuer(t *testing.T) {
|
|
providerID := uint(11)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.zerossl-test.com",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "cloudflare"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"zerossl", // Use ZeroSSL provider
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "cloudflare",
|
|
UseMultiCredentials: true,
|
|
ZoneCredentials: map[string]map[string]string{
|
|
"zerossl-test.com": {"api_token": "zerossl-token"},
|
|
},
|
|
}},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
|
|
// Find ZeroSSL issuer in policies
|
|
foundZeroSSL := false
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, it := range p.IssuersRaw {
|
|
if m, ok := it.(map[string]any); ok {
|
|
if m["module"] == "zerossl" {
|
|
foundZeroSSL = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
require.True(t, foundZeroSSL, "expected ZeroSSL issuer in multi-credential policy")
|
|
}
|
|
|
|
// TestGenerateConfig_MultiCredential_BothIssuers verifies multi-credential with both ACME and ZeroSSL issuers.
|
|
func TestGenerateConfig_MultiCredential_BothIssuers(t *testing.T) {
|
|
providerID := uint(12)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.both-test.com",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "cloudflare"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"both", // Use both providers
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "cloudflare",
|
|
UseMultiCredentials: true,
|
|
ZoneCredentials: map[string]map[string]string{
|
|
"both-test.com": {"api_token": "both-token"},
|
|
},
|
|
}},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
|
|
// Find both ACME and ZeroSSL issuers in policies
|
|
foundACME := false
|
|
foundZeroSSL := false
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, it := range p.IssuersRaw {
|
|
if m, ok := it.(map[string]any); ok {
|
|
switch m["module"] {
|
|
case "acme":
|
|
foundACME = true
|
|
case "zerossl":
|
|
foundZeroSSL = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
require.True(t, foundACME, "expected ACME issuer in multi-credential policy")
|
|
require.True(t, foundZeroSSL, "expected ZeroSSL issuer in multi-credential policy")
|
|
}
|
|
|
|
// TestGenerateConfig_MultiCredential_ACMEStaging verifies multi-credential with ACME staging CA.
|
|
func TestGenerateConfig_MultiCredential_ACMEStaging(t *testing.T) {
|
|
providerID := uint(13)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.staging-test.com",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "cloudflare"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"letsencrypt",
|
|
true, // ACME staging
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "cloudflare",
|
|
UseMultiCredentials: true,
|
|
ZoneCredentials: map[string]map[string]string{
|
|
"staging-test.com": {"api_token": "staging-token"},
|
|
},
|
|
}},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
|
|
// Find ACME issuer with staging CA
|
|
foundStagingCA := false
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, it := range p.IssuersRaw {
|
|
if m, ok := it.(map[string]any); ok {
|
|
if m["module"] == "acme" {
|
|
if ca, ok := m["ca"].(string); ok && ca == "https://acme-staging-v02.api.letsencrypt.org/directory" {
|
|
foundStagingCA = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
require.True(t, foundStagingCA, "expected ACME staging CA in multi-credential policy")
|
|
}
|
|
|
|
// TestGenerateConfig_MultiCredential_NoMatchingDomains verifies that zones with no matching domains are skipped.
|
|
func TestGenerateConfig_MultiCredential_NoMatchingDomains(t *testing.T) {
|
|
providerID := uint(14)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.actual.com",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "cloudflare"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"letsencrypt",
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "cloudflare",
|
|
UseMultiCredentials: true,
|
|
ZoneCredentials: map[string]map[string]string{
|
|
"unmatched.com": {"api_token": "unmatched-token"}, // This zone won't match any domains
|
|
"actual.com": {"api_token": "actual-token"}, // This zone will match
|
|
},
|
|
}},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
|
|
// Should only have policy for actual.com, not unmatched.com
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, s := range p.Subjects {
|
|
require.NotContains(t, s, "unmatched", "unmatched domain should not appear in policies")
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestGenerateConfig_MultiCredential_ProviderTypeNotFound verifies graceful handling when provider type is not in registry.
|
|
func TestGenerateConfig_MultiCredential_ProviderTypeNotFound(t *testing.T) {
|
|
providerID := uint(15)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.unknown-provider.com",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "nonexistent_provider"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"letsencrypt",
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "nonexistent_provider", // Not in registry
|
|
UseMultiCredentials: true,
|
|
ZoneCredentials: map[string]map[string]string{
|
|
"unknown-provider.com": {"api_token": "token"},
|
|
},
|
|
}},
|
|
)
|
|
// Should not error, just skip the provider
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
}
|
|
|
|
func TestGenerateConfig_MultiCredential_SupportsMultiCredential_UsesZoneConfigAndStagingBothIssuers(t *testing.T) {
|
|
if err := dnsprovider.Global().Register(&multiCredTestProvider{}); err == nil {
|
|
t.Cleanup(func() { dnsprovider.Global().Unregister("testmulti") })
|
|
}
|
|
|
|
providerID := uint(16)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.z1.com,z1.com",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "testmulti"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"both",
|
|
true,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "testmulti",
|
|
UseMultiCredentials: true,
|
|
ZoneCredentials: map[string]map[string]string{
|
|
"z1.com": {"token": "tok-z1"},
|
|
},
|
|
}},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
require.NotNil(t, conf.Apps.TLS)
|
|
require.NotNil(t, conf.Apps.TLS.Automation)
|
|
require.NotEmpty(t, conf.Apps.TLS.Automation.Policies)
|
|
|
|
var foundCA string
|
|
var foundProvider map[string]any
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, it := range p.IssuersRaw {
|
|
issuer, ok := it.(map[string]any)
|
|
if !ok || issuer["module"] != "acme" {
|
|
continue
|
|
}
|
|
if ca, ok := issuer["ca"].(string); ok {
|
|
foundCA = ca
|
|
}
|
|
ch, _ := issuer["challenges"].(map[string]any)
|
|
dnsCh, _ := ch["dns"].(map[string]any)
|
|
prov, _ := dnsCh["provider"].(map[string]any)
|
|
foundProvider = prov
|
|
break
|
|
}
|
|
if foundProvider != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
require.Equal(t, "https://acme-staging-v02.api.letsencrypt.org/directory", foundCA)
|
|
require.NotNil(t, foundProvider)
|
|
require.Equal(t, "z1.com", foundProvider["zone"], "expected zone-specific provider config")
|
|
}
|
|
|
|
func TestGenerateConfig_DNSChallenge_SingleCredential_BothIssuers_ACMEStaging(t *testing.T) {
|
|
providerID := uint(17)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.both-staging.com",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "cloudflare"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"both",
|
|
true,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "cloudflare",
|
|
PropagationTimeout: 1,
|
|
Credentials: map[string]string{"api_token": "tok"},
|
|
}},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
|
|
var foundIssuer map[string]any
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, s := range p.Subjects {
|
|
if s != "*.both-staging.com" {
|
|
continue
|
|
}
|
|
for _, it := range p.IssuersRaw {
|
|
if m, ok := it.(map[string]any); ok && m["module"] == "acme" {
|
|
foundIssuer = m
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if foundIssuer != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
require.NotNil(t, foundIssuer)
|
|
require.Equal(t, "https://acme-staging-v02.api.letsencrypt.org/directory", foundIssuer["ca"])
|
|
}
|
|
|
|
func TestGenerateConfig_DNSChallenge_SingleCredential_ProviderTypeNotFound_SkipsPolicy(t *testing.T) {
|
|
providerID := uint(18)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
DomainNames: "*.missing-registry.com",
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "not_registered"},
|
|
DNSProviderID: mustProviderID(providerID),
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"letsencrypt",
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
[]DNSProviderConfig{{
|
|
ID: providerID,
|
|
ProviderType: "not_registered",
|
|
PropagationTimeout: 1,
|
|
Credentials: map[string]string{"token": "x"},
|
|
}},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
}
|
|
|
|
func TestGenerateConfig_DefaultPolicy_LetsEncrypt_StagingCA(t *testing.T) {
|
|
host := models.ProxyHost{Enabled: true, DomainNames: "192.0.2.1"}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"letsencrypt",
|
|
true,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
require.NotNil(t, conf.Apps.TLS)
|
|
require.NotNil(t, conf.Apps.TLS.Automation)
|
|
require.NotEmpty(t, conf.Apps.TLS.Automation.Policies)
|
|
|
|
found := false
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, it := range p.IssuersRaw {
|
|
if m, ok := it.(map[string]any); ok && m["module"] == "acme" {
|
|
require.Equal(t, "https://acme-staging-v02.api.letsencrypt.org/directory", m["ca"])
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
break
|
|
}
|
|
}
|
|
require.True(t, found)
|
|
}
|
|
|
|
func TestGenerateConfig_DefaultPolicy_ZeroSSL_Issuer(t *testing.T) {
|
|
host := models.ProxyHost{Enabled: true, DomainNames: "192.0.2.2"}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"zerossl",
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
require.NotNil(t, conf.Apps.TLS)
|
|
require.NotNil(t, conf.Apps.TLS.Automation)
|
|
|
|
found := false
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, it := range p.IssuersRaw {
|
|
if m, ok := it.(map[string]any); ok && m["module"] == "zerossl" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
break
|
|
}
|
|
}
|
|
require.True(t, found)
|
|
}
|
|
|
|
func TestGenerateConfig_DefaultPolicy_BothIssuers_StagingCA(t *testing.T) {
|
|
host := models.ProxyHost{Enabled: true, DomainNames: "192.0.2.3"}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"acme@example.com",
|
|
"",
|
|
"",
|
|
true,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
|
|
caFound := false
|
|
zeroFound := false
|
|
for _, p := range conf.Apps.TLS.Automation.Policies {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
for _, it := range p.IssuersRaw {
|
|
m, ok := it.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch m["module"] {
|
|
case "acme":
|
|
if m["ca"] == "https://acme-staging-v02.api.letsencrypt.org/directory" {
|
|
caFound = true
|
|
}
|
|
case "zerossl":
|
|
zeroFound = true
|
|
}
|
|
}
|
|
}
|
|
require.True(t, caFound)
|
|
require.True(t, zeroFound)
|
|
}
|
|
|
|
func TestGenerateConfig_IPSubjects_InitializesTLSAppAndAutomation(t *testing.T) {
|
|
providerID := uint(19)
|
|
host := models.ProxyHost{
|
|
Enabled: true,
|
|
UUID: "ip-host",
|
|
DomainNames: "1.2.3.4",
|
|
ForwardHost: "app",
|
|
ForwardPort: 8080,
|
|
DNSProvider: &models.DNSProvider{ID: providerID, ProviderType: "cloudflare"},
|
|
}
|
|
|
|
conf, err := GenerateConfig(
|
|
[]models.ProxyHost{host},
|
|
t.TempDir(),
|
|
"",
|
|
"",
|
|
"",
|
|
false,
|
|
false, false, false, false,
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
&models.SecurityConfig{},
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conf)
|
|
require.NotNil(t, conf.Apps.TLS)
|
|
require.NotNil(t, conf.Apps.TLS.Automation)
|
|
require.NotEmpty(t, conf.Apps.TLS.Automation.Policies)
|
|
}
|
|
|
|
func TestGetAccessLogPath_DockerEnv_UsesCrowdSecPath(t *testing.T) {
|
|
const dockerMarker = "/.dockerenv"
|
|
if err := os.WriteFile(dockerMarker, []byte("test"), 0o600); err != nil {
|
|
t.Skipf("cannot create %s: %v", dockerMarker, err)
|
|
}
|
|
t.Cleanup(func() { _ = os.Remove(dockerMarker) })
|
|
|
|
path := getAccessLogPath(t.TempDir(), false)
|
|
require.Equal(t, "/var/log/caddy/access.log", path)
|
|
}
|