# Multi-Credential per DNS Provider ## Feature Overview ### What is Multi-Credential Support? Multi-Credential per Provider is an advanced feature that allows you to configure multiple sets of API credentials for the same DNS provider. Instead of using a single API key for all domains managed by a provider (e.g., Cloudflare), you can configure different credentials for different DNS zones. ### Why Use Multi-Credentials? **Security Benefits:** - **Zone-level Isolation**: Compromise of one credential doesn't expose all your domains - **Least Privilege Principle**: Each credential can have minimal permissions for only the zones it manages - **Independent Rotation**: Rotate credentials for specific zones without affecting others - **Audit Trail**: Track which credentials were used for certificate operations **Business Use Cases:** - **Managed Service Providers (MSPs)**: Use separate customer-specific credentials for each client's domains - **Large Enterprises**: Isolate credentials by department, environment, or geographic region - **Multi-Tenant Platforms**: Provide credential isolation between tenants - **Compliance Requirements**: Meet security standards requiring credential segregation ### Single vs Multi-Credential Architecture ``` Single Credential Mode: ┌─────────────────────────┐ │ Cloudflare Provider │ │ API Key: xyz123 │ └───────────┬─────────────┘ │ ┌───────┴────────┬────────────┬──────────────┐ │ │ │ │ example.com customer-a.com *.dev.com acme.org ``` ``` Multi-Credential Mode: ┌──────────────────────────────────────────────────┐ │ Cloudflare Provider │ ├──────────────────────────────────────────────────┤ │ Credential 1: "Production" │ │ Zone Filter: example.com │ │ ├─→ example.com │ │ └─→ www.example.com │ ├──────────────────────────────────────────────────┤ │ Credential 2: "Customer A" │ │ Zone Filter: *.customer-a.com │ │ └─→ *.customer-a.com │ ├──────────────────────────────────────────────────┤ │ Credential 3: "Development" │ │ Zone Filter: *.dev.com │ │ └─→ *.dev.com │ ├──────────────────────────────────────────────────┤ │ Credential 4: "Catch-all" │ │ Zone Filter: (empty - matches anything else) │ │ └─→ acme.org, other.net, etc. │ └──────────────────────────────────────────────────┘ ``` ## When to Use Multi-Credentials ### Decision Criteria **Use Multi-Credentials When:** - You manage domains for multiple customers or tenants - You need credential isolation for security or compliance - Different teams or departments manage different zones - You want to limit blast radius of credential compromise - You have different security postures for different environments (prod/staging/dev) - You need independent credential rotation schedules **Use Single Credential When:** - You manage a small number of domains under one organization - All domains have the same security requirements - Simpler management is preferred over isolation - You're just getting started with Charon ### Comparison Matrix | Aspect | Single Credential | Multi-Credential | |--------|------------------|------------------| | **Security Isolation** | ❌ All zones use same key | ✅ Per-zone isolation | | **Blast Radius** | ❌ High (all zones affected) | ✅ Limited to filtered zones | | **Setup Complexity** | ✅ Simple | ⚠️ Moderate | | **Credential Rotation** | ❌ Affects all zones | ✅ Independent per zone | | **Audit Granularity** | ⚠️ Provider-level only | ✅ Credential-level detail | | **Multi-Tenancy** | ❌ Not suitable | ✅ Ideal | | **Best For** | Small deployments | MSPs, enterprises, multi-tenant | ## Enabling Multi-Credential Mode ### Prerequisites - Charon v1.3.0 or later - Admin access to the Charon dashboard - DNS provider account with API access - (Optional) Multiple API keys already generated at your DNS provider ### Step-by-Step Enable Process 1. **Navigate to DNS Provider Settings** - Go to **Settings** → **DNS Providers** - Locate the provider you want to enable multi-credential for (e.g., Cloudflare) 2. **Click "Enable Multi-Credential"** - Click the **Enable Multi-Credential** button next to the provider - A confirmation dialog will appear 3. **Review Migration Impact** ``` ⚠️ IMPORTANT: This action will: - Convert your existing provider credential into a "catch-all" credential - Preserve all existing proxy host configurations - Enable credential management UI for this provider - This change is reversible (you can disable and revert) ``` 4. **Confirm Enable** - Click **Enable** to proceed - The provider will now show "Multi-Credential Mode: Enabled" 5. **Verify Migration** - Your existing credential is now listed as a credential with no zone filter (catch-all) - All existing proxy hosts continue to work without interruption - You can now add additional credentials ### What Happens During Migration 1. **Existing Configuration Preserved**: Your current API key/token remains active 2. **Automatic Credential Creation**: The existing credential is converted to a credential entry with: - Name: "{Provider} Primary Credential" - Zone Filter: Empty (matches all zones) - Description: "Migrated from single-credential mode" 3. **Zero Downtime**: All certificate operations continue without interruption 4. **Backward Compatible**: If you disable multi-credential mode, you revert to the original setup ### Backward Compatibility - **Disabling Multi-Credential**: You can disable multi-credential mode by clicking **Disable Multi-Credential** - **Reversion**: Disabling converts the first credential back to the provider's primary credential - **Data Loss**: Other credentials will be retained in the database but won't be used - **Re-enabling**: You can re-enable multi-credential mode at any time without data loss ## Managing Credentials ### Accessing Credential Management 1. Navigate to **Settings** → **DNS Providers** 2. Find your provider with "Multi-Credential Mode: Enabled" 3. Click **Manage Credentials** 4. The credential management interface displays all credentials for this provider ### Adding a New Credential #### Step 1: Click "Add Credential" Click the **Add Credential** button in the credential management interface. #### Step 2: Fill in Credential Details **Required Fields:** - **Credential Name**: A descriptive name (e.g., "Customer A Production", "US Zones", "Dev Environment") - Must be unique within the provider - Recommended: Use descriptive names that indicate purpose or zone scope - **API Credentials**: Provider-specific authentication fields - **Cloudflare**: API Token or Global API Key + Email - **Route53**: Access Key ID + Secret Access Key - **DigitalOcean**: API Token - (Refer to provider-specific guides for required fields) **Optional Fields:** - **Description**: Additional notes about the credential's purpose - **Zone Filter**: Comma-separated list of zones this credential manages (see Zone Filter Configuration below) #### Step 3: Configure Zone Filter The zone filter determines which domains this credential will be used for: **Option 1: Exact Domain Match** ``` Zone Filter: example.com Matches: example.com, www.example.com, api.example.com Does NOT Match: subdomain.example.com.au, example.org ``` **Option 2: Wildcard Match** ``` Zone Filter: *.customer-a.com Matches: shop.customer-a.com, api.customer-a.com, *.customer-a.com Does NOT Match: customer-a.com (root), customer-b.com ``` **Option 3: Multiple Zones (Comma-Separated)** ``` Zone Filter: example.com,api.example.org,*.dev.example.net Matches: - example.com and all subdomains - api.example.org and all subdomains under api.example.org - *.dev.example.net (all subdomains of dev.example.net) ``` **Option 4: Catch-All (Empty Filter)** ``` Zone Filter: (leave empty) Matches: Any domain not matched by other credentials Use Case: Fallback credential for miscellaneous domains ``` #### Step 4: Test the Credential (Recommended) 1. Click **Test Credential** before saving 2. Charon will attempt to authenticate with the DNS provider using the supplied credentials 3. If successful, you'll see: ✅ "Credential validated successfully" 4. If failed, review the error message and correct the credentials #### Step 5: Save the Credential - Click **Save Credential** - The credential is now active and will be used for matching domains ### Zone Filter Configuration #### Syntax Rules - **Comma-separated**: Separate multiple patterns with commas (no spaces) - **Case-insensitive**: `Example.com` matches `example.com` - **Wildcard prefix**: Use `*.` at the start for subdomain matching - **No regex**: Only exact and wildcard matches are supported - **No trailing dots**: Don't use `example.com.` (trailing dot is stripped) #### Examples | Zone Filter | Matches | Does NOT Match | |-------------|---------|----------------| | `example.com` | `example.com`, `www.example.com`, `api.example.com` | `example.org`, `sub.example.com.au` | | `*.customer-a.com` | `shop.customer-a.com`, `api.customer-a.com` | `customer-a.com` (root) | | `*.staging.example.com` | `app.staging.example.com`, `api.staging.example.com` | `staging.example.com`, `prod.example.com` | | `example.com,example.org` | Both `example.com` and `example.org` domains | `example.net` | | _(empty)_ | Any domain not matched by other credentials | _(none - this is catch-all)_ | #### Validation Rules When saving a credential, Charon validates: - ✅ Zone filter syntax is correct - ✅ No duplicate exact matches across credentials - ⚠️ Warning if multiple wildcard patterns could overlap - ✅ At most one credential per provider can have an empty zone filter (catch-all) ### Editing Credentials 1. In the credential management interface, click **Edit** next to the credential 2. Modify any field (name, description, credentials, zone filter) 3. Click **Test Credential** to validate changes 4. Click **Save Changes** **⚠️ Important Notes:** - Changing zone filters may affect which credential is used for existing proxy hosts - Charon will re-evaluate credential matching for all proxy hosts after the change - Consider testing in a non-production environment first if making significant changes ### Testing Credentials You can test credentials at any time to verify they still work: 1. Click **Test** next to the credential in the management interface 2. Charon will attempt a test DNS record lookup or API call 3. Results: - ✅ **Success**: Credential is valid and working - ❌ **Failed**: Credential is invalid or has insufficient permissions - ⚠️ **Warning**: Credential works but may have limited permissions ### Deleting Credentials 1. Click **Delete** next to the credential 2. Charon will check if any proxy hosts are currently using this credential 3. **If in use**: You'll be warned and must migrate those proxy hosts to another credential first 4. **If not in use**: Confirm deletion and the credential will be removed **⚠️ Warning**: Deleting a credential that is actively in use for certificate operations will cause certificate renewals to fail. Always ensure proxy hosts are migrated to another credential before deletion. ## Zone Matching Rules ### How Zone Matching Works When Charon needs to issue or renew a certificate for a domain, it selects a credential using this process: ``` 1. Extract DNS zone from the domain Example: For "www.api.example.com", zone is "example.com" 2. Query all credentials for the provider (e.g., Cloudflare) 3. Match credentials against the zone using priority order: a. Exact match (highest priority) b. Wildcard match c. Catch-all (empty zone filter) (lowest priority) 4. Return the first matching credential 5. Use the credential to perform DNS-01 challenge ``` ### Priority Order Credentials are evaluated in this order: **1. Exact Match (Highest Priority)** ``` Zone Filter: example.com Domain: www.example.com → Zone: example.com → ✅ MATCH ``` **2. Wildcard Match** ``` Zone Filter: *.customer-a.com Domain: shop.customer-a.com → Zone: customer-a.com → ✅ MATCH (after exact check fails) ``` **3. Catch-All (Lowest Priority)** ``` Zone Filter: (empty) Domain: anything.com → Zone: anything.com → ✅ MATCH (if no exact or wildcard matches) ``` ### Matching Examples #### Example 1: MSP with Multiple Customers **Configured Credentials:** ``` 1. Name: "Customer A Production" Zone Filter: *.customer-a.com Priority: Wildcard 2. Name: "Customer B Production" Zone Filter: *.customer-b.com Priority: Wildcard 3. Name: "Catch-all" Zone Filter: (empty) Priority: Catch-all ``` **Domain Matching:** - `shop.customer-a.com` → Credential 1 ("Customer A Production") - `api.customer-b.com` → Credential 2 ("Customer B Production") - `example.com` → Credential 3 ("Catch-all") - `random.org` → Credential 3 ("Catch-all") #### Example 2: Environment Separation **Configured Credentials:** ``` 1. Name: "Production" Zone Filter: example.com Priority: Exact 2. Name: "Staging" Zone Filter: *.staging.example.com Priority: Wildcard 3. Name: "Development" Zone Filter: *.dev.example.com Priority: Wildcard ``` **Domain Matching:** - `www.example.com` → Credential 1 ("Production") - `api.example.com` → Credential 1 ("Production") - `app.staging.example.com` → Credential 2 ("Staging") - `api.dev.example.com` → Credential 3 ("Development") - `staging.example.com` → Credential 1 ("Production") - root of staging doesn't match `*.staging.example.com` #### Example 3: Geographic Separation **Configured Credentials:** ``` 1. Name: "US Zones" Zone Filter: *.us.example.com Priority: Wildcard 2. Name: "EU Zones" Zone Filter: *.eu.example.com Priority: Wildcard 3. Name: "APAC Zones" Zone Filter: *.apac.example.com Priority: Wildcard ``` **Domain Matching:** - `shop.us.example.com` → Credential 1 ("US Zones") - `api.eu.example.com` → Credential 2 ("EU Zones") - `portal.apac.example.com` → Credential 3 ("APAC Zones") - `www.example.com` → ❌ NO MATCH (no catch-all defined) ### Overlapping Patterns **⚠️ What Happens When Patterns Overlap?** If multiple credentials could match the same zone, Charon uses **first match** based on priority order: **Example:** ``` Credential A: Zone Filter: example.com (Exact) Credential B: Zone Filter: *.example.com (Wildcard) Domain: www.example.com Zone: example.com Match Process: 1. Check Exact: Credential A matches "example.com" → ✅ Use Credential A 2. (Wildcard check not reached) ``` **Best Practice**: Avoid overlapping patterns when possible. Design zone filters to be mutually exclusive. ### Troubleshooting Zone Matching #### Issue: Domain doesn't match any credential **Symptoms:** - Certificate issuance fails with "No matching credential for zone" - Error message: `No credential found for provider 'cloudflare' and zone 'example.com'` **Solutions:** 1. **Add a catch-all credential**: Create a credential with an empty zone filter 2. **Add specific credential**: Create a credential with zone filter matching your domain 3. **Check zone extraction**: Ensure Charon is correctly extracting the zone from your domain #### Issue: Wrong credential is being used **Symptoms:** - Expected credential "Production" but "Catch-all" is being used - Certificate issued but not with the intended credential **Solutions:** 1. **Check zone filter syntax**: Verify your zone filters are correctly configured 2. **Check priority order**: Exact matches override wildcards; ensure your exact match is configured 3. **Review audit logs**: Check which credential was actually selected and why #### Issue: Zone filter validation error **Symptoms:** - Error: "Invalid zone filter format" - Error: "Zone filter 'example.com' conflicts with existing credential" **Solutions:** 1. **Check syntax**: Ensure no spaces, only commas separating patterns 2. **Check for duplicates**: Ensure no two credentials have the exact same zone filter pattern 3. **Review wildcard syntax**: Wildcards must be `*.domain.com`, not `*domain.com` ## Creating Proxy Hosts with Multi-Credentials ### Automatic Credential Selection When you create a proxy host with multi-credential mode enabled: 1. **You don't select a credential** - Charon selects automatically 2. **Zone matching** - Charon extracts the DNS zone from your domain 3. **Credential lookup** - Charon finds the best matching credential using zone matching rules 4. **Certificate issuance** - The selected credential is used for DNS-01 challenge ### Step-by-Step Process 1. **Create Proxy Host as Normal** - Go to **Proxy Hosts** → **Add Proxy Host** - Enter domain name (e.g., `shop.customer-a.com`) - Configure forward host/port and other settings - Enable SSL/TLS and select Let's Encrypt 2. **Charon Selects Credential Automatically** - Charon extracts zone: `customer-a.com` - Searches for matching credentials for the DNS provider - Finds: "Customer A Production" (Zone Filter: `*.customer-a.com`) - Uses this credential for certificate issuance 3. **Certificate Issuance** - Charon requests certificate from Let's Encrypt - Uses selected credential to create DNS TXT record for `_acme-challenge.shop.customer-a.com` - Completes DNS-01 challenge - Certificate is issued 4. **View Selected Credential** - After creation, view the proxy host details - The **Certificate** section shows: "Issued using credential: Customer A Production" ### Viewing Which Credential Was Used **Method 1: Proxy Host Details** 1. Open the proxy host from the dashboard 2. In the **SSL/TLS** section, look for: ``` Certificate: Active (Expires: 2026-04-01) Credential Used: Customer A Production (Cloudflare) Last Renewed: 2026-01-02 14:30 UTC ``` **Method 2: Audit Logs** 1. Go to **Settings** → **Audit Logs** 2. Filter by: `Action: certificate_issued` or `Action: certificate_renewed` 3. View log entry: ```json { "timestamp": "2026-01-02T14:30:00Z", "action": "certificate_issued", "domain": "shop.customer-a.com", "provider": "cloudflare", "credential_id": 42, "credential_name": "Customer A Production", "user": "admin@example.com", "result": "success" } ``` **Method 3: Credential Statistics** 1. Go to **Settings** → **DNS Providers** → **Manage Credentials** 2. Each credential shows: - **Usage Count**: Number of domains using this credential - **Last Used**: Timestamp of last certificate operation - **Success Rate**: Success/failure ratio ### Troubleshooting Certificate Issuance #### Issue: Certificate issuance fails with "No matching credential" **Error Message:** ``` Failed to issue certificate for shop.customer-a.com: No credential found for provider 'cloudflare' and zone 'customer-a.com' ``` **Solution:** 1. Check DNS provider has multi-credential enabled 2. Verify a credential exists with zone filter matching `customer-a.com` 3. Add a credential with zone filter: `*.customer-a.com` or use catch-all #### Issue: Certificate issuance fails with "API authentication failed" **Error Message:** ``` Failed to issue certificate for shop.customer-a.com: Cloudflare API returned 403: Invalid credentials ``` **Solution:** 1. Test the credential being used: **Manage Credentials** → **Test** 2. Verify API token/key is still valid in your DNS provider dashboard 3. Check API token has correct permissions (`Zone:DNS:Edit`) 4. Update the credential with valid API credentials #### Issue: Wrong credential is being used **Symptoms:** - Certificate issued successfully but with unexpected credential - Audit logs show different credential than expected **Solution:** 1. Review zone filter configuration for all credentials 2. Check priority order (exact > wildcard > catch-all) 3. Ensure your expected credential has the most specific zone filter 4. Test zone matching logic in **Manage Credentials** interface ## Credential Organization Best Practices ### Naming Conventions **Recommended Naming Patterns:** **By Customer/Tenant:** ``` - "Customer A - Production" - "Customer B - Staging" - "Tenant XYZ - All Zones" ``` **By Environment:** ``` - "Production Zones" - "Staging Zones" - "Development Zones" ``` **By Department:** ``` - "Marketing - example.com" - "Engineering - api.example.com" - "Sales - shop.example.com" ``` **By Geography:** ``` - "US East Zones" - "EU West Zones" - "APAC Zones" ``` ### Zone Filter Strategies #### Strategy 1: Exact Domain Per Credential **Use Case:** Small number of high-value domains **Example:** ``` Credential: "example.com Primary" Zone Filter: example.com Credential: "api.example.org" Zone Filter: api.example.org ``` **Pros:** - Maximum specificity - Easy to understand - Clear audit trail **Cons:** - Requires one credential per domain - Not scalable for many domains #### Strategy 2: Wildcard by Namespace **Use Case:** Logical grouping of subdomains **Example:** ``` Credential: "Customer Zones" Zone Filter: *.customers.example.com Credential: "Internal Services" Zone Filter: *.internal.example.com ``` **Pros:** - Scalable for many subdomains - Clear organizational boundaries - Reduces credential count **Cons:** - Broader scope than exact match - Requires careful namespace planning #### Strategy 3: Hybrid Approach **Use Case:** Most common for production deployments **Example:** ``` 1. Exact matches for critical domains: - "Production Root" → example.com 2. Wildcards for namespaces: - "Customer A" → *.customer-a.com - "Customer B" → *.customer-b.com - "Staging" → *.staging.example.com 3. Catch-all for miscellaneous: - "Legacy & Misc" → (empty) ``` **Pros:** - Balance of specificity and scalability - Flexible and maintainable - Handles edge cases **Cons:** - More credentials to manage - Requires understanding of priority order ### When to Use Catch-All Credentials **✅ Good Use Cases:** 1. **Proof-of-Concept/Testing**: Start with catch-all, refine later 2. **Backward Compatibility**: Ensure existing domains continue working during migration 3. **Miscellaneous Domains**: Handle legacy or one-off domains 4. **Gradual Migration**: Add specific credentials over time while catch-all handles the rest **❌ Avoid Catch-All When:** 1. **High-Security Environments**: Catch-all defeats purpose of zone isolation 2. **Multi-Tenancy**: Each tenant should have explicit credentials 3. **Compliance**: Regulations may require explicit credential assignment 4. **Credential Rotation**: Catch-all makes rotation harder ### Credential Rotation Strategy **Best Practice Rotation Schedule:** - **High-Risk Credentials** (catch-all, root domains): Every 30 days - **Production Credentials**: Every 90 days - **Staging/Development**: Every 180 days - **Test Credentials**: Every 365 days or as needed **Rotation Process:** 1. **Generate New Credentials** at DNS provider 2. **Add New Credential** in Charon with same zone filter 3. **Test New Credential** - verify it works 4. **Update Zone Filter** of old credential to `__deprecated__` (forces Charon to use new credential) 5. **Wait for Certificate Renewals** - monitor for 7-30 days 6. **Delete Old Credential** once confirmed new credential is working **Automation:** - Use provider API to programmatically generate new credentials - Use Charon API to add/update credentials - Schedule rotation using cron or CI/CD pipeline - Monitor audit logs for credential usage ## Monitoring and Maintenance ### Viewing Credential Usage Statistics **Dashboard View:** 1. Navigate to **Settings** → **DNS Providers** 2. For each provider with multi-credential enabled, click **View Statistics** 3. Dashboard shows: - Total credentials configured - Active credentials (used in last 30 days) - Inactive credentials (not used in last 90 days) - Top credentials by usage **Per-Credential View:** 1. Go to **Settings** → **DNS Providers** → **Manage Credentials** 2. Each credential displays: ``` ┌─────────────────────────────────────────────────┐ │ Customer A Production │ │ Zone Filter: *.customer-a.com │ ├─────────────────────────────────────────────────┤ │ Domains Using: 12 │ │ Success Count: 156 │ │ Failure Count: 2 │ │ Success Rate: 98.7% │ │ Last Used: 2026-01-03 10:45 UTC │ │ Last Success: 2026-01-03 10:45 UTC │ │ Last Failure: 2025-12-28 03:12 UTC │ ├─────────────────────────────────────────────────┤ │ [Test] [Edit] [View Domains] [Delete] │ └─────────────────────────────────────────────────┘ ``` ### Success/Failure Counts **Success Count**: Number of successful certificate operations (issuance, renewal) using this credential **Failure Count**: Number of failed certificate operations **Success Rate**: Percentage of successful operations (Success / (Success + Failure) × 100%) **⚠️ Low Success Rate Alert:** - If success rate drops below 90%, investigate immediately - Common causes: expired API token, insufficient permissions, DNS provider API issues - Click **Test Credential** to diagnose ### Last Used Timestamps **Last Used**: Timestamp of the most recent certificate operation (success or failure) **Last Success**: Timestamp of the most recent successful operation **Last Failure**: Timestamp of the most recent failed operation **Use Cases:** - **Identify Unused Credentials**: If "Last Used" is > 90 days ago, consider removing - **Credential Rotation**: Track when credentials were last active - **Incident Response**: Correlate failures with outages or credential changes ### Audit Trail for Credential Operations **Viewing Audit Logs:** 1. Go to **Settings** → **Audit Logs** 2. Filter by: - **Action Type**: `credential_created`, `credential_updated`, `credential_deleted`, `certificate_issued`, `certificate_renewed` - **Provider**: Filter by DNS provider - **User**: Filter by who performed the action **Log Entry Example:** ```json { "timestamp": "2026-01-04T15:30:00Z", "action": "credential_created", "resource_type": "dns_credential", "resource_id": 42, "resource_name": "Customer A Production", "provider": "cloudflare", "zone_filter": "*.customer-a.com", "user": "admin@example.com", "ip_address": "192.168.1.100", "details": { "description": "Credential for Customer A production domains", "created_via": "web_ui" } } ``` **Exported Logs:** - Export to CSV or JSON for external analysis - Integrate with SIEM (Security Information and Event Management) systems - Use for compliance reporting and security audits ## Troubleshooting ### Common Issues and Solutions #### Issue 1: No matching credential for domain **Symptoms:** - Certificate issuance fails - Error: `No credential found for provider 'cloudflare' and zone 'example.com'` - Proxy host shows certificate status: "Failed" **Root Causes:** 1. No credential configured for the DNS zone 2. Zone filter doesn't match the domain's zone 3. Multi-credential mode not enabled **Solutions:** **Step 1: Verify Multi-Credential Mode is Enabled** ``` Settings → DNS Providers → Check "Multi-Credential Mode: Enabled" ``` **Step 2: Check Existing Credentials** ``` Settings → DNS Providers → Manage Credentials Review zone filters for all credentials ``` **Step 3: Add Missing Credential or Catch-All** **Option A: Add Specific Credential** ``` Credential Name: example.com Production Zone Filter: example.com ``` **Option B: Add Catch-All** ``` Credential Name: Catch-All Zone Filter: (leave empty) ``` **Step 4: Retry Certificate Issuance** ``` Proxy Hosts → Select proxy host → SSL/TLS → Renew Certificate ``` #### Issue 2: Certificate issuance fails with API error **Symptoms:** - Certificate issuance fails - Error: `Cloudflare API returned 403: Invalid credentials` or similar - Credential test fails **Root Causes:** 1. API token/key expired or revoked 2. Insufficient API permissions 3. DNS provider account suspended 4. Rate limiting or API quota exceeded **Solutions:** **Step 1: Test the Credential** ``` Settings → DNS Providers → Manage Credentials → Click "Test" next to credential ``` **Step 2: Check API Token Validity** - Log in to your DNS provider dashboard (e.g., Cloudflare) - Navigate to API Tokens - Verify token is active and not expired - Check token permissions: `Zone:DNS:Edit` permission required **Step 3: Regenerate API Token** - Generate new API token at DNS provider - Update credential in Charon: ``` Settings → DNS Providers → Manage Credentials → Edit → Update API credentials → Test → Save ``` **Step 4: Check API Rate Limits** - Review DNS provider's rate limit documentation - Check if you've exceeded API quotas - Wait for rate limit to reset (typically hourly) - Consider spreading certificate operations over time **Step 5: Retry Certificate Issuance** ``` Proxy Hosts → Select proxy host → SSL/TLS → Renew Certificate ``` #### Issue 3: Zone filter validation error **Symptoms:** - Cannot save credential - Error: `Invalid zone filter format: 'example..com'` - Error: `Zone filter 'example.com' conflicts with existing credential` **Root Causes:** 1. Syntax error in zone filter (typo, invalid characters) 2. Duplicate zone filter across multiple credentials 3. Conflicting exact and wildcard patterns **Solutions:** **Step 1: Check Syntax** **Valid Formats:** ``` ✅ example.com ✅ *.customer-a.com ✅ example.com,api.example.org ✅ *.staging.example.com,*.dev.example.com ✅ (empty - catch-all) ``` **Invalid Formats:** ``` ❌ example..com (double dot) ❌ example.com. (trailing dot) ❌ *customer-a.com (missing dot after asterisk) ❌ example.com, api.example.org (space after comma) ❌ example.com; api.example.org (semicolon instead of comma) ``` **Step 2: Check for Duplicates** - Review all credentials for the provider - Ensure no two credentials have the exact same zone filter pattern - If duplicate found, edit one credential to have a different zone filter **Step 3: Resolve Conflicts** - If you have both `example.com` and `*.example.com`, this is allowed but may cause confusion - Ensure you understand priority order: exact match takes precedence **Step 4: Save Again** - After fixing syntax/duplicates, click **Save Credential** #### Issue 4: Wrong credential is being used **Symptoms:** - Certificate issued successfully but audit logs show unexpected credential - Credential statistics don't match expectations - Security/compliance concern about which credential was used **Root Causes:** 1. Zone filter misconfiguration (too broad or too narrow) 2. Misunderstanding of zone matching priority 3. Overlapping patterns causing unexpected matches **Solutions:** **Step 1: Review Zone Matching Logic** ``` Priority Order: 1. Exact match: example.com 2. Wildcard match: *.customer-a.com 3. Catch-all: (empty) ``` **Step 2: Check Zone Extraction** - For domain `shop.customer-a.com`, zone is `customer-a.com` - For domain `www.example.com`, zone is `example.com` - For domain `api.sub.example.com`, zone is `example.com` (not `sub.example.com`) **Step 3: Review All Credential Zone Filters** ``` Settings → DNS Providers → Manage Credentials List all zone filters and check for overlaps: Credential A: example.com (exact) Credential B: *.example.com (wildcard) Credential C: (empty - catch-all) For www.example.com: - Zone: example.com - Match: Credential A (exact match wins) ``` **Step 4: Adjust Zone Filters** - Make zone filters more specific to avoid unwanted matches - Remove or narrow catch-all if it's too broad - Use exact matches for critical domains **Step 5: Test Zone Matching** - Some Charon versions may include a zone matching test utility - Go to **Settings** → **DNS Providers** → **Test Zone Matching** - Enter a domain and see which credential would be selected #### Issue 5: Credential test succeeds but certificate issuance fails **Symptoms:** - Credential test passes: ✅ "Credential validated successfully" - Certificate issuance fails with DNS-related error - Error: `DNS propagation timeout` or `TXT record not found` **Root Causes:** 1. API permissions sufficient for test but not for DNS-01 challenge 2. DNS propagation delay 3. Credential has access to different zones than expected 4. Firewall/network issue blocking DNS updates **Solutions:** **Step 1: Check API Permissions** **Cloudflare:** - Required: `Zone:DNS:Edit` permission - Test permission alone may only check `Zone:DNS:Read` **Route53:** - Required: `route53:ChangeResourceRecordSets`, `route53:GetChange` - Test permission alone may only check `route53:ListHostedZones` **Step 2: Verify Zone Access** - Ensure credential has access to the specific zone - Check DNS provider dashboard for zone visibility - For Route53, ensure IAM policy includes the correct hosted zone ID **Step 3: Check DNS Propagation** - DNS-01 challenge requires TXT record to propagate - Default timeout: 60 seconds - Increase timeout in Charon settings if DNS provider is slow: ``` Settings → Advanced → DNS Propagation Timeout: 120 seconds ``` **Step 4: Manual DNS Test** - After certificate issuance fails, check if TXT record was created: ```bash dig TXT _acme-challenge.shop.customer-a.com nslookup -type=TXT _acme-challenge.shop.customer-a.com ``` - If record exists, issue is with propagation delay - If record doesn't exist, issue is with API permissions or credential **Step 5: Review Let's Encrypt Logs** - View detailed certificate issuance logs: ``` Settings → Logs → Filter by: "certificate_issuance" ``` - Look for specific error messages from Let's Encrypt or DNS provider ### Getting Help If you continue to experience issues: 1. **Check Documentation**: Review [DNS provider-specific guides](#) for configuration details 2. **Review Audit Logs**: Check audit trail for clues about what went wrong 3. **Test Credentials**: Use credential test feature to isolate API issues 4. **Enable Debug Logging**: Temporarily enable debug logging for certificate operations 5. **Community Support**: Visit Charon community forums or GitHub discussions 6. **Professional Support**: Contact Charon support team for enterprise customers ## API Reference ### Authentication All API requests require authentication using a Charon API token: ```bash curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://your-charon-instance/api/v1/... ``` **Getting an API Token:** 1. Go to **Settings** → **API Tokens** 2. Click **Generate Token** 3. Copy token (shown only once) ### Credential Management API Endpoints #### List Credentials **Endpoint:** `GET /api/v1/dns-providers/{providerId}/credentials` **Description:** List all credentials for a DNS provider **Request:** ```bash curl -X GET \ https://your-charon-instance/api/v1/dns-providers/1/credentials \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` **Response:** ```json { "credentials": [ { "id": 42, "provider_id": 1, "name": "Customer A Production", "description": "Credential for Customer A production domains", "zone_filter": "*.customer-a.com", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-03T10:45:00Z", "last_used_at": "2026-01-03T10:45:00Z", "usage_count": 12, "success_count": 156, "failure_count": 2 }, { "id": 43, "provider_id": 1, "name": "Catch-All", "description": "Fallback credential", "zone_filter": "", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z", "last_used_at": "2026-01-02T15:30:00Z", "usage_count": 5, "success_count": 10, "failure_count": 0 } ], "total": 2 } ``` #### Get Credential **Endpoint:** `GET /api/v1/dns-providers/{providerId}/credentials/{credentialId}` **Description:** Get details of a specific credential **Request:** ```bash curl -X GET \ https://your-charon-instance/api/v1/dns-providers/1/credentials/42 \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` **Response:** ```json { "id": 42, "provider_id": 1, "name": "Customer A Production", "description": "Credential for Customer A production domains", "zone_filter": "*.customer-a.com", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-03T10:45:00Z", "last_used_at": "2026-01-03T10:45:00Z", "usage_count": 12, "success_count": 156, "failure_count": 2, "domains": [ "shop.customer-a.com", "api.customer-a.com", "portal.customer-a.com" ] } ``` #### Create Credential **Endpoint:** `POST /api/v1/dns-providers/{providerId}/credentials` **Description:** Create a new credential for a DNS provider **Request:** ```bash curl -X POST \ https://your-charon-instance/api/v1/dns-providers/1/credentials \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer B Production", "description": "Credential for Customer B production domains", "zone_filter": "*.customer-b.com", "credentials": { "api_token": "your-cloudflare-api-token" } }' ``` **Provider-Specific Credential Fields:** **Cloudflare:** ```json "credentials": { "api_token": "your-cloudflare-api-token" } // OR "credentials": { "api_key": "your-cloudflare-api-key", "email": "your-email@example.com" } ``` **Route53:** ```json "credentials": { "access_key_id": "AKIAIOSFODNN7EXAMPLE", "secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" } ``` **DigitalOcean:** ```json "credentials": { "api_token": "your-digitalocean-api-token" } ``` **Response:** ```json { "id": 44, "provider_id": 1, "name": "Customer B Production", "description": "Credential for Customer B production domains", "zone_filter": "*.customer-b.com", "created_at": "2026-01-04T15:30:00Z", "updated_at": "2026-01-04T15:30:00Z", "last_used_at": null, "usage_count": 0, "success_count": 0, "failure_count": 0 } ``` #### Update Credential **Endpoint:** `PATCH /api/v1/dns-providers/{providerId}/credentials/{credentialId}` **Description:** Update an existing credential **Request:** ```bash curl -X PATCH \ https://your-charon-instance/api/v1/dns-providers/1/credentials/44 \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "description": "Updated credential description", "zone_filter": "*.customer-b.com,*.customer-b.net" }' ``` **Response:** ```json { "id": 44, "provider_id": 1, "name": "Customer B Production", "description": "Updated credential description", "zone_filter": "*.customer-b.com,*.customer-b.net", "created_at": "2026-01-04T15:30:00Z", "updated_at": "2026-01-04T16:00:00Z", "last_used_at": null, "usage_count": 0, "success_count": 0, "failure_count": 0 } ``` #### Delete Credential **Endpoint:** `DELETE /api/v1/dns-providers/{providerId}/credentials/{credentialId}` **Description:** Delete a credential (fails if credential is in use) **Request:** ```bash curl -X DELETE \ https://your-charon-instance/api/v1/dns-providers/1/credentials/44 \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` **Response (Success):** ```json { "message": "Credential deleted successfully", "id": 44 } ``` **Response (Error - In Use):** ```json { "error": "Cannot delete credential: 3 proxy hosts are using this credential", "affected_domains": [ "shop.customer-b.com", "api.customer-b.com", "portal.customer-b.com" ] } ``` #### Test Credential **Endpoint:** `POST /api/v1/dns-providers/{providerId}/credentials/{credentialId}/test` **Description:** Test if a credential is valid and has correct permissions **Request:** ```bash curl -X POST \ https://your-charon-instance/api/v1/dns-providers/1/credentials/42/test \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` **Response (Success):** ```json { "status": "success", "message": "Credential validated successfully", "details": { "provider": "cloudflare", "test_performed": "zone_list", "accessible_zones": [ "customer-a.com" ] } } ``` **Response (Failure):** ```json { "status": "failed", "message": "API authentication failed", "details": { "provider": "cloudflare", "error_code": "403", "error_message": "Invalid credentials" } } ``` #### Enable Multi-Credential Mode **Endpoint:** `POST /api/v1/dns-providers/{providerId}/enable-multi-credential` **Description:** Enable multi-credential mode for a DNS provider **Request:** ```bash curl -X POST \ https://your-charon-instance/api/v1/dns-providers/1/enable-multi-credential \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` **Response:** ```json { "message": "Multi-credential mode enabled", "provider_id": 1, "migrated_credential": { "id": 45, "name": "Cloudflare Primary Credential", "zone_filter": "", "description": "Migrated from single-credential mode" } } ``` #### Disable Multi-Credential Mode **Endpoint:** `POST /api/v1/dns-providers/{providerId}/disable-multi-credential` **Description:** Disable multi-credential mode (reverts to first credential as primary) **Request:** ```bash curl -X POST \ https://your-charon-instance/api/v1/dns-providers/1/disable-multi-credential \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` **Response:** ```json { "message": "Multi-credential mode disabled", "provider_id": 1, "primary_credential": { "id": 45, "name": "Cloudflare Primary Credential" }, "note": "Other credentials are retained but not used. Re-enable multi-credential mode to use them again." } ``` ### Error Responses All API endpoints may return the following error responses: **400 Bad Request:** ```json { "error": "Invalid zone filter format", "details": "Zone filter cannot contain spaces" } ``` **401 Unauthorized:** ```json { "error": "Unauthorized", "message": "Invalid or expired API token" } ``` **403 Forbidden:** ```json { "error": "Forbidden", "message": "Insufficient permissions to manage credentials" } ``` **404 Not Found:** ```json { "error": "Not found", "message": "Credential with ID 999 not found" } ``` **409 Conflict:** ```json { "error": "Conflict", "message": "Zone filter 'example.com' conflicts with existing credential", "conflicting_credential_id": 42 } ``` **500 Internal Server Error:** ```json { "error": "Internal server error", "message": "An unexpected error occurred. Please contact support.", "request_id": "req_abc123xyz" } ``` ## Cross-References ### Related Documentation - **[DNS Provider Setup Guides](../dns-providers/)** - Configure individual DNS providers - **[Audit Logging](../security/audit-logging.md)** - View and export audit logs for credential operations - **[Security Best Practices](../security/best-practices.md)** - Security guidelines for credential management - **[Key Rotation](../security/key-rotation.md)** - Automated credential rotation strategies - **[Certificate Management](../certificates/)** - Understanding Let's Encrypt certificate lifecycle - **[API Documentation](../api/)** - Complete API reference for automation - **[Multi-Tenancy Guide](../deployment/multi-tenancy.md)** - Deploying Charon for multi-tenant scenarios - **[Backup and Recovery](../maintenance/backup-recovery.md)** - Backing up credential configuration ### Provider-Specific Guides - **[Cloudflare Multi-Credential Setup](../dns-providers/cloudflare-multi-credential.md)** - **[Route53 IAM Policies for Multi-Credential](../dns-providers/route53-multi-credential.md)** - **[DigitalOcean Token Scoping](../dns-providers/digitalocean-multi-credential.md)** ### Tutorials - **[Tutorial: Setting Up Multi-Credential for an MSP](../tutorials/msp-multi-credential.md)** - **[Tutorial: Environment Separation with Multi-Credentials](../tutorials/environment-separation.md)** - **[Tutorial: Migrating from Single to Multi-Credential Mode](../tutorials/migration-multi-credential.md)** --- ## Support and Feedback **Questions?** Visit the [Charon Community Forums](https://community.charon.example.com) **Found a Bug?** Report it on [GitHub Issues](https://github.com/charon/charon/issues) **Feature Request?** Submit your ideas in [GitHub Discussions](https://github.com/charon/charon/discussions) **Need Help?** Contact support at [support@charon.example.com](mailto:support@charon.example.com) --- _Last Updated: January 4, 2026_ _Version: 1.3.0_