No description
  • Go 96.2%
  • Makefile 3%
  • Dockerfile 0.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2025-12-31 00:22:33 +01:00
.github/workflows productionized version 2025-12-31 00:22:33 +01:00
cmd/dns-pihole productionized version 2025-12-31 00:22:33 +01:00
deploy productionized version 2025-12-31 00:22:33 +01:00
internal productionized version 2025-12-31 00:22:33 +01:00
pkg/models productionized version 2025-12-31 00:22:33 +01:00
.dockerignore productionized version 2025-12-31 00:22:33 +01:00
.gitignore productionized version 2025-12-31 00:22:33 +01:00
.golangci.yml init 2025-12-31 00:03:21 +01:00
docker-compose.test.yml init 2025-12-31 00:03:21 +01:00
Dockerfile productionized version 2025-12-31 00:22:33 +01:00
e2e_test.go productionized version 2025-12-31 00:22:33 +01:00
go.mod init 2025-12-31 00:03:21 +01:00
go.sum init 2025-12-31 00:03:21 +01:00
Makefile productionized version 2025-12-31 00:22:33 +01:00
mise.toml init 2025-12-31 00:03:21 +01:00
README.md productionized version 2025-12-31 00:22:33 +01:00

Pi-hole DNS Backend

A DNS provider backend for the homelab-dns-operator that integrates with Pi-hole's custom DNS API.

Overview

This backend implements the DNS Operator webhook protocol, allowing the DNS operator to manage DNS records through Pi-hole's custom DNS feature. It provides REST endpoints for creating, updating, deleting, and querying DNS records.

Features

  • Full implementation of DNS Operator webhook protocol
  • Support for A and AAAA records
  • HMAC authentication support
  • Health check endpoint
  • Idempotent operations
  • Comprehensive error handling

Prerequisites

  • Go 1.21 or later (managed via mise)
  • Pi-hole instance with API access enabled
  • Pi-hole API password (configured via FTLCONF_webserver_api_password in Docker or web interface)

Configuration

The backend is configured via environment variables:

Variable Description Required Default
PORT HTTP server port No 7100
PIHOLE_URL Base URL of Pi-hole instance Yes -
PIHOLE_PASSWORD Pi-hole API password Yes -
HMAC_SECRET HMAC secret for request authentication No -

Getting Pi-hole API Password

The backend uses password-based authentication with Pi-hole's REST API. You can set the password in one of two ways:

Docker Deployment

Set the FTLCONF_webserver_api_password environment variable when starting Pi-hole:

docker run -e FTLCONF_webserver_api_password="your-password" pihole/pihole

Web Interface

  1. Log into your Pi-hole web interface
  2. Navigate to Settings → API / Web interface
  3. Set or change the API password
  4. Use this password in the PIHOLE_PASSWORD environment variable

Building

Using mise

# Install dependencies
make deps

# Build the application
make build

Manual build

go build -o dns-pihole ./cmd/dns-pihole

Docker Image

Pre-built multi-architecture Docker images are available on GitHub Container Registry:

docker pull ghcr.io/morten-olsen/homelab-dns-pihole:latest

Images are automatically built for linux/amd64 and linux/arm64 architectures.

Running

Local development

export PIHOLE_URL="http://pihole.local:80"
export PIHOLE_PASSWORD="your-api-password"
export HMAC_SECRET="your-hmac-secret"  # Optional
export PORT="7100"  # Optional

./dns-pihole

Docker

# Build the image
make docker-build

# Run the container
docker run -p 7100:7100 \
  -e PIHOLE_URL=http://pihole:80 \
  -e PIHOLE_PASSWORD=your-password \
  -e HMAC_SECRET=your-secret \
  dns-pihole:latest

Kubernetes

See the deploy/ directory for Kubernetes deployment examples.

API Endpoints

Health Check

GET /health

Returns the health status of the backend and Pi-hole connectivity.

Response:

{
  "status": "healthy",
  "message": "DNS server is operational",
  "timestamp": "2025-12-30T10:00:00Z"
}

Create/Update Record

POST /records

Creates or updates a DNS record.

Request:

{
  "record": {
    "type": "A",
    "domain": "example.com",
    "subdomain": "www",
    "values": ["192.168.1.100"],
    "ttl": 600
  },
  "operation": "upsert"
}

Response:

{
  "success": true,
  "record": {
    "type": "A",
    "domain": "example.com",
    "subdomain": "www",
    "fqdn": "www.example.com",
    "values": ["192.168.1.100"],
    "ttl": 600
  },
  "message": "Record created successfully"
}

Get Record

GET /records/{type}/{domain}/{subdomain}

Retrieves a DNS record.

Response:

{
  "success": true,
  "record": {
    "type": "A",
    "domain": "example.com",
    "subdomain": "www",
    "fqdn": "www.example.com",
    "values": ["192.168.1.100"],
    "ttl": 600
  }
}

Delete Record

DELETE /records/{type}/{domain}/{subdomain}

Deletes a DNS record.

Response:

{
  "success": true,
  "message": "Record deleted successfully"
}

Limitations

  • CNAME Records: Pi-hole's custom DNS feature does not support CNAME records. The backend will return an error if you attempt to create a CNAME record. Use A records instead.
  • Multiple IPs: Pi-hole custom DNS only supports a single IP address per domain. If multiple values are provided, only the first one will be used.
  • TTL: Pi-hole custom DNS does not support custom TTL values. The TTL field in requests is accepted but not applied.

Testing

The project includes comprehensive unit tests with 72%+ code coverage and end-to-end tests against a real Pi-hole instance.

Running Tests

# Run unit tests
make test

# Run tests with coverage report
make test-coverage

# Run tests with race detector
make test-race

# Run e2e tests (requires Docker)
make test-e2e

# Run all tests (unit + e2e)
make test-all

Unit Test Coverage

The unit test suite covers:

  • HTTP handlers (health, upsert, get, delete)
  • Pi-hole API client integration (mocked)
  • HMAC authentication and verification
  • Request validation (IPv4, IPv6, CNAME)
  • Error handling and edge cases
  • Server middleware and routing

End-to-End Tests

The e2e tests run against a real Pi-hole instance in Docker:

  • Health check against real Pi-hole
  • Create and retrieve A/AAAA records
  • Update existing records
  • Delete records (including idempotent behavior)
  • Full HTTP endpoint integration

The e2e tests use Docker Compose to spin up a Pi-hole instance for testing. See docker-compose.test.yml for the test environment configuration.

Manual Testing

# Test health endpoint
curl http://localhost:7100/health

# Test creating a record (with HMAC if configured)
curl -X POST http://localhost:7100/records \
  -H "Content-Type: application/json" \
  -H "X-DNS-Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  -H "X-DNS-Nonce: $(uuidgen)" \
  -H "X-DNS-Signature: <calculated-hmac-signature>" \
  -d '{
    "record": {
      "type": "A",
      "domain": "example.com",
      "subdomain": "test",
      "values": ["192.168.1.100"]
    },
    "operation": "upsert"
  }'

Security

  • HMAC Authentication: When HMAC_SECRET is configured, all requests must include valid HMAC signatures in the X-DNS-Signature header.
  • API Password: The Pi-hole API password should be stored securely (e.g., Kubernetes secrets).
  • Network: Consider running the backend in the same network as Pi-hole or using TLS for communication.

CI/CD

The project uses GitHub Actions for continuous integration and deployment:

Test Workflow (.github/workflows/test.yml)

Runs on every push and pull request:

  • Unit tests with race detector (Go 1.21 and 1.22)
  • Linting with golangci-lint
  • End-to-end tests against Dockerized Pi-hole
  • Code coverage reports uploaded as artifacts

Build Workflow (.github/workflows/build.yml)

Builds and pushes multi-architecture Docker images:

  • Multi-architecture builds (linux/amd64, linux/arm64)
  • Pushes to GitHub Container Registry (ghcr.io)
  • Automatic tagging:
    • latest for main branch
    • main-<sha> for commits
    • Semantic version tags (v1.0.0, v1.0, v1) for releases
  • Docker layer caching for faster builds
  • OCI image labels with metadata

Image Registry: ghcr.io/morten-olsen/homelab-dns-pihole

See .github/workflows/README.md for detailed workflow documentation.

Development

Linting

The project uses golangci-lint for code quality checks.

# Install golangci-lint (if not already installed)
make install-lint

# Run linters
make lint

# Run linters and auto-fix issues
make lint-fix

The linting configuration is in .golangci.yml. Common checks include:

  • Error handling verification
  • Code simplification
  • Security checks
  • Unused code detection
  • Code formatting

Project Structure

The project follows the standard Go project layout:

.
├── cmd/
│   └── dns-pihole/
│       └── main.go              # Application entry point
├── internal/
│   ├── auth/
│   │   ├── hmac.go              # HMAC authentication
│   │   └── hmac_test.go         # HMAC tests
│   ├── handlers/
│   │   └── handlers.go          # HTTP request handlers
│   ├── pihole/
│   │   ├── client.go            # Pi-hole API client
│   │   ├── interface.go         # Pi-hole client interface
│   │   ├── mock.go              # Mock Pi-hole client for testing
│   │   └── pihole_test.go       # Pi-hole client tests
│   └── server/
│       ├── server.go            # HTTP server setup and routing
│       └── server_test.go       # Server tests
├── pkg/
│   └── models/
│       └── records.go           # Shared data models
├── deploy/                      # Kubernetes deployment manifests
├── e2e_test.go                  # End-to-end tests
├── go.mod                       # Go module definition
├── Dockerfile                   # Docker build configuration
├── Makefile                     # Build automation
└── README.md                    # Project documentation

License

See LICENSE file for details.

Contributing

Contributions are welcome! Please open an issue or submit a pull request.