open-policy-agentrego

How to prevent http.send if subsequent allow's are true?


I am trying to use OPA as authorization service for Trino. I wrote a rego file according to my needs.

package mytrino

import rego.v1

default allow := false

allow if {
    input.action.operation == "ExecuteQuery"
    not input.action.resource
}

allow if {
    input.action.operation == "AccessCatalog"
}

allow if {
    input.action.operation == "FilterCatalogs"
}

allow if {
    input.action.operation == "FilterSchemas"
}

allow if {
    input.action.operation == "SelectFromColumns"
    input.action.resource.table.catalogName == "system"
}

allow if {
    response := http.send({
        "method": "post",
        "url": "http://host.docker.internal:8085/api/products/check-table-access",
        "headers": {"Content-Type": "application/json"},
        "body": {
            "user": input.context.identity.user,
            "table": input.action.resource.table.tableName
        }
    })
    response.status_code == 200
}

I was checking my API logs and I saw there have been requests that are 100% percent true without the last allow if statement. So I think even though the previous allow if's are true, it evaluate subsequent statements?

How can I prevent final allow if statement to make http requests if the previous ones are true?

Thank you a lot!


Solution

  • Rego rules are all evaluated at once, so if you want to block the calling of http.send then you can introduce a dependency on the other rules before calling it with a function like this:

    package play
    
    import rego.v1
    
    default allow := false
    
    # this will be true if the conditions that can be checked locally are validated ok
    allow if deterministic_allow
    
    allow if check_table_access(
        # this enforces a dependency of this rule on deterministic_allow
        # and so it will not be called until deterministic_allow is known
        deterministic_allow,
        input.context.identity.user,
        input.action.resource.table.tableName,
    )
    
    default deterministic_allow := false
    
    deterministic_allow if {
        input.action.operation == "ExecuteQuery"
        not input.action.resource
    }
    
    deterministic_allow if {
        input.action.operation == "AccessCatalog"
    }
    
    deterministic_allow if {
        input.action.operation == "FilterCatalogs"
    }
    
    deterministic_allow if {
        input.action.operation == "FilterSchemas"
    }
    
    deterministic_allow if {
        input.action.operation == "SelectFromColumns"
        input.action.resource.table.catalogName == "system"
    }
    
    # when deterministic_allow is true, then this function just short circuits and returns true
    check_table_access(true, _, _) := true
    
    # when deterministic_allow is false, the http call is made to perform the final chance check
    check_table_access(false, user, table_name) := result if {
        response := http.send({
            "method": "post",
            "url": "http://host.docker.internal:8085/api/products/check-table-access",
            "headers": {"Content-Type": "application/json"},
            "body": {
                "user": user,
                "table": table_name,
            },
        })
    
        result := response.status_code == 200
    }
    

    This is based on 'Expressing OR using helper functions'.