Skip to content

Commit

Permalink
CASMPET-5898 Split OPA policies by use case (#59)
Browse files Browse the repository at this point in the history
* 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
kburns-hpe authored Aug 25, 2022
1 parent 6e3760c commit 48830ab
Show file tree
Hide file tree
Showing 52 changed files with 2,054 additions and 4,226 deletions.
17 changes: 6 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,9 @@ chart_test:

rego_test:
docker build -f ${CHART_PATH}/cray-opa/tests/opa/Dockerfile --tag cray-opa-test ${CHART_PATH}/cray-opa
docker build -f ${CHART_PATH}/cray-opa/tests/opa/xname-enabled/Dockerfile --tag cray-opa-test-xname-enabled ${CHART_PATH}/cray-opa
docker build -f ${CHART_PATH}/cray-opa/tests/opa/xname-disabled/Dockerfile --tag cray-opa-test-xname-disabled ${CHART_PATH}/cray-opa
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test-xname-enabled /mnt/templates/_policy-ingressgateway.tpl /mnt/tests/opa/ingressgateway_policy_test.xname_workloads.rego.tpl ingressgateway.policy
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test-xname-enabled /mnt/templates/_policy-ingressgateway.tpl /mnt/tests/opa/ingressgateway_policy_test.xname_workloads.invalid_xname.rego.tpl ingressgateway.policy
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test-xname-disabled /mnt/templates/_policy-ingressgateway.tpl /mnt/tests/opa/ingressgateway_policy_test.xname_workloads.rego.tpl ingressgateway.policy
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway-customer-admin.tpl /mnt/tests/opa/ingressgateway-customer-admin_policy_test.rego.tpl ingressgateway-customer-admin.policy
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway-customer-user.tpl /mnt/tests/opa/ingressgateway-customer-user_policy_test.rego.tpl ingressgateway-customer-user.policy
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway.tpl /mnt/tests/opa/ingressgateway_policy_xforward_test.rego.tpl ingressgateway.policy
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway-customer-admin.tpl /mnt/tests/opa/ingressgateway-customer-admin_policy_xforward_test.rego.tpl ingressgateway-customer-admin.policy
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway-customer-user.tpl /mnt/tests/opa/ingressgateway-customer-user_policy_xforward_test.rego.tpl ingressgateway-customer-user.policy
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway-hmn.tpl /mnt/tests/opa/ingressgateway-hmn_policy_test.rego.tpl ingressgateway-hmn.policy
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/policies/hmn.yaml /mnt/tests/opa/hmn_test.rego.tpl
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/policies/keycloak-admin.yaml /mnt/tests/opa/keycloak-admin_test.rego.tpl
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/policies/keycloak-user.yaml /mnt/tests/opa/keycloak-user_test.rego.tpl
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/policies/spire.yaml /mnt/tests/opa/spire_test.rego.tpl
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test -x /mnt/templates/policies/spire.yaml /mnt/tests/opa/spire_test.rego.tpl
docker run --rm -v ${PWD}/${CHART_PATH}/cray-opa/:/mnt --entrypoint "/app/run_tests" cray-opa-test -x /mnt/templates/policies/spire.yaml /mnt/tests/opa/spire_xname_test.rego.tpl
2 changes: 1 addition & 1 deletion kubernetes/cray-opa/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#
apiVersion: v2
name: cray-opa
version: 1.25.0
version: 1.26.0
description: Cray Open Policy Agent
keywords:
- opa
Expand Down
56 changes: 18 additions & 38 deletions kubernetes/cray-opa/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#
# MIT License
#
# (C) Copyright 2021-2022 Hewlett Packard Enterprise Development LP
# (C) Copyright 2022 Hewlett Packard Enterprise Development LP
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
Expand All @@ -20,47 +20,27 @@
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
current_dir := $(shell pwd)
tmp_dir := $(shell mktemp -d)

.SILENT: helmlint eval test test-ingressgateway test-ingressgateway-customer-admin test-ingressgateway-xname-enabled test-ingressgateway-xname-disabled
.PHONY: helmlint eval test test-ingressgateway test-ingressgateway-customer-admin test-ingressgateway-xname-enabled test-ingressgateway-xname-disabled

all: helmlint eval test test-ingressgateway test-ingressgateway-customer-admin test-ingressgateway-customer-user test-ingressgateway-xname-enabled test-ingressgateway-xname-disabled

lint: helmlint eval
test-opa: test-ingressgateway test-ingressgateway-customer-admin test-ingressgateway-customer-user test-ingressgateway-xname-enabled test-ingressgateway-xname-disabled
HELM_UNITTEST_IMAGE ?= quintush/helm-unittest:3.7.1-0.2.8
current_dir := $(shell pwd)

eval:
helm kubeval -n opa cray-opa . --ignore-missing-schemas
.SILENT: kuttl_test chart_test rego_test
.PHONY: kuttl_test chart_test rego_test

helmlint:
helm lint
test: chart_test rego_test kuttl_test

cray-opa-test:
docker build -f tests/opa/Dockerfile --tag cray-opa-test .
docker build -f tests/opa/xname-enabled/Dockerfile --tag cray-opa-test-xname-enabled .
docker build -f tests/opa/xname-disabled/Dockerfile --tag cray-opa-test-xname-disabled .
chart_test:
helm lint
docker run --rm -v $(current_dir):/apps ${HELM_UNITTEST_IMAGE} -3 .

test:
kuttl_test:
kubectl kuttl test --kind-config kind.yaml --artifacts-dir /tmp/kuttl

test-ingressgateway:
docker run --rm -v $(current_dir):/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway.tpl /mnt/tests/opa/ingressgateway_policy_test.rego.tpl ingressgateway.policy
docker run --rm -v $(current_dir):/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway.tpl /mnt/tests/opa/ingressgateway_policy_xforward_test.rego.tpl ingressgateway.policy

test-ingressgateway-xname-enabled:
docker run --rm -v $(current_dir):/mnt --entrypoint "/app/run_tests" cray-opa-test-xname-enabled /mnt/templates/_policy-ingressgateway.tpl /mnt/tests/opa/ingressgateway_policy_test.xname_workloads.rego.tpl ingressgateway.policy
docker run --rm -v $(current_dir):/mnt --entrypoint "/app/run_tests" cray-opa-test-xname-enabled /mnt/templates/_policy-ingressgateway.tpl /mnt/tests/opa/ingressgateway_policy_test.xname_workloads.invalid_xname.rego.tpl ingressgateway.policy

test-ingressgateway-xname-disabled:
docker run --rm -v $(current_dir):/mnt --entrypoint "/app/run_tests" cray-opa-test-xname-disabled /mnt/templates/_policy-ingressgateway.tpl /mnt/tests/opa/ingressgateway_policy_test.xname_workloads.rego.tpl ingressgateway.policy

test-ingressgateway-customer-admin:
docker run --rm -v $(current_dir):/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway-customer-admin.tpl /mnt/tests/opa/ingressgateway-customer-admin_policy_test.rego.tpl ingressgateway-customer-admin.policy
docker run --rm -v $(current_dir):/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway-customer-admin.tpl /mnt/tests/opa/ingressgateway-customer-admin_policy_xforward_test.rego.tpl ingressgateway-customer-admin.policy

test-ingressgateway-customer-user:
docker run --rm -v $(current_dir):/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway-customer-user.tpl /mnt/tests/opa/ingressgateway-customer-user_policy_test.rego.tpl ingressgateway-customer-user.policy
docker run --rm -v $(current_dir):/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/_policy-ingressgateway-customer-user.tpl /mnt/tests/opa/ingressgateway-customer-user_policy_xforward_test.rego.tpl ingressgateway-customer-user.policy
rego_test:
docker build -f $(current_dir)/tests/opa/Dockerfile --tag cray-opa-test $(current_dir)
docker run --rm -v $(current_dir)/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/policies/hmn.yaml /mnt/tests/opa/hmn_test.rego.tpl
docker run --rm -v $(current_dir)/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/policies/keycloak-admin.yaml /mnt/tests/opa/keycloak-admin_test.rego.tpl
docker run --rm -v $(current_dir)/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/policies/keycloak-user.yaml /mnt/tests/opa/keycloak-user_test.rego.tpl
docker run --rm -v $(current_dir)/:/mnt --entrypoint "/app/run_tests" cray-opa-test /mnt/templates/policies/spire.yaml /mnt/tests/opa/spire_test.rego.tpl
docker run --rm -v $(current_dir)/:/mnt --entrypoint "/app/run_tests" cray-opa-test -x /mnt/templates/policies/spire.yaml /mnt/tests/opa/spire_test.rego.tpl
docker run --rm -v $(current_dir)/:/mnt --entrypoint "/app/run_tests" cray-opa-test -x /mnt/templates/policies/spire.yaml /mnt/tests/opa/spire_xname_test.rego.tpl
41 changes: 30 additions & 11 deletions kubernetes/cray-opa/README.md
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.
75 changes: 75 additions & 0 deletions kubernetes/cray-opa/examples/custom-keycloak.yaml
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.*$`},
],
}
64 changes: 64 additions & 0 deletions kubernetes/cray-opa/examples/custom-spire.yaml
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"],
}
4 changes: 2 additions & 2 deletions kubernetes/cray-opa/kind.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/v1.19.7@sha256:a70639454e97a4b733f9d9b67e12c01f6b0297449d5b9cbbef87473458e26dca
- role: control-plane
image: kindest/node:v1.21.12@sha256:05eefdedfcb6113402ac631782adfa3d9f8b75c38eac783e3da4f44f6404dae0
25 changes: 7 additions & 18 deletions kubernetes/cray-opa/kuttl-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,13 @@
apiVersion: kuttl.dev/v1beta1
kind: TestSuite
commands:
- command: helm repo add cray-csm https://arti.dev.cray.com/artifactory/csm-helm-stable-local/
ignoreFailure: true
- command: kubectl create ns ceph-rgw
- command: kubectl create ns cert-manager-init
- command: kubectl create ns istio-system
- command: kubectl create ns opa
- command: kubectl create ns services
- command: kubectl create ns sma
- command: kubectl create ns spire
- command: kubectl apply -f ./tests/kuttl/cray-configmap-ca-public-key.yaml
- command: helm install --wait -f ../../../cray-istio/kubernetes/cray-istio-operator/tests/kuttl/values.yaml --namespace istio-system cray-istio-operator ../../../cray-istio/kubernetes/cray-istio-operator
- command: helm install --wait -f ../../../cray-istio/kubernetes/cray-istio-deploy/tests/kuttl/values-disable-prometheus.yaml --namespace istio-system cray-istio-deploy ../../../cray-istio/kubernetes/cray-istio-deploy
- command: helm install --wait -f ../../../cray-istio/kubernetes/cray-istio/tests/kuttl/cray-certmanager-init.yaml --namespace cert-manager-init cray-certmanager-init cray-csm/cray-certmanager-init
- command: helm install --wait -f ../../../cray-istio/kubernetes/cray-istio/tests/kuttl/cray-certmanager.yaml --namespace cert-manager cray-certmanager cray-csm/cray-certmanager
- command: helm install --wait -f ../../../cray-istio/kubernetes/cray-istio/tests/kuttl/cray-certmanager-issuers.yaml --namespace cert-manager cray-certmanager-issuers cray-csm/cray-certmanager-issuers
- command: helm install -f ../../../cray-istio/kubernetes/cray-istio/tests/kuttl/values.yaml --namespace istio-system cray-istio ../../../cray-istio/kubernetes/cray-istio
- command: helm install -f ./tests/kuttl/values.yaml --namespace opa cray-opa .
- command: kubectl create ns istio-system
- command: kubectl create ns opa
- command: kubectl apply -f ./tests/kuttl/cray-configmap-ca-public-key.yaml
- command: kubectl apply -f ./tests/kuttl/envoyfilters.crd.yaml
- command: kubectl apply -f ./tests/kuttl/custompolicy.yaml
- command: helm install -f ./tests/kuttl/values.yaml --namespace opa --wait cray-opa .
testDirs:
- ./tests/kuttl
- ./tests/kuttl
startKIND: true
kindNodeCache: true
Loading

0 comments on commit 48830ab

Please sign in to comment.