Skip to content

Repository files navigation

octorules-sync

This action runs octorules from doctena-org/octorules to deploy your WAF rules config.

octorules allows you to manage WAF and security rules (redirects, rewrites, headers, cache, WAF, rate limiting, and more) as YAML files and publish changes via your provider's API. Supported providers: Cloudflare, AWS WAF, Google Cloud Armor, Azure WAF, Bunny.net.

When you manage your octorules configuration in a GitHub repository, this GitHub Action allows you to test and publish your changes automatically using a workflow you define.

Example workflows

Cloudflare

name: octorules-sync

on:
  push:
    branches:
      - main
    paths:
      - 'rules/**'
      - 'config.yaml'

jobs:
  publish:
    name: Publish rules from main
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
      - run: pip install octorules-cloudflare
      - uses: doctena-org/octorules-sync@v1
        with:
          config_path: config.yaml
          doit: '--doit'
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

AWS WAF

      - run: pip install octorules-aws
      - uses: doctena-org/octorules-sync@v1
        with:
          config_path: config.yaml
          doit: '--doit'
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: eu-central-1

Google Cloud Armor

      - run: pip install octorules-google
      - uses: doctena-org/octorules-sync@v1
        with:
          config_path: config.yaml
          doit: '--doit'
        env:
          GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}

Azure WAF

      - run: pip install octorules-azure
      - uses: doctena-org/octorules-sync@v1
        with:
          config_path: config.yaml
          doit: '--doit'
        env:
          AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}

Bunny.net

      - run: pip install octorules-bunny
      - uses: doctena-org/octorules-sync@v1
        with:
          config_path: config.yaml
          doit: '--doit'
        env:
          BUNNY_API_KEY: ${{ secrets.BUNNY_API_KEY }}

Multi-provider

Install all provider packages your config uses:

      - run: pip install octorules-cloudflare octorules-aws octorules-google octorules-azure octorules-bunny

Inputs

Secrets

Provider credentials are passed as environment variables using encrypted secrets. The secret name and config syntax depend on your provider:

Provider Secret example Config syntax
Cloudflare CLOUDFLARE_API_TOKEN token: env/CLOUDFLARE_API_TOKEN
AWS WAF Standard boto3 credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) or IAM role No token needed — boto3 credential chain
Google Cloud Armor GOOGLE_APPLICATION_CREDENTIALS or Workload Identity No token needed — Application Default Credentials
Azure WAF AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET No token needed — Azure SDK credential chain
Bunny.net BUNNY_API_KEY token: env/BUNNY_API_KEY

The env/ prefix in config values resolves environment variables at runtime. See each provider's README for full authentication details.

config_path

Path, relative to your repository root, of the config file you would like octorules to use.

Default "config.yaml".

doit

Set "--doit" to apply changes, leave empty to plan only. Only "" and "--doit" are accepted — any other value fails immediately.

Default "" (plan only).

force

Set "Yes" to bypass safety thresholds, leave empty to enforce them. Only "" and "Yes" are accepted.

Default "" (thresholds enforced).

checksum

Pass a plan checksum to octorules sync for drift protection. The workflow is:

  1. octorules plan computes a SHA-256 hash of the planned changes and prints checksum=<hex> in its log output.
  2. Between plan and sync, the remote state might change (another deploy, manual edit, API-side update). This is "drift".
  3. octorules sync --checksum <hex> re-plans internally and compares the new hash against the provided one. If they differ, sync aborts with an error instead of applying stale changes.

Only used when doit is "--doit". Default "" (empty string, no checksum verification).

Example workflow:

- uses: doctena-org/octorules-sync@v1
  id: plan
  with:
    config_path: config.yaml

- uses: doctena-org/octorules-sync@v1
  with:
    config_path: config.yaml
    doit: "--doit"
    checksum: ${{ steps.plan.outputs.checksum }}

phases

Space separated list of rule phases to sync. Leave empty to sync all phases in the config file. Useful for targeting specific rule types.

Available phases depend on the configured provider. See each provider's README for the full list: Cloudflare (23 phases) | AWS WAF (4 phases) | Google Cloud Armor (4 phases) | Azure WAF (3 phases) | Bunny.net (4 phases)

Default "" (empty string, all phases).

zones

Space separated list of zones to sync, leave empty to sync all zones in the config file.

Default "" (empty string, all zones).

lint

Run octorules lint before plan/sync? Set "Yes" to enable. When enabled, the linter runs before the plan/sync step. Lint errors (exit code 1) block sync mode from applying changes; warnings do not.

Default "No".

lint_severity

Minimum lint severity to report: "error", "warning", or "info".

Default "warning".

lint_plan

Plan tier override for Cloudflare lint entitlement checks: "free", "pro", "business", "enterprise". Non-Cloudflare providers ignore this setting.

Default "" (defaults to "enterprise").

add_pr_comment

Add plan as a comment, when triggered by a pull request? Set "Yes" to enable.

When enabled, this action creates a single PR comment with the plan output and updates it in place on subsequent runs (instead of creating a new comment each time).

Default "No".

pr_comment_token

Provide a token to use, if you set add_pr_comment to "Yes".

Important: No token is automatically injected — you must explicitly pass a token (e.g. ${{ github.token }}). The default github.token has sufficient permissions for same-repo PRs. For cross-repo or fork PRs, use a token with pull_requests: write scope.

Default "" (empty string, must be provided when add_pr_comment is "Yes").

audit

Run octorules audit (IP overlap, IP shadow, CDN range, zone drift analysis) before plan/sync? Set "Yes" to enable. When enabled, audit findings are reported in the action output. Only errors (exit code 1) block sync; warnings (exit code 2) are reported but do not prevent deployment.

Default "No".

audit_checks

Space-separated list of audit checks to run: ip-overlap, ip-shadow, cdn-ranges, zone-drift. Leave empty for all checks.

Default "" (all checks).

audit_severity

Minimum audit severity: "error", "warning", or "info". Controls which findings are displayed. Default: "warning".

audit_log

Path to write a JSON-lines audit log of sync results. Only used when doit is "--doit" (sync mode). Each line is a JSON object with the sync operation details (zone, phase, action, result). Useful for compliance and post-sync analysis.

Default "" (disabled).

Outputs

plan

The planned changes output from octorules. The output format is controlled via manager.plan_outputs in your octorules config file. Also written to $GITHUB_WORKSPACE/octorules-sync.plan.

log

The octorules command log output. Also written to $GITHUB_WORKSPACE/octorules-sync.log.

exit_code

The raw exit code from the octorules command. Useful for branching in downstream steps:

Mode Code Meaning
plan 0 No changes detected
plan 2 Changes detected
plan 1 Error
sync 0 Applied successfully
sync 1 Error

Example usage in a subsequent step:

- name: Notify on changes
  if: steps.octorules.outputs.exit_code == '2'
  run: echo "Changes were detected"

checksum

SHA-256 plan checksum for drift protection (plan mode only). Pass this value to a subsequent sync step via the checksum input to ensure the state hasn't drifted between plan and apply. Empty when running in sync mode or if no checksum was emitted.

lint_exit_code

Exit code from octorules lint: 0 = clean, 1 = errors, 2 = warnings only, empty = lint disabled.

lint_results

Lint results text. Empty when lint is disabled or clean.

audit_exit_code

Exit code from octorules audit: 0 = clean, 1 = errors, 2 = warnings only, empty = audit disabled. Only exit code 1 (errors) blocks sync; warnings are reported but do not prevent deployment.

audit_results

Audit results text. Empty when audit is disabled or clean.

Auditing

When audit is set to "Yes", the action runs octorules audit before the plan/sync step. Audit findings are included in PR comments (when enabled) and exposed via the audit_exit_code and audit_results outputs.

  • Plan mode: audit errors are reported but the plan still runs, so you can see both audit findings and planned changes. The action fails at the end if audit found errors.
  • Sync mode: audit errors block the sync step entirely — changes are not applied. Warnings do not block sync.

Linting

When lint is set to "Yes", the action runs octorules lint --exit-code before the plan/sync step. Lint results are included in PR comments (when enabled) and exposed via the lint_exit_code and lint_results outputs.

  • Plan mode: lint errors are reported but the plan still runs, so you can see both lint issues and planned changes. The action fails at the end if lint found errors.
  • Sync mode: lint errors block the sync step entirely — changes are not applied. Warnings do not block sync.

Wirefilter support (Cloudflare)

When linting Cloudflare rules, expressions are parsed by Cloudflare's real wirefilter engine, which ships as a required dependency of octorules-cloudflare — no extra install step. This catches syntax errors, unknown or misspelled fields, type mismatches, invalid operator usage, and unknown function names that name-only heuristics cannot.

Non-Cloudflare providers (AWS WAF, Google Cloud Armor, Azure WAF, Bunny.net) include their own expression validation and do not need wirefilter.

Pull request plan comments

To have this action post the plan as a PR comment, configure your workflow to:

  1. Run on the pull_request event
  2. Set add_pr_comment to "Yes"
  3. Provide a pr_comment_token

The action will create a single comment and update it in place on subsequent pushes to the same PR. Comment deduplication uses a hidden HTML marker (<!-- octorules-sync-plan -->) to find and update existing comments.

name: octorules-plan

on:
  pull_request:

jobs:
  plan:
    name: Plan WAF rules changes
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
      - run: pip install -r requirements.txt
      - uses: doctena-org/octorules-sync@v1
        with:
          config_path: config.yaml
          lint: 'Yes'
          audit: 'Yes'
          add_pr_comment: 'Yes'
          pr_comment_token: '${{ github.token }}'
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Full workflow example

A common pattern is to plan on pull requests and apply on merge to main:

name: octorules-sync

on:
  pull_request:
    paths:
      - 'rules/**'
      - 'config.yaml'
  push:
    branches:
      - main
    paths:
      - 'rules/**'
      - 'config.yaml'

jobs:
  plan:
    if: github.event_name == 'pull_request'
    name: Plan changes
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
      - run: pip install -r requirements.txt
      - uses: doctena-org/octorules-sync@v1
        id: octorules
        with:
          config_path: config.yaml
          lint: 'Yes'
          audit: 'Yes'
          add_pr_comment: 'Yes'
          pr_comment_token: '${{ github.token }}'
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

  deploy:
    if: github.event_name == 'push'
    name: Deploy changes
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
      - run: pip install -r requirements.txt
      - uses: doctena-org/octorules-sync@v1
        with:
          config_path: config.yaml
          doit: '--doit'
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Advanced: Phase filtering

To sync only specific rule phases, use the phases input:

- uses: doctena-org/octorules-sync@v1
  with:
    config_path: config.yaml
    doit: '--doit'
    phases: 'cache_rules redirect_rules'
  env:
    CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Advanced: Checksum drift protection

The action outputs a checksum from octorules plan --checksum. Pass it to a subsequent sync step to ensure the state hasn't drifted between plan and apply:

jobs:
  plan:
    name: Plan
    runs-on: ubuntu-latest
    outputs:
      checksum: ${{ steps.plan.outputs.checksum }}
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
      - run: pip install -r requirements.txt
      - uses: doctena-org/octorules-sync@v1
        id: plan
        with:
          config_path: config.yaml
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

  deploy:
    needs: plan
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
      - run: pip install -r requirements.txt
      - uses: doctena-org/octorules-sync@v1
        with:
          config_path: config.yaml
          doit: '--doit'
          checksum: '${{ needs.plan.outputs.checksum }}'
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Troubleshooting

octorules not found on PATH

The action requires octorules and a provider package to be installed before it runs. Add an install step to your workflow:

# Cloudflare
- run: pip install octorules-cloudflare

# AWS WAF
- run: pip install octorules-aws

# Google Cloud Armor
- run: pip install octorules-google

# Azure WAF
- run: pip install octorules-azure

# Bunny.net
- run: pip install octorules-bunny

# Multiple providers
- run: pip install octorules-cloudflare octorules-aws

# Or use a requirements.txt
- run: pip install -r requirements.txt

Provider token / invalid token errors

Ensure you have created a repository secret for your provider credentials and passed it as an environment variable. See the Secrets section above for provider-specific examples. Config values that reference environment variables must use the env/ prefix (e.g. token: env/CLOUDFLARE_API_TOKEN).

Config file not found

The config_path input defaults to config.yaml in your repository root. If your config is elsewhere, set config_path accordingly. The path is relative to the repository root (workspace).

PR comment permission errors

If the action fails to create or update PR comments, check that:

  1. You passed a valid token via pr_comment_token (e.g. ${{ github.token }}).
  2. The token has pull_requests: write permission. For same-repo PRs, the default github.token is sufficient.
  3. For fork PRs or cross-repo workflows, use a Personal Access Token or GitHub App token with appropriate scopes.

Contributing

Contributions are welcome!

Development setup

  1. Fork and clone the repository.

  2. Install the development dependencies:

    # macOS
    brew install shellcheck yamllint bats-core
    
    # Debian / Ubuntu
    apt-get install shellcheck yamllint bats
  3. Run the full check suite:

    make

Making changes

  1. Create a branch from main.

  2. Make your changes. If you modify scripts/*.sh, add or update the corresponding tests in tests/.

  3. Run linting and tests before committing:

    make lint   # yamllint + shellcheck
    make test   # bats tests/
  4. Open a pull request against main.

Code style

  • Shell scripts follow ShellCheck recommendations.
  • YAML files must pass yamllint --no-warnings.
  • Use set -euo pipefail in new scripts.
  • Prefer arrays for command construction (see scripts/run.sh for the pattern).

Releases

Releases are automated. Push a semver tag (v1.2.3) to create a GitHub Release. Publishing the release updates the major version tag (v1) automatically.

Update CHANGELOG.md with your changes under the [Unreleased] section before tagging.

License

octorules-sync is licensed under the Apache License 2.0.

About

GitHub Action to run octorules plan/sync

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages