Cloudflare DDNS

repository·main·Indexed 25 days ago

https://github.com/favonia/cloudflare-ddns

A robust updater that detects a machine's public IP addresses and updates Cloudflare DNS records and WAF lists via the Cloudflare API. It supports cron-based scheduling, single-run mode, and advanced detection filters using a boolean expression language. The tool features a multi-stage configuration lifecycle and a structured update-round model consisting of detection, derivation, and reconciliation phases.

Tokens
16.7K
Snippets
24
Records
89
Agent score
78%

What's inside cloudflare-ddns

  1. Understand the Network Security Model for public-IP detection

    main

    The security model for cloudflare-ddns focuses on the correctness of the raw data used for public-IP detection. The primary goal is to prevent an attacker from forcing the updater to publish an attacker-chosen IP address in managed DNS records or WAF content.

    Trust Boundary and Attacker Model

    • Trust Boundary: The network path used by public-IP detection.
    • Attacker Model: The system is designed to resist off-path attacks (where an attacker injects traffic without controlling the route).
    • Limitation: If an attacker is on-path (can observe or alter traffic on the route), no DDNS updater can guarantee security through application-layer logic alone.

    Security Protections

    • The updater uses HTTPS for connections to Cloudflare. This provides higher resistance to off-path packet forgery and DNS spoofing compared to traditional DNS-based public-IP detection.

    Unsafe Scenarios (When to use Static IPs instead)

    Do not rely on this updater if an attacker can become on-path for the network path. Secure public-IP detection is impossible in the following scenarios:

    1. Unsafe Wi-Fi: Using networks like WPA2 Enterprise without server identity verification.
    2. Traffic Interception: If an adversary can intercept traffic near Cloudflare's servers.
    3. Network Path Access: If an adversary can access the physical cable, broader network path, or country-scale firewalls.

    Note: HTTPS protects the content of the communication but does not protect source or destination IP addresses.

  2. Understand the Reconciliation Algorithm

    main

    The reconciliation algorithm is the core logic used to synchronize managed resources (like DNS records or WAF rules) with a desired state. It is designed to be a 'satisfier' rather than a full canonicalizer, meaning it prioritizes preserving existing objects that already meet requirements to minimize unnecessary mutations.

    Intent Handling

    The algorithm behaves differently based on the reconciliation intent:

    • preserve: Keep all existing managed content unchanged (resource is out of scope).
    • abort: Keep all existing managed content unchanged (resource is in scope, but data was unavailable).
    • clear: Proceed with an empty desired target set.
    • update: Proceed with the derived desired target set.

    Core Reconciliation Logic

    For every managed resource unit, the algorithm follows these steps:

    1. Keep owned objects that already satisfy at least one desired target.
    2. Recycle remaining owned objects to satisfy uncovered desired targets.
    3. Create new objects only if recycling cannot satisfy all desired targets.
    4. Delete leftover owned objects that satisfy no desired target.
  3. Understand the Cloudflare DDNS Lifecycle Model

    main

    The project follows a structured lifecycle model divided into two timelines: the process lifecycle (startup, waiting/triggering, and shutdown) and the update-round lifecycle (detection, derivation, reconciliation). This model ensures that raw data is validated and admissible before any mutations occur on remote resources.

    Core Phases

    1. Startup: Prepares the runtime (output, reporters, config validation, API handles).
    2. Waiting and Triggering: The process waits for the next trigger via startup policy or cron scheduling.
    3. Detection: Yields reconciliation intents based on observed raw data.
    4. Derivation: Transforms admissible raw data into resource-specific targets (e.g., DNS addresses or WAF prefixes).
    5. Reconciliation: Mutates managed remote state toward the desired result based on intents and targets.
    6. Cleanup: Performs shutdown-time mutations, deleting resources eligible for deletion upon process stop.
  4. Understand the Ownership Model for DNS and WAF management

    main

    The project uses an ownership model to determine which DNS records, WAF lists, and IP addresses the updater is permitted to manage, mutate, or delete. Ownership is determined by the intersection of three static predicates: Resource Ownership, IP-Family Ownership, and Attribute-Based Ownership. If a resource does not satisfy all three, it is considered out of scope.

    Resource Ownership

    Determines if a DNS domain or WAF list is a managed resource root. If a domain or list is not explicitly configured, it is out of scope for all operations.

    IP-Family Ownership

    Determines if an IP family (IPv4 or IPv6) is in scope.

    • Set IP4_PROVIDER=none or IP6_PROVIDER=none to exclude that specific family from management.
    • Any other provider mode enables management for that family.

    Attribute-Based Ownership

    Determines which specific records or WAF items are recognized as belonging to the updater. This is typically handled via comments. To prevent the updater from orphaning its own objects, write-side values must satisfy the ownership selectors:

    • For DNS: Uses RECORD_COMMENT.
    • For WAF: Uses WAF_LIST_ITEM_COMMENT.

    Deletion Eligibility

    An item is only eligible for deletion during a shutdown/reconciliation if the updater can recreate the fully reconciled state of that resource using only the current configuration.

  5. Configure Toolchain and CI for Formal Verification

    main

    To ensure reliable verification, follow these toolchain and CI requirements:

    • Pin the Prover: Pin the exact prover version in proofs/lean-toolchain. Upgrades must be deliberate.
    • Verification CI Job: Implement a path-filtered CI job that:
      • Installs the pinned prover.
      • Builds all proofs and enforces a zero-sorry gate (no unproven goals).
      • Builds the oracle.
      • Runs the build-tagged differential tests.
    • Normal CI Job: The standard go test job should run always-on tests that do not depend on the prover.
  6. Format operator messages for different output channels

    main

    When generating logs or notifications, adjust the message shape based on the target channel:

    • Heartbeat messages: Use terse, compact status lines. For multi-item lists, use compact joins like a, b.
    • Notifier messages: Use prose-like summaries. When combining multiple outcomes, join follow-up fragments with semicolons and end the entire message with a trailing period. For lists, use English joins like a and b or a, b, and c.
    • Runtime messages (Noticef, Infof): Keep these compact and omit the trailing period.
  7. Deploy with Docker Compose

    main

    To deploy the Cloudflare DDNS updater using Docker Compose, add the service definition to your docker-compose.yml file. It is recommended to use a specific major version tag (e.g., favonia/cloudflare-ddns:1) for production stability rather than latest or edge.

    services:
      cloudflare-ddns:
        image: favonia/cloudflare-ddns:1
        network_mode: host
        restart: always
        user: "1000:1000"
        read_only: true
        cap_drop: [all]
        security_opt: [no-new-privileges:true]
        environment:
          - CLOUDFLARE_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN
          - DOMAINS=example.org,www.example.org,example.io
          - PROXIED=true
  8. Quick Start: Run Cloudflare DDNS via Docker

    main

    You can run the Cloudflare DDNS updater directly using Docker. This requires a Cloudflare API token with Zone - DNS - Edit and Account - Account Filter Lists - Edit permissions. Using --network host ensures the container can accurately detect the host machine's public IP addresses.

    docker run \
      --network host \
      -e CLOUDFLARE_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \
      -e DOMAINS=example.org,www.example.org,example.io \
      -e PROXIED=true \
      favonia/cloudflare-ddns:1
  9. Use Docker secrets for Cloudflare API Tokens

    main

    To avoid storing the Cloudflare API token in plain text within your Compose file or .env file, use Docker secrets. Set the CLOUDFLARE_API_TOKEN_FILE environment variable to the path where the secret is mounted. Ensure the token file is readable by the user specified by user: "UID:GID".

    services:
      cloudflare-ddns:
        environment:
          - CLOUDFLARE_API_TOKEN_FILE=/run/secrets/cloudflare_api_token
        secrets:
          - cloudflare_api_token
    
    secrets:
      cloudflare_api_token:
        file: ./secrets/cloudflare_api_token.txt
  10. Understand the linting policy for GitHub Action scripts

    main

    Standalone Go modules located under scripts/github-actions/* are permitted to define their own module-local .golangci.yaml files. This allows these scripts to have a linting policy tailored to their specific, standalone purpose rather than following the repository-wide configuration.

    When to use module-local .golangci.yaml:

    • For stable, module-wide exceptions that arise from the script's context.
    • When relaxing rules that primarily address ceremony or refactoring pressure specific to small, standalone runner shapes.
    • When scale-sensitive maintainability or style rules do not materially impact the project's core priorities (correctness, security, resilience, and operator-clarity).

    When NOT to use module-local .golangci.yaml:

    • Repository-wide judgments: These must be placed in the root .golangci.yaml.
    • Statement-local exceptions: Use inline suppression //nolint:<linter> // reason for specific declarations or statements.
    • Relaxing core protections: Do not relax linters that protect security, failure handling, or operator-facing clarity.
  11. Test a new setup with explicit IPs

    main

    To validate the updater without waiting for a real IP change, use the static:<ip> provider. This allows you to point the updater at dedicated test domains and feed it fixed IP addresses. Note that static is an advanced provider intended for testing and debugging, not for long-running production DDNS.

    environment:
      - DOMAINS=ddns-test.example.org
      - IP4_PROVIDER=static:203.0.113.10
      - IP6_PROVIDER=static:2001:db8::10