chore: git cache cleanup
This commit is contained in:
551
docs/guides/crowdsec-setup.md
Normal file
551
docs/guides/crowdsec-setup.md
Normal file
@@ -0,0 +1,551 @@
|
||||
---
|
||||
title: CrowdSec Setup Guide
|
||||
description: A beginner-friendly guide to setting up CrowdSec with Charon for threat protection.
|
||||
---
|
||||
|
||||
# CrowdSec Setup Guide
|
||||
|
||||
Protect your websites from hackers, bots, and other bad actors. This guide walks you through setting up CrowdSec with Charon—even if you've never touched security software before.
|
||||
|
||||
---
|
||||
|
||||
## What Is CrowdSec?
|
||||
|
||||
Imagine a neighborhood watch program, but for the internet. CrowdSec watches the traffic coming to your server and identifies troublemakers—hackers trying to guess passwords, bots scanning for vulnerabilities, or attackers probing your defenses.
|
||||
|
||||
When CrowdSec spots suspicious behavior, it blocks that visitor before they can cause harm. Even better, CrowdSec shares information with thousands of other users worldwide. If someone attacks a server in Germany, your server in California can block them before they even knock on your door.
|
||||
|
||||
**What CrowdSec Catches:**
|
||||
|
||||
- 🔓 **Password guessing** — Someone trying thousands of passwords to break into your apps
|
||||
- 🕷️ **Malicious bots** — Automated scripts looking for security holes
|
||||
- 💥 **Known attackers** — IP addresses flagged as dangerous by the global community
|
||||
- 🔍 **Reconnaissance** — Hackers mapping out your server before attacking
|
||||
|
||||
---
|
||||
|
||||
## How Charon Makes It Easy
|
||||
|
||||
Here's the good news: **Charon handles most of the CrowdSec setup automatically**. You don't need to edit configuration files, run terminal commands, or understand networking. Just flip a switch in the Settings.
|
||||
|
||||
### What Happens Behind the Scenes
|
||||
|
||||
When you enable CrowdSec in Charon:
|
||||
|
||||
1. **Charon starts the CrowdSec engine** — A security service begins running inside your container
|
||||
2. **A "bouncer" is registered** — This allows Charon to communicate with CrowdSec (more on this below)
|
||||
3. **Your websites are protected** — Bad traffic gets blocked before reaching your apps
|
||||
4. **Decisions sync in real-time** — You can see who's blocked in the Security dashboard
|
||||
|
||||
All of this happens in about 15 seconds after you flip the toggle.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start: Enable CrowdSec
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
- Charon is installed and running
|
||||
- You can access the Charon web interface
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Open Charon in your browser (usually `http://your-server:8080`)
|
||||
2. Click **Security** in the left sidebar
|
||||
3. Find the **CrowdSec** card
|
||||
4. Flip the toggle to **ON**
|
||||
5. Wait about 15 seconds for the status to show "Active"
|
||||
|
||||
That's it! Your server is now protected by CrowdSec.
|
||||
|
||||
> **✨ New in Recent Versions**
|
||||
>
|
||||
> Charon now **automatically generates and registers** your bouncer key the first time you enable CrowdSec. No terminal commands needed—just flip the switch and you're protected!
|
||||
|
||||
### Verify It's Working
|
||||
|
||||
After enabling, the CrowdSec card should display:
|
||||
|
||||
- **Status:** Active (with a green indicator)
|
||||
- **PID:** A number like `12345` (this is the CrowdSec process)
|
||||
- **LAPI:** Connected
|
||||
|
||||
If you see these, CrowdSec is running properly.
|
||||
|
||||
---
|
||||
|
||||
## Understanding "Bouncers" (Important!)
|
||||
|
||||
A **bouncer** is like a security guard at a nightclub door. It checks each visitor's ID against a list of banned people and either lets them in or turns them away.
|
||||
|
||||
In CrowdSec terms:
|
||||
|
||||
- The **CrowdSec engine** decides who's dangerous and maintains the ban list
|
||||
- The **bouncer** enforces those decisions by blocking bad traffic
|
||||
|
||||
**Critical Point:** For the bouncer to work, it needs a special password (called an **API key**) to communicate with the CrowdSec engine. This key must be **generated by CrowdSec itself**—you cannot make one up.
|
||||
|
||||
> **✅ Good News: Charon Handles This For You!**
|
||||
>
|
||||
> When you enable CrowdSec for the first time, Charon automatically:
|
||||
> 1. Starts the CrowdSec engine
|
||||
> 2. Registers a bouncer and generates a valid API key
|
||||
> 3. Saves the key so it survives container restarts
|
||||
>
|
||||
> You don't need to touch the terminal or set any environment variables.
|
||||
|
||||
> **⚠️ Common Mistake Alert**
|
||||
>
|
||||
> If you set `CHARON_SECURITY_CROWDSEC_API_KEY=mySecureKey123` in your docker-compose.yml, **it won't work**. CrowdSec has never heard of "mySecureKey123" and will reject it.
|
||||
>
|
||||
> **Solution:** Remove any manually-set API key and let Charon generate one automatically.
|
||||
|
||||
---
|
||||
|
||||
## How Auto-Registration Works
|
||||
|
||||
When you flip the CrowdSec toggle ON, here's what happens behind the scenes:
|
||||
|
||||
1. **Charon starts CrowdSec** and waits for it to be ready
|
||||
2. **A bouncer is registered** with the name `caddy-bouncer`
|
||||
3. **The API key is saved** to `/app/data/crowdsec/bouncer_key`
|
||||
4. **Caddy connects** using the saved key
|
||||
|
||||
### Your Key Is Saved Forever
|
||||
|
||||
The bouncer key is stored in your data volume at:
|
||||
|
||||
```
|
||||
/app/data/crowdsec/bouncer_key
|
||||
```
|
||||
|
||||
This means:
|
||||
|
||||
- ✅ Your key survives container restarts
|
||||
- ✅ Your key survives Charon updates
|
||||
- ✅ You don't need to re-register after pulling a new image
|
||||
|
||||
### Finding Your Key in the Logs
|
||||
|
||||
When Charon generates a new bouncer key, you'll see a formatted banner in the container logs:
|
||||
|
||||
```bash
|
||||
docker logs charon
|
||||
```
|
||||
|
||||
Look for a section like this:
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════╗
|
||||
║ 🔑 CrowdSec Bouncer Registered! ║
|
||||
╠══════════════════════════════════════════════════════════════╣
|
||||
║ Your bouncer API key has been auto-generated. ║
|
||||
║ Key saved to: /app/data/crowdsec/bouncer_key ║
|
||||
╚══════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
### Providing Your Own Key (Advanced)
|
||||
|
||||
If you prefer to use your own pre-registered bouncer key, you still can! Environment variables take priority over auto-generated keys:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- CHARON_SECURITY_CROWDSEC_API_KEY=your-pre-registered-key
|
||||
```
|
||||
|
||||
> **⚠️ Important:** This key must be registered with CrowdSec first using `cscli bouncers add`. See [Manual Bouncer Registration](#manual-bouncer-registration) for details.
|
||||
|
||||
---
|
||||
|
||||
## Viewing Your Bouncer Key in the UI
|
||||
|
||||
Need to see your bouncer key? Charon makes it easy:
|
||||
|
||||
1. Open Charon and go to **Security**
|
||||
2. Look at the **CrowdSec** card
|
||||
3. Your bouncer key is displayed (masked for security)
|
||||
4. Click the **copy button** to copy the full key to your clipboard
|
||||
|
||||
This is useful when:
|
||||
|
||||
- 🔧 Troubleshooting connection issues
|
||||
- 📋 Sharing the key with another application
|
||||
- ✅ Verifying the correct key is in use
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables Reference
|
||||
|
||||
Here's everything you can configure for CrowdSec. For most users, **you don't need to set any of these**—Charon's defaults work great.
|
||||
|
||||
### Safe to Set
|
||||
|
||||
| Variable | Description | Default | When to Use |
|
||||
|----------|-------------|---------|-------------|
|
||||
| `CHARON_SECURITY_CROWDSEC_CONSOLE_KEY` | Your CrowdSec Console enrollment token | None | When enrolling in CrowdSec Console (optional) |
|
||||
|
||||
### Do NOT Set Manually
|
||||
|
||||
| Variable | Description | Why You Should NOT Set It |
|
||||
|----------|-------------|--------------------------|
|
||||
| `CHARON_SECURITY_CROWDSEC_API_KEY` | Bouncer authentication key | Must be generated by CrowdSec, not invented |
|
||||
| `CHARON_SECURITY_CROWDSEC_API_URL` | LAPI address | Uses correct default (port 8085 internally) |
|
||||
| `CHARON_SECURITY_CROWDSEC_MODE` | Enable/disable mode | Use GUI toggle instead |
|
||||
|
||||
### Correct Docker Compose Example
|
||||
|
||||
```yaml
|
||||
services:
|
||||
charon:
|
||||
image: ghcr.io/wikid82/charon:latest
|
||||
container_name: charon
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:8080" # Charon web interface
|
||||
- "80:80" # HTTP traffic
|
||||
- "443:443" # HTTPS traffic
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
- CHARON_ENV=production
|
||||
# ✅ CrowdSec is enabled via the GUI, no env vars needed
|
||||
# ✅ API key is auto-generated, never set manually
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manual Bouncer Registration
|
||||
|
||||
In rare cases, you might need to register the bouncer manually. This is useful if:
|
||||
|
||||
- You're recovering from a broken configuration
|
||||
- Automatic registration failed
|
||||
- You're debugging connection issues
|
||||
|
||||
### Step 1: Access the Container Terminal
|
||||
|
||||
```bash
|
||||
docker exec -it charon bash
|
||||
```
|
||||
|
||||
### Step 2: Register the Bouncer
|
||||
|
||||
```bash
|
||||
cscli bouncers add caddy-bouncer
|
||||
```
|
||||
|
||||
CrowdSec will output an API key. It looks something like this:
|
||||
|
||||
```
|
||||
Api key for 'caddy-bouncer':
|
||||
|
||||
f8a7b2c9d3e4a5b6c7d8e9f0a1b2c3d4
|
||||
|
||||
Please keep it safe, you won't be able to retrieve it!
|
||||
```
|
||||
|
||||
### Step 3: Verify Registration
|
||||
|
||||
```bash
|
||||
cscli bouncers list
|
||||
```
|
||||
|
||||
You should see `caddy-bouncer` in the list.
|
||||
|
||||
### Step 4: Restart Charon
|
||||
|
||||
Exit the container and restart:
|
||||
|
||||
```bash
|
||||
exit
|
||||
docker restart charon
|
||||
```
|
||||
|
||||
### Step 5: Re-enable CrowdSec
|
||||
|
||||
Toggle CrowdSec OFF and then ON again in the Security dashboard. Charon will detect the registered bouncer and connect.
|
||||
|
||||
---
|
||||
|
||||
## CrowdSec Console Enrollment (Optional)
|
||||
|
||||
The CrowdSec Console is a free online dashboard where you can:
|
||||
|
||||
- 📊 View attack statistics across all your servers
|
||||
- 🌍 See threats on a world map
|
||||
- 🔔 Get email alerts about attacks
|
||||
- 📡 Subscribe to premium blocklists
|
||||
|
||||
### Getting Your Enrollment Key
|
||||
|
||||
1. Go to [app.crowdsec.net](https://app.crowdsec.net) and create a free account
|
||||
2. Click **Engines** in the sidebar
|
||||
3. Click **Add Engine**
|
||||
4. Copy the enrollment key (a long string starting with `clapi-`)
|
||||
|
||||
### Enrolling Through Charon
|
||||
|
||||
1. Open Charon and go to **Security**
|
||||
2. Click on the **CrowdSec** card to expand options
|
||||
3. Find **Console Enrollment**
|
||||
4. Paste your enrollment key
|
||||
5. Click **Enroll**
|
||||
|
||||
Within 60 seconds, your instance should appear in the CrowdSec Console.
|
||||
|
||||
### Enrollment via Command Line
|
||||
|
||||
If the GUI enrollment isn't working:
|
||||
|
||||
```bash
|
||||
docker exec -it charon cscli console enroll YOUR_ENROLLMENT_KEY
|
||||
```
|
||||
|
||||
Replace `YOUR_ENROLLMENT_KEY` with the key from your Console.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Access Forbidden" Error
|
||||
|
||||
**Symptom:** Logs show "API error: access forbidden" when CrowdSec tries to connect.
|
||||
|
||||
**Cause:** The bouncer API key is invalid or was never registered with CrowdSec.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Check if you're manually setting an API key:
|
||||
```bash
|
||||
grep -i "crowdsec_api_key" docker-compose.yml
|
||||
```
|
||||
|
||||
2. If you find one, **remove it**:
|
||||
```yaml
|
||||
# REMOVE this line:
|
||||
- CHARON_SECURITY_CROWDSEC_API_KEY=anything
|
||||
```
|
||||
|
||||
3. Follow the [Manual Bouncer Registration](#manual-bouncer-registration) steps above
|
||||
|
||||
4. Restart the container:
|
||||
```bash
|
||||
docker restart charon
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### "Connection Refused" to LAPI
|
||||
|
||||
**Symptom:** CrowdSec shows "connection refused" errors.
|
||||
|
||||
**Cause:** CrowdSec is still starting up (takes 30-60 seconds) or isn't running.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Wait 60 seconds after container start
|
||||
|
||||
2. Check if CrowdSec is running:
|
||||
```bash
|
||||
docker exec charon cscli lapi status
|
||||
```
|
||||
|
||||
3. If you see "connection refused," try toggling CrowdSec OFF then ON in the GUI
|
||||
|
||||
4. Check the logs:
|
||||
```bash
|
||||
docker logs charon | grep -i crowdsec
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bouncer Status Check
|
||||
|
||||
To see all registered bouncers:
|
||||
|
||||
```bash
|
||||
docker exec charon cscli bouncers list
|
||||
```
|
||||
|
||||
You should see `caddy-bouncer` with a "validated" status.
|
||||
|
||||
---
|
||||
|
||||
### How to Delete and Re-Register a Bouncer
|
||||
|
||||
If the bouncer is corrupted or misconfigured:
|
||||
|
||||
```bash
|
||||
# Delete the existing bouncer
|
||||
docker exec charon cscli bouncers delete caddy-bouncer
|
||||
|
||||
# Register a fresh one
|
||||
docker exec charon cscli bouncers add caddy-bouncer
|
||||
|
||||
# Restart
|
||||
docker restart charon
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Console Shows Engine "Offline"
|
||||
|
||||
**Symptom:** CrowdSec Console dashboard shows your engine as "Offline" even though it's running.
|
||||
|
||||
**Cause:** Network issues preventing heartbeats from reaching CrowdSec servers.
|
||||
|
||||
**Check connectivity:**
|
||||
|
||||
```bash
|
||||
# Test DNS
|
||||
docker exec charon nslookup api.crowdsec.net
|
||||
|
||||
# Test HTTPS connection
|
||||
docker exec charon curl -I https://api.crowdsec.net
|
||||
```
|
||||
|
||||
**Required outbound connections:**
|
||||
|
||||
| Host | Port | Purpose |
|
||||
|------|------|---------|
|
||||
| `api.crowdsec.net` | 443 | Console heartbeats |
|
||||
| `hub.crowdsec.net` | 443 | Security preset downloads |
|
||||
|
||||
If you're behind a corporate firewall, you may need to allow these connections.
|
||||
|
||||
---
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Using an External CrowdSec Instance
|
||||
|
||||
If you already run CrowdSec separately (not inside Charon), you can connect to it.
|
||||
|
||||
> **⚠️ Warning:** This is an advanced configuration. Most users should use Charon's built-in CrowdSec.
|
||||
|
||||
> **📝 Note: Auto-Registration Doesn't Apply Here**
|
||||
>
|
||||
> The auto-registration feature only works with Charon's **built-in** CrowdSec. When connecting to an external CrowdSec instance, you **must** manually register a bouncer and provide the key.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Register a bouncer on your external CrowdSec:
|
||||
```bash
|
||||
cscli bouncers add charon-bouncer
|
||||
```
|
||||
|
||||
2. Save the API key that's generated (you won't see it again!)
|
||||
|
||||
3. In your docker-compose.yml:
|
||||
```yaml
|
||||
environment:
|
||||
- CHARON_SECURITY_CROWDSEC_API_URL=http://your-crowdsec-server:8080
|
||||
- CHARON_SECURITY_CROWDSEC_API_KEY=your-generated-key
|
||||
```
|
||||
|
||||
4. Restart Charon:
|
||||
```bash
|
||||
docker restart charon
|
||||
```
|
||||
|
||||
**Why manual registration is required:**
|
||||
|
||||
Charon cannot automatically register a bouncer on an external CrowdSec instance because:
|
||||
|
||||
- It doesn't have terminal access to the external server
|
||||
- It doesn't know the external CrowdSec's admin credentials
|
||||
- The external CrowdSec may have custom security policies
|
||||
|
||||
---
|
||||
|
||||
### Installing Security Presets
|
||||
|
||||
CrowdSec offers pre-built detection rules called "presets" from their Hub. Charon includes common ones by default, but you can add more:
|
||||
|
||||
1. Go to **Security → CrowdSec → Hub Presets**
|
||||
2. Browse or search for presets
|
||||
3. Click **Install** on the ones you want
|
||||
|
||||
Popular presets:
|
||||
|
||||
- **crowdsecurity/http-probing** — Detect reconnaissance scanning
|
||||
- **crowdsecurity/http-bad-user-agent** — Block known malicious bots
|
||||
- **crowdsecurity/http-cve** — Protect against known vulnerabilities
|
||||
|
||||
---
|
||||
|
||||
### Viewing Active Blocks (Decisions)
|
||||
|
||||
To see who's currently blocked:
|
||||
|
||||
**In the GUI:**
|
||||
|
||||
1. Go to **Security → Live Decisions**
|
||||
2. View blocked IPs, reasons, and duration
|
||||
|
||||
**Via Command Line:**
|
||||
|
||||
```bash
|
||||
docker exec charon cscli decisions list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Manually Banning an IP
|
||||
|
||||
If you want to block someone immediately:
|
||||
|
||||
**GUI:**
|
||||
|
||||
1. Go to **Security → CrowdSec**
|
||||
2. Click **Add Decision**
|
||||
3. Enter the IP address
|
||||
4. Set duration (e.g., 24h)
|
||||
5. Click **Ban**
|
||||
|
||||
**Command Line:**
|
||||
|
||||
```bash
|
||||
docker exec charon cscli decisions add --ip 1.2.3.4 --duration 24h --reason "Manual ban"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Unbanning an IP
|
||||
|
||||
If you accidentally blocked a legitimate user:
|
||||
|
||||
```bash
|
||||
docker exec charon cscli decisions delete --ip 1.2.3.4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Task | Method |
|
||||
|------|--------|
|
||||
| Enable CrowdSec | Toggle in Security dashboard |
|
||||
| Verify it's running | Check for "Active" status in dashboard |
|
||||
| Fix "access forbidden" | Remove hardcoded API key, let Charon generate one |
|
||||
| Register bouncer manually | `docker exec charon cscli bouncers add caddy-bouncer` |
|
||||
| Enroll in Console | Paste key in Security → CrowdSec → Console Enrollment |
|
||||
| View who's blocked | Security → Live Decisions |
|
||||
|
||||
---
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Web Application Firewall (WAF)](../features/waf.md) — Additional application-layer protection
|
||||
- [Access Control Lists](../features/access-control.md) — Manual IP blocking and GeoIP rules
|
||||
- [Rate Limiting](../features/rate-limiting.md) — Prevent abuse by limiting request rates
|
||||
- [CrowdSec Feature Documentation](../features/crowdsec.md) — Detailed feature reference
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- 📖 [Full Documentation](../index.md)
|
||||
- 🐛 [Report an Issue](https://github.com/Wikid82/Charon/issues)
|
||||
- 💬 [Community Discussions](https://github.com/Wikid82/Charon/discussions)
|
||||
259
docs/guides/dns-providers.md
Normal file
259
docs/guides/dns-providers.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# DNS Providers Guide
|
||||
|
||||
## Overview
|
||||
|
||||
DNS providers enable Charon to obtain SSL/TLS certificates for wildcard domains (e.g., `*.example.com`) using the ACME DNS-01 challenge. This challenge proves domain ownership by creating a temporary TXT record in your DNS zone, which is required for wildcard certificates since HTTP-01 challenges cannot validate wildcards.
|
||||
|
||||
## Why DNS Providers Are Required
|
||||
|
||||
- **Wildcard Certificates:** ACME providers (like Let's Encrypt) require DNS-01 challenges for wildcard domains
|
||||
- **Automated Validation:** Charon automatically creates and removes DNS records during certificate issuance
|
||||
- **Secure Storage:** All credentials are encrypted at rest using AES-256-GCM encryption
|
||||
|
||||
## Supported DNS Providers
|
||||
|
||||
Charon dynamically discovers available DNS provider types from an internal registry. This registry includes:
|
||||
|
||||
- **Built-in providers** — Compiled into Charon (Cloudflare, Route 53, etc.)
|
||||
- **Custom providers** — Special-purpose providers like `manual` for unsupported DNS services
|
||||
- **External plugins** — Third-party `.so` plugin files loaded at runtime
|
||||
|
||||
### Built-in Providers
|
||||
|
||||
| Provider | Type | Setup Guide |
|
||||
|----------|------|-------------|
|
||||
| Cloudflare | `cloudflare` | [Cloudflare Setup](dns-providers/cloudflare.md) |
|
||||
| AWS Route 53 | `route53` | [Route 53 Setup](dns-providers/route53.md) |
|
||||
| DigitalOcean | `digitalocean` | [DigitalOcean Setup](dns-providers/digitalocean.md) |
|
||||
| Google Cloud DNS | `googleclouddns` | [Documentation](https://caddyserver.com/docs/modules/dns.providers.googleclouddns) |
|
||||
| Azure DNS | `azure` | [Documentation](https://caddyserver.com/docs/modules/dns.providers.azure) |
|
||||
| Namecheap | `namecheap` | [Documentation](https://caddyserver.com/docs/modules/dns.providers.namecheap) |
|
||||
| GoDaddy | `godaddy` | [Documentation](https://caddyserver.com/docs/modules/dns.providers.godaddy) |
|
||||
| Hetzner | `hetzner` | [Documentation](https://caddyserver.com/docs/modules/dns.providers.hetzner) |
|
||||
| Vultr | `vultr` | [Documentation](https://caddyserver.com/docs/modules/dns.providers.vultr) |
|
||||
| DNSimple | `dnsimple` | [Documentation](https://caddyserver.com/docs/modules/dns.providers.dnsimple) |
|
||||
|
||||
### Custom Providers
|
||||
|
||||
| Provider | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| Manual DNS | `manual` | For DNS providers without API support. Displays TXT record for manual creation. |
|
||||
|
||||
### Discovering Available Provider Types
|
||||
|
||||
Query available provider types programmatically via the API:
|
||||
|
||||
```bash
|
||||
curl https://your-charon-instance/api/v1/dns-providers/types \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
**Example Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"types": [
|
||||
{
|
||||
"type": "cloudflare",
|
||||
"name": "Cloudflare",
|
||||
"description": "Cloudflare DNS provider",
|
||||
"documentation_url": "https://developers.cloudflare.com/api/",
|
||||
"is_built_in": true,
|
||||
"fields": [...]
|
||||
},
|
||||
{
|
||||
"type": "manual",
|
||||
"name": "Manual DNS",
|
||||
"description": "Manually create DNS TXT records",
|
||||
"documentation_url": "",
|
||||
"is_built_in": false,
|
||||
"fields": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response fields:**
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `type` | Unique identifier used in API requests |
|
||||
| `name` | Human-readable display name |
|
||||
| `description` | Brief description of the provider |
|
||||
| `documentation_url` | Link to provider's API documentation |
|
||||
| `is_built_in` | `true` for compiled providers, `false` for plugins/custom |
|
||||
| `fields` | Required credential fields and their specifications |
|
||||
|
||||
> **Tip:** Use `is_built_in` to distinguish official providers from external plugins in your automation workflows.
|
||||
|
||||
## Adding External Plugins
|
||||
|
||||
Extend Charon with third-party DNS provider plugins by placing `.so` files in the plugin directory.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Set the plugin directory environment variable:
|
||||
|
||||
```bash
|
||||
export CHARON_PLUGINS_DIR=/etc/charon/plugins
|
||||
```
|
||||
|
||||
2. Copy plugin files:
|
||||
|
||||
```bash
|
||||
cp powerdns.so /etc/charon/plugins/
|
||||
chmod 755 /etc/charon/plugins/powerdns.so
|
||||
```
|
||||
|
||||
3. Restart Charon — plugins load automatically at startup.
|
||||
|
||||
4. Verify the plugin appears in `GET /api/v1/dns-providers/types` with `is_built_in: false`.
|
||||
|
||||
For detailed plugin installation and security guidance, see [Custom Plugins](../features/custom-plugins.md).
|
||||
|
||||
## General Setup Workflow
|
||||
|
||||
### 1. Prerequisites
|
||||
|
||||
- Active account with a supported DNS provider
|
||||
- Domain's DNS hosted with the provider
|
||||
- API access enabled on your account
|
||||
- Generated API credentials (tokens, keys, etc.)
|
||||
|
||||
### 2. Configure Encryption Key
|
||||
|
||||
DNS provider credentials are encrypted at rest. Before adding providers, ensure the encryption key is configured:
|
||||
|
||||
```bash
|
||||
# Generate a 32-byte (256-bit) random key and encode as base64
|
||||
openssl rand -base64 32
|
||||
|
||||
# Set as environment variable
|
||||
export CHARON_ENCRYPTION_KEY="your-base64-encoded-key-here"
|
||||
```
|
||||
|
||||
> **Warning:** The encryption key must be 32 bytes (44 characters in base64). Store it securely and back it up. If lost, you'll need to reconfigure all DNS providers.
|
||||
|
||||
Add to your Docker Compose or systemd configuration:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
charon:
|
||||
environment:
|
||||
- CHARON_ENCRYPTION_KEY=${CHARON_ENCRYPTION_KEY}
|
||||
```
|
||||
|
||||
### 3. Add DNS Provider
|
||||
|
||||
1. Navigate to **DNS Providers** in the Charon UI
|
||||
2. Click **Add Provider**
|
||||
3. Select your DNS provider type
|
||||
4. Enter a descriptive name (e.g., "Cloudflare Production")
|
||||
5. Fill in the required credentials
|
||||
6. (Optional) Adjust propagation timeout and polling interval
|
||||
7. Click **Test Connection** to verify credentials
|
||||
8. Click **Save**
|
||||
|
||||
### 4. Set Default Provider (Optional)
|
||||
|
||||
If you manage multiple domains across different DNS providers, you can designate one as the default. This will be pre-selected when creating new wildcard proxy hosts.
|
||||
|
||||
### 5. Create Wildcard Proxy Host
|
||||
|
||||
1. Navigate to **Proxy Hosts**
|
||||
2. Click **Add Proxy Host**
|
||||
3. Enter a wildcard domain (e.g., `*.example.com`)
|
||||
4. Select your DNS provider from the dropdown
|
||||
5. Configure other settings as needed
|
||||
6. Save the proxy host
|
||||
|
||||
Charon will automatically use DNS-01 challenge for certificate issuance.
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Credential Management
|
||||
|
||||
- **Least Privilege:** Create API tokens with minimum required permissions (DNS zone edit only)
|
||||
- **Scope Tokens:** Limit tokens to specific DNS zones when supported by the provider
|
||||
- **Rotate Regularly:** Periodically regenerate API tokens
|
||||
- **Secure Storage:** Never commit credentials to version control
|
||||
|
||||
### Encryption Key
|
||||
|
||||
- **Backup:** Store the `CHARON_ENCRYPTION_KEY` in a secure password manager
|
||||
- **Environment Variable:** Never hardcode the key in configuration files
|
||||
- **Rotate Carefully:** Changing the key requires reconfiguring all DNS providers
|
||||
|
||||
### Network Security
|
||||
|
||||
- **Firewall Rules:** Ensure Charon can reach DNS provider APIs (typically HTTPS outbound)
|
||||
- **Monitor Access:** Review API access logs in your DNS provider dashboard
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Propagation Timeout
|
||||
|
||||
Time (in seconds) to wait for DNS changes to propagate before ACME validation. Default: **120 seconds**.
|
||||
|
||||
- **Increase** if you experience validation failures due to slow DNS propagation
|
||||
- **Decrease** if your DNS provider has fast global propagation (e.g., Cloudflare)
|
||||
|
||||
### Polling Interval
|
||||
|
||||
Time (in seconds) between checks for DNS record propagation. Default: **10 seconds**.
|
||||
|
||||
- Most users should keep the default value
|
||||
- Adjust if hitting DNS provider API rate limits
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For detailed troubleshooting, see [DNS Challenges Troubleshooting](../troubleshooting/dns-challenges.md).
|
||||
|
||||
### Common Issues
|
||||
|
||||
**"Encryption key not configured"**
|
||||
|
||||
- Ensure `CHARON_ENCRYPTION_KEY` environment variable is set
|
||||
- Restart Charon after setting the variable
|
||||
|
||||
**"Connection test failed"**
|
||||
|
||||
- Verify credentials are correct
|
||||
- Check API token permissions
|
||||
- Ensure firewall allows outbound HTTPS to provider
|
||||
- Review provider-specific troubleshooting guides
|
||||
|
||||
**"DNS propagation timeout"**
|
||||
|
||||
- Increase propagation timeout in provider settings
|
||||
- Verify DNS provider is authoritative for the domain
|
||||
- Check provider status page for service issues
|
||||
|
||||
**"Certificate issuance failed"**
|
||||
|
||||
- Test DNS provider connection in UI
|
||||
- Check Charon logs for detailed error messages
|
||||
- Verify domain DNS is properly configured
|
||||
- Ensure DNS provider has edit permissions for the zone
|
||||
|
||||
## Provider-Specific Guides
|
||||
|
||||
- [Cloudflare Setup Guide](dns-providers/cloudflare.md)
|
||||
- [AWS Route 53 Setup Guide](dns-providers/route53.md)
|
||||
- [DigitalOcean Setup Guide](dns-providers/digitalocean.md)
|
||||
|
||||
For other providers, consult the official Caddy libdns module documentation linked in the table above.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Certificates Guide](certificates.md)
|
||||
- [Proxy Hosts Guide](proxy-hosts.md)
|
||||
- [DNS Challenges Troubleshooting](../troubleshooting/dns-challenges.md)
|
||||
- [Security Best Practices](../security/best-practices.md)
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Let's Encrypt DNS-01 Challenge Documentation](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)
|
||||
- [Caddy DNS Providers](https://caddyserver.com/docs/modules/)
|
||||
- [ACME Protocol Specification](https://datatracker.ietf.org/doc/html/rfc8555)
|
||||
369
docs/guides/dns-providers/azure-dns.md
Normal file
369
docs/guides/dns-providers/azure-dns.md
Normal file
@@ -0,0 +1,369 @@
|
||||
````markdown
|
||||
# Azure DNS Provider Setup
|
||||
|
||||
## Overview
|
||||
|
||||
Azure DNS is Microsoft's cloud-based DNS hosting service that provides name resolution using Microsoft Azure infrastructure. This guide covers setting up Azure DNS as a provider in Charon for wildcard certificate management.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Azure subscription (pay-as-you-go or Enterprise Agreement)
|
||||
- Azure DNS zone created for your domain
|
||||
- Domain nameservers pointing to Azure DNS
|
||||
- Permissions to create App registrations in Microsoft Entra ID (Azure AD)
|
||||
- Permissions to assign roles in Azure RBAC
|
||||
|
||||
## Step 1: Gather Azure Subscription Information
|
||||
|
||||
1. Log in to the [Azure Portal](https://portal.azure.com/)
|
||||
2. Navigate to **Subscriptions**
|
||||
3. Note your **Subscription ID** (e.g., `12345678-1234-1234-1234-123456789abc`)
|
||||
4. Navigate to **Resource groups**
|
||||
5. Note the **Resource group name** containing your DNS zone
|
||||
|
||||
> **Tip:** You can find this information in the DNS zone overview page as well.
|
||||
|
||||
## Step 2: Verify DNS Zone Configuration
|
||||
|
||||
Ensure your domain is properly configured in Azure DNS:
|
||||
|
||||
1. Navigate to **DNS zones**
|
||||
2. Select your DNS zone
|
||||
3. Note the **Azure nameservers** listed (typically 4 servers like `ns1-01.azure-dns.com`)
|
||||
4. Verify your domain registrar is configured to use these nameservers
|
||||
|
||||
<!-- Screenshot placeholder: Azure DNS zone overview showing nameservers -->
|
||||
|
||||
## Step 3: Create App Registration in Microsoft Entra ID
|
||||
|
||||
Create an application identity for Charon:
|
||||
|
||||
1. Navigate to **Microsoft Entra ID** (formerly Azure Active Directory)
|
||||
2. Select **App registrations** from the left menu
|
||||
3. Click **New registration**
|
||||
4. Configure the application:
|
||||
- **Name:** `charon-dns-challenge`
|
||||
- **Supported account types:** Select **Accounts in this organizational directory only**
|
||||
- **Redirect URI:** Leave blank (not needed for service-to-service auth)
|
||||
5. Click **Register**
|
||||
|
||||
### Note Application Details
|
||||
|
||||
After registration, note the following from the **Overview** page:
|
||||
|
||||
- **Application (client) ID:** `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||
- **Directory (tenant) ID:** `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||
|
||||
<!-- Screenshot placeholder: App registration overview showing client and tenant IDs -->
|
||||
|
||||
## Step 4: Create Client Secret
|
||||
|
||||
1. In your app registration, navigate to **Certificates & secrets**
|
||||
2. Click **New client secret**
|
||||
3. Configure the secret:
|
||||
- **Description:** `Charon DNS Challenge`
|
||||
- **Expires:** Choose an expiration period (recommended: 12 months or 24 months)
|
||||
4. Click **Add**
|
||||
5. **Copy the secret value immediately** (shown only once)
|
||||
|
||||
> **Warning:** The client secret value is displayed only once. Copy it now and store it securely. If you lose it, you'll need to create a new secret.
|
||||
|
||||
### Secret Expiration Management
|
||||
|
||||
| Expiration | Use Case |
|
||||
|------------|----------|
|
||||
| 6 months | Development/testing environments |
|
||||
| 12 months | Production with regular rotation schedule |
|
||||
| 24 months | Production with less frequent rotation |
|
||||
| Custom | Enterprise requirements |
|
||||
|
||||
## Step 5: Assign DNS Zone Contributor Role
|
||||
|
||||
Grant the app registration permission to manage DNS records:
|
||||
|
||||
1. Navigate to your **DNS zone**
|
||||
2. Select **Access control (IAM)** from the left menu
|
||||
3. Click **Add** → **Add role assignment**
|
||||
4. In the **Role** tab:
|
||||
- Search for **DNS Zone Contributor**
|
||||
- Select **DNS Zone Contributor**
|
||||
- Click **Next**
|
||||
5. In the **Members** tab:
|
||||
- Select **User, group, or service principal**
|
||||
- Click **Select members**
|
||||
- Search for `charon-dns-challenge`
|
||||
- Select the app registration
|
||||
- Click **Select**
|
||||
6. Click **Review + assign**
|
||||
7. Click **Review + assign** again to confirm
|
||||
|
||||
> **Note:** Role assignments may take a few minutes to propagate.
|
||||
|
||||
### Required Permissions
|
||||
|
||||
The **DNS Zone Contributor** role includes:
|
||||
|
||||
| Permission | Purpose |
|
||||
|------------|---------|
|
||||
| `Microsoft.Network/dnsZones/read` | Read DNS zone configuration |
|
||||
| `Microsoft.Network/dnsZones/TXT/read` | Read TXT records |
|
||||
| `Microsoft.Network/dnsZones/TXT/write` | Create/update TXT records |
|
||||
| `Microsoft.Network/dnsZones/TXT/delete` | Delete TXT records |
|
||||
| `Microsoft.Network/dnsZones/recordsets/read` | List DNS record sets |
|
||||
|
||||
> **Security Note:** For tighter security, you can create a custom role with only the permissions listed above.
|
||||
|
||||
## Step 6: Configure in Charon
|
||||
|
||||
1. Navigate to **DNS Providers** in Charon
|
||||
2. Click **Add Provider**
|
||||
3. Fill in the form:
|
||||
- **Provider Type:** Select `Azure DNS`
|
||||
- **Name:** Enter a descriptive name (e.g., "Azure DNS - Production")
|
||||
- **Tenant ID:** Paste the Directory (tenant) ID from Step 3
|
||||
- **Client ID:** Paste the Application (client) ID from Step 3
|
||||
- **Client Secret:** Paste the secret value from Step 4
|
||||
- **Subscription ID:** Paste the Subscription ID from Step 1
|
||||
- **Resource Group:** Enter the resource group name containing your DNS zone
|
||||
|
||||
### Configuration Fields Summary
|
||||
|
||||
| Field | Description | Example |
|
||||
|-------|-------------|---------|
|
||||
| **Tenant ID** | Microsoft Entra ID tenant identifier | `12345678-1234-5678-9abc-123456789abc` |
|
||||
| **Client ID** | App registration application ID | `abcdef12-3456-7890-abcd-ef1234567890` |
|
||||
| **Client Secret** | App registration secret value | `abc123~XYZ...` |
|
||||
| **Subscription ID** | Azure subscription identifier | `98765432-1234-5678-9abc-987654321abc` |
|
||||
| **Resource Group** | Resource group containing DNS zone | `rg-dns-production` |
|
||||
|
||||
### Advanced Settings (Optional)
|
||||
|
||||
Expand **Advanced Settings** to customize:
|
||||
|
||||
- **Propagation Timeout:** `120` seconds (Azure DNS propagates quickly)
|
||||
- **Polling Interval:** `10` seconds (default)
|
||||
- **Set as Default:** Enable if this is your primary DNS provider
|
||||
|
||||
## Step 7: Test Connection
|
||||
|
||||
1. Click **Test Connection** button
|
||||
2. Wait for validation (usually 5-10 seconds)
|
||||
3. Verify you see: ✅ **Connection successful**
|
||||
|
||||
The test verifies:
|
||||
- Credentials are valid
|
||||
- App registration has required permissions
|
||||
- DNS zone is accessible
|
||||
- Azure DNS API is reachable
|
||||
|
||||
If the test fails, see [Troubleshooting](#troubleshooting) below.
|
||||
|
||||
## Step 8: Save Configuration
|
||||
|
||||
Click **Save** to store the DNS provider configuration. All credentials are encrypted at rest using AES-256-GCM.
|
||||
|
||||
## Step 9: Use with Wildcard Certificates
|
||||
|
||||
When creating a proxy host with a wildcard domain:
|
||||
|
||||
1. Navigate to **Proxy Hosts** → **Add Proxy Host**
|
||||
2. Enter a wildcard domain: `*.example.com`
|
||||
3. Select **Azure DNS** from the DNS Provider dropdown
|
||||
4. Configure remaining settings
|
||||
5. Save
|
||||
|
||||
Charon will automatically obtain a wildcard certificate using DNS-01 challenge.
|
||||
|
||||
## Example Configuration
|
||||
|
||||
```yaml
|
||||
Provider Type: azure
|
||||
Name: Azure DNS - example.com
|
||||
Tenant ID: 12345678-1234-5678-9abc-123456789abc
|
||||
Client ID: abcdef12-3456-7890-abcd-ef1234567890
|
||||
Client Secret: ****************************************
|
||||
Subscription ID: 98765432-1234-5678-9abc-987654321abc
|
||||
Resource Group: rg-dns-production
|
||||
Propagation Timeout: 120 seconds
|
||||
Polling Interval: 10 seconds
|
||||
Default: Yes
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Test Fails
|
||||
|
||||
**Error:** `Invalid credentials` or `AADSTS7000215: Invalid client secret`
|
||||
|
||||
- Verify the client secret was copied correctly
|
||||
- Check the secret hasn't expired
|
||||
- Ensure no extra whitespace was added
|
||||
- Create a new client secret if necessary
|
||||
|
||||
**Error:** `AADSTS700016: Application not found`
|
||||
|
||||
- Verify the Client ID is correct
|
||||
- Ensure the app registration exists in the correct tenant
|
||||
- Check the Tenant ID matches your organization
|
||||
|
||||
**Error:** `AADSTS90002: Tenant not found`
|
||||
|
||||
- Verify the Tenant ID is correct
|
||||
- Ensure you're using the correct Azure environment (public vs. government)
|
||||
|
||||
**Error:** `Authorization failed` or `Forbidden`
|
||||
|
||||
- Verify the DNS Zone Contributor role is assigned
|
||||
- Check the role is assigned at the DNS zone level
|
||||
- Wait a few minutes for role assignment propagation
|
||||
- Verify the resource group name is correct
|
||||
|
||||
**Error:** `Resource group not found`
|
||||
|
||||
- Check the resource group name spelling (case-sensitive)
|
||||
- Ensure the resource group exists in the specified subscription
|
||||
- Verify the subscription ID is correct
|
||||
|
||||
**Error:** `DNS zone not found`
|
||||
|
||||
- Verify the DNS zone exists in the resource group
|
||||
- Check the domain matches the DNS zone name
|
||||
- Ensure the app has access to the subscription
|
||||
|
||||
### Certificate Issuance Fails
|
||||
|
||||
**Error:** `DNS propagation timeout`
|
||||
|
||||
- Azure DNS typically propagates in 30-60 seconds
|
||||
- Increase Propagation Timeout to 180 seconds
|
||||
- Verify nameservers are correctly configured with your registrar
|
||||
- Check Azure Status page for service issues
|
||||
|
||||
**Error:** `Record creation failed`
|
||||
|
||||
- Verify app registration has DNS Zone Contributor role
|
||||
- Check for existing `_acme-challenge` TXT records that may conflict
|
||||
- Review Charon logs for detailed API errors
|
||||
|
||||
**Error:** `Rate limit exceeded`
|
||||
|
||||
- Azure DNS has API rate limits per subscription
|
||||
- Increase Polling Interval to reduce API calls
|
||||
- Contact Azure support to increase limits if needed
|
||||
|
||||
### Nameserver Propagation
|
||||
|
||||
**Issue:** DNS changes not visible globally
|
||||
|
||||
- Nameserver changes can take 24-48 hours to propagate
|
||||
- Use [DNS Checker](https://dnschecker.org/) to verify global propagation
|
||||
- Verify your registrar shows Azure DNS nameservers
|
||||
- Wait for full propagation before attempting certificate issuance
|
||||
|
||||
### Client Secret Expiration
|
||||
|
||||
**Issue:** Certificates stop renewing
|
||||
|
||||
- Client secrets have expiration dates
|
||||
- Set calendar reminders before expiration
|
||||
- Create new secret and update Charon configuration before expiry
|
||||
- Consider using Managed Identities for Azure-hosted Charon deployments
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
1. **Dedicated App Registration:** Create a separate app registration for Charon
|
||||
2. **Least Privilege:** Use DNS Zone Contributor role (not broader roles)
|
||||
3. **Secret Rotation:** Rotate client secrets before expiration (every 6-12 months)
|
||||
4. **Conditional Access:** Consider conditional access policies for the app
|
||||
5. **Audit Logging:** Enable Azure Activity Log for DNS operations
|
||||
6. **Private Endpoints:** Use private endpoints if Charon runs in Azure
|
||||
7. **Managed Identity:** Use Managed Identity if Charon is hosted in Azure (eliminates secrets)
|
||||
8. **Monitor Sign-ins:** Review app sign-in logs in Microsoft Entra ID
|
||||
|
||||
## Client Secret Rotation
|
||||
|
||||
To rotate the client secret:
|
||||
|
||||
1. Navigate to your app registration → **Certificates & secrets**
|
||||
2. Create a new client secret
|
||||
3. Update the configuration in Charon with the new secret
|
||||
4. Test the connection to verify the new secret works
|
||||
5. Delete the old secret from the Azure portal
|
||||
|
||||
> **Best Practice:** Create the new secret before the old one expires to avoid downtime.
|
||||
|
||||
## Using Azure CLI for Verification (Optional)
|
||||
|
||||
Test configuration before adding to Charon:
|
||||
|
||||
```bash
|
||||
# Login with service principal
|
||||
az login --service-principal \
|
||||
--username CLIENT_ID \
|
||||
--password CLIENT_SECRET \
|
||||
--tenant TENANT_ID
|
||||
|
||||
# Set subscription
|
||||
az account set --subscription SUBSCRIPTION_ID
|
||||
|
||||
# List DNS zones
|
||||
az network dns zone list \
|
||||
--resource-group RESOURCE_GROUP_NAME
|
||||
|
||||
# Test record creation
|
||||
az network dns record-set txt add-record \
|
||||
--resource-group RESOURCE_GROUP_NAME \
|
||||
--zone-name example.com \
|
||||
--record-set-name _acme-challenge-test \
|
||||
--value "test-value"
|
||||
|
||||
# Clean up test record
|
||||
az network dns record-set txt remove-record \
|
||||
--resource-group RESOURCE_GROUP_NAME \
|
||||
--zone-name example.com \
|
||||
--record-set-name _acme-challenge-test \
|
||||
--value "test-value"
|
||||
```
|
||||
|
||||
## Using Managed Identity (Azure-Hosted Charon)
|
||||
|
||||
If Charon runs in Azure (VM, Container Instance, AKS), consider using Managed Identity:
|
||||
|
||||
1. Enable System-assigned managed identity on your Azure resource
|
||||
2. Assign **DNS Zone Contributor** role to the managed identity
|
||||
3. Configure Charon to use managed identity authentication (no secrets needed)
|
||||
|
||||
> **Benefits:** No client secrets to manage, automatic credential rotation, enhanced security.
|
||||
|
||||
## Azure DNS Limitations
|
||||
|
||||
- **Zone-scoped permissions only:** Cannot restrict to specific record types within a zone
|
||||
- **No private DNS support:** Charon requires public DNS for ACME challenges
|
||||
- **Regional availability:** Azure DNS is a global service, no regional selection needed
|
||||
- **Billing:** Azure DNS charges per zone and per million queries
|
||||
|
||||
## Cost Considerations
|
||||
|
||||
Azure DNS pricing (approximate):
|
||||
|
||||
- **Hosted zones:** ~$0.50/month per zone
|
||||
- **DNS queries:** ~$0.40 per million queries
|
||||
|
||||
Certificate challenges generate minimal queries (<100 per certificate issuance).
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Azure DNS Documentation](https://learn.microsoft.com/en-us/azure/dns/)
|
||||
- [Microsoft Entra ID App Registration](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app)
|
||||
- [Azure RBAC for DNS](https://learn.microsoft.com/en-us/azure/dns/dns-protect-zones-recordsets)
|
||||
- [Caddy Azure DNS Module](https://caddyserver.com/docs/modules/dns.providers.azure)
|
||||
- [Azure Status Page](https://status.azure.com/)
|
||||
- [Azure CLI DNS Commands](https://learn.microsoft.com/en-us/cli/azure/network/dns)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [DNS Providers Overview](../dns-providers.md)
|
||||
- [Wildcard Certificates Guide](../certificates.md#wildcard-certificates)
|
||||
- [DNS Challenges Troubleshooting](../../troubleshooting/dns-challenges.md)
|
||||
|
||||
````
|
||||
160
docs/guides/dns-providers/cloudflare.md
Normal file
160
docs/guides/dns-providers/cloudflare.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Cloudflare DNS Provider Setup
|
||||
|
||||
## Overview
|
||||
|
||||
Cloudflare is one of the most popular DNS providers and offers a free tier with API access. This guide walks you through setting up Cloudflare as a DNS provider in Charon for wildcard certificate support.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Active Cloudflare account (free tier is sufficient)
|
||||
- Domain added to Cloudflare with nameservers configured
|
||||
- Domain status: **Active** (not pending nameserver update)
|
||||
|
||||
## Step 1: Generate API Token
|
||||
|
||||
Cloudflare API Tokens provide scoped access and are more secure than Global API Keys.
|
||||
|
||||
1. Log in to [Cloudflare Dashboard](https://dash.cloudflare.com/)
|
||||
2. Click on your profile icon (top right) → **My Profile**
|
||||
3. Select **API Tokens** from the left sidebar
|
||||
4. Click **Create Token**
|
||||
5. Use the **Edit zone DNS** template or create a custom token
|
||||
6. Configure token permissions:
|
||||
- **Permissions:**
|
||||
- Zone → DNS → Edit
|
||||
- **Zone Resources:**
|
||||
- Include → Specific zone → Select your domain
|
||||
- OR Include → All zones (if managing multiple domains)
|
||||
7. (Optional) Set **Client IP Address Filtering** for additional security
|
||||
8. (Optional) Set **TTL** for token expiration
|
||||
9. Click **Continue to summary**
|
||||
10. Review permissions and click **Create Token**
|
||||
11. **Copy the token immediately** (shown only once)
|
||||
|
||||
> **Tip:** Store the API token in a password manager. Cloudflare won't display it again.
|
||||
|
||||
## Step 2: Configure in Charon
|
||||
|
||||
1. Navigate to **DNS Providers** in Charon
|
||||
2. Click **Add Provider**
|
||||
3. Fill in the form:
|
||||
- **Provider Type:** Select `Cloudflare`
|
||||
- **Name:** Enter a descriptive name (e.g., "Cloudflare Production")
|
||||
- **API Token:** Paste the token from Step 1
|
||||
|
||||
### Advanced Settings (Optional)
|
||||
|
||||
Expand **Advanced Settings** to customize:
|
||||
|
||||
- **Propagation Timeout:** `60` seconds (Cloudflare has fast global propagation)
|
||||
- **Polling Interval:** `10` seconds (default)
|
||||
- **Set as Default:** Enable if this is your primary DNS provider
|
||||
|
||||
## Step 3: Test Connection
|
||||
|
||||
1. Click **Test Connection** button
|
||||
2. Wait for validation (usually 2-5 seconds)
|
||||
3. Verify you see: ✅ **Connection successful**
|
||||
|
||||
If the test fails, see [Troubleshooting](#troubleshooting) below.
|
||||
|
||||
## Step 4: Save Configuration
|
||||
|
||||
Click **Save** to store the DNS provider configuration. Credentials are encrypted at rest using AES-256-GCM.
|
||||
|
||||
## Step 5: Use with Wildcard Certificates
|
||||
|
||||
When creating a proxy host with a wildcard domain:
|
||||
|
||||
1. Navigate to **Proxy Hosts** → **Add Proxy Host**
|
||||
2. Enter a wildcard domain: `*.example.com`
|
||||
3. Select **Cloudflare** from the DNS Provider dropdown
|
||||
4. Configure remaining settings
|
||||
5. Save
|
||||
|
||||
Charon will automatically obtain a wildcard certificate using DNS-01 challenge.
|
||||
|
||||
## Example Configuration
|
||||
|
||||
```yaml
|
||||
Provider Type: cloudflare
|
||||
Name: Cloudflare - example.com
|
||||
API Token: ********************************
|
||||
Propagation Timeout: 60 seconds
|
||||
Polling Interval: 10 seconds
|
||||
Default: Yes
|
||||
```
|
||||
|
||||
## Required Permissions
|
||||
|
||||
The API token needs the following Cloudflare permissions:
|
||||
|
||||
- **Zone → DNS → Edit:** Create and delete TXT records for ACME challenges
|
||||
|
||||
> **Note:** The token does NOT need Zone → Edit or Account-level permissions.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Test Fails
|
||||
|
||||
**Error:** `Invalid API token`
|
||||
|
||||
- Verify the token was copied correctly (no extra spaces)
|
||||
- Ensure the token has Zone → DNS → Edit permission
|
||||
- Check token hasn't expired (if TTL was set)
|
||||
- Regenerate the token if necessary
|
||||
|
||||
**Error:** `Zone not found`
|
||||
|
||||
- Verify the domain is added to your Cloudflare account
|
||||
- Ensure domain status is **Active** (nameservers updated)
|
||||
- Check API token includes the correct zone in Zone Resources
|
||||
|
||||
### Certificate Issuance Fails
|
||||
|
||||
**Error:** `DNS propagation timeout`
|
||||
|
||||
- Cloudflare typically propagates in <30 seconds
|
||||
- Check Cloudflare Status page for service issues
|
||||
- Verify DNSSEC is configured correctly (if enabled)
|
||||
- Try increasing Propagation Timeout to 120 seconds
|
||||
|
||||
**Error:** `Unauthorized to edit DNS`
|
||||
|
||||
- API token may have been revoked
|
||||
- Regenerate a new token with correct permissions
|
||||
- Update configuration in Charon
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
Cloudflare has generous API rate limits:
|
||||
|
||||
- Free plan: 1,200 requests per 5 minutes
|
||||
- Certificate challenges typically use <10 requests
|
||||
|
||||
If you hit limits:
|
||||
|
||||
- Reduce polling frequency
|
||||
- Avoid unnecessary test connection attempts
|
||||
- Consider upgrading Cloudflare plan
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
1. **Scope Tokens:** Limit to specific zones rather than "All zones"
|
||||
2. **IP Filtering:** Add your server's IP to Client IP Address Filtering
|
||||
3. **Set Expiration:** Use token TTL for automatic expiration (renew before expiry)
|
||||
4. **Rotate Regularly:** Generate new tokens every 90-180 days
|
||||
5. **Monitor Usage:** Review API token activity in Cloudflare dashboard
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Cloudflare API Documentation](https://developers.cloudflare.com/api/)
|
||||
- [API Token Permissions](https://developers.cloudflare.com/api/tokens/create/)
|
||||
- [Caddy Cloudflare Module](https://caddyserver.com/docs/modules/dns.providers.cloudflare)
|
||||
- [Cloudflare Status Page](https://www.cloudflarestatus.com/)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [DNS Providers Overview](../dns-providers.md)
|
||||
- [Wildcard Certificates Guide](../certificates.md#wildcard-certificates)
|
||||
- [DNS Challenges Troubleshooting](../../troubleshooting/dns-challenges.md)
|
||||
198
docs/guides/dns-providers/digitalocean.md
Normal file
198
docs/guides/dns-providers/digitalocean.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# DigitalOcean DNS Provider Setup
|
||||
|
||||
## Overview
|
||||
|
||||
DigitalOcean provides DNS hosting for free with any DigitalOcean account. This guide covers setting up DigitalOcean DNS as a provider in Charon for wildcard certificate management.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- DigitalOcean account (free tier is sufficient)
|
||||
- Domain added to DigitalOcean DNS
|
||||
- Domain nameservers pointing to DigitalOcean:
|
||||
- `ns1.digitalocean.com`
|
||||
- `ns2.digitalocean.com`
|
||||
- `ns3.digitalocean.com`
|
||||
|
||||
## Step 1: Generate Personal Access Token
|
||||
|
||||
1. Log in to [DigitalOcean Control Panel](https://cloud.digitalocean.com/)
|
||||
2. Click on **API** in the left sidebar (under Account)
|
||||
3. Navigate to the **Tokens/Keys** tab
|
||||
4. Click **Generate New Token** (in the Personal access tokens section)
|
||||
5. Configure the token:
|
||||
- **Token Name:** `charon-dns-challenge` (or any descriptive name)
|
||||
- **Expiration:** Choose expiration period (90 days, 1 year, or no expiry)
|
||||
- **Scopes:** Select **Write** (this includes Read access)
|
||||
6. Click **Generate Token**
|
||||
7. **Copy the token immediately** (shown only once)
|
||||
|
||||
> **Warning:** DigitalOcean shows the token only once. Store it securely in a password manager.
|
||||
|
||||
## Step 2: Verify DNS Configuration
|
||||
|
||||
Ensure your domain is properly configured in DigitalOcean DNS:
|
||||
|
||||
1. Navigate to **Networking** → **Domains** in the DigitalOcean control panel
|
||||
2. Verify your domain is listed
|
||||
3. Click on the domain to view DNS records
|
||||
4. Ensure at least one A or CNAME record exists (for the domain itself)
|
||||
|
||||
> **Note:** Charon will create and remove TXT records automatically; no manual DNS configuration is needed.
|
||||
|
||||
## Step 3: Configure in Charon
|
||||
|
||||
1. Navigate to **DNS Providers** in Charon
|
||||
2. Click **Add Provider**
|
||||
3. Fill in the form:
|
||||
- **Provider Type:** Select `DigitalOcean`
|
||||
- **Name:** Enter a descriptive name (e.g., "DigitalOcean DNS")
|
||||
- **API Token:** Paste the Personal Access Token from Step 1
|
||||
|
||||
### Advanced Settings (Optional)
|
||||
|
||||
Expand **Advanced Settings** to customize:
|
||||
|
||||
- **Propagation Timeout:** `90` seconds (DigitalOcean propagates quickly)
|
||||
- **Polling Interval:** `10` seconds (default)
|
||||
- **Set as Default:** Enable if this is your primary DNS provider
|
||||
|
||||
## Step 4: Test Connection
|
||||
|
||||
1. Click **Test Connection** button
|
||||
2. Wait for validation (usually 3-5 seconds)
|
||||
3. Verify you see: ✅ **Connection successful**
|
||||
|
||||
The test verifies:
|
||||
|
||||
- Token is valid and active
|
||||
- Account has DNS write permissions
|
||||
- DigitalOcean API is accessible
|
||||
|
||||
If the test fails, see [Troubleshooting](#troubleshooting) below.
|
||||
|
||||
## Step 5: Save Configuration
|
||||
|
||||
Click **Save** to store the DNS provider configuration. The token is encrypted at rest using AES-256-GCM.
|
||||
|
||||
## Step 6: Use with Wildcard Certificates
|
||||
|
||||
When creating a proxy host with a wildcard domain:
|
||||
|
||||
1. Navigate to **Proxy Hosts** → **Add Proxy Host**
|
||||
2. Enter a wildcard domain: `*.example.com`
|
||||
3. Select **DigitalOcean** from the DNS Provider dropdown
|
||||
4. Configure remaining settings
|
||||
5. Save
|
||||
|
||||
Charon will automatically obtain a wildcard certificate using DNS-01 challenge.
|
||||
|
||||
## Example Configuration
|
||||
|
||||
```yaml
|
||||
Provider Type: digitalocean
|
||||
Name: DigitalOcean - example.com
|
||||
API Token: dop_v1_********************************
|
||||
Propagation Timeout: 90 seconds
|
||||
Polling Interval: 10 seconds
|
||||
Default: Yes
|
||||
```
|
||||
|
||||
## Required Permissions
|
||||
|
||||
The Personal Access Token needs **Write** scope, which includes:
|
||||
|
||||
- Read access to domains and DNS records
|
||||
- Write access to create/update/delete DNS records
|
||||
|
||||
> **Note:** Token scope is account-wide. You cannot restrict to specific domains in DigitalOcean.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Test Fails
|
||||
|
||||
**Error:** `Invalid token` or `Unauthorized`
|
||||
|
||||
- Verify the token was copied correctly (should start with `dop_v1_`)
|
||||
- Ensure token has **Write** scope (not just Read)
|
||||
- Check token hasn't expired (if expiration was set)
|
||||
- Regenerate the token if necessary
|
||||
|
||||
**Error:** `Domain not found`
|
||||
|
||||
- Verify the domain is added to DigitalOcean DNS
|
||||
- Ensure domain nameservers point to DigitalOcean
|
||||
- Check domain status in the Networking section
|
||||
- Wait 24-48 hours if nameservers were recently changed
|
||||
|
||||
### Certificate Issuance Fails
|
||||
|
||||
**Error:** `DNS propagation timeout`
|
||||
|
||||
- DigitalOcean DNS typically propagates in <60 seconds
|
||||
- Verify nameservers are correctly configured:
|
||||
|
||||
```bash
|
||||
dig NS example.com +short
|
||||
```
|
||||
|
||||
- Check DigitalOcean Status page for service issues
|
||||
- Increase Propagation Timeout to 120 seconds as a workaround
|
||||
|
||||
**Error:** `Record creation failed`
|
||||
|
||||
- Check token permissions (must be Write scope)
|
||||
- Verify domain exists in DigitalOcean DNS
|
||||
- Review Charon logs for detailed API errors
|
||||
- Ensure no conflicting TXT records exist with name `_acme-challenge`
|
||||
|
||||
### Nameserver Propagation
|
||||
|
||||
**Issue:** DNS changes not visible globally
|
||||
|
||||
- Nameserver changes can take 24-48 hours to propagate
|
||||
- Use [DNS Checker](https://dnschecker.org/) to verify global propagation
|
||||
- Ensure your domain registrar shows DigitalOcean nameservers
|
||||
- Wait for full propagation before attempting certificate issuance
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
DigitalOcean API rate limits:
|
||||
|
||||
- 5,000 requests per hour (per account)
|
||||
- Certificate challenges typically use <20 requests
|
||||
|
||||
If you hit limits:
|
||||
|
||||
- Reduce frequency of certificate renewals
|
||||
- Avoid unnecessary test connection attempts
|
||||
- Contact DigitalOcean support if consistently hitting limits
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
1. **Token Expiration:** Set 90-day expiration and rotate regularly
|
||||
2. **Dedicated Token:** Create a separate token for Charon (easier to revoke)
|
||||
3. **Monitor Usage:** Review API logs in DigitalOcean control panel
|
||||
4. **Least Privilege:** Use Write scope (don't grant Full Access)
|
||||
5. **Backup Access:** Keep a backup token in secure storage (offline)
|
||||
6. **Revoke Unused:** Delete tokens that are no longer needed
|
||||
|
||||
## DigitalOcean DNS Limitations
|
||||
|
||||
- **No per-domain token scoping:** Tokens grant access to all domains in the account
|
||||
- **No rate limit customization:** Fixed at 5,000 requests/hour
|
||||
- **Public zones only:** Private DNS not supported
|
||||
- **No DNSSEC:** DigitalOcean does not support DNSSEC at this time
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [DigitalOcean DNS Documentation](https://docs.digitalocean.com/products/networking/dns/)
|
||||
- [DigitalOcean API Documentation](https://docs.digitalocean.com/reference/api/)
|
||||
- [Personal Access Tokens Guide](https://docs.digitalocean.com/reference/api/create-personal-access-token/)
|
||||
- [Caddy DigitalOcean Module](https://caddyserver.com/docs/modules/dns.providers.digitalocean)
|
||||
- [DigitalOcean Status Page](https://status.digitalocean.com/)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [DNS Providers Overview](../dns-providers.md)
|
||||
- [Wildcard Certificates Guide](../certificates.md#wildcard-certificates)
|
||||
- [DNS Challenges Troubleshooting](../../troubleshooting/dns-challenges.md)
|
||||
327
docs/guides/dns-providers/google-cloud-dns.md
Normal file
327
docs/guides/dns-providers/google-cloud-dns.md
Normal file
@@ -0,0 +1,327 @@
|
||||
````markdown
|
||||
# Google Cloud DNS Provider Setup
|
||||
|
||||
## Overview
|
||||
|
||||
Google Cloud DNS is a high-performance, scalable DNS service built on Google's global infrastructure. This guide covers setting up Google Cloud DNS as a provider in Charon for wildcard certificate management.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Google Cloud Platform (GCP) account
|
||||
- GCP project with billing enabled
|
||||
- Cloud DNS API enabled
|
||||
- DNS zone created in Cloud DNS
|
||||
- Domain nameservers pointing to Google Cloud DNS
|
||||
|
||||
## Step 1: Enable Cloud DNS API
|
||||
|
||||
1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
|
||||
2. Select your project (or create a new one)
|
||||
3. Navigate to **APIs & Services** → **Library**
|
||||
4. Search for **Cloud DNS API**
|
||||
5. Click **Enable**
|
||||
|
||||
> **Note:** The API may take a few minutes to activate after enabling.
|
||||
|
||||
## Step 2: Create a Service Account
|
||||
|
||||
Create a dedicated service account for Charon with minimal permissions:
|
||||
|
||||
1. Navigate to **IAM & Admin** → **Service Accounts**
|
||||
2. Click **Create Service Account**
|
||||
3. Configure the service account:
|
||||
- **Service account name:** `charon-dns-challenge`
|
||||
- **Service account ID:** `charon-dns-challenge` (auto-filled)
|
||||
- **Description:** `Service account for Charon DNS-01 ACME challenges`
|
||||
4. Click **Create and Continue**
|
||||
|
||||
## Step 3: Assign DNS Admin Role
|
||||
|
||||
1. In the **Grant this service account access to project** section:
|
||||
- Click **Select a role**
|
||||
- Search for **DNS Administrator**
|
||||
- Select **DNS Administrator** (`roles/dns.admin`)
|
||||
2. Click **Continue**
|
||||
3. Skip the optional **Grant users access** section
|
||||
4. Click **Done**
|
||||
|
||||
> **Security Note:** For production environments, consider creating a custom role with only the specific permissions needed:
|
||||
> - `dns.changes.create`
|
||||
> - `dns.changes.get`
|
||||
> - `dns.managedZones.list`
|
||||
> - `dns.resourceRecordSets.create`
|
||||
> - `dns.resourceRecordSets.delete`
|
||||
> - `dns.resourceRecordSets.list`
|
||||
> - `dns.resourceRecordSets.update`
|
||||
|
||||
## Step 4: Generate Service Account Key
|
||||
|
||||
1. Click on the newly created service account
|
||||
2. Navigate to the **Keys** tab
|
||||
3. Click **Add Key** → **Create new key**
|
||||
4. Select **JSON** format
|
||||
5. Click **Create**
|
||||
6. **Save the downloaded JSON file securely** (shown only once)
|
||||
|
||||
> **Warning:** The JSON key file contains sensitive credentials. Store it in a password manager or secure vault. Never commit it to version control.
|
||||
|
||||
### Example JSON Key Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "your-project-id",
|
||||
"private_key_id": "key-id",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "charon-dns-challenge@your-project-id.iam.gserviceaccount.com",
|
||||
"client_id": "123456789012345678901",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/..."
|
||||
}
|
||||
```
|
||||
|
||||
## Step 5: Verify DNS Zone Configuration
|
||||
|
||||
Ensure your domain is properly configured in Cloud DNS:
|
||||
|
||||
1. Navigate to **Network services** → **Cloud DNS**
|
||||
2. Verify your zone is listed and active
|
||||
3. Note the **Zone name** (not the DNS name)
|
||||
4. Confirm nameservers are correctly assigned:
|
||||
- `ns-cloud-a1.googledomains.com`
|
||||
- `ns-cloud-a2.googledomains.com`
|
||||
- `ns-cloud-a3.googledomains.com`
|
||||
- `ns-cloud-a4.googledomains.com`
|
||||
|
||||
> **Important:** Update your domain registrar to use Google Cloud DNS nameservers if not already configured.
|
||||
|
||||
## Step 6: Configure in Charon
|
||||
|
||||
1. Navigate to **DNS Providers** in Charon
|
||||
2. Click **Add Provider**
|
||||
3. Fill in the form:
|
||||
- **Provider Type:** Select `Google Cloud DNS`
|
||||
- **Name:** Enter a descriptive name (e.g., "GCP Cloud DNS - Production")
|
||||
- **Project ID:** Enter your GCP project ID (e.g., `my-project-123456`)
|
||||
- **Service Account JSON:** Paste the entire contents of the downloaded JSON key file
|
||||
|
||||
### Advanced Settings (Optional)
|
||||
|
||||
Expand **Advanced Settings** to customize:
|
||||
|
||||
- **Propagation Timeout:** `120` seconds (Cloud DNS propagation is typically fast)
|
||||
- **Polling Interval:** `10` seconds (default)
|
||||
- **Set as Default:** Enable if this is your primary DNS provider
|
||||
|
||||
## Step 7: Test Connection
|
||||
|
||||
1. Click **Test Connection** button
|
||||
2. Wait for validation (usually 5-10 seconds)
|
||||
3. Verify you see: ✅ **Connection successful**
|
||||
|
||||
The test verifies:
|
||||
- Service account credentials are valid
|
||||
- Project ID matches the credentials
|
||||
- Service account has required permissions
|
||||
- Cloud DNS API is accessible
|
||||
|
||||
If the test fails, see [Troubleshooting](#troubleshooting) below.
|
||||
|
||||
## Step 8: Save Configuration
|
||||
|
||||
Click **Save** to store the DNS provider configuration. Credentials are encrypted at rest using AES-256-GCM.
|
||||
|
||||
## Step 9: Use with Wildcard Certificates
|
||||
|
||||
When creating a proxy host with a wildcard domain:
|
||||
|
||||
1. Navigate to **Proxy Hosts** → **Add Proxy Host**
|
||||
2. Enter a wildcard domain: `*.example.com`
|
||||
3. Select **Google Cloud DNS** from the DNS Provider dropdown
|
||||
4. Configure remaining settings
|
||||
5. Save
|
||||
|
||||
Charon will automatically obtain a wildcard certificate using DNS-01 challenge.
|
||||
|
||||
## Example Configuration
|
||||
|
||||
```yaml
|
||||
Provider Type: googleclouddns
|
||||
Name: GCP Cloud DNS - example.com
|
||||
Project ID: my-project-123456
|
||||
Service Account JSON: {"type":"service_account",...}
|
||||
Propagation Timeout: 120 seconds
|
||||
Polling Interval: 10 seconds
|
||||
Default: Yes
|
||||
```
|
||||
|
||||
## Required Permissions
|
||||
|
||||
The service account needs the following Cloud DNS permissions:
|
||||
|
||||
| Permission | Purpose |
|
||||
|------------|---------|
|
||||
| `dns.changes.create` | Create DNS record changes |
|
||||
| `dns.changes.get` | Check status of DNS changes |
|
||||
| `dns.managedZones.list` | List available DNS zones |
|
||||
| `dns.resourceRecordSets.create` | Create TXT records for ACME challenges |
|
||||
| `dns.resourceRecordSets.delete` | Clean up TXT records after validation |
|
||||
| `dns.resourceRecordSets.list` | List existing DNS records |
|
||||
| `dns.resourceRecordSets.update` | Update DNS records if needed |
|
||||
|
||||
> **Note:** The **DNS Administrator** role includes all these permissions. For fine-grained control, create a custom role.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Test Fails
|
||||
|
||||
**Error:** `Invalid service account JSON`
|
||||
|
||||
- Verify the entire JSON content was pasted correctly
|
||||
- Ensure no extra whitespace or line breaks were added
|
||||
- Check the JSON is valid (use a JSON validator)
|
||||
- Re-download the key file and try again
|
||||
|
||||
**Error:** `Project not found` or `Project mismatch`
|
||||
|
||||
- Verify the Project ID matches the project in the service account JSON
|
||||
- Check the `project_id` field in the JSON matches your input
|
||||
- Ensure the project exists and is active
|
||||
|
||||
**Error:** `Permission denied` or `Forbidden`
|
||||
|
||||
- Verify the service account has the DNS Administrator role
|
||||
- Check the role is assigned at the project level
|
||||
- Ensure Cloud DNS API is enabled
|
||||
- Wait a few minutes after role assignment (propagation delay)
|
||||
|
||||
**Error:** `API not enabled`
|
||||
|
||||
- Navigate to APIs & Services → Library
|
||||
- Search for and enable Cloud DNS API
|
||||
- Wait 2-3 minutes for activation
|
||||
|
||||
### Certificate Issuance Fails
|
||||
|
||||
**Error:** `DNS propagation timeout`
|
||||
|
||||
- Cloud DNS typically propagates in 30-60 seconds
|
||||
- Increase Propagation Timeout to 180 seconds
|
||||
- Verify nameservers are correctly configured with your registrar
|
||||
- Check Google Cloud Status page for service issues
|
||||
|
||||
**Error:** `Zone not found`
|
||||
|
||||
- Ensure the DNS zone exists in Cloud DNS
|
||||
- Verify the domain matches the zone's DNS name
|
||||
- Check the service account has access to the zone
|
||||
|
||||
**Error:** `Record creation failed`
|
||||
|
||||
- Check for existing `_acme-challenge` TXT records that may conflict
|
||||
- Verify service account permissions
|
||||
- Review Charon logs for detailed API errors
|
||||
|
||||
### Nameserver Propagation
|
||||
|
||||
**Issue:** DNS changes not visible globally
|
||||
|
||||
- Nameserver changes can take 24-48 hours to propagate globally
|
||||
- Use [DNS Checker](https://dnschecker.org/) to verify propagation
|
||||
- Verify your registrar shows Google Cloud DNS nameservers
|
||||
- Wait for full propagation before attempting certificate issuance
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
Google Cloud DNS API quotas:
|
||||
|
||||
- 10,000 queries per day (default)
|
||||
- 1,000 changes per day (default)
|
||||
- Certificate challenges typically use <20 requests
|
||||
|
||||
If you hit limits:
|
||||
|
||||
- Request quota increase via Google Cloud Console
|
||||
- Reduce frequency of certificate renewals
|
||||
- Contact Google Cloud support for production workloads
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
1. **Dedicated Service Account:** Create a separate service account for Charon
|
||||
2. **Least Privilege:** Use a custom role with only required permissions
|
||||
3. **Key Rotation:** Rotate service account keys every 90 days
|
||||
4. **Key Security:** Store JSON key in a secrets manager, never in version control
|
||||
5. **Audit Logging:** Enable Cloud Audit Logs for DNS API calls
|
||||
6. **VPC Service Controls:** Consider using VPC Service Controls for additional security
|
||||
7. **Disable Unused Keys:** Delete old keys immediately after rotation
|
||||
|
||||
## Service Account Key Rotation
|
||||
|
||||
To rotate the service account key:
|
||||
|
||||
1. Create a new key following Step 4
|
||||
2. Update the configuration in Charon with the new JSON
|
||||
3. Test the connection to verify the new key works
|
||||
4. Delete the old key from the GCP console
|
||||
|
||||
```bash
|
||||
# Using gcloud CLI (optional)
|
||||
# List existing keys
|
||||
gcloud iam service-accounts keys list \
|
||||
--iam-account=charon-dns-challenge@PROJECT_ID.iam.gserviceaccount.com
|
||||
|
||||
# Create new key
|
||||
gcloud iam service-accounts keys create new-key.json \
|
||||
--iam-account=charon-dns-challenge@PROJECT_ID.iam.gserviceaccount.com
|
||||
|
||||
# Delete old key (after updating Charon)
|
||||
gcloud iam service-accounts keys delete KEY_ID \
|
||||
--iam-account=charon-dns-challenge@PROJECT_ID.iam.gserviceaccount.com
|
||||
```
|
||||
|
||||
## gcloud CLI Verification (Optional)
|
||||
|
||||
Test credentials before adding to Charon:
|
||||
|
||||
```bash
|
||||
# Activate service account
|
||||
gcloud auth activate-service-account \
|
||||
--key-file=/path/to/service-account-key.json
|
||||
|
||||
# Set project
|
||||
gcloud config set project YOUR_PROJECT_ID
|
||||
|
||||
# List DNS zones
|
||||
gcloud dns managed-zones list
|
||||
|
||||
# Test record creation (creates and deletes a test TXT record)
|
||||
gcloud dns record-sets create test-acme-challenge.example.com. \
|
||||
--zone=your-zone-name \
|
||||
--type=TXT \
|
||||
--ttl=60 \
|
||||
--rrdatas='"test-value"'
|
||||
|
||||
# Clean up test record
|
||||
gcloud dns record-sets delete test-acme-challenge.example.com. \
|
||||
--zone=your-zone-name \
|
||||
--type=TXT
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Google Cloud DNS Documentation](https://cloud.google.com/dns/docs)
|
||||
- [Service Account Documentation](https://cloud.google.com/iam/docs/service-accounts)
|
||||
- [Cloud DNS API Reference](https://cloud.google.com/dns/docs/reference/v1)
|
||||
- [Caddy Google Cloud DNS Module](https://caddyserver.com/docs/modules/dns.providers.googleclouddns)
|
||||
- [Google Cloud Status Page](https://status.cloud.google.com/)
|
||||
- [IAM Roles for Cloud DNS](https://cloud.google.com/dns/docs/access-control)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [DNS Providers Overview](../dns-providers.md)
|
||||
- [Wildcard Certificates Guide](../certificates.md#wildcard-certificates)
|
||||
- [DNS Challenges Troubleshooting](../../troubleshooting/dns-challenges.md)
|
||||
|
||||
````
|
||||
237
docs/guides/dns-providers/route53.md
Normal file
237
docs/guides/dns-providers/route53.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# AWS Route 53 DNS Provider Setup
|
||||
|
||||
## Overview
|
||||
|
||||
Amazon Route 53 is AWS's scalable DNS service. This guide covers setting up Route 53 as a DNS provider in Charon for wildcard certificate management.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- AWS account with Route 53 access
|
||||
- Domain hosted in Route 53 (public hosted zone)
|
||||
- IAM permissions to create users and policies
|
||||
- AWS CLI (optional, for verification)
|
||||
|
||||
## Step 1: Create IAM Policy
|
||||
|
||||
Create a custom IAM policy with minimum required permissions:
|
||||
|
||||
1. Log in to [AWS Console](https://console.aws.amazon.com/)
|
||||
2. Navigate to **IAM** → **Policies**
|
||||
3. Click **Create Policy**
|
||||
4. Select **JSON** tab
|
||||
5. Paste the following policy:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"route53:ListHostedZones",
|
||||
"route53:GetChange"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"route53:ChangeResourceRecordSets"
|
||||
],
|
||||
"Resource": "arn:aws:route53:::hostedzone/*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
1. Click **Next: Tags** (optional tags)
|
||||
2. Click **Next: Review**
|
||||
3. **Name:** `CharonRoute53DNSChallenge`
|
||||
4. **Description:** `Allows Charon to manage DNS TXT records for ACME challenges`
|
||||
5. Click **Create Policy**
|
||||
|
||||
> **Tip:** For production, scope the policy to specific hosted zones by replacing `*` with your zone ID.
|
||||
|
||||
## Step 2: Create IAM User
|
||||
|
||||
Create a dedicated IAM user for Charon:
|
||||
|
||||
1. Navigate to **IAM** → **Users**
|
||||
2. Click **Add Users**
|
||||
3. **User name:** `charon-dns`
|
||||
4. Select **Access key - Programmatic access**
|
||||
5. Click **Next: Permissions**
|
||||
6. Select **Attach existing policies directly**
|
||||
7. Search for and select `CharonRoute53DNSChallenge`
|
||||
8. Click **Next: Tags** (optional)
|
||||
9. Click **Next: Review**
|
||||
10. Click **Create User**
|
||||
11. **Save the credentials** (shown only once):
|
||||
- Access Key ID
|
||||
- Secret Access Key
|
||||
|
||||
> **Warning:** Download the CSV or copy credentials immediately. AWS won't show the secret again.
|
||||
|
||||
## Step 3: Configure in Charon
|
||||
|
||||
1. Navigate to **DNS Providers** in Charon
|
||||
2. Click **Add Provider**
|
||||
3. Fill in the form:
|
||||
- **Provider Type:** Select `AWS Route 53`
|
||||
- **Name:** Enter a descriptive name (e.g., "AWS Route 53 - Production")
|
||||
- **AWS Access Key ID:** Paste the access key from Step 2
|
||||
- **AWS Secret Access Key:** Paste the secret key from Step 2
|
||||
- **AWS Region:** (Optional) Specify region (default: `us-east-1`)
|
||||
|
||||
### Advanced Settings (Optional)
|
||||
|
||||
Expand **Advanced Settings** to customize:
|
||||
|
||||
- **Propagation Timeout:** `120` seconds (Route 53 propagation can take 60-120 seconds)
|
||||
- **Polling Interval:** `10` seconds (default)
|
||||
- **Set as Default:** Enable if this is your primary DNS provider
|
||||
|
||||
## Step 4: Test Connection
|
||||
|
||||
1. Click **Test Connection** button
|
||||
2. Wait for validation (may take 5-10 seconds)
|
||||
3. Verify you see: ✅ **Connection successful**
|
||||
|
||||
The test verifies:
|
||||
|
||||
- Credentials are valid
|
||||
- IAM user has required permissions
|
||||
- Route 53 hosted zones are accessible
|
||||
|
||||
If the test fails, see [Troubleshooting](#troubleshooting) below.
|
||||
|
||||
## Step 5: Save Configuration
|
||||
|
||||
Click **Save** to store the DNS provider configuration. Credentials are encrypted at rest using AES-256-GCM.
|
||||
|
||||
## Step 6: Use with Wildcard Certificates
|
||||
|
||||
When creating a proxy host with a wildcard domain:
|
||||
|
||||
1. Navigate to **Proxy Hosts** → **Add Proxy Host**
|
||||
2. Enter a wildcard domain: `*.example.com`
|
||||
3. Select **AWS Route 53** from the DNS Provider dropdown
|
||||
4. Configure remaining settings
|
||||
5. Save
|
||||
|
||||
Charon will automatically obtain a wildcard certificate using DNS-01 challenge.
|
||||
|
||||
## Example Configuration
|
||||
|
||||
```yaml
|
||||
Provider Type: route53
|
||||
Name: AWS Route 53 - example.com
|
||||
Access Key ID: AKIAIOSFODNN7EXAMPLE
|
||||
Secret Access Key: ****************************************
|
||||
Region: us-east-1
|
||||
Propagation Timeout: 120 seconds
|
||||
Polling Interval: 10 seconds
|
||||
Default: Yes
|
||||
```
|
||||
|
||||
## Required IAM Permissions
|
||||
|
||||
The IAM user needs the following Route 53 permissions:
|
||||
|
||||
| Action | Resource | Purpose |
|
||||
|--------|----------|---------|
|
||||
| `route53:ListHostedZones` | `*` | List available hosted zones |
|
||||
| `route53:GetChange` | `*` | Check status of DNS changes |
|
||||
| `route53:ChangeResourceRecordSets` | `arn:aws:route53:::hostedzone/*` | Create/delete TXT records for challenges |
|
||||
|
||||
> **Security Best Practice:** Scope `ChangeResourceRecordSets` to specific hosted zone ARNs:
|
||||
|
||||
```json
|
||||
"Resource": "arn:aws:route53:::hostedzone/Z1234567890ABC"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Test Fails
|
||||
|
||||
**Error:** `Invalid credentials`
|
||||
|
||||
- Verify Access Key ID and Secret Access Key were copied correctly
|
||||
- Check IAM user exists and is active
|
||||
- Ensure no extra spaces or characters in credentials
|
||||
|
||||
**Error:** `Access denied`
|
||||
|
||||
- Verify IAM policy is attached to the user
|
||||
- Check policy includes all required permissions
|
||||
- Review CloudTrail logs for denied API calls
|
||||
|
||||
**Error:** `Hosted zone not found`
|
||||
|
||||
- Ensure domain has a public hosted zone in Route 53
|
||||
- Verify hosted zone is in the same AWS account
|
||||
- Check zone is not private (private zones not supported)
|
||||
|
||||
### Certificate Issuance Fails
|
||||
|
||||
**Error:** `DNS propagation timeout`
|
||||
|
||||
- Route 53 propagation typically takes 60-120 seconds
|
||||
- Increase Propagation Timeout to 180 seconds
|
||||
- Verify hosted zone is authoritative for the domain
|
||||
- Check Route 53 name servers match domain registrar settings
|
||||
|
||||
**Error:** `Rate limit exceeded`
|
||||
|
||||
- Route 53 has API rate limits (5 requests/second per account)
|
||||
- Increase Polling Interval to 15-20 seconds
|
||||
- Avoid concurrent certificate requests
|
||||
- Contact AWS support to increase limits
|
||||
|
||||
### Region Configuration
|
||||
|
||||
**Issue:** Specifying the wrong region
|
||||
|
||||
- Route 53 is a global service; region typically doesn't matter
|
||||
- Use `us-east-1` (default) if unsure
|
||||
- Some endpoints may require specific regions
|
||||
- Check Charon logs if region-specific errors occur
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
1. **IAM User:** Create a dedicated user for Charon (don't reuse credentials)
|
||||
2. **Least Privilege:** Use the minimal policy provided above
|
||||
3. **Scope to Zones:** Limit policy to specific hosted zones in production
|
||||
4. **Rotate Keys:** Rotate access keys every 90 days
|
||||
5. **Monitor Usage:** Enable CloudTrail for API activity auditing
|
||||
6. **MFA Protection:** Enable MFA on the AWS account (not the IAM user)
|
||||
7. **Access Advisor:** Review IAM Access Advisor to ensure permissions are used
|
||||
|
||||
## AWS CLI Verification (Optional)
|
||||
|
||||
Test credentials before adding to Charon:
|
||||
|
||||
```bash
|
||||
# Configure AWS CLI with credentials
|
||||
aws configure --profile charon-dns
|
||||
|
||||
# List hosted zones
|
||||
aws route53 list-hosted-zones --profile charon-dns
|
||||
|
||||
# Verify permissions
|
||||
aws iam get-user --profile charon-dns
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [AWS Route 53 Documentation](https://docs.aws.amazon.com/route53/)
|
||||
- [IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)
|
||||
- [Route 53 API Reference](https://docs.aws.amazon.com/route53/latest/APIReference/)
|
||||
- [Caddy Route 53 Module](https://caddyserver.com/docs/modules/dns.providers.route53)
|
||||
- [AWS CloudTrail](https://console.aws.amazon.com/cloudtrail/)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [DNS Providers Overview](../dns-providers.md)
|
||||
- [Wildcard Certificates Guide](../certificates.md#wildcard-certificates)
|
||||
- [DNS Challenges Troubleshooting](../../troubleshooting/dns-challenges.md)
|
||||
468
docs/guides/local-key-management.md
Normal file
468
docs/guides/local-key-management.md
Normal file
@@ -0,0 +1,468 @@
|
||||
# Local Key Management for Cosign Signing
|
||||
|
||||
## Overview
|
||||
|
||||
This guide provides comprehensive procedures for managing Cosign signing keys in local development environments. It covers key generation, secure storage, rotation, and air-gapped signing workflows.
|
||||
|
||||
**Audience**: Developers, DevOps engineers, Security team
|
||||
**Last Updated**: 2026-01-10
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Key Generation](#key-generation)
|
||||
2. [Secure Storage](#secure-storage)
|
||||
3. [Key Usage](#key-usage)
|
||||
4. [Key Rotation](#key-rotation)
|
||||
5. [Backup and Recovery](#backup-and-recovery)
|
||||
6. [Air-Gapped Signing](#air-gapped-signing)
|
||||
7. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Key Generation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Cosign 2.4.0 or higher installed
|
||||
- Strong password (20+ characters, mixed case, numbers, special characters)
|
||||
- Secure environment (trusted machine, no malware)
|
||||
|
||||
### Generate Key Pair
|
||||
|
||||
```bash
|
||||
# Navigate to secure directory
|
||||
cd ~/.cosign
|
||||
|
||||
# Generate key pair interactively
|
||||
cosign generate-key-pair
|
||||
|
||||
# You will be prompted for a password
|
||||
# Enter a strong password (minimum 20 characters recommended)
|
||||
|
||||
# This creates two files:
|
||||
# - cosign.key (PRIVATE KEY - keep secure!)
|
||||
# - cosign.pub (public key - share freely)
|
||||
```
|
||||
|
||||
### Non-Interactive Generation (for automation)
|
||||
|
||||
```bash
|
||||
# Generate with password from environment
|
||||
export COSIGN_PASSWORD="your-strong-password"
|
||||
cosign generate-key-pair --output-key-prefix=cosign-dev
|
||||
|
||||
# Cleanup environment variable
|
||||
unset COSIGN_PASSWORD
|
||||
```
|
||||
|
||||
### Key Naming Convention
|
||||
|
||||
Use descriptive prefixes for different environments:
|
||||
|
||||
```
|
||||
cosign-dev.key # Development environment
|
||||
cosign-staging.key # Staging environment
|
||||
cosign-prod.key # Production environment (use HSM if possible)
|
||||
```
|
||||
|
||||
**⚠️ WARNING**: Never use the same key for multiple environments!
|
||||
|
||||
---
|
||||
|
||||
## Secure Storage
|
||||
|
||||
### File System Permissions
|
||||
|
||||
```bash
|
||||
# Set restrictive permissions on private key
|
||||
chmod 600 ~/.cosign/cosign.key
|
||||
|
||||
# Verify permissions
|
||||
ls -l ~/.cosign/cosign.key
|
||||
# Should show: -rw------- (only owner can read/write)
|
||||
```
|
||||
|
||||
### Password Manager Integration
|
||||
|
||||
Store private keys in a password manager:
|
||||
|
||||
1. **1Password, Bitwarden, or LastPass**:
|
||||
- Create a secure note
|
||||
- Add the private key content
|
||||
- Add the password as a separate field
|
||||
- Tag as "cosign-key"
|
||||
|
||||
2. **Retrieve when needed**:
|
||||
|
||||
```bash
|
||||
# Example with op (1Password CLI)
|
||||
op read "op://Private/cosign-dev-key/private key" > /tmp/cosign.key
|
||||
chmod 600 /tmp/cosign.key
|
||||
|
||||
# Use the key
|
||||
COSIGN_PRIVATE_KEY="$(cat /tmp/cosign.key)" \
|
||||
COSIGN_PASSWORD="$(op read 'op://Private/cosign-dev-key/password')" \
|
||||
cosign sign --key /tmp/cosign.key charon:local
|
||||
|
||||
# Cleanup
|
||||
shred -u /tmp/cosign.key
|
||||
```
|
||||
|
||||
### Hardware Security Module (HSM)
|
||||
|
||||
For production keys, use an HSM or YubiKey:
|
||||
|
||||
```bash
|
||||
# Generate key on YubiKey
|
||||
cosign generate-key-pair --key-slot 9a
|
||||
|
||||
# Sign with YubiKey
|
||||
cosign sign --key yubikey://slot-id charon:latest
|
||||
```
|
||||
|
||||
### Environment Variables (Development Only)
|
||||
|
||||
For development convenience:
|
||||
|
||||
```bash
|
||||
# Add to ~/.bashrc or ~/.zshrc (NEVER commit this file!)
|
||||
export COSIGN_PRIVATE_KEY="$(cat ~/.cosign/cosign-dev.key)"
|
||||
export COSIGN_PASSWORD="your-dev-password"
|
||||
|
||||
# Source the file
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
**⚠️ WARNING**: Only use environment variables in trusted development environments!
|
||||
|
||||
---
|
||||
|
||||
## Key Usage
|
||||
|
||||
### Sign Docker Image
|
||||
|
||||
```bash
|
||||
# Export private key and password
|
||||
export COSIGN_PRIVATE_KEY="$(cat ~/.cosign/cosign-dev.key)"
|
||||
export COSIGN_PASSWORD="your-password"
|
||||
|
||||
# Sign the image
|
||||
cosign sign --yes --key <(echo "${COSIGN_PRIVATE_KEY}") charon:local
|
||||
|
||||
# Or use the Charon skill
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign docker charon:local
|
||||
|
||||
# Cleanup
|
||||
unset COSIGN_PRIVATE_KEY
|
||||
unset COSIGN_PASSWORD
|
||||
```
|
||||
|
||||
### Sign Release Artifacts
|
||||
|
||||
```bash
|
||||
# Sign a binary
|
||||
cosign sign-blob --yes \
|
||||
--key ~/.cosign/cosign-prod.key \
|
||||
--output-signature ./dist/charon-linux-amd64.sig \
|
||||
./dist/charon-linux-amd64
|
||||
|
||||
# Verify signature
|
||||
cosign verify-blob ./dist/charon-linux-amd64 \
|
||||
--signature ./dist/charon-linux-amd64.sig \
|
||||
--key ~/.cosign/cosign-prod.pub
|
||||
```
|
||||
|
||||
### Batch Signing
|
||||
|
||||
```bash
|
||||
# Sign all artifacts in a directory
|
||||
for artifact in ./dist/charon-*; do
|
||||
if [[ -f "$artifact" && ! "$artifact" == *.sig ]]; then
|
||||
echo "Signing: $(basename $artifact)"
|
||||
cosign sign-blob --yes \
|
||||
--key ~/.cosign/cosign-prod.key \
|
||||
--output-signature "${artifact}.sig" \
|
||||
"$artifact"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Rotation
|
||||
|
||||
### When to Rotate
|
||||
|
||||
- **Every 90 days** (recommended)
|
||||
- After any suspected compromise
|
||||
- When team members with key access leave
|
||||
- After security incidents
|
||||
- Before major releases
|
||||
|
||||
### Rotation Procedure
|
||||
|
||||
1. **Generate new key pair**:
|
||||
|
||||
```bash
|
||||
cd ~/.cosign
|
||||
cosign generate-key-pair --output-key-prefix=cosign-prod-v2
|
||||
```
|
||||
|
||||
2. **Test new key**:
|
||||
|
||||
```bash
|
||||
# Sign test artifact
|
||||
cosign sign-blob --yes \
|
||||
--key cosign-prod-v2.key \
|
||||
--output-signature test.sig \
|
||||
test-file
|
||||
|
||||
# Verify
|
||||
cosign verify-blob test-file \
|
||||
--signature test.sig \
|
||||
--key cosign-prod-v2.pub
|
||||
|
||||
# Cleanup test files
|
||||
rm test-file test.sig
|
||||
```
|
||||
|
||||
3. **Update documentation**:
|
||||
- Update README with new public key
|
||||
- Update CI/CD secrets (if key-based signing)
|
||||
- Notify team members
|
||||
|
||||
4. **Transition period**:
|
||||
- Sign new artifacts with new key
|
||||
- Keep old key available for verification
|
||||
- Document transition date
|
||||
|
||||
5. **Retire old key**:
|
||||
- After 30-90 days (all old artifacts verified)
|
||||
- Archive old key securely (for historical verification)
|
||||
- Delete from active use
|
||||
|
||||
6. **Archive old key**:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.cosign/archive/$(date +%Y-%m)
|
||||
mv cosign-prod.key ~/.cosign/archive/$(date +%Y-%m)/
|
||||
chmod 400 ~/.cosign/archive/$(date +%Y-%m)/cosign-prod.key
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backup and Recovery
|
||||
|
||||
### Backup Procedure
|
||||
|
||||
```bash
|
||||
# Create encrypted backup
|
||||
cd ~/.cosign
|
||||
tar czf cosign-keys-backup.tar.gz cosign*.key cosign*.pub
|
||||
|
||||
# Encrypt with GPG
|
||||
gpg --symmetric --cipher-algo AES256 cosign-keys-backup.tar.gz
|
||||
|
||||
# This creates: cosign-keys-backup.tar.gz.gpg
|
||||
|
||||
# Remove unencrypted backup
|
||||
shred -u cosign-keys-backup.tar.gz
|
||||
|
||||
# Store encrypted backup in:
|
||||
# - Password manager
|
||||
# - Encrypted USB drive (stored in safe)
|
||||
# - Encrypted cloud storage (e.g., Tresorit, ProtonDrive)
|
||||
```
|
||||
|
||||
### Recovery Procedure
|
||||
|
||||
```bash
|
||||
# Decrypt backup
|
||||
gpg --decrypt cosign-keys-backup.tar.gz.gpg > cosign-keys-backup.tar.gz
|
||||
|
||||
# Extract keys
|
||||
tar xzf cosign-keys-backup.tar.gz
|
||||
|
||||
# Set permissions
|
||||
chmod 600 cosign*.key
|
||||
chmod 644 cosign*.pub
|
||||
|
||||
# Verify keys work
|
||||
cosign sign-blob --yes \
|
||||
--key cosign-dev.key \
|
||||
--output-signature test.sig \
|
||||
<(echo "test")
|
||||
|
||||
# Cleanup
|
||||
rm cosign-keys-backup.tar.gz
|
||||
shred -u test.sig
|
||||
```
|
||||
|
||||
### Disaster Recovery
|
||||
|
||||
If private key is lost:
|
||||
|
||||
1. **Generate new key pair** (see Key Generation)
|
||||
2. **Revoke old public key** (update documentation)
|
||||
3. **Re-sign critical artifacts** with new key
|
||||
4. **Notify stakeholders** of key change
|
||||
5. **Update CI/CD pipelines** with new key
|
||||
6. **Document incident** for compliance
|
||||
|
||||
---
|
||||
|
||||
## Air-Gapped Signing
|
||||
|
||||
For environments without internet access:
|
||||
|
||||
### Setup
|
||||
|
||||
1. **On internet-connected machine**:
|
||||
|
||||
```bash
|
||||
# Download Cosign binary
|
||||
curl -O -L https://github.com/sigstore/cosign/releases/download/v2.4.1/cosign-linux-amd64
|
||||
sha256sum cosign-linux-amd64
|
||||
|
||||
# Transfer to air-gapped machine via USB
|
||||
```
|
||||
|
||||
2. **On air-gapped machine**:
|
||||
|
||||
```bash
|
||||
# Install Cosign
|
||||
sudo install cosign-linux-amd64 /usr/local/bin/cosign
|
||||
|
||||
# Verify installation
|
||||
cosign version
|
||||
```
|
||||
|
||||
### Signing Without Rekor
|
||||
|
||||
```bash
|
||||
# Sign without transparency log
|
||||
COSIGN_EXPERIMENTAL=0 \
|
||||
COSIGN_PRIVATE_KEY="$(cat ~/.cosign/cosign-airgap.key)" \
|
||||
COSIGN_PASSWORD="your-password" \
|
||||
cosign sign --yes --key ~/.cosign/cosign-airgap.key charon:local
|
||||
|
||||
# Note: This disables Rekor transparency log
|
||||
# Suitable only for internal use or air-gapped environments
|
||||
```
|
||||
|
||||
### Verification (Air-Gapped)
|
||||
|
||||
```bash
|
||||
# Verify signature with public key only
|
||||
cosign verify charon:local --key ~/.cosign/cosign-airgap.pub --insecure-ignore-tlog
|
||||
```
|
||||
|
||||
**⚠️ SECURITY NOTE**: Air-gapped signing without Rekor loses public auditability. Use only when necessary and document the decision.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "cosign: error: signing: getting signer: reading key: decrypt: encrypted: no password provided"
|
||||
|
||||
**Cause**: Missing COSIGN_PASSWORD environment variable
|
||||
**Solution**:
|
||||
|
||||
```bash
|
||||
export COSIGN_PASSWORD="your-password"
|
||||
cosign sign --key cosign.key charon:local
|
||||
```
|
||||
|
||||
### "cosign: error: signing: getting signer: reading key: decrypt: openpgp: invalid data: private key checksum failure"
|
||||
|
||||
**Cause**: Incorrect password
|
||||
**Solution**: Verify you're using the correct password for the key
|
||||
|
||||
### "Error: signing charon:local: uploading signature: PUT <https://registry/v2/.../manifests/sha256->...: UNAUTHORIZED"
|
||||
|
||||
**Cause**: Not authenticated with Docker registry
|
||||
**Solution**:
|
||||
|
||||
```bash
|
||||
docker login ghcr.io
|
||||
# Enter credentials, then retry signing
|
||||
```
|
||||
|
||||
### "Error: verifying charon:local: fetching signatures: getting signature manifest: GET <https://registry/>...: NOT_FOUND"
|
||||
|
||||
**Cause**: Image not signed yet, or signature not pushed to registry
|
||||
**Solution**: Sign the image first with `cosign sign`
|
||||
|
||||
### Key File Corrupted
|
||||
|
||||
**Symptoms**: Decryption errors, unusual characters in key file
|
||||
**Solution**:
|
||||
|
||||
1. Restore from encrypted backup (see Backup and Recovery)
|
||||
2. If no backup: Generate new key pair and re-sign artifacts
|
||||
3. Update documentation and notify stakeholders
|
||||
|
||||
### Lost Password
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. **Cannot recover** - private key is permanently inaccessible
|
||||
2. Generate new key pair
|
||||
3. Revoke old public key from documentation
|
||||
4. Re-sign all artifacts
|
||||
5. Consider using password manager to prevent future loss
|
||||
|
||||
---
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
### DO
|
||||
|
||||
✅ Use strong passwords (20+ characters)
|
||||
✅ Store keys in password manager or HSM
|
||||
✅ Set restrictive file permissions (600 on private keys)
|
||||
✅ Rotate keys every 90 days
|
||||
✅ Create encrypted backups
|
||||
✅ Use different keys for different environments
|
||||
✅ Test keys after generation
|
||||
✅ Document key rotation dates
|
||||
✅ Use keyless signing in CI/CD when possible
|
||||
|
||||
### DON'T
|
||||
|
||||
❌ Commit private keys to version control
|
||||
❌ Share private keys via email or chat
|
||||
❌ Store keys in plaintext files
|
||||
❌ Use the same key for multiple environments
|
||||
❌ Hardcode passwords in scripts
|
||||
❌ Skip backups
|
||||
❌ Ignore rotation schedules
|
||||
❌ Use weak passwords
|
||||
❌ Store keys on network shares
|
||||
|
||||
---
|
||||
|
||||
## Security Contacts
|
||||
|
||||
If you suspect key compromise:
|
||||
|
||||
1. **Immediately**: Stop using the compromised key
|
||||
2. **Notify**: Security team at <security@example.com>
|
||||
3. **Rotate**: Generate new key pair
|
||||
4. **Audit**: Review all signatures made with compromised key
|
||||
5. **Document**: Create incident report
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Cosign Documentation](https://docs.sigstore.dev/cosign/overview/)
|
||||
- [Key Management Best Practices (NIST)](https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-5/final)
|
||||
- [OpenSSF Security Best Practices](https://best.openssf.org/)
|
||||
- [SLSA Requirements](https://slsa.dev/spec/v1.0/requirements)
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Last Reviewed**: 2026-01-10
|
||||
**Next Review**: 2026-04-10 (quarterly)
|
||||
406
docs/guides/manual-dns-provider.md
Normal file
406
docs/guides/manual-dns-provider.md
Normal file
@@ -0,0 +1,406 @@
|
||||
# Manual DNS Provider Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The Manual DNS Provider allows you to obtain SSL/TLS certificates using the ACME DNS-01 challenge when your DNS provider is not directly supported by Charon. Instead of automating DNS record creation, Charon displays the required TXT record details for you to create manually at your DNS provider.
|
||||
|
||||
### When to Use Manual DNS
|
||||
|
||||
Use the Manual DNS Provider when:
|
||||
|
||||
- Your DNS provider is not in the [supported providers list](dns-providers.md)
|
||||
- You need a one-time certificate for testing or development
|
||||
- You want to verify your DNS setup before configuring automated providers
|
||||
- Your organization requires manual approval for DNS changes
|
||||
|
||||
### How It Works
|
||||
|
||||
1. You request a certificate for your domain (e.g., `*.example.com`)
|
||||
2. Charon generates the DNS challenge and displays the TXT record details
|
||||
3. You create the TXT record at your DNS provider
|
||||
4. You click "Verify" to confirm the record exists
|
||||
5. Charon completes the ACME challenge and issues the certificate
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before using the Manual DNS Provider, ensure you have:
|
||||
|
||||
- **DNS Management Access:** Login credentials for your DNS provider's control panel
|
||||
- **Domain Ownership:** Administrative access to the domain you want to secure
|
||||
- **Time Availability:** The challenge must be completed within **10 minutes**
|
||||
- **Charon Setup:** A running Charon instance with the encryption key configured
|
||||
|
||||
## Setup Guide
|
||||
|
||||
### Step 1: Add the Manual DNS Provider
|
||||
|
||||
1. Log in to your Charon dashboard
|
||||
2. Navigate to **Settings** → **DNS Providers**
|
||||
3. Click **Add Provider**
|
||||
4. Select **Manual (No Automation)** from the provider list
|
||||
|
||||
### Step 2: Configure Provider Settings
|
||||
|
||||
Fill in the configuration form:
|
||||
|
||||
| Field | Description | Recommended Value |
|
||||
|-------|-------------|-------------------|
|
||||
| **Provider Name** | A descriptive name for this provider | "Manual DNS" |
|
||||
| **Challenge Timeout** | Time (in minutes) to complete the challenge | 10 |
|
||||
|
||||
Click **Save** to create the provider.
|
||||
|
||||
### Step 3: Create a Proxy Host with Manual DNS
|
||||
|
||||
1. Navigate to **Proxy Hosts**
|
||||
2. Click **Add Proxy Host**
|
||||
3. Enter your domain (e.g., `*.example.com` for wildcard)
|
||||
4. Select your **Manual DNS** provider
|
||||
5. Configure other proxy settings as needed
|
||||
6. Click **Save**
|
||||
|
||||
Charon will begin the certificate request and display the Manual DNS Challenge interface.
|
||||
|
||||
## Using Manual DNS Challenges
|
||||
|
||||
### Understanding the Challenge Interface
|
||||
|
||||
When you request a certificate using the Manual DNS provider, Charon displays a challenge screen:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Manual DNS Challenge │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Certificate Request: *.example.com │
|
||||
│ │
|
||||
│ CREATE THIS TXT RECORD AT YOUR DNS PROVIDER: │
|
||||
│ │
|
||||
│ Record Name: _acme-challenge.example.com [Copy] │
|
||||
│ Record Type: TXT │
|
||||
│ Record Value: gZrH7wL9t3kM2nP4qX5yR8sT0uV1wZ2a... [Copy] │
|
||||
│ TTL: 300 (5 minutes) │
|
||||
│ │
|
||||
│ Time Remaining: 7:23 │
|
||||
│ [━━━━━━━━━━━━━━━━░░░░░░░░░░░░░░░░] 73% │
|
||||
│ │
|
||||
│ [Check DNS Now] [I've Created the Record - Verify] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key Elements:**
|
||||
|
||||
- **Record Name:** The full DNS name where you create the TXT record
|
||||
- **Record Value:** The token value that proves domain ownership
|
||||
- **Time Remaining:** Countdown until the challenge expires
|
||||
- **Copy Buttons:** Click to copy values to your clipboard
|
||||
|
||||
### Step-by-Step: Creating the TXT Record
|
||||
|
||||
Follow these steps to complete the challenge:
|
||||
|
||||
1. **Copy the Record Name**
|
||||
- Click the **Copy** button next to the Record Name
|
||||
- This copies `_acme-challenge.example.com` to your clipboard
|
||||
|
||||
2. **Copy the Record Value**
|
||||
- Click the **Copy** button next to the Record Value
|
||||
- This copies the challenge token to your clipboard
|
||||
|
||||
3. **Log in to Your DNS Provider**
|
||||
- Open your DNS provider's control panel in a new browser tab
|
||||
- Navigate to the DNS management section for your domain
|
||||
|
||||
4. **Create a New TXT Record**
|
||||
- Click "Add Record" or similar button
|
||||
- Select **TXT** as the record type
|
||||
- Paste the Record Name (or just `_acme-challenge` depending on your provider)
|
||||
- Paste the Record Value
|
||||
- Set TTL to **300** seconds (5 minutes) or the lowest available option
|
||||
|
||||
5. **Save the DNS Record**
|
||||
- Confirm and save the new TXT record
|
||||
- Wait a few seconds for the change to process
|
||||
|
||||
### Provider-Specific Instructions
|
||||
|
||||
Different DNS providers have different interfaces. Here are common patterns:
|
||||
|
||||
#### Cloudflare (Manual)
|
||||
|
||||
1. Go to **DNS** → **Records**
|
||||
2. Click **Add record**
|
||||
3. Type: `TXT`
|
||||
4. Name: `_acme-challenge` (Cloudflare adds the domain automatically)
|
||||
5. Content: Paste the challenge token
|
||||
6. TTL: `Auto` or `5 min`
|
||||
|
||||
#### GoDaddy
|
||||
|
||||
1. Go to **DNS Management**
|
||||
2. Click **Add** in the Records section
|
||||
3. Type: `TXT`
|
||||
4. Host: `_acme-challenge`
|
||||
5. TXT Value: Paste the challenge token
|
||||
6. TTL: `1/2 Hour` (minimum)
|
||||
|
||||
#### Namecheap
|
||||
|
||||
1. Go to **Advanced DNS**
|
||||
2. Click **Add New Record**
|
||||
3. Type: `TXT Record`
|
||||
4. Host: `_acme-challenge`
|
||||
5. Value: Paste the challenge token
|
||||
6. TTL: `Automatic`
|
||||
|
||||
#### Generic Providers
|
||||
|
||||
Most providers follow this pattern:
|
||||
|
||||
| Field | What to Enter |
|
||||
|-------|---------------|
|
||||
| Type | TXT |
|
||||
| Host/Name | `_acme-challenge` or full `_acme-challenge.yourdomain.com` |
|
||||
| Value/Content | The challenge token from Charon |
|
||||
| TTL | 300 or lowest available |
|
||||
|
||||
### Verifying the Challenge
|
||||
|
||||
After creating the TXT record:
|
||||
|
||||
1. **Wait for Propagation**
|
||||
- DNS changes can take 30 seconds to several minutes to propagate
|
||||
- The "Check DNS Now" button lets you verify without triggering the full challenge
|
||||
|
||||
2. **Click "Check DNS Now" (Optional)**
|
||||
- Charon queries DNS to see if your record exists
|
||||
- Status updates to show if the record was found
|
||||
|
||||
3. **Click "I've Created the Record - Verify"**
|
||||
- Charon sends the verification to the ACME server
|
||||
- If successful, the certificate is issued
|
||||
- If the record is not found, you can try again (within the time limit)
|
||||
|
||||
### Challenge Status Messages
|
||||
|
||||
| Status | Meaning | Action |
|
||||
|--------|---------|--------|
|
||||
| **Pending** | Waiting for you to create the DNS record | Create the TXT record |
|
||||
| **Checking DNS** | Charon is verifying the record exists | Wait for result |
|
||||
| **DNS Found** | Record detected, verifying with ACME | Wait for completion |
|
||||
| **Verified** | Challenge completed successfully | Certificate issued! |
|
||||
| **Expired** | Time limit exceeded | Start a new challenge |
|
||||
| **Failed** | Verification failed | Check record and retry |
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
### "DNS record not found"
|
||||
|
||||
**Possible Causes:**
|
||||
|
||||
1. **Typo in record name or value**
|
||||
- Double-check you copied the exact values from Charon
|
||||
- Some providers require just `_acme-challenge`, others need the full domain
|
||||
|
||||
2. **DNS propagation delay**
|
||||
- Wait 1-2 minutes and try "Check DNS Now" again
|
||||
- Use [DNS Checker](https://dnschecker.org/) to verify propagation
|
||||
|
||||
3. **Wrong DNS zone**
|
||||
- Ensure you're editing the correct domain's DNS
|
||||
- For subdomains, the record goes in the parent zone
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Verify your record from command line
|
||||
dig TXT _acme-challenge.example.com +short
|
||||
|
||||
# Expected output: Your challenge token in quotes
|
||||
"gZrH7wL9t3kM2nP4qX5yR8sT0uV1wZ2aB3cD4eF5gH6i"
|
||||
```
|
||||
|
||||
### "Challenge expired"
|
||||
|
||||
**Cause:** The 10-minute time limit was exceeded.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Click **Cancel Challenge** or wait for it to clear
|
||||
2. Start a new certificate request
|
||||
3. Have your DNS provider's control panel ready before starting
|
||||
4. Create the record immediately after copying the values
|
||||
|
||||
### "Challenge already in progress"
|
||||
|
||||
**Cause:** Another challenge is active for the same domain.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Wait for the existing challenge to complete or expire
|
||||
2. If you started the challenge, navigate to the pending challenge screen
|
||||
3. Complete or cancel the existing challenge before starting a new one
|
||||
|
||||
### "Verification failed"
|
||||
|
||||
**Possible Causes:**
|
||||
|
||||
1. **Record value mismatch**
|
||||
- Ensure no extra spaces or characters in the TXT value
|
||||
- Some providers add quotes automatically; don't add your own
|
||||
|
||||
2. **Wrong record type**
|
||||
- Must be a TXT record, not CNAME or other type
|
||||
|
||||
3. **Cached old record**
|
||||
- If you had a previous challenge, the old record might be cached
|
||||
- Delete any existing `_acme-challenge` records before creating new ones
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Delete the existing TXT record
|
||||
2. Wait 2 minutes for cache to clear
|
||||
3. Create a new record with the exact values from Charon
|
||||
4. Click "Verify" again
|
||||
|
||||
### DNS Provider Rate Limits
|
||||
|
||||
Some providers limit how frequently you can modify DNS records.
|
||||
|
||||
**Symptoms:**
|
||||
|
||||
- "Too many requests" error
|
||||
- Changes not appearing immediately
|
||||
- API errors in provider dashboard
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Wait 5-10 minutes before retrying
|
||||
2. Contact your DNS provider if issues persist
|
||||
3. Consider using a provider with better API limits for frequent certificate operations
|
||||
|
||||
## Limitations
|
||||
|
||||
### Auto-Renewal Not Supported
|
||||
|
||||
> **Important:** The Manual DNS Provider **does not support automatic certificate renewal**.
|
||||
|
||||
When your certificate approaches expiration:
|
||||
|
||||
1. You will receive a notification (if notifications are configured)
|
||||
2. You must manually initiate a new certificate request
|
||||
3. You must complete the DNS challenge again
|
||||
|
||||
**Recommendation:** Use the Manual DNS Provider only for:
|
||||
|
||||
- Initial testing and verification
|
||||
- One-time certificates
|
||||
- Domains where you plan to migrate to an automated provider
|
||||
|
||||
For production use with automatic renewal, consider:
|
||||
|
||||
- [Supported DNS Providers](dns-providers.md)
|
||||
- [Webhook DNS Provider](../features/webhook-dns.md) for custom integrations
|
||||
- [RFC 2136 Provider](../features/rfc2136-dns.md) for self-hosted DNS
|
||||
|
||||
### Challenge Timeout
|
||||
|
||||
The DNS challenge must be completed within **10 minutes** (default). This includes:
|
||||
|
||||
- Creating the TXT record
|
||||
- Waiting for DNS propagation
|
||||
- Clicking "Verify"
|
||||
|
||||
If you frequently run out of time:
|
||||
|
||||
1. Have your DNS provider control panel open before starting
|
||||
2. Use a provider with faster propagation
|
||||
3. Consider a different approach for complex setups
|
||||
|
||||
### Single Challenge at a Time
|
||||
|
||||
Only one manual challenge can be active per domain (FQDN) at a time. If you need certificates for multiple domains, complete each challenge sequentially.
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### Can I use Manual DNS for production certificates?
|
||||
|
||||
Yes, but with caveats. The certificate itself is the same as those obtained through automated providers. However, you must remember to manually renew before expiration. For production systems, automated renewal is strongly recommended.
|
||||
|
||||
### How long does DNS propagation take?
|
||||
|
||||
DNS propagation typically takes:
|
||||
|
||||
- **Cloudflare:** Near-instant (seconds)
|
||||
- **Most providers:** 30 seconds to 2 minutes
|
||||
- **Some providers:** Up to 5-10 minutes
|
||||
|
||||
The Manual DNS Provider's 10-minute timeout accommodates most scenarios.
|
||||
|
||||
### Can I use a shorter TTL?
|
||||
|
||||
Yes. Lower TTL values (60-300 seconds) help because:
|
||||
|
||||
- Changes propagate faster
|
||||
- Cached records expire sooner if you need to retry
|
||||
|
||||
Set the TTL to the lowest value your provider allows.
|
||||
|
||||
### What happens if I enter the wrong value?
|
||||
|
||||
The verification will fail with "DNS record not found" or "Verification failed." Simply:
|
||||
|
||||
1. Delete the incorrect TXT record
|
||||
2. Create a new record with the correct value
|
||||
3. Click "Verify" again (if time permits)
|
||||
|
||||
### Can I use Manual DNS for multi-domain certificates?
|
||||
|
||||
Yes, but each domain requires its own TXT record. For a certificate covering `example.com` and `www.example.com`:
|
||||
|
||||
1. Charon displays challenges for each domain
|
||||
2. Create TXT records for each `_acme-challenge` subdomain
|
||||
3. Verify each challenge in sequence
|
||||
|
||||
### Is the Manual DNS Provider secure?
|
||||
|
||||
Yes. The Manual DNS Provider:
|
||||
|
||||
- Uses the same ACME protocol as automated providers
|
||||
- Encrypts all data at rest
|
||||
- Requires authentication for all operations
|
||||
- Logs all challenge activity for auditing
|
||||
|
||||
The security of your certificate depends on:
|
||||
|
||||
- Protecting your DNS provider credentials
|
||||
- Not sharing challenge tokens publicly
|
||||
- Completing challenges promptly
|
||||
|
||||
### How do I delete a Manual DNS Provider?
|
||||
|
||||
1. Navigate to **Settings** → **DNS Providers**
|
||||
2. Find your Manual DNS provider in the list
|
||||
3. Ensure no proxy hosts are using it (migrate them first)
|
||||
4. Click the **Delete** button
|
||||
5. Confirm deletion
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [DNS Providers Overview](dns-providers.md)
|
||||
- [Certificates Guide](certificates.md)
|
||||
- [DNS Challenges Troubleshooting](../troubleshooting/dns-challenges.md)
|
||||
- [Custom DNS Plugins](../features/custom-plugins.md)
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues not covered in this guide:
|
||||
|
||||
1. Check the [Troubleshooting Guide](../troubleshooting/dns-challenges.md)
|
||||
2. Search [GitHub Discussions](https://github.com/Wikid82/charon/discussions)
|
||||
3. Open an issue with:
|
||||
- Your Charon version
|
||||
- DNS provider name
|
||||
- Error messages
|
||||
- Steps you've tried
|
||||
696
docs/guides/supply-chain-security-developer-guide.md
Normal file
696
docs/guides/supply-chain-security-developer-guide.md
Normal file
@@ -0,0 +1,696 @@
|
||||
# Supply Chain Security - Developer Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide explains how to use Charon's supply chain security tools during development, testing, and release preparation. It covers the three agent skills, when to use them, and how they integrate into your workflow.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Quick Reference](#quick-reference)
|
||||
2. [Agent Skills Overview](#agent-skills-overview)
|
||||
3. [Development Workflow](#development-workflow)
|
||||
4. [Testing and Validation](#testing-and-validation)
|
||||
5. [Release Process](#release-process)
|
||||
6. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Available VS Code Tasks
|
||||
|
||||
```bash
|
||||
# Verify SBOM and scan for vulnerabilities
|
||||
Task: "Security: Verify SBOM"
|
||||
|
||||
# Sign a container image with Cosign
|
||||
Task: "Security: Sign with Cosign"
|
||||
|
||||
# Generate SLSA provenance for a binary
|
||||
Task: "Security: Generate SLSA Provenance"
|
||||
|
||||
# Run all supply chain checks
|
||||
Task: "Security: Full Supply Chain Audit"
|
||||
```
|
||||
|
||||
### Direct Skill Invocation
|
||||
|
||||
```bash
|
||||
# From project root
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom [image]
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign [type] [target]
|
||||
.github/skills/scripts/skill-runner.sh security-slsa-provenance [action] [target]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Agent Skills Overview
|
||||
|
||||
### 1. security-verify-sbom
|
||||
|
||||
**Purpose:** Verify SBOM contents and scan for vulnerabilities
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
# Verify container image SBOM
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom docker charon:local
|
||||
|
||||
# Verify directory SBOM
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom dir ./backend
|
||||
|
||||
# Verify file SBOM
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom file ./backend/main
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
1. Generates SBOM using Syft (if not exists)
|
||||
2. Validates SBOM format (SPDX JSON)
|
||||
3. Scans for vulnerabilities using Grype
|
||||
4. Reports findings with severity levels
|
||||
|
||||
**When to use:**
|
||||
|
||||
- Before committing dependency updates
|
||||
- After building new images
|
||||
- Before releases
|
||||
- During security audits
|
||||
|
||||
**Output:**
|
||||
|
||||
- SBOM file (SPDX JSON format)
|
||||
- Vulnerability report
|
||||
- Summary of critical/high findings
|
||||
|
||||
### 2. security-sign-cosign
|
||||
|
||||
**Purpose:** Sign container images or binaries with Cosign
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
# Sign Docker image
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign docker charon:local
|
||||
|
||||
# Sign binary file
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign file ./backend/main
|
||||
|
||||
# Sign OCI artifact
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign oci ghcr.io/wikid82/charon:latest
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
1. Verifies target exists
|
||||
2. Signs with Cosign (keyless or with key)
|
||||
3. Records signature in Rekor transparency log
|
||||
4. Generates verification commands
|
||||
|
||||
**When to use:**
|
||||
|
||||
- After building local test images
|
||||
- Before pushing to registry
|
||||
- During release preparation
|
||||
- For artifact attestation
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Cosign installed (`make install-cosign`)
|
||||
- Docker running (for image signing)
|
||||
- Network access (for Rekor)
|
||||
|
||||
### 3. security-slsa-provenance
|
||||
|
||||
**Purpose:** Generate and verify SLSA provenance attestation
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
# Generate provenance for binary
|
||||
.github/skills/scripts/skill-runner.sh security-slsa-provenance generate ./backend/main
|
||||
|
||||
# Verify provenance
|
||||
.github/skills/scripts/skill-runner.sh security-slsa-provenance verify ./backend/main provenance.json
|
||||
|
||||
# Validate provenance format
|
||||
.github/skills/scripts/skill-runner.sh security-slsa-provenance validate provenance.json
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
1. Collects build metadata (commit, branch, timestamp)
|
||||
2. Generates SLSA provenance document
|
||||
3. Signs provenance with Cosign
|
||||
4. Verifies provenance integrity
|
||||
|
||||
**When to use:**
|
||||
|
||||
- After building release binaries
|
||||
- Before publishing releases
|
||||
- For compliance requirements
|
||||
- To prove build reproducibility
|
||||
|
||||
**Output:**
|
||||
|
||||
- `provenance.json` - SLSA provenance attestation
|
||||
- Verification status
|
||||
- Build metadata
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Daily Development
|
||||
|
||||
#### 1. Dependency Updates
|
||||
|
||||
When updating dependencies:
|
||||
|
||||
```bash
|
||||
# 1. Update dependencies
|
||||
cd backend && go get -u ./...
|
||||
cd ../frontend && npm update
|
||||
|
||||
# 2. Build and test
|
||||
make build-all
|
||||
make test-all
|
||||
|
||||
# 3. Verify SBOM (check for new vulnerabilities)
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom docker charon:local
|
||||
```
|
||||
|
||||
**Review output:**
|
||||
|
||||
- ✅ No critical/high vulnerabilities → Proceed
|
||||
- ⚠️ Vulnerabilities found → Review, patch, or document
|
||||
|
||||
#### 2. Local Testing
|
||||
|
||||
Before committing:
|
||||
|
||||
```bash
|
||||
# 1. Build local image
|
||||
docker build -t charon:dev .
|
||||
|
||||
# 2. Generate and verify SBOM
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom docker charon:dev
|
||||
|
||||
# 3. Sign image (optional, for testing)
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign docker charon:dev
|
||||
```
|
||||
|
||||
#### 3. Pre-Commit Checks
|
||||
|
||||
Add to your pre-commit routine:
|
||||
|
||||
```bash
|
||||
# .git/hooks/pre-commit (or pre-commit config)
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🔍 Running supply chain checks..."
|
||||
|
||||
# Build
|
||||
make build-all
|
||||
|
||||
# Verify SBOM
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom dir ./backend
|
||||
|
||||
# Check for critical vulnerabilities
|
||||
if grep -i "critical" sbom-scan-output.txt; then
|
||||
echo "❌ Critical vulnerabilities found! Review before committing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Supply chain checks passed"
|
||||
```
|
||||
|
||||
### Pull Request Workflow
|
||||
|
||||
#### As a Developer
|
||||
|
||||
```bash
|
||||
# 1. Build and test locally
|
||||
make build-all
|
||||
make test-all
|
||||
|
||||
# 2. Run full supply chain audit
|
||||
# (Uses the composite VS Code task)
|
||||
# Run via VS Code: Ctrl+Shift+P → "Tasks: Run Task" → "Security: Full Supply Chain Audit"
|
||||
|
||||
# 3. Document findings in PR description
|
||||
# - SBOM changes (new dependencies)
|
||||
# - Vulnerability scan results
|
||||
# - Signature verification status
|
||||
```
|
||||
|
||||
#### As a Reviewer
|
||||
|
||||
Verify supply chain artifacts:
|
||||
|
||||
```bash
|
||||
# 1. Checkout PR branch
|
||||
git fetch origin pull/123/head:pr-123
|
||||
git checkout pr-123
|
||||
|
||||
# 2. Build
|
||||
make build-all
|
||||
|
||||
# 3. Verify SBOM
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom docker charon:local
|
||||
|
||||
# 4. Check for regressions
|
||||
# - New vulnerabilities introduced?
|
||||
# - Unexpected dependency changes?
|
||||
# - SBOM completeness?
|
||||
```
|
||||
|
||||
**Review checklist:**
|
||||
|
||||
- [ ] SBOM includes all new dependencies
|
||||
- [ ] No new critical/high vulnerabilities
|
||||
- [ ] Dependency licenses compatible
|
||||
- [ ] Security documentation updated
|
||||
|
||||
---
|
||||
|
||||
## Testing and Validation
|
||||
|
||||
### Unit Testing Supply Chain Skills
|
||||
|
||||
```bash
|
||||
# Test SBOM generation
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom dir ./backend
|
||||
test -f sbom.spdx.json || echo "❌ SBOM not generated"
|
||||
|
||||
# Test signing (requires Cosign)
|
||||
docker build -t charon:test .
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign docker charon:test
|
||||
echo $? # Should be 0 for success
|
||||
|
||||
# Test provenance generation
|
||||
go build -o main ./backend/cmd/charon
|
||||
.github/skills/scripts/skill-runner.sh security-slsa-provenance generate ./main
|
||||
test -f provenance.json || echo "❌ Provenance not generated"
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
|
||||
Create a test script:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# test-supply-chain.sh
|
||||
set -e
|
||||
|
||||
echo "🔧 Building test image..."
|
||||
docker build -t charon:integration-test .
|
||||
|
||||
echo "🔍 Verifying SBOM..."
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom docker charon:integration-test
|
||||
|
||||
echo "✍️ Signing image..."
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign docker charon:integration-test
|
||||
|
||||
echo "🔐 Verifying signature..."
|
||||
cosign verify \
|
||||
--certificate-identity-regexp='.*' \
|
||||
--certificate-oidc-issuer='.*' \
|
||||
charon:integration-test || echo "⚠️ Verification expected to fail for local image"
|
||||
|
||||
echo "📄 Generating provenance..."
|
||||
.github/skills/scripts/skill-runner.sh security-slsa-provenance generate ./backend/main
|
||||
|
||||
echo "✅ All supply chain tests passed!"
|
||||
```
|
||||
|
||||
Run in CI/CD:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
- name: Test Supply Chain
|
||||
run: |
|
||||
chmod +x test-supply-chain.sh
|
||||
./test-supply-chain.sh
|
||||
```
|
||||
|
||||
### Validation Checklist
|
||||
|
||||
Before marking a feature complete:
|
||||
|
||||
- [ ] SBOM generation works for all artifacts
|
||||
- [ ] Signing works for images and binaries
|
||||
- [ ] Provenance generation includes correct metadata
|
||||
- [ ] Verification commands documented
|
||||
- [ ] CI/CD integration tested
|
||||
- [ ] Error handling validated
|
||||
- [ ] Documentation updated
|
||||
|
||||
---
|
||||
|
||||
## Release Process
|
||||
|
||||
### Pre-Release Checklist
|
||||
|
||||
#### 1. Version Bump and Tag
|
||||
|
||||
```bash
|
||||
# Update version
|
||||
echo "v1.0.0" > VERSION
|
||||
|
||||
# Commit and tag
|
||||
git add VERSION
|
||||
git commit -m "chore: bump version to v1.0.0"
|
||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||
```
|
||||
|
||||
#### 2. Build Release Artifacts
|
||||
|
||||
```bash
|
||||
# Build backend binary
|
||||
cd backend
|
||||
go build -ldflags="-s -w -X main.Version=v1.0.0" -o charon-linux-amd64 ./cmd/charon
|
||||
|
||||
# Build frontend
|
||||
cd ../frontend
|
||||
npm run build
|
||||
|
||||
# Build Docker image
|
||||
cd ..
|
||||
docker build -t charon:v1.0.0 .
|
||||
```
|
||||
|
||||
#### 3. Generate Supply Chain Artifacts
|
||||
|
||||
```bash
|
||||
# Generate SBOM for image
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom docker charon:v1.0.0
|
||||
mv sbom.spdx.json sbom-v1.0.0.spdx.json
|
||||
|
||||
# Generate SBOM for binary
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom file ./backend/charon-linux-amd64
|
||||
mv sbom.spdx.json sbom-binary-v1.0.0.spdx.json
|
||||
|
||||
# Generate provenance for binary
|
||||
.github/skills/scripts/skill-runner.sh security-slsa-provenance generate ./backend/charon-linux-amd64
|
||||
mv provenance.json provenance-v1.0.0.json
|
||||
|
||||
# Sign binary
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign file ./backend/charon-linux-amd64
|
||||
```
|
||||
|
||||
#### 4. Push and Sign Image
|
||||
|
||||
```bash
|
||||
# Tag image for registry
|
||||
docker tag charon:v1.0.0 ghcr.io/wikid82/charon:v1.0.0
|
||||
docker tag charon:v1.0.0 ghcr.io/wikid82/charon:latest
|
||||
|
||||
# Push to registry
|
||||
docker push ghcr.io/wikid82/charon:v1.0.0
|
||||
docker push ghcr.io/wikid82/charon:latest
|
||||
|
||||
# Sign images
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign oci ghcr.io/wikid82/charon:v1.0.0
|
||||
.github/skills/scripts/skill-runner.sh security-sign-cosign oci ghcr.io/wikid82/charon:latest
|
||||
```
|
||||
|
||||
#### 5. Verify Release Artifacts
|
||||
|
||||
```bash
|
||||
# Verify image signature
|
||||
cosign verify \
|
||||
--certificate-identity-regexp='https://github.com/Wikid82/charon' \
|
||||
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
|
||||
ghcr.io/wikid82/charon:v1.0.0
|
||||
|
||||
# Verify provenance
|
||||
slsa-verifier verify-artifact \
|
||||
--provenance-path provenance-v1.0.0.json \
|
||||
--source-uri github.com/Wikid82/charon \
|
||||
./backend/charon-linux-amd64
|
||||
|
||||
# Scan SBOM
|
||||
grype sbom:sbom-v1.0.0.spdx.json
|
||||
```
|
||||
|
||||
#### 6. Create GitHub Release
|
||||
|
||||
Upload these files as release assets:
|
||||
|
||||
- `charon-linux-amd64` - Binary
|
||||
- `charon-linux-amd64.sig` - Binary signature
|
||||
- `sbom-v1.0.0.spdx.json` - Image SBOM
|
||||
- `sbom-binary-v1.0.0.spdx.json` - Binary SBOM
|
||||
- `provenance-v1.0.0.json` - SLSA provenance
|
||||
|
||||
Release notes should include:
|
||||
|
||||
- Verification commands
|
||||
- Link to user guide
|
||||
- Known vulnerabilities (if any)
|
||||
|
||||
### Automated Release (GitHub Actions)
|
||||
|
||||
The release process is automated via GitHub Actions. The workflow:
|
||||
|
||||
1. Triggers on version tags (`v*`)
|
||||
2. Builds artifacts
|
||||
3. Generates SBOMs and provenance
|
||||
4. Signs with Cosign (keyless)
|
||||
5. Pushes to registry
|
||||
6. Creates GitHub release with assets
|
||||
|
||||
See `.github/workflows/release.yml` for implementation.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "syft: command not found"
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
make install-syft
|
||||
# Or manually:
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
|
||||
```
|
||||
|
||||
#### "cosign: command not found"
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
make install-cosign
|
||||
# Or manually:
|
||||
curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
|
||||
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
|
||||
sudo chmod +x /usr/local/bin/cosign
|
||||
```
|
||||
|
||||
#### "grype: command not found"
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
make install-grype
|
||||
# Or manually:
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
|
||||
```
|
||||
|
||||
#### SBOM Generation Fails
|
||||
|
||||
**Possible causes:**
|
||||
|
||||
- Docker image doesn't exist
|
||||
- Directory/file path incorrect
|
||||
- Syft version incompatible
|
||||
|
||||
**Debug:**
|
||||
|
||||
```bash
|
||||
# Check image exists
|
||||
docker images | grep charon
|
||||
|
||||
# Test Syft manually
|
||||
syft docker:charon:local -o spdx-json
|
||||
|
||||
# Check Syft version
|
||||
syft version
|
||||
```
|
||||
|
||||
#### Signing Fails with "no ambient OIDC credentials"
|
||||
|
||||
**Cause:** Cosign keyless signing requires OIDC authentication (GitHub Actions, Google Cloud, etc.)
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Use key-based signing for local development:
|
||||
|
||||
```bash
|
||||
cosign generate-key-pair
|
||||
cosign sign --key cosign.key charon:local
|
||||
```
|
||||
|
||||
2. Set up OIDC provider (GitHub Actions example):
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
```
|
||||
|
||||
3. Use environment variables:
|
||||
|
||||
```bash
|
||||
export COSIGN_EXPERIMENTAL=1
|
||||
```
|
||||
|
||||
#### Provenance Verification Fails
|
||||
|
||||
**Possible causes:**
|
||||
|
||||
- Provenance file doesn't match binary
|
||||
- Binary was modified after provenance generation
|
||||
- Wrong source URI
|
||||
|
||||
**Debug:**
|
||||
|
||||
```bash
|
||||
# Check binary hash
|
||||
sha256sum ./backend/charon-linux-amd64
|
||||
|
||||
# Check hash in provenance
|
||||
cat provenance.json | jq -r '.subject[0].digest.sha256'
|
||||
|
||||
# Hashes should match
|
||||
```
|
||||
|
||||
### Performance Optimization
|
||||
|
||||
#### SBOM Generation is Slow
|
||||
|
||||
**Optimization:**
|
||||
|
||||
```bash
|
||||
# Cache SBOM between runs
|
||||
SBOM_FILE="sbom-$(git rev-parse --short HEAD).spdx.json"
|
||||
if [ ! -f "$SBOM_FILE" ]; then
|
||||
syft docker:charon:local -o spdx-json > "$SBOM_FILE"
|
||||
fi
|
||||
```
|
||||
|
||||
#### Large Image Scans Timeout
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Increase timeout
|
||||
export GRYPE_CHECK_FOR_APP_UPDATE=false
|
||||
export GRYPE_DB_AUTO_UPDATE=false
|
||||
grype --timeout 10m docker:charon:local
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
Enable verbose logging:
|
||||
|
||||
```bash
|
||||
# For skill scripts
|
||||
export SKILL_DEBUG=1
|
||||
.github/skills/scripts/skill-runner.sh security-verify-sbom docker charon:local
|
||||
|
||||
# For Syft
|
||||
export SYFT_LOG_LEVEL=debug
|
||||
syft docker:charon:local
|
||||
|
||||
# For Cosign
|
||||
export COSIGN_LOG_LEVEL=debug
|
||||
cosign sign charon:local
|
||||
|
||||
# For Grype
|
||||
export GRYPE_LOG_LEVEL=debug
|
||||
grype docker:charon:local
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Security
|
||||
|
||||
1. **Never commit private keys**: Use keyless signing or store keys securely
|
||||
2. **Verify before sign**: Always verify artifacts before signing
|
||||
3. **Use specific versions**: Pin tool versions in CI/CD
|
||||
4. **Rotate keys regularly**: If using key-based signing
|
||||
5. **Monitor transparency logs**: Check Rekor for unexpected signatures
|
||||
|
||||
### Development
|
||||
|
||||
1. **Generate SBOM early**: Run during development, not just before release
|
||||
2. **Automate verification**: Add to CI/CD and pre-commit hooks
|
||||
3. **Document vulnerabilities**: Track known issues in SECURITY.md
|
||||
4. **Test locally**: Verify skills work on developer machines
|
||||
5. **Update dependencies**: Keep tools (Syft, Cosign, Grype) current
|
||||
|
||||
### CI/CD
|
||||
|
||||
1. **Cache tools**: Cache tool installations between runs
|
||||
2. **Parallel execution**: Run SBOM generation and signing in parallel
|
||||
3. **Fail fast**: Exit early on critical vulnerabilities
|
||||
4. **Artifact retention**: Store SBOMs and provenance as artifacts
|
||||
5. **Release automation**: Fully automate release signing and verification
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Documentation
|
||||
|
||||
- [User Guide](supply-chain-security-user-guide.md) - End-user verification
|
||||
- [SECURITY.md](../../SECURITY.md) - Security policy and contacts
|
||||
- [Skill Implementation](../.github/skills/security-supply-chain/) - Skill source code
|
||||
|
||||
### External Resources
|
||||
|
||||
- [Sigstore Documentation](https://docs.sigstore.dev/)
|
||||
- [SLSA Framework](https://slsa.dev/)
|
||||
- [Syft Documentation](https://github.com/anchore/syft)
|
||||
- [Grype Documentation](https://github.com/anchore/grype)
|
||||
- [Cosign Documentation](https://docs.sigstore.dev/cosign/overview/)
|
||||
|
||||
### Tools
|
||||
|
||||
- [Sigstore Rekor Search](https://search.sigstore.dev/)
|
||||
- [SPDX Online Tools](https://tools.spdx.org/)
|
||||
- [Supply Chain Security Best Practices](https://slsa.dev/spec/v1.0/requirements)
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Questions**: [GitHub Discussions](https://github.com/Wikid82/charon/discussions)
|
||||
- **Bug Reports**: [GitHub Issues](https://github.com/Wikid82/charon/issues)
|
||||
- **Security**: [Security Advisory](https://github.com/Wikid82/charon/security/advisories)
|
||||
|
||||
### Contributing
|
||||
|
||||
Found a bug or want to improve the supply chain security implementation?
|
||||
|
||||
1. Open an issue describing the problem
|
||||
2. Submit a PR with fixes/improvements
|
||||
3. Update tests and documentation
|
||||
4. Run full supply chain audit before submitting
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: January 10, 2026
|
||||
**Version**: 1.0
|
||||
360
docs/guides/supply-chain-security-user-guide.md
Normal file
360
docs/guides/supply-chain-security-user-guide.md
Normal file
@@ -0,0 +1,360 @@
|
||||
# Supply Chain Security - User Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Charon implements comprehensive supply chain security measures to ensure you can verify the authenticity and integrity of every release. This guide shows you how to verify signatures, check build provenance, and inspect Software Bill of Materials (SBOM).
|
||||
|
||||
## Why Supply Chain Security Matters
|
||||
|
||||
When you download and run software, you're trusting that:
|
||||
|
||||
- The software came from the legitimate source
|
||||
- It hasn't been tampered with during distribution
|
||||
- The build process was secure and reproducible
|
||||
- You know exactly what dependencies are included
|
||||
|
||||
Supply chain attacks are increasingly common. Charon's verification tools help you confirm what you're running is exactly what the developers built.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start: Verify a Release
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Install verification tools (one-time setup):
|
||||
|
||||
```bash
|
||||
# Install Cosign (for signature verification)
|
||||
curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
|
||||
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
|
||||
sudo chmod +x /usr/local/bin/cosign
|
||||
|
||||
# Install slsa-verifier (for provenance verification)
|
||||
curl -LO https://github.com/slsa-framework/slsa-verifier/releases/latest/download/slsa-verifier-linux-amd64
|
||||
sudo mv slsa-verifier-linux-amd64 /usr/local/bin/slsa-verifier
|
||||
sudo chmod +x /usr/local/bin/slsa-verifier
|
||||
|
||||
# Install Grype (optional, for SBOM vulnerability scanning)
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
|
||||
```
|
||||
|
||||
### Verify Container Image (Recommended)
|
||||
|
||||
Verify the Charon container image before running it:
|
||||
|
||||
```bash
|
||||
cosign verify \
|
||||
--certificate-identity-regexp='https://github.com/Wikid82/charon' \
|
||||
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
|
||||
ghcr.io/wikid82/charon:latest
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
|
||||
```
|
||||
Verification for ghcr.io/wikid82/charon:latest --
|
||||
The following checks were performed on each of these signatures:
|
||||
- The cosign claims were validated
|
||||
- Existence of the claims in the transparency log was verified offline
|
||||
- The code-signing certificate was verified using trusted certificate authority certificates
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Detailed Verification Steps
|
||||
|
||||
### 1. Verify Image Signature with Cosign
|
||||
|
||||
**What it does:** Confirms the image was signed by the Charon project and hasn't been modified.
|
||||
|
||||
**Command:**
|
||||
|
||||
```bash
|
||||
cosign verify \
|
||||
--certificate-identity-regexp='https://github.com/Wikid82/charon' \
|
||||
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
|
||||
ghcr.io/wikid82/charon:v1.0.0
|
||||
```
|
||||
|
||||
**What to check:**
|
||||
|
||||
- ✅ "Verification for ... --" message appears
|
||||
- ✅ Certificate identity matches `https://github.com/Wikid82/charon`
|
||||
- ✅ OIDC issuer is `https://token.actions.githubusercontent.com`
|
||||
- ✅ No errors or warnings
|
||||
|
||||
**Troubleshooting:**
|
||||
|
||||
- **Error: "no matching signatures"** → The image may not be signed, or you have the wrong tag
|
||||
- **Error: "certificate identity doesn't match"** → The image may be compromised or unofficial
|
||||
- **Error: "OIDC issuer doesn't match"** → The signing process didn't use GitHub Actions
|
||||
|
||||
### 2. Verify SLSA Provenance
|
||||
|
||||
**What it does:** Proves the Docker images were built by the official GitHub Actions workflow from the official repository.
|
||||
|
||||
**Note:** Charon uses a Docker-only deployment model. SLSA provenance is attached to container images, not standalone binaries.
|
||||
|
||||
**For Docker images, provenance is automatically embedded.** You can inspect it using Cosign:
|
||||
|
||||
```bash
|
||||
# View attestations attached to the image
|
||||
cosign verify-attestation \
|
||||
--type slsaprovenance \
|
||||
--certificate-identity-regexp='https://github.com/Wikid82/charon' \
|
||||
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
|
||||
ghcr.io/wikid82/charon:v1.0.0 | jq -r '.payload' | base64 -d | jq
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
|
||||
```json
|
||||
{
|
||||
"_type": "https://in-toto.io/Statement/v0.1",
|
||||
"predicateType": "https://slsa.dev/provenance/v0.2",
|
||||
"subject": [...],
|
||||
"predicate": {
|
||||
"builder": {
|
||||
"id": "https://github.com/slsa-framework/slsa-github-generator/..."
|
||||
},
|
||||
"buildType": "https://github.com/slsa-framework/slsa-github-generator@v1",
|
||||
"invocation": {
|
||||
"configSource": {
|
||||
"uri": "git+https://github.com/Wikid82/charon@refs/tags/v1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**What to check:**
|
||||
|
||||
- ✅ `predicateType` is SLSA provenance
|
||||
- ✅ `builder.id` references the official SLSA generator
|
||||
- ✅ `configSource.uri` matches `github.com/Wikid82/charon`
|
||||
- ✅ No errors during verification
|
||||
|
||||
**Troubleshooting:**
|
||||
|
||||
- **Error: "no matching attestations"** → The image may not have provenance attached
|
||||
- **Error: "certificate identity doesn't match"** → The attestation came from an unofficial source
|
||||
- **Error: "invalid provenance"** → The provenance may be corrupted
|
||||
|
||||
### 3. Inspect Software Bill of Materials (SBOM)
|
||||
|
||||
**What it does:** Shows all dependencies included in Charon, allowing you to check for known vulnerabilities.
|
||||
|
||||
**Step 1: Download SBOM**
|
||||
|
||||
```bash
|
||||
curl -LO https://github.com/Wikid82/charon/releases/download/v1.0.0/sbom.spdx.json
|
||||
```
|
||||
|
||||
**Step 2: View SBOM contents**
|
||||
|
||||
```bash
|
||||
# Pretty-print the SBOM
|
||||
cat sbom.spdx.json | jq .
|
||||
|
||||
# List all packages
|
||||
cat sbom.spdx.json | jq -r '.packages[].name' | sort
|
||||
```
|
||||
|
||||
**Step 3: Check for vulnerabilities**
|
||||
|
||||
```bash
|
||||
# Requires Grype (see prerequisites)
|
||||
grype sbom:sbom.spdx.json
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
|
||||
```
|
||||
NAME INSTALLED VULNERABILITY SEVERITY
|
||||
github.com/caddyserver/caddy/v2 v2.11.0 (no vulnerabilities found)
|
||||
...
|
||||
```
|
||||
|
||||
**What to check:**
|
||||
|
||||
- ✅ SBOM contains expected packages (Go modules, npm packages)
|
||||
- ✅ Package versions match release notes
|
||||
- ✅ No critical or high-severity vulnerabilities
|
||||
- ⚠️ Known acceptable vulnerabilities are documented in SECURITY.md
|
||||
|
||||
**Troubleshooting:**
|
||||
|
||||
- **High/Critical vulnerabilities found** → Check SECURITY.md for known issues and mitigation status
|
||||
- **SBOM format error** → Download may be corrupted, try again
|
||||
- **Missing packages** → SBOM may be incomplete, report as an issue
|
||||
|
||||
---
|
||||
|
||||
## Verify in Your CI/CD Pipeline
|
||||
|
||||
Integrate verification into your deployment workflow:
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
```yaml
|
||||
name: Deploy Charon
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
verify-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3
|
||||
|
||||
- name: Verify Charon Image
|
||||
run: |
|
||||
cosign verify \
|
||||
--certificate-identity-regexp='https://github.com/Wikid82/charon' \
|
||||
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
|
||||
ghcr.io/wikid82/charon:latest
|
||||
|
||||
- name: Deploy
|
||||
if: success()
|
||||
run: |
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Docker Compose with Pre-Pull Verification
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
IMAGE="ghcr.io/wikid82/charon:latest"
|
||||
|
||||
echo "🔍 Verifying image signature..."
|
||||
cosign verify \
|
||||
--certificate-identity-regexp='https://github.com/Wikid82/charon' \
|
||||
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
|
||||
"$IMAGE"
|
||||
|
||||
echo "✅ Signature verified!"
|
||||
echo "🚀 Pulling and starting Charon..."
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
|
||||
echo "✅ Charon started successfully"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Transparency and Audit Trail
|
||||
|
||||
### Sigstore Rekor Transparency Log
|
||||
|
||||
All signatures are recorded in the public Rekor transparency log:
|
||||
|
||||
1. **Visit**: <https://search.sigstore.dev/>
|
||||
2. **Search**: Enter `ghcr.io/wikid82/charon` or a specific tag
|
||||
3. **View Entry**: Click on an entry to see:
|
||||
- Signing timestamp
|
||||
- Git commit SHA
|
||||
- GitHub Actions workflow run ID
|
||||
- Certificate details
|
||||
|
||||
**Why this matters:** The transparency log provides an immutable, public record of all signatures. If a compromise occurs, it can be detected by comparing signatures against the log.
|
||||
|
||||
### GitHub Release Assets
|
||||
|
||||
Each Docker image release includes embedded attestations:
|
||||
|
||||
- **Image Signatures** - Cosign signatures (keyless signing via Sigstore)
|
||||
- **SLSA Provenance** - Build attestation proving the image was built by official GitHub Actions
|
||||
- **SBOM** - Software Bill of Materials attached to the image
|
||||
|
||||
**View releases at**: <https://github.com/Wikid82/charon/releases>
|
||||
|
||||
**Note:** Charon uses a Docker-only deployment model. All artifacts are embedded in container images - no standalone binaries are distributed.
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Before Deploying
|
||||
|
||||
1. ✅ Always verify signatures before first deployment
|
||||
2. ✅ Check SBOM for known vulnerabilities
|
||||
3. ✅ Verify provenance for critical environments
|
||||
4. ✅ Pin to specific version tags (not `latest`)
|
||||
|
||||
### During Operations
|
||||
|
||||
1. ✅ Set up automated verification in CI/CD
|
||||
2. ✅ Monitor SECURITY.md for vulnerability updates
|
||||
3. ✅ Subscribe to GitHub release notifications
|
||||
4. ✅ Re-verify after any manual image pulls
|
||||
|
||||
### For Production Environments
|
||||
|
||||
1. ✅ Require signature verification before deployment
|
||||
2. ✅ Use admission controllers (e.g., Kyverno, OPA) to enforce verification
|
||||
3. ✅ Maintain audit logs of verified deployments
|
||||
4. ✅ Scan SBOM against private vulnerability databases
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "cosign: command not found"
|
||||
|
||||
**Solution:** Install Cosign (see Prerequisites section)
|
||||
|
||||
#### "Error: no matching signatures"
|
||||
|
||||
**Possible causes:**
|
||||
|
||||
- Image tag doesn't exist
|
||||
- Image was pulled before signing implementation
|
||||
- Using an unofficial image source
|
||||
|
||||
**Solution:** Use official images from `ghcr.io/wikid82/charon` with tags v1.0.0 or later
|
||||
|
||||
#### "Error: certificate identity doesn't match"
|
||||
|
||||
**Possible causes:**
|
||||
|
||||
- Image is from an unofficial source
|
||||
- Image may be compromised
|
||||
|
||||
**Solution:** Only use images from the official repository. Report suspicious images.
|
||||
|
||||
#### Grype shows vulnerabilities
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Check SECURITY.md for known issues
|
||||
2. Review vulnerability severity and exploitability
|
||||
3. Check if patches are available in newer releases
|
||||
4. Report new vulnerabilities via GitHub Security Advisory
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Documentation**: [Developer Guide](supply-chain-security-developer-guide.md)
|
||||
- **Security Issues**: <https://github.com/Wikid82/charon/security/advisories>
|
||||
- **Questions**: <https://github.com/Wikid82/charon/discussions>
|
||||
- **Bug Reports**: <https://github.com/Wikid82/charon/issues>
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **[Sigstore Documentation](https://docs.sigstore.dev/)** - Learn about keyless signing
|
||||
- **[SLSA Framework](https://slsa.dev/)** - Supply chain security levels
|
||||
- **[SPDX Specification](https://spdx.dev/)** - SBOM format details
|
||||
- **[Rekor Transparency Log](https://docs.sigstore.dev/rekor/overview/)** - Audit trail documentation
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: January 10, 2026
|
||||
**Version**: 1.0
|
||||
Reference in New Issue
Block a user