Skip to content

Deleted policy in API source type is not propagated to client #900

@JockiHendry

Description

@JockiHendry

Describe the bug
When using API policy source type, if a policy file is deleted in bundle.tar.gz, the deletion is not propagated to client.

To Reproduce
For example, this is a setup where bundle.tar.gz contains policy1.cedar and policy2.cedar, then after 40 seconds, the policy2.cedar file is deleted:

services:
  policies:
    image: python:3.12-alpine
    ports:
      - "5555:5555"
    command: |
      sh -c '
        set -e
        POLICY="permit (principal, action, resource) when { false };"
        echo "$$POLICY" > policy1.cedar
        echo "$$POLICY" > policy2.cedar
        tar -czf bundle.tar.gz *.cedar
      
        # Start a background timer to remove the file after 40 seconds
        sh -c "sleep 40 && rm -v policy2.cedar && tar -czf bundle.tar.gz *.cedar" &
      
        echo "Serving on port 5555..."
        python3 -m http.server --bind 0.0.0.0 5555
      '
  opal_server:
    image: permitio/opal-server:0.9.4-alpine
    environment:
      - OPAL_BROADCAST_URI=postgres://opal:opal@opal_broadcast:5432/opal
      - UVICORN_NUM_WORKERS=4
      - OPAL_POLICY_BUNDLE_URL=http://policies:5555
      - OPAL_POLICY_SOURCE_TYPE=API
      - OPAL_POLICY_REPO_POLLING_INTERVAL=10
      - OPAL_DATA_CONFIG_SOURCES={"config":{"entries":[{"url":"http://opal_server:7002/policy-data","topics":["policy_data"],"dst_path":"/static"}]}}
      - OPAL_FILTER_FILE_EXTENSIONS=.cedar
      - OPAL_POLICY_REPO_POLICY_EXTENSIONS=.cedar
    ports:
      - "7002:7002"
    depends_on:
      - opal_broadcast
      - policies

  opal_cedar_agent:
    image: permitio/opal-client-cedar:0.9.4
    ports:
      - "7766:7766"
      - "8180:8180"
      - "8181:8181"
      - "7000:7000"
    environment:
      - OPAL_SERVER_URL=http://opal_server:7002
      - OPAL_DATA_UPDATER_ENABLED=false
    depends_on:
      - opal_server

  opal_broadcast:
    image: postgres:16-alpine
    environment:
      - POSTGRES_DB=opal
      - POSTGRES_USER=opal
      - POSTGRES_PASSWORD=opal

When I run this, at startup, the client received 2 policies:

$ curl -X GET http://localhost:8180/v1/policies
[{"id":"policy1.cedar","content":"permit(\n  principal,\n  action,\n  resource\n) when {\n  false\n};"},{"id":"policy2.cedar","content":"permit(\n  principal,\n  action,\n  resource\n) when {\n  false\n};"}]

After 40 seconds, server detected changes as suggested by the logs:

2026-04-21T17:43:16.223118+0000 | opal_common.sources.api_policy_source   | INFO  | Fetching changes from remote: 'http://policies:5555'
2026-04-21T17:43:16.227587+0000 | opal_common.sources.api_policy_source   | INFO  | Etag is turned off, you may want to turn it on at your bundle server
2026-04-21T17:43:16.227821+0000 | opal_common.sources.api_policy_source   | INFO  | Bundle hash is 6d7e8c5476ae97462b271f43239304d4b19796295f2f81c3ff7816b75fd592fb
2026-04-21T17:43:16.227916+0000 | opal_common.sources.api_policy_source   | INFO  | New bundle found, hash is: 6d7e8c5476ae97462b271f43239304d4b19796295f2f81c3ff7816b75fd592fb
2026-04-21T17:43:16.240610+0000 | opal_common.sources.api_policy_source   | INFO  | Found new version: old version hash was 'd3897a494ef6cfb23d943c08b6b057b3de5e8826a4d348f4ab7d8a714d665f80', new version hash is '6d7e8c5476ae97462b271f43239304d4b19796295f2f81c3ff7816b75fd592fb'
2026-04-21T17:43:16.244165+0000 | opal_server.policy.watcher.callbacks    |WARNING | new commits detected but no tracked files were affected: 'a85eb5b3484d7abb361bbf7b24d001a3a002964e' -> '26f4ea8424f869dbcb61745de4b58630557b9a57'

But running the curl request on client still returned 2 policies even after one of the policy has been deleted in the source:

$ curl -X GET http://localhost:8180/v1/policies
[{"id":"policy1.cedar","content":"permit(\n  principal,\n  action,\n  resource\n) when {\n  false\n};"},{"id":"policy2.cedar","content":"permit(\n  principal,\n  action,\n  resource\n) when {\n  false\n};"}]

It maybe caused by TarFileToLocalGitExtractor.commit_local_git() in opal-common/opal_common/git_utils/tar_file_to_local_git_extractor.py in:

local_git.index.add(self.policy_bundle_git_add_pattern)

which can be replaced with git.add(..., all=True) to include deleted files:

local_git.git.add(self.policy_bundle_git_add_pattern, all=True)

Expected behavior
After the second policy is deleted from the source, it should also be removed from the client so that curl -X GET http://localhost:8180/v1/policies should only return 1 policy like:

[{"id":"policy1.cedar","content":"permit(\n  principal,\n  action,\n  resource\n) when {\n  false\n};"}]

OPAL version

  • Version: 0.9.4

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions