Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: support OpenPolicyAgent for access control #5714

Closed
bzp2010 opened this issue Dec 6, 2021 · 19 comments · Fixed by #5734
Closed

Proposal: support OpenPolicyAgent for access control #5714

bzp2010 opened this issue Dec 6, 2021 · 19 comments · Fixed by #5734
Assignees
Labels
discuss enhancement New feature or request

Comments

@bzp2010
Copy link
Contributor

bzp2010 commented Dec 6, 2021

Background

OPA is an open source lightweight general-purpose policy engine, which is a full-featured policy engine that can replace the built-in policy module in your software and decouple the service from the policy engine.
It describes policies through a policy DSL language "Rego" and stores policy data through JSON, after which the user can send a query request and OPA will combine the policy with the data and the query request entered by the user to generate policy decisions.
image
Users can easily integrate OPA with its services, such as program libraries, HTTP API, etc. In this plugin, we will integrate OPA using HTTP API.

Test Environment

# Run the OPA throught Docker
docker run -d --name opa -p 8181:8181 openpolicyagent/opa:0.35.0 run -s

# Create policy 'example'
curl -XPUT 'localhost:8181/v1/policies/example' \
--header 'Content-Type: text/plain' \
--data-raw 'package example

default allow = false

allow {
  input.request.http.headers["test-header"] == "only-for-test"
}'

# Test: success
curl -XPOST 'localhost:8181/v1/data/example/allow' \
--header 'Content-Type: application/json' \
--data-raw '{
  "input": {
    "request": {
      "http": {
        "headers": {
          "test-header": "only-for-test"
        }
      }
    }
  }
}'

{
    "result": true
}

# Test: failed
curl -XPOST 'localhost:8181/v1/data/example/allow' \
--header 'Content-Type: application/json' \
--data-raw '{
  "input": {
    "request": {
      "http": {
        "headers": {
          "test-header": "not-for-test"
        }
      }
    }
  }
}'

{
    "result": false
}

Solutions

Configure schema

Name Type Requirement Default Description
host string required OPA host (eg. https://localhost:8181)
ssl_verify boolean optional true Whether to verify the certificate
package string required   Policy package
decision string required   Policy rule name

More functions (Need more discuss)

Name Type Requirement Default Description
with_route boolean optional false Carry current Route information in OPA API requests
with_consumer boolean optional false Carry current Consumer information in OPA API requests
with_upstream boolean optional false Carry current Upstream information in OPA API requests

Implemention

Create a plugin that encodes the request information as JSON during the access execution phase, sends it to the OPA Data API and gets the response, decides whether to overwrite the response and terminates the request processing accordingly.
We provide a standard OPA response body specification, and the parts that conform to it will be parsed by APISIX. (complex response as follows)

## Requests like this `https://apisix.apache.org/contribute?abc=123`
{
  "input": {
    "request": {
      "http": { # for HTTP subsystem(maybe can used by Stream subsystem also)
        "host": "apisix.apache.org",
        "port": "443",
        "tls": {},
        "method": "GET",
        "scheme": "https",
        "path": "/contribute",
        "query": {
          "abc" : "123"
        },
        "headers": {
          "accept-encoding": "gzip, deflate"
        }
      }
    },
    "remote_addr": "127.0.0.1",
    "upstream": {},
    "route": {},
    "consumer": {}
  }
}

## Simply decision response from OPA 
{
    "result": true | false
}

## Complex decision response from OPA (supports overwriting responses and terminating request processing)
{
    "result": {
        "allow": true | false,
        "headers":{
            "opa": "forbidden"
        },
        "statusCode": 403,
        "body": "You are not authorized to access."
    }
}

Roadmap

The plugin implementation will follow the following roadmap, splitting into different PRs for submission.

  1. Implement MVP: Supports sending request information to OPA and processing the request with a simple response
  2. Support OPA complex response with response overwrite
  3. Support sending routing, upstream and other information to OPA to assist in policy determination

Other

What are your ideas?

@bzp2010 bzp2010 added enhancement New feature or request discuss labels Dec 6, 2021
@bzp2010 bzp2010 self-assigned this Dec 6, 2021
@tokers
Copy link
Contributor

tokers commented Dec 6, 2021

SGTM!

@tzssangglass
Copy link
Member

sounds good

@juzhiyuan
Copy link
Member

SGTM!! I noticed Open Policy Agent supports Ecosystem 😄 see https://www.openpolicyagent.org/docs/latest/ecosystem/

image

cc @yzeng25 @bzp2010 @moonming

@leslie-tsang
Copy link
Member

leslie-tsang commented Dec 7, 2021 via email

@juzhiyuan
Copy link
Member

Just added a Discussion here[1], looking forward to more reviews from community :)

[1] open-policy-agent/community#73

@anderseknert
Copy link

Thanks for raising this @bzp2010 👍 Let me know if there's any questions along the way with regards to OPA, and I'd be happy to answer them :)

As for the suggested input format - looks mostly good to me! I would suggest however to not use http as the "top level" attribute inside of the request, but rather put that detail inside of the request object. That way one can write and use the same policies for any type of request, and just check a single attribute later if one needs to take the protocol into account.

As for the response object, also looks pretty good to me! I'd suggest renaming body to reason, and it looks a bit odd that statusCode in the response is camel case, while the input attributes are snake case, so I'd suggest using one or the other consistently.

But either way, looks like a great start!

@bzp2010 bzp2010 linked a pull request Dec 9, 2021 that will close this issue
4 tasks
@juzhiyuan
Copy link
Member

Great!!

@bzp2010 bzp2010 reopened this Dec 13, 2021
@bzp2010
Copy link
Contributor Author

bzp2010 commented Dec 13, 2021

Update

The current OPA basic support plugin has been completed and will begin the phase 2 of feature development on the roadmap. So I reopened this issue to track the status of the next PR.

@juzhiyuan
Copy link
Member

Update

The current OPA basic support plugin has been completed and will begin the phase 2 of feature development on the roadmap. So I reopened this issue to track the status of the next PR.

cc @yzeng25 to know this. 😄

@yzeng25
Copy link
Contributor

yzeng25 commented Dec 13, 2021

Cool! Got it!

@bzp2010
Copy link
Contributor Author

bzp2010 commented Dec 13, 2021

Hi, @anderseknert.

The development of stage 1: basic support in the current roadmap has been completed and the PR is here #5734.
I have some questions for you here. Does the OPA community have any use case or best practices for "response overwriting (e.g., status codes or headers) in case of authentication failure"?

Thanks.

@bzp2010

This comment has been minimized.

@anderseknert
Copy link

That's really exciting! Thanks for your work on this @bzp2010 , and everyone else who has been involved 👍

As for best practices, no not really. It's important to note that OPA doesn't do enforcement by design, so IMHO the status code for the client to return upstream should be decided by the client/caller. For an HTTP Gateway this would likely be a 403, but if we leave that decision to the client (i.e. not in the policy response) we can be flexible in reusing the same policy across other clients, where a different type of enforcement (like a 500 response, or just an audit log entry) might be desirable. So for me, I'd say the minimum decision to return would be 1) whether the request is allowed or not, and 2) (optionally) a string explaining the reason if the request was denied.

As for reusing the Envoy/Istio authorization input structure, that's an interesting idea, as it would mean you could pretty much take any policy written for those systems and directly use it with APISIX. The downside of that (again, IMHO ;)) is that there's a lot of (most often) unused data being sent with each request. No policy I've seen has used things like the content-length of the incoming request. I guess an interesting option could be to allow the input format to be configurable, so that you could either send a minimal request (request method, path, authorization header, etc) or you could configure the plugin to send Envoy/Istio formatted input. That'd provide a good default, while allowing compatibility with those other proxies for those that preferred it.

@bzp2010
Copy link
Contributor Author

bzp2010 commented Dec 13, 2021

The first point is got, while I will try to do some work on the second point.

@bzp2010
Copy link
Contributor Author

bzp2010 commented Dec 13, 2021

Update

Stage 2 PR #5779 and scheme here #5779 (comment)

@yzeng25
Copy link
Contributor

yzeng25 commented Dec 15, 2021

Apache APISIX is added to OPA's ecosystem page :) Thanks @anderseknert for reviewing open-policy-agent/opa#4137 and open-policy-agent/opa#4138

image

cc @juzhiyuan @bzp2010

@juzhiyuan
Copy link
Member

👏

@juzhiyuan
Copy link
Member

@spacewander
Copy link
Member

Already done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discuss enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants