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.
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 }} - 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 - 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 }} - 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 }} - 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 }}Install all provider packages your config uses:
- run: pip install octorules-cloudflare octorules-aws octorules-google octorules-azure octorules-bunnyProvider 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.
Path, relative to your repository root, of the config file you would like octorules to use.
Default "config.yaml".
Set "--doit" to apply changes, leave empty to plan only. Only "" and
"--doit" are accepted — any other value fails immediately.
Default "" (plan only).
Set "Yes" to bypass safety thresholds, leave empty to enforce them. Only ""
and "Yes" are accepted.
Default "" (thresholds enforced).
Pass a plan checksum to octorules sync for drift protection. The workflow
is:
octorules plancomputes a SHA-256 hash of the planned changes and printschecksum=<hex>in its log output.- Between plan and sync, the remote state might change (another deploy, manual edit, API-side update). This is "drift".
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 }}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).
Space separated list of zones to sync, leave empty to sync all zones in the config file.
Default "" (empty string, all zones).
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".
Minimum lint severity to report: "error", "warning", or "info".
Default "warning".
Plan tier override for Cloudflare lint entitlement checks: "free", "pro", "business", "enterprise". Non-Cloudflare providers ignore this setting.
Default "" (defaults to "enterprise").
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".
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").
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".
Space-separated list of audit checks to run: ip-overlap, ip-shadow, cdn-ranges, zone-drift. Leave empty for all checks.
Default "" (all checks).
Minimum audit severity: "error", "warning", or "info". Controls which findings are displayed. Default: "warning".
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).
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.
The octorules command log output. Also written to $GITHUB_WORKSPACE/octorules-sync.log.
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"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.
Exit code from octorules lint: 0 = clean, 1 = errors, 2 = warnings only, empty = lint disabled.
Lint results text. Empty when lint is disabled or clean.
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 text. Empty when audit is disabled or clean.
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.
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.
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.
To have this action post the plan as a PR comment, configure your workflow to:
- Run on the
pull_requestevent - Set
add_pr_commentto"Yes" - 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 }}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 }}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 }}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 }}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.txtEnsure 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).
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).
If the action fails to create or update PR comments, check that:
- You passed a valid token via
pr_comment_token(e.g.${{ github.token }}). - The token has
pull_requests: writepermission. For same-repo PRs, the defaultgithub.tokenis sufficient. - For fork PRs or cross-repo workflows, use a Personal Access Token or GitHub App token with appropriate scopes.
Contributions are welcome!
-
Fork and clone the repository.
-
Install the development dependencies:
# macOS brew install shellcheck yamllint bats-core # Debian / Ubuntu apt-get install shellcheck yamllint bats
-
Run the full check suite:
make
-
Create a branch from
main. -
Make your changes. If you modify
scripts/*.sh, add or update the corresponding tests intests/. -
Run linting and tests before committing:
make lint # yamllint + shellcheck make test # bats tests/
-
Open a pull request against
main.
- Shell scripts follow ShellCheck recommendations.
- YAML files must pass
yamllint --no-warnings. - Use
set -euo pipefailin new scripts. - Prefer arrays for command construction (see
scripts/run.shfor the pattern).
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.
octorules-sync is licensed under the Apache License 2.0.