Files
Charon/docs/plans/archive/phase3_completion_summary.md
2026-02-19 16:34:10 +00:00

6.2 KiB

Phase 3 Multi-Credential Integration - Quick Reference

Full Plan: phase3_caddy_integration_completion.md

3-Step Implementation

1. Add Fields to DNSProviderConfig (manager.go:38-44)

type DNSProviderConfig struct {
    ID                 uint
    ProviderType       string
    PropagationTimeout int
    Credentials        map[string]string              // Single-cred mode
    UseMultiCredentials bool                          // NEW
    ZoneCredentials     map[string]map[string]string  // NEW: map[baseDomain]credentials
}

2. Add Credential Resolution Loop (manager.go:~125)

Insert after line 125 (after dnsProviderConfigs built):

// Phase 2: Resolve zone-specific credentials for multi-credential providers
for i := range dnsProviderConfigs {
    cfg := &dnsProviderConfigs[i]

    // Find provider and check UseMultiCredentials flag
    var provider *models.DNSProvider
    for j := range dnsProviders {
        if dnsProviders[j].ID == cfg.ID {
            provider = &dnsProviders[j]
            break
        }
    }

    if provider == nil || !provider.UseMultiCredentials {
        continue  // Skip single-credential providers
    }

    // Enable multi-credential mode
    cfg.UseMultiCredentials = true
    cfg.ZoneCredentials = make(map[string]map[string]string)

    // Preload credentials
    if err := m.db.Preload("Credentials").First(provider, provider.ID).Error; err != nil {
        logger.Log().WithError(err).WithField("provider_id", provider.ID).Warn("failed to preload credentials")
        continue
    }

    // Resolve credentials for each host's domain
    for _, host := range hosts {
        if !host.Enabled || host.DNSProviderID == nil || *host.DNSProviderID != provider.ID {
            continue
        }

        baseDomain := extractBaseDomain(host.DomainNames)
        if baseDomain == "" || cfg.ZoneCredentials[baseDomain] != nil {
            continue  // Already resolved
        }

        // Resolve credential for this domain
        credentials, err := m.getCredentialForDomain(provider.ID, baseDomain, provider)
        if err != nil {
            logger.Log().WithError(err).WithField("domain", baseDomain).Warn("credential resolution failed")
            continue
        }

        cfg.ZoneCredentials[baseDomain] = credentials
        logger.Log().WithField("domain", baseDomain).Debug("resolved credential")
    }

    logger.Log().WithField("domains_resolved", len(cfg.ZoneCredentials)).Info("multi-credential resolution complete")
}

3. Update Config Generation (config.go:~190-198)

Replace credential assembly logic in DNS challenge policy creation:

// Find DNS provider config
dnsConfig, ok := dnsProviderMap[providerID]
if !ok {
    logger.Log().WithField("provider_id", providerID).Warn("DNS provider not found")
    continue
}

// MULTI-CREDENTIAL MODE: Create separate policy per domain
if dnsConfig.UseMultiCredentials && len(dnsConfig.ZoneCredentials) > 0 {
    for baseDomain, credentials := range dnsConfig.ZoneCredentials {
        // Find domains matching this base domain
        var matchingDomains []string
        for _, domain := range domains {
            if extractBaseDomain(domain) == baseDomain {
                matchingDomains = append(matchingDomains, domain)
            }
        }
        if len(matchingDomains) == 0 {
            continue
        }

        // Build provider config with zone-specific credentials
        providerConfig := map[string]any{"name": dnsConfig.ProviderType}
        for key, value := range credentials {
            providerConfig[key] = value
        }

        // Build issuer with DNS challenge (same as original, but with zone-specific credentials)
        var issuers []any
        // ... (same issuer creation logic as original, using providerConfig)

        // Create TLS automation policy for this domain
        tlsPolicies = append(tlsPolicies, &AutomationPolicy{
            Subjects:   dedupeDomains(matchingDomains),
            IssuersRaw: issuers,
        })

        logger.Log().WithField("base_domain", baseDomain).Debug("created DNS challenge policy")
    }
    continue  // Skip single-credential logic below
}

// SINGLE-CREDENTIAL MODE: Original logic (backward compatible)
providerConfig := map[string]any{"name": dnsConfig.ProviderType}
for key, value := range dnsConfig.Credentials {
    providerConfig[key] = value
}
// ... (rest of original logic)

Testing Checklist

  • Run go test ./internal/caddy -run TestExtractBaseDomain (should pass)
  • Run go test ./internal/caddy -run TestMatchesZoneFilter (should pass)
  • Run go test ./internal/caddy -run TestManager_GetCredentialForDomain (should pass)
  • Run go test ./internal/caddy/... (all tests should pass after changes)
  • Create integration test for multi-credential provider
  • Manual test: Create provider with 2+ credentials, verify separate TLS policies

Validation Commands

# Test helpers
go test -v ./internal/caddy -run TestExtractBaseDomain
go test -v ./internal/caddy -run TestMatchesZoneFilter

# Test integration
go test -v ./internal/caddy/... -count=1

# Check logs for credential resolution
docker logs charon-app 2>&1 | grep "multi-credential"
docker logs charon-app 2>&1 | grep "resolved credential"

# Verify generated Caddy config
curl -s http://localhost:2019/config/ | jq '.apps.tls.automation.policies[] | select(.subjects[] | contains("example"))'

Success Criteria

All existing tests pass Helper function tests pass Integration tests pass (once added) Manual testing with 2+ domains works Backward compatibility validated Logs show credential selection

Rollback

If issues occur:

  1. Set UseMultiCredentials=false on all providers via API
  2. Restart Charon
  3. Investigate logs for credential resolution errors

Files Modified

  • backend/internal/caddy/manager.go - Add fields, add resolution loop
  • backend/internal/caddy/config.go - Update DNS challenge policy generation
  • backend/internal/caddy/manager_multicred_integration_test.go - Add integration tests (new file)

Estimated Time

  • Implementation: 2-3 hours
  • Testing: 1-2 hours
  • Documentation: 1 hour
  • Total: 4-6 hours