-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CASMPET-5898 Split OPA policies by use case (#59)
* CASMPET-5898 Split policies based on use case This changes how policies are split. Instead of them being split by ingress gateway, they're split by use case. This also allows custom OPA entries, provided they're in the same package istio.authz. * CASMPET-5898 Fix tests * CASMPET-5898 Update tests * CASMPET-5898 Update documentation * CASMPET-5898 Fix linter issues * CASMPET-5898 Add cray-opa-test info * CASMPET-5898 Add tests for custom policies * CASMPET-5898 tidy go.mod
- Loading branch information
1 parent
6e3760c
commit 48830ab
Showing
52 changed files
with
2,054 additions
and
4,226 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,34 @@ | ||
# Copyright 2021 Hewlett Packard Enterprise Development LP | ||
# cray-opa | ||
|
||
TODO: We should be taring up our policies and serving them up using | ||
OPA's bundles. We should then configure OPA to fetch the bundles. | ||
However, since we only have one policy and we have a lot of missing | ||
pieces to accommodate this, it makes sense to just use a config map for now. | ||
This chart installs the [OPA Envoy Plugin](https://github.com/open-policy-agent/opa-envoy-plugin) | ||
used to secure API endpoints in CSM. | ||
|
||
Running unit tests: From the cray-opa directory, | ||
## Custom OPA Policies | ||
|
||
``` | ||
$ make cray-opa-test | ||
$ make test-opa | ||
``` | ||
It's possible to set custom OPA policy modules per OPA Gateway. To configure | ||
this, set `.spec.ingresses.[INGRESS GATEWAY].custom` to a list containing the | ||
ConfigMaps that hold the policy modules you wish to apply. Each module needs | ||
to have the package name `istio.authz`. The file name in the ConfigMap should be | ||
named `policy.rego`. | ||
|
||
Note: Make sure the image in the Dockerfile matches the actual OPA image used, check the version. | ||
## Testing | ||
|
||
Tests are run using `make test`. | ||
|
||
## Manually run OPA unit tests | ||
|
||
To run opa test manually, first build the cray-opa-test containers. | ||
|
||
docker build -f tests/opa/Dockerfile --tag cray-opa-test . | ||
|
||
The docker file takes the policy in the yaml file and the test tpl as arguments. | ||
It also has an optional `-x` switch which will enable xname validation. | ||
|
||
docker run --rm -v ${PWD}:/mnt --entrypoint "/app/run_tests" \ | ||
cray-opa-test [-x] policy.yaml test.tpl | ||
|
||
### Requirements | ||
|
||
Tests use [Docker](docker.io), [kuttl](https://kuttl.dev), and | ||
[kind](https://kind.sigs.k8s.io). You will need each of these applications | ||
installed in order for `make test` to run properly. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
--- | ||
apiVersion: v1 | ||
kind: ConfigMap | ||
metadata: | ||
name: opa-policy-custom-keycloak | ||
namespace: opa | ||
data: | ||
policy.rego: |- | ||
# Custom Keycloak Policy | ||
package istio.authz | ||
import input.attributes.request.http as http_request | ||
# Whitelist Keycloak, since it allows users to login and obtain JWTs. | ||
allow { startswith(original_path, "/keycloak") } | ||
# The path being requested from the user. When the envoy filter is configured for | ||
# SIDECAR_INBOUND this is: http_request.headers["x-envoy-original-path"]. | ||
# When configured for GATEWAY this is http_request.path | ||
original_path = o_path { | ||
o_path := http_request.path | ||
} | ||
allow { | ||
roles_for_user[r] | ||
required_roles[r] | ||
} | ||
# Check if there is an authorization header and split the type from token | ||
found_auth = {"type": a_type, "token": a_token} { | ||
[a_type, a_token] := split(http_request.headers.authorization, " ") | ||
} | ||
# If the auth type is bearer, decode the JWT | ||
parsed_kc_token = {"payload": payload} { | ||
found_auth.type == "Bearer" | ||
response := http.send({"method": "get", "url": "https://istio-ingressgateway.istio-system.svc.cluster.local./keycloak/realms/shasta/protocol/openid-connect/certs", "cache": true, "tls_ca_cert_file": "/jwtValidationFetchTls/certificate_authority.crt"}) | ||
[_, _, payload] := io.jwt.decode_verify(found_auth.token, {"cert": response.raw_body, "aud": "shasta"}) | ||
# Verify that the issuer is as expected. | ||
allowed_issuers := [ | ||
"https://keycloak1" | ||
] | ||
allowed_issuers[_] = payload.iss | ||
} | ||
# Get the users roles from the JWT token | ||
roles_for_user[r] { | ||
r := parsed_kc_token.payload.resource_access.shasta.roles[_] | ||
} | ||
# Determine if the path/verb requests is authorized based on the JWT roles | ||
required_roles[r] { | ||
perm := role_perms[r][_] | ||
perm.method = http_request.method | ||
re_match(perm.path, original_path) | ||
} | ||
# Our list of endpoints we accept based on roles. | ||
role_perms = { | ||
"custom": allowed_methods["custom"], | ||
} | ||
allowed_methods := { | ||
"custom": [ | ||
# Custom API Access | ||
{"method": "DELETE", "path": `^/custom/api.*$`}, | ||
{"method": "GET", "path": `^/custom/api.*$`}, | ||
{"method": "HEAD", "path": `^/custom/api.*$`}, | ||
{"method": "PATCH", "path": `^/custom/api.*$`}, | ||
{"method": "POST", "path": `^/custom/api.*$`}, | ||
{"method": "PUT", "path": `^/custom/api.*$`}, | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
--- | ||
apiVersion: v1 | ||
kind: ConfigMap | ||
metadata: | ||
name: custom-spire | ||
namespace: opa | ||
data: | ||
policy.rego: |- | ||
# Custom Spire Policy | ||
package istio.authz | ||
import input.attributes.request.http as http_request | ||
# The path being requested from the user. When the envoy filter is configured for | ||
# SIDECAR_INBOUND this is: http_request.headers["x-envoy-original-path"]. | ||
# When configured for GATEWAY this is http_request.path | ||
original_path = o_path { | ||
o_path := http_request.path | ||
} | ||
# Check if there is an authorization header and split the type from token | ||
found_auth = {"type": a_type, "token": a_token} { | ||
[a_type, a_token] := split(http_request.headers.authorization, " ") | ||
} | ||
spire_methods := { | ||
"custom": [ | ||
{"method": "DELETE", "path": `^/custom/api.*$`}, | ||
{"method": "GET", "path": `^/custom/api.*$`}, | ||
{"method": "HEAD", "path": `^/custom/api.*$`}, | ||
{"method": "PATCH", "path": `^/custom/api.*$`}, | ||
{"method": "POST", "path": `^/custom/api.*$`}, | ||
{"method": "PUT", "path": `^/custom/api.*$`}, | ||
], | ||
} | ||
# If the auth type is bearer, decode the JWT | ||
parsed_spire_token = {"payload": payload, "xname": xname} { | ||
found_auth.type == "Bearer" | ||
response := http.send({"method": "get", "url": "https://istio-ingressgateway.istio-system.svc.cluster.local./keycloak/realms/shasta/protocol/openid-connect/certs", "cache": true, "tls_ca_cert_file": "/jwtValidationFetchTls/certificate_authority.crt"}) | ||
[_, _, payload] := io.jwt.decode_verify(found_auth.token, {"cert": response.raw_body, "aud": "system-compute"}) | ||
# Verify that the issuer is as expected. | ||
allowed_issuers := [ | ||
- https://keycloak1 | ||
] | ||
allowed_issuers[_] = payload.iss | ||
xname := regex.split("/", payload.sub)[4] | ||
} | ||
# Validate claims for SPIRE issued JWT tokens with xname support | ||
allow { | ||
s := replace(parsed_spire_token.payload.sub, parsed_spire_token.xname, "XNAME") | ||
# Test subject matches destination | ||
perm := sub_match[s][_] | ||
perm.method = http_request.method | ||
re_match(perm.path, original_path) | ||
} | ||
sub_match = { | ||
"spiffe://shasta/compute/XNAME/workload/custom-spire-agent": spire_methods["custom"], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.