├── tests ├── templates │ ├── .gitkeep │ └── kuttl │ │ ├── ad-user-info │ │ ├── 30-prepare-test-regorule.yaml │ │ ├── 00-limit-range.yaml │ │ ├── 01-assert.yaml.j2 │ │ ├── 30-assert.yaml │ │ ├── 10-assert.yaml │ │ ├── 01-install-vector-aggregator-discovery-configmap.yaml.j2 │ │ ├── 20-assert.yaml │ │ ├── 00-patch-ns.yaml.j2 │ │ ├── 20-install-test-regorule.yaml │ │ └── 10-install-opa.yaml.j2 │ │ ├── aas-user-info │ │ ├── 30-prepare-test-regorule.yaml │ │ ├── 02-assert.yaml │ │ ├── 01-assert.yaml.j2 │ │ ├── 10-assert.yaml │ │ ├── 30-assert.yaml │ │ ├── 01-install-vector-aggregator-discovery-configmap.yaml.j2 │ │ ├── 20-assert.yaml │ │ ├── 00-patch-ns.yaml.j2 │ │ ├── 20-install-test-regorule.yaml │ │ ├── 00-misc-setup.yaml.j2 │ │ ├── 10-install-opa.yaml.j2 │ │ ├── test-regorule.py │ │ └── 02-install-aas.yaml.j2 │ │ ├── keycloak-user-info │ │ ├── 30-prepare-test-regorule.yaml │ │ ├── 00-limit-range.yaml │ │ ├── 04-assert.yaml │ │ ├── 01-assert.yaml.j2 │ │ ├── 10-assert.yaml │ │ ├── 30-assert.yaml │ │ ├── 01-install-vector-aggregator-discovery-configmap.yaml.j2 │ │ ├── 20-assert.yaml │ │ ├── 00-patch-ns.yaml.j2 │ │ ├── 20-install-test-regorule.yaml │ │ ├── 10-install-opa.yaml.j2 │ │ ├── test-regorule.py │ │ └── 04-keycloak-realm-cm.yaml │ │ ├── openldap-user-info │ │ ├── 50-prepare-test-regorule.yaml │ │ ├── 10-assert.yaml │ │ ├── 00-limit-range.yaml │ │ ├── 01-assert.yaml.j2 │ │ ├── 01-install-vector-aggregator-discovery-configmap.yaml.j2 │ │ ├── 40-assert.yaml │ │ ├── 00-patch-ns.yaml.j2 │ │ ├── 20-load-ldif.yaml │ │ ├── 30-assert.yaml │ │ ├── 50-assert.yaml │ │ └── 40-install-test-regorule.yaml │ │ ├── logging │ │ ├── 03-assert.yaml │ │ ├── 04-assert.yaml │ │ ├── 01-assert.yaml │ │ ├── 05-test-log-aggregation.yaml │ │ ├── 02-create-configmap-with-prepared-logs.yaml │ │ ├── 05-assert.yaml │ │ ├── 00-patch-ns.yaml.j2 │ │ ├── 01-install-opa-vector-aggregator.yaml │ │ ├── test_log_aggregation.sh │ │ ├── 04-install-opa-test-runner.yaml │ │ ├── prepared-bundle-builder-logs.tracing-rs.json │ │ ├── prepared-opa-logs.json │ │ ├── test_log_aggregation.py │ │ └── opa-vector-aggregator-values.yaml.j2 │ │ ├── smoke │ │ ├── 00-limit-range.yaml │ │ ├── 20-assert.yaml │ │ ├── 30-prepare-test-opa.yaml │ │ ├── 01-assert.yaml.j2 │ │ ├── 01-install-vector-aggregator-discovery-configmap.yaml.j2 │ │ ├── 00-patch-ns.yaml.j2 │ │ ├── 32-assert.yaml │ │ ├── 11-assert.yaml │ │ ├── 30-assert.yaml.j2 │ │ ├── 31-assert.yaml.j2 │ │ ├── 09-install-secretclass.yaml.j2 │ │ ├── 30_test-metrics.py │ │ ├── 10-assert.yaml.j2 │ │ ├── 30_test-regorule.py │ │ ├── 20-install-test-opa.yaml.j2 │ │ └── 10-install-opa.yaml.j2 │ │ ├── resources │ │ ├── 00-limit-range.yaml │ │ ├── 01-assert.yaml.j2 │ │ ├── 01-install-vector-aggregator-discovery-configmap.yaml.j2 │ │ ├── 00-patch-ns.yaml.j2 │ │ ├── 10-install-opa.yaml.j2 │ │ └── 10-assert.yaml.j2 │ │ └── cluster-operation │ │ ├── 00-assert.yaml.j2 │ │ ├── 00-install-vector-aggregator-discovery-configmap.yaml.j2 │ │ ├── 10-assert.yaml │ │ ├── 20-assert.yaml │ │ ├── 40-assert.yaml │ │ ├── 30-assert.yaml │ │ ├── 00-patch-ns.yaml.j2 │ │ ├── 10-install-opa.yaml.j2 │ │ ├── 40-restart-opa.yaml.j2 │ │ ├── 20-stop-opa.yaml.j2 │ │ └── 30-pause-opa.yaml.j2 ├── release.yaml ├── interu.yaml ├── kuttl-test.yaml.jinja2 ├── test-definition.yaml └── README-templating.md ├── rust ├── operator-binary │ ├── src │ │ ├── lib.rs │ │ ├── operations │ │ │ ├── mod.rs │ │ │ └── graceful_shutdown.rs │ │ └── product_logging.rs │ ├── build.rs │ └── Cargo.toml ├── user-info-fetcher │ ├── src │ │ ├── utils │ │ │ ├── mod.rs │ │ │ ├── http.rs │ │ │ └── tls.rs │ │ ├── backend │ │ │ └── mod.rs │ │ └── http_error.rs │ ├── build.rs │ ├── README.md │ └── Cargo.toml ├── bundle-builder │ ├── build.rs │ └── Cargo.toml └── regorule-library │ ├── src │ ├── lib.rs │ └── userinfo │ │ └── v1.rego │ ├── Cargo.toml │ └── README.md ├── docs ├── antora.yml ├── modules │ └── opa │ │ ├── examples │ │ └── getting_started │ │ │ ├── expected_response_world.json │ │ │ ├── expected_response_hello.json │ │ │ ├── install_output.txt │ │ │ ├── install_output.txt.j2 │ │ │ ├── test_getting_started_helm.sh │ │ │ ├── test_getting_started_stackablectl.sh │ │ │ ├── opa.yaml │ │ │ ├── opa.yaml.j2 │ │ │ ├── simple-rule.yaml │ │ │ ├── getting_started.sh │ │ │ └── getting_started.sh.j2 │ │ ├── images │ │ └── keycloak-user-info-fetcher │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ └── 4.png │ │ ├── pages │ │ ├── reference │ │ │ ├── crds.adoc │ │ │ ├── index.adoc │ │ │ ├── commandline-parameters.adoc │ │ │ ├── discovery.adoc │ │ │ └── environment-variables.adoc │ │ ├── usage-guide │ │ │ ├── operations │ │ │ │ ├── cluster-operations.adoc │ │ │ │ ├── index.adoc │ │ │ │ ├── pod-disruptions.adoc │ │ │ │ └── graceful-shutdown.adoc │ │ │ ├── index.adoc │ │ │ ├── listenerclass.adoc │ │ │ ├── monitoring.adoc │ │ │ ├── policies.adoc │ │ │ ├── resources.adoc │ │ │ ├── configuration-environment-overrides.adoc │ │ │ ├── OpenTelemetry.adoc │ │ │ ├── logging.adoc │ │ │ └── tls.adoc │ │ ├── getting_started │ │ │ ├── index.adoc │ │ │ ├── installation.adoc │ │ │ └── first_steps.adoc │ │ └── implementation-notes.adoc │ │ └── partials │ │ ├── supported-versions.adoc │ │ └── nav.adoc └── templating_vars.yaml ├── scripts ├── run_tests.sh ├── render_readme.sh ├── generate-manifests.sh ├── docs_templating.sh └── ensure_one_trailing_newline.py ├── deploy ├── config-spec │ └── properties.yaml ├── helm │ ├── opa-operator │ │ ├── configs │ │ │ └── properties.yaml │ │ ├── templates │ │ │ ├── configmap.yaml │ │ │ ├── service.yaml │ │ │ ├── _maintenance.tpl │ │ │ ├── roles-opa-builder.yaml │ │ │ ├── serviceaccount.yaml │ │ │ ├── _telemetry.tpl │ │ │ ├── _helpers.tpl │ │ │ └── roles.yaml │ │ ├── Chart.yaml │ │ ├── .helmignore │ │ ├── README.md │ │ └── values.yaml │ ├── chart_testing.yaml │ └── ct.yaml ├── stackable-operators-ns.yaml └── DO_NOT_EDIT.md ├── .gitattributes ├── .actionlint.yaml ├── .github ├── actionlint.yaml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── normal-issue.md │ ├── 01-normal-issue.md │ ├── new_version.md │ └── 02-bug_report.yml ├── workflows │ ├── general_daily_security.yml │ ├── pr_pre-commit.yaml │ └── integration-test.yml ├── PULL_REQUEST_TEMPLATE │ ├── pre-release-getting-started-script.md │ └── pre-release-rust-deps.md └── pull_request_template.md ├── rust-toolchain.toml ├── .readme ├── static │ └── borrowed │ │ ├── stackable_overview.png │ │ └── Icon_Stackable.svg ├── partials │ ├── borrowed │ │ ├── header.md.j2 │ │ ├── related_reading.md.j2 │ │ ├── overview_blurb.md.j2 │ │ ├── documentation.md.j2 │ │ ├── links.md.j2 │ │ └── footer.md.j2 │ └── main.md.j2 └── README.md.j2 ├── .envrc.sample ├── nix ├── meta.json ├── README.md └── sources.json ├── .vscode ├── settings.json └── launch.json ├── .pylintrc ├── .gitignore ├── renovate.json ├── .dockerignore ├── .hadolint.yaml ├── rustfmt.toml ├── .yamllint.yaml ├── .markdownlint.yaml ├── crate-hashes.json ├── shell.nix ├── Cargo.toml ├── deny.toml ├── Tiltfile └── Makefile /tests/templates/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rust/operator-binary/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod crd; 2 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: home 3 | version: "nightly" 4 | -------------------------------------------------------------------------------- /rust/operator-binary/src/operations/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod graceful_shutdown; 2 | -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/expected_response_world.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /rust/user-info-fetcher/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod http; 2 | pub mod tls; 3 | -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ./scripts/run-tests "$@" 4 | -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/expected_response_hello.json: -------------------------------------------------------------------------------- 1 | {"result":true} 2 | -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/install_output.txt: -------------------------------------------------------------------------------- 1 | Installed opa=0.0.0-dev operator 2 | -------------------------------------------------------------------------------- /rust/bundle-builder/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | built::write_built_file().unwrap(); 3 | } 4 | -------------------------------------------------------------------------------- /rust/operator-binary/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | built::write_built_file().unwrap(); 3 | } 4 | -------------------------------------------------------------------------------- /rust/user-info-fetcher/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | built::write_built_file().unwrap(); 3 | } 4 | -------------------------------------------------------------------------------- /deploy/config-spec/properties.yaml: -------------------------------------------------------------------------------- 1 | version: 0.1.0 2 | spec: 3 | units: [] 4 | 5 | properties: [] 6 | -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/install_output.txt.j2: -------------------------------------------------------------------------------- 1 | Installed opa={{ versions.opa }} operator 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | nix/** linguist-generated 2 | Cargo.nix linguist-generated 3 | crate-hashes.json linguist-generated 4 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/configs/properties.yaml: -------------------------------------------------------------------------------- 1 | version: 0.1.0 2 | spec: 3 | units: [] 4 | 5 | properties: [] 6 | -------------------------------------------------------------------------------- /deploy/helm/chart_testing.yaml: -------------------------------------------------------------------------------- 1 | remote: origin 2 | target-branch: main 3 | chart-dirs: 4 | - deploy/helm 5 | all: true 6 | -------------------------------------------------------------------------------- /deploy/stackable-operators-ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: stackable-operators 6 | -------------------------------------------------------------------------------- /.actionlint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | self-hosted-runner: 3 | # Ubicloud machines we are using 4 | labels: 5 | - ubicloud-standard-8-arm 6 | -------------------------------------------------------------------------------- /.github/actionlint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | self-hosted-runner: 3 | # Ubicloud machines we are using 4 | labels: 5 | - ubicloud-standard-8-arm 6 | -------------------------------------------------------------------------------- /docs/templating_vars.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | helm: 3 | repo_name: sdp-charts 4 | repo_url: oci.stackable.tech 5 | versions: 6 | opa: 0.0.0-dev 7 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT, this file is generated by operator-templating 2 | [toolchain] 3 | channel = "1.89.0" 4 | profile = "default" 5 | -------------------------------------------------------------------------------- /.readme/static/borrowed/stackable_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackabletech/opa-operator/HEAD/.readme/static/borrowed/stackable_overview.png -------------------------------------------------------------------------------- /rust/user-info-fetcher/src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod active_directory; 2 | pub mod entra; 3 | pub mod keycloak; 4 | pub mod openldap; 5 | pub mod xfsc_aas; 6 | -------------------------------------------------------------------------------- /.envrc.sample: -------------------------------------------------------------------------------- 1 | # vim: syntax=conf 2 | # 3 | # If you use direnv, you can autoload the nix shell: 4 | # You will need to allow the directory the first time. 5 | use nix 6 | -------------------------------------------------------------------------------- /rust/regorule-library/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub const REGORULES: &[(&str, &str)] = &[( 2 | "stackable/opa/userinfo/v1.rego", 3 | include_str!("userinfo/v1.rego"), 4 | )]; 5 | -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/test_getting_started_helm.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | set -euo pipefail 3 | 4 | cd "$(dirname "$0")" 5 | ./getting_started.sh helm 6 | -------------------------------------------------------------------------------- /docs/modules/opa/images/keycloak-user-info-fetcher/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackabletech/opa-operator/HEAD/docs/modules/opa/images/keycloak-user-info-fetcher/1.png -------------------------------------------------------------------------------- /docs/modules/opa/images/keycloak-user-info-fetcher/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackabletech/opa-operator/HEAD/docs/modules/opa/images/keycloak-user-info-fetcher/2.png -------------------------------------------------------------------------------- /docs/modules/opa/images/keycloak-user-info-fetcher/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackabletech/opa-operator/HEAD/docs/modules/opa/images/keycloak-user-info-fetcher/3.png -------------------------------------------------------------------------------- /docs/modules/opa/images/keycloak-user-info-fetcher/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackabletech/opa-operator/HEAD/docs/modules/opa/images/keycloak-user-info-fetcher/4.png -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/test_getting_started_stackablectl.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | set -euo pipefail 3 | 4 | cd "$(dirname "$0")" 5 | ./getting_started.sh stackablectl 6 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/reference/crds.adoc: -------------------------------------------------------------------------------- 1 | = CRD reference 2 | 3 | Find all CRD reference for the Stackable operator for OpenPolicyAgent at: {crd-docs-base-url}/opa-operator/{crd-docs-version}. 4 | -------------------------------------------------------------------------------- /nix/meta.json: -------------------------------------------------------------------------------- 1 | {"operator": {"name": "opa-operator", "extra_crates": ["stackable-opa-bundle-builder"], "pretty_string": "OpenPolicyAgent", "product_string": "opa", "url": "stackabletech/opa-operator.git"}} 2 | -------------------------------------------------------------------------------- /tests/templates/kuttl/ad-user-info/30-prepare-test-regorule.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: kubectl cp -n $NAMESPACE ./test-regorule.py test-regorule-0:/tmp 6 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/30-prepare-test-regorule.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: kubectl cp -n $NAMESPACE ./test-regorule.py test-regorule-0:/tmp 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.rustfmt.overrideCommand": [ 3 | "rustfmt", 4 | "+nightly-2025-10-23", 5 | "--edition", 6 | "2024", 7 | "--" 8 | ], 9 | } 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/30-prepare-test-regorule.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: kubectl cp -n $NAMESPACE ./test-regorule.py test-regorule-0:/tmp 6 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/50-prepare-test-regorule.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: kubectl cp -n $NAMESPACE ./test-regorule.py test-regorule-0:/tmp 6 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MESSAGES CONTROL] 2 | 3 | # These rules are for missing docstrings which doesn't matter much for most of our simple scripts 4 | disable=C0114,C0115,C0116 5 | 6 | [FORMAT] 7 | 8 | max-line-length=999 9 | indent-string=' ' 10 | -------------------------------------------------------------------------------- /.readme/partials/borrowed/header.md.j2: -------------------------------------------------------------------------------- 1 | 2 |

3 | Stackable Logo 4 |

5 | 6 |

{{title}}

7 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/03-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 600 5 | commands: 6 | - script: kubectl -n $NAMESPACE rollout status daemonset test-opa-server-automatic-log-config --timeout 600s 7 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/10-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 300 5 | commands: 6 | - script: kubectl wait --for=condition=ready pod/test-openldap-0 -n $NAMESPACE --timeout=300s 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/_work/ 2 | debug/ 3 | target/ 4 | **/*.rs.bk 5 | 6 | .idea/ 7 | *.iws 8 | *.iml 9 | 10 | *.tgz 11 | 12 | result 13 | image.tar 14 | 15 | tilt_options.json 16 | 17 | .direnv/ 18 | .direnvrc 19 | .envrc 20 | 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /.readme/partials/borrowed/related_reading.md.j2: -------------------------------------------------------------------------------- 1 | 2 | {%- if related_reading_links -%} 3 | ## Related Reading 4 | {% for (text, link) in related_reading_links %} 5 | * [{{text}}]({{link}}) 6 | {%- endfor %} 7 | {%- endif -%} 8 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/00-limit-range.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: LimitRange 4 | metadata: 5 | name: limit-request-ratio 6 | spec: 7 | limits: 8 | - type: "Container" 9 | maxLimitRequestRatio: 10 | cpu: 5 11 | memory: 1 12 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/20-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 300 5 | --- 6 | apiVersion: apps/v1 7 | kind: StatefulSet 8 | metadata: 9 | name: test-opa 10 | status: 11 | readyReplicas: 1 12 | replicas: 1 13 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/30-prepare-test-opa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: kubectl cp -n $NAMESPACE ./30_test-regorule.py test-opa-0:/tmp 5 | - script: kubectl cp -n $NAMESPACE ./30_test-metrics.py test-opa-0:/tmp 6 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/02-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 300 5 | --- 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | name: aas 10 | status: 11 | readyReplicas: 1 12 | replicas: 1 13 | -------------------------------------------------------------------------------- /tests/templates/kuttl/resources/00-limit-range.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: LimitRange 4 | metadata: 5 | name: limit-request-ratio 6 | spec: 7 | limits: 8 | - type: "Container" 9 | maxLimitRequestRatio: 10 | cpu: 5 11 | memory: 1 12 | -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/opa.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: opa.stackable.tech/v1alpha1 3 | kind: OpaCluster 4 | metadata: 5 | name: simple-opa 6 | spec: 7 | image: 8 | productVersion: "1.8.0" 9 | servers: 10 | roleGroups: 11 | default: {} 12 | -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/opa.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: opa.stackable.tech/v1alpha1 3 | kind: OpaCluster 4 | metadata: 5 | name: simple-opa 6 | spec: 7 | image: 8 | productVersion: "1.8.0" 9 | servers: 10 | roleGroups: 11 | default: {} 12 | -------------------------------------------------------------------------------- /tests/templates/kuttl/ad-user-info/00-limit-range.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: LimitRange 4 | metadata: 5 | name: limit-request-ratio 6 | spec: 7 | limits: 8 | - type: "Container" 9 | maxLimitRequestRatio: 10 | cpu: 5 11 | memory: 1 12 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/04-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 300 5 | --- 6 | apiVersion: apps/v1 7 | kind: StatefulSet 8 | metadata: 9 | name: opa-test-runner 10 | status: 11 | readyReplicas: 1 12 | replicas: 1 13 | -------------------------------------------------------------------------------- /tests/templates/kuttl/resources/01-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 5 | --- 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: vector-aggregator-discovery 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/01-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 5 | --- 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: vector-aggregator-discovery 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/01-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 5 | --- 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: vector-aggregator-discovery 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /tests/templates/kuttl/ad-user-info/01-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 5 | --- 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: vector-aggregator-discovery 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/00-limit-range.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: LimitRange 4 | metadata: 5 | name: limit-request-ratio 6 | spec: 7 | limits: 8 | - type: "Container" 9 | maxLimitRequestRatio: 10 | cpu: 5 11 | memory: 1 12 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/04-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 300 5 | --- 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | name: keycloak 10 | status: 11 | readyReplicas: 1 12 | replicas: 1 13 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/01-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 600 5 | --- 6 | apiVersion: apps/v1 7 | kind: StatefulSet 8 | metadata: 9 | name: opa-vector-aggregator 10 | status: 11 | readyReplicas: 1 12 | replicas: 1 13 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/00-limit-range.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: LimitRange 4 | metadata: 5 | name: limit-request-ratio 6 | spec: 7 | limits: 8 | - type: "Container" 9 | maxLimitRequestRatio: 10 | cpu: 5 11 | memory: 1 12 | -------------------------------------------------------------------------------- /docs/modules/opa/partials/supported-versions.adoc: -------------------------------------------------------------------------------- 1 | // The version ranges supported by the OPA operator 2 | // This is a separate file, since it is used by both the direct OPA documentation, and the overarching 3 | // Stackable Platform documentation. 4 | 5 | * 1.8.0 6 | * 1.4.2 (deprecated) 7 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/00-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 5 | --- 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: vector-aggregator-discovery 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/01-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 5 | --- 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: vector-aggregator-discovery 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/01-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 5 | --- 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: vector-aggregator-discovery 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /tests/templates/kuttl/ad-user-info/30-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-regorule 6 | commands: 7 | - script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-server:8081/v1/data/test' 8 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/10-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: install-opa 6 | timeout: 300 7 | commands: 8 | - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa --timeout 301s 9 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/30-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-regorule 6 | commands: 7 | - script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-server:8081/v1/data/test' 8 | -------------------------------------------------------------------------------- /tests/templates/kuttl/ad-user-info/10-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: install-opa 6 | timeout: 300 7 | commands: 8 | - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa --timeout 301s 9 | -------------------------------------------------------------------------------- /deploy/helm/ct.yaml: -------------------------------------------------------------------------------- 1 | # This file is used for chart-testing (https://github.com/helm/chart-testing) 2 | # The name "ct.yaml" is not very self-descriptive but it is the default that chart-testing is looking for 3 | --- 4 | remote: origin 5 | target-branch: main 6 | chart-dirs: 7 | - deploy/helm 8 | all: true 9 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | data: 4 | {{ (.Files.Glob "configs/*").AsConfig | indent 2 }} 5 | kind: ConfigMap 6 | metadata: 7 | name: {{ include "operator.fullname" . }}-configmap 8 | labels: 9 | {{- include "operator.labels" . | nindent 4 }} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/10-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: install-opa 6 | timeout: 300 7 | commands: 8 | - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa --timeout 301s 9 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/30-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-regorule 6 | commands: 7 | - script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-server:8081/v1/data/test' 8 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/operations/cluster-operations.adoc: -------------------------------------------------------------------------------- 1 | = Cluster Operation 2 | 3 | OPA installations can be configured with different cluster operations like pausing reconciliation or stopping the cluster. See xref:concepts:operations/cluster_operations.adoc[cluster operations] for more details. 4 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/01-install-vector-aggregator-discovery-configmap.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 2 | --- 3 | apiVersion: v1 4 | kind: ConfigMap 5 | metadata: 6 | name: vector-aggregator-discovery 7 | data: 8 | ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/01-install-vector-aggregator-discovery-configmap.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 2 | --- 3 | apiVersion: v1 4 | kind: ConfigMap 5 | metadata: 6 | name: vector-aggregator-discovery 7 | data: 8 | ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/20-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-regorule 6 | timeout: 300 7 | --- 8 | apiVersion: apps/v1 9 | kind: StatefulSet 10 | metadata: 11 | name: test-regorule 12 | status: 13 | readyReplicas: 1 14 | replicas: 1 15 | -------------------------------------------------------------------------------- /tests/templates/kuttl/ad-user-info/01-install-vector-aggregator-discovery-configmap.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 2 | --- 3 | apiVersion: v1 4 | kind: ConfigMap 5 | metadata: 6 | name: vector-aggregator-discovery 7 | data: 8 | ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/ad-user-info/20-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-regorule 6 | timeout: 300 7 | --- 8 | apiVersion: apps/v1 9 | kind: StatefulSet 10 | metadata: 11 | name: test-regorule 12 | status: 13 | readyReplicas: 1 14 | replicas: 1 15 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/05-test-log-aggregation.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: | 6 | kubectl cp ./test_log_aggregation.py $NAMESPACE/opa-test-runner-0:/tmp 7 | - script: | 8 | kubectl cp ./test_log_aggregation.sh $NAMESPACE/opa-test-runner-0:/tmp 9 | -------------------------------------------------------------------------------- /tests/templates/kuttl/resources/01-install-vector-aggregator-discovery-configmap.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 2 | --- 3 | apiVersion: v1 4 | kind: ConfigMap 5 | metadata: 6 | name: vector-aggregator-discovery 7 | data: 8 | ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/00-install-vector-aggregator-discovery-configmap.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 2 | --- 3 | apiVersion: v1 4 | kind: ConfigMap 5 | metadata: 6 | name: vector-aggregator-discovery 7 | data: 8 | ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/01-install-vector-aggregator-discovery-configmap.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 2 | --- 3 | apiVersion: v1 4 | kind: ConfigMap 5 | metadata: 6 | name: vector-aggregator-discovery 7 | data: 8 | ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/20-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-regorule 6 | timeout: 300 7 | --- 8 | apiVersion: apps/v1 9 | kind: StatefulSet 10 | metadata: 11 | name: test-regorule 12 | status: 13 | readyReplicas: 1 14 | replicas: 1 15 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/01-install-vector-aggregator-discovery-configmap.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 2 | --- 3 | apiVersion: v1 4 | kind: ConfigMap 5 | metadata: 6 | name: vector-aggregator-discovery 7 | data: 8 | ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/40-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-regorule 6 | timeout: 300 7 | --- 8 | apiVersion: apps/v1 9 | kind: StatefulSet 10 | metadata: 11 | name: test-regorule 12 | status: 13 | readyReplicas: 1 14 | replicas: 1 15 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/Chart.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v2 3 | name: opa-operator 4 | version: "0.0.0-dev" 5 | appVersion: "0.0.0-dev" 6 | description: The Stackable Operator for OpenPolicyAgent 7 | home: https://github.com/stackabletech/opa-operator 8 | maintainers: 9 | - name: Stackable 10 | url: https://www.stackable.tech 11 | -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/simple-rule.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: test 6 | labels: 7 | opa.stackable.tech/bundle: "true" 8 | data: 9 | test.rego: | 10 | package test 11 | 12 | hello if { 13 | true 14 | } 15 | 16 | world if { 17 | false 18 | } 19 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>stackabletech/.github:renovate-config" 5 | ], 6 | "ignorePaths": [".github/workflows/build.yaml", ".github/workflows/general_daily_security.yml", ".github/workflows/integration-test.yml", ".github/workflows/pr_pre-commit.yaml"] 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/02-create-configmap-with-prepared-logs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: > 6 | kubectl create configmap prepared-logs 7 | --from-file=prepared-opa-logs.json 8 | --from-file=prepared-bundle-builder-logs.tracing-rs.json 9 | --namespace=$NAMESPACE 10 | -------------------------------------------------------------------------------- /rust/user-info-fetcher/README.md: -------------------------------------------------------------------------------- 1 | # User info fetcher 2 | 3 | Fetches user metadata from a directory service, and exposes it in a normalized format for OPA rules to read. 4 | 5 | It is deployed by the Stackable OPA Operator, and is not recommended for standalone use. 6 | 7 | ## Supported backends 8 | 9 | - `none` - Dummy backend 10 | - `keycloak` - Keycloak 11 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/10-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 600 5 | commands: 6 | - script: kubectl -n $NAMESPACE rollout status daemonset test-opa-server-default --timeout 600s 7 | - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa --timeout 600s 8 | -------------------------------------------------------------------------------- /.readme/partials/borrowed/overview_blurb.md.j2: -------------------------------------------------------------------------------- 1 | 2 | It is part of the Stackable Data Platform, a curated selection of the best open source data apps like Apache Kafka, Apache Druid, Trino or Apache Spark, [all](#other-operators) working together seamlessly. Based on Kubernetes, it runs everywhere – [on prem or in the cloud](#supported-platforms). 3 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/operations/index.adoc: -------------------------------------------------------------------------------- 1 | 2 | = Operations 3 | 4 | This section of the documentation is intended for the operations teams that maintain a Stackable Data Platform installation. 5 | 6 | Please read the xref:concepts:operations/index.adoc[Concepts page on Operations] that contains the necessary details to operate the platform in a production environment. 7 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/05-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | commands: 5 | - script: >- 6 | kubectl exec --namespace=$NAMESPACE opa-test-runner-0 -- 7 | python /tmp/test_log_aggregation.py -n $NAMESPACE 8 | - script: >- 9 | kubectl exec --namespace=$NAMESPACE opa-test-runner-0 -- 10 | /tmp/test_log_aggregation.sh 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | **/*.rs.bk 4 | 5 | .idea/ 6 | *.iws 7 | 8 | Cargo.nix 9 | crate-hashes.json 10 | result 11 | image.tar 12 | 13 | # We do NOT want to ignore .git because we use the `built` crate to gather the current git commit hash at built time 14 | # This means we need the .git directory in our Docker image, it will be thrown away and won't be included in the final image 15 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/20-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 180 5 | commands: 6 | - script: kubectl -n $NAMESPACE wait --for=condition=stopped opaclusters.opa.stackable.tech/test-opa --timeout 181s 7 | --- 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: test-opa-server-default 12 | status: 13 | numberReady: 0 14 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/40-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 300 5 | commands: 6 | # Wait for at least one pod come back up 7 | - script: sleep 5 && kubectl -n $NAMESPACE get pods | grep "opa-server-default-*" | grep Running 8 | - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa --timeout 301s 9 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/00-patch-ns.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if test_scenario['values']['openshift'] == 'true' %} 2 | # see https://github.com/stackabletech/issues/issues/566 3 | --- 4 | apiVersion: kuttl.dev/v1beta1 5 | kind: TestStep 6 | commands: 7 | - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' 8 | timeout: 120 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/00-patch-ns.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if test_scenario['values']['openshift'] == 'true' %} 2 | # see https://github.com/stackabletech/issues/issues/566 3 | --- 4 | apiVersion: kuttl.dev/v1beta1 5 | kind: TestStep 6 | commands: 7 | - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' 8 | timeout: 120 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/resources/00-patch-ns.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if test_scenario['values']['openshift'] == 'true' %} 2 | # see https://github.com/stackabletech/issues/issues/566 3 | --- 4 | apiVersion: kuttl.dev/v1beta1 5 | kind: TestStep 6 | commands: 7 | - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' 8 | timeout: 120 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/00-patch-ns.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if test_scenario['values']['openshift'] == 'true' %} 2 | # see https://github.com/stackabletech/issues/issues/566 3 | --- 4 | apiVersion: kuttl.dev/v1beta1 5 | kind: TestStep 6 | commands: 7 | - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' 8 | timeout: 120 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/ad-user-info/00-patch-ns.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if test_scenario['values']['openshift'] == 'true' %} 2 | # see https://github.com/stackabletech/issues/issues/566 3 | --- 4 | apiVersion: kuttl.dev/v1beta1 5 | kind: TestStep 6 | commands: 7 | - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' 8 | timeout: 120 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/30-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 180 5 | commands: 6 | - script: kubectl -n $NAMESPACE wait --for=condition=reconciliationPaused opaclusters.opa.stackable.tech/test-opa --timeout 181s 7 | --- 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: test-opa-server-default 12 | status: 13 | numberReady: 0 14 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/00-patch-ns.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if test_scenario['values']['openshift'] == 'true' %} 2 | # see https://github.com/stackabletech/issues/issues/566 3 | --- 4 | apiVersion: kuttl.dev/v1beta1 5 | kind: TestStep 6 | commands: 7 | - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' 8 | timeout: 120 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/00-patch-ns.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if test_scenario['values']['openshift'] == 'true' %} 2 | # see https://github.com/stackabletech/issues/issues/566 3 | --- 4 | apiVersion: kuttl.dev/v1beta1 5 | kind: TestStep 6 | commands: 7 | - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' 8 | timeout: 120 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/00-patch-ns.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if test_scenario['values']['openshift'] == 'true' %} 2 | # see https://github.com/stackabletech/issues/issues/566 3 | --- 4 | apiVersion: kuttl.dev/v1beta1 5 | kind: TestStep 6 | commands: 7 | - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' 8 | timeout: 120 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/20-load-ldif.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: | 6 | # Load group structure and memberships (use -c to continue on errors like "already exists") 7 | kubectl exec -n $NAMESPACE test-openldap-0 -- \ 8 | ldapadd -c -x -H ldap://localhost:1389 -D "cn=ldapadmin,dc=example,dc=org" -w ldapadminpassword -f /tmp/ldifs/add-groups.ldif 9 | -------------------------------------------------------------------------------- /scripts/render_readme.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # Check if jinja2 is there 5 | if ! command -v jinja2 &> /dev/null 6 | then 7 | echo "jinja2 could not be found. Use 'pip install jinja2-cli' to install it." 8 | exit 1 9 | fi 10 | 11 | SCRIPT_DIR=$(dirname "$0") 12 | cd "$SCRIPT_DIR/../.readme" 13 | jinja2 README.md.j2 -o ../README.md 14 | cd .. 15 | 16 | python3 scripts/ensure_one_trailing_newline.py README.md 17 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/32-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-env-overrides 6 | commands: 7 | # Role level env var 8 | - script: kubectl exec -n $NAMESPACE -c opa svc/test-opa-server -- env | grep SERVER_ROLE_LEVEL_ENV_VAR 9 | # RoleGroup level env var 10 | - script: kubectl exec -n $NAMESPACE -c opa svc/test-opa-server -- env | grep SERVER_ROLE_GROUP_LEVEL_ENV_VAR 11 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/reference/index.adoc: -------------------------------------------------------------------------------- 1 | = Reference 2 | 3 | Consult the reference documentation section to find exhaustive information on: 4 | 5 | * Descriptions and default values of all properties in the CRDs used by this operator in the xref:reference/crds.adoc[]. 6 | * The properties in the xref:reference/discovery.adoc[]. 7 | * The xref:reference/commandline-parameters.adoc[] and xref:reference/environment-variables.adoc[] accepted by the operator. 8 | -------------------------------------------------------------------------------- /rust/regorule-library/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stackable-opa-regorule-library" 3 | description = "Contains Stackable's library of common regorules" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | repository.workspace = true 9 | publish = false 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: true 3 | contact_links: 4 | - name: 🙋🏾 Question 5 | about: Use this to ask a question about this project 6 | url: https://github.com/orgs/stackabletech/discussions/new?category=q-a 7 | - name: 🚀 Feature Requests and other things 8 | about: Open an issue with your feature request or any other issue not covered elsewhere 9 | url: https://github.com/stackabletech/opa-operator/issues/new 10 | -------------------------------------------------------------------------------- /.hadolint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ignored: 3 | # Warning: Use the -y switch to avoid manual input dnf install -y 4 | # https://github.com/hadolint/hadolint/wiki/DL3038 5 | # Reason: We set `assumeyes=True` in dnf.conf in our base image 6 | - DL3038 7 | 8 | # Warning: Specify version with dnf install -y - 9 | # https://github.com/hadolint/hadolint/wiki/DL3041 10 | # Reason: It's good advice, but we're not set up to pin versions just yet 11 | - DL3041 12 | -------------------------------------------------------------------------------- /deploy/DO_NOT_EDIT.md: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT 2 | 3 | These Helm charts and manifests are automatically generated. 4 | Please do not edit anything except for files explicitly mentioned below in this 5 | directory manually. 6 | 7 | The following files are ok to edit: 8 | 9 | - helm/opa-operator/templates/roles.yaml 10 | - helm/opa-operator/values.yaml 11 | 12 | The details are in-motion but check this repository for a few details: 13 | 14 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/index.adoc: -------------------------------------------------------------------------------- 1 | = Usage guide 2 | :description: Learn how to configure and use the Stackable OPA operator. Follow the guide for setup instructions and advanced configurations. 3 | :page-aliases: usage.adoc 4 | 5 | This section helps you to use and configure the Stackable operator for OPA in various ways. 6 | You should already be familiar with how to set up a basic instance. 7 | Follow the xref:getting_started/index.adoc[] guide to learn how to set up a basic instance. 8 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # This file includes unstable features, so you need to run "cargo +nightly fmt" to format your code. 2 | # It's also ok to use the stable toolchain by simple running "cargo fmt", but using the nigthly formatter is prefered. 3 | 4 | # https://doc.rust-lang.org/nightly/edition-guide/rust-2024/rustfmt-style-edition.html 5 | style_edition = "2024" 6 | imports_granularity = "Crate" 7 | group_imports = "StdExternalCrate" 8 | reorder_impl_items = true 9 | use_field_init_shorthand = true 10 | -------------------------------------------------------------------------------- /tests/release.yaml: -------------------------------------------------------------------------------- 1 | # Contains all operators required to run the test suite. 2 | --- 3 | releases: 4 | # Do not change the name of the release as it's referenced from run-tests 5 | tests: 6 | releaseDate: 1970-01-01 7 | description: Integration test 8 | products: 9 | commons: 10 | operatorVersion: 0.0.0-dev 11 | secret: 12 | operatorVersion: 0.0.0-dev 13 | listener: 14 | operatorVersion: 0.0.0-dev 15 | opa: 16 | operatorVersion: 0.0.0-dev 17 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/01-install-opa-vector-aggregator.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: >- 6 | helm install opa-vector-aggregator vector 7 | --namespace $NAMESPACE 8 | --version 0.45.0 9 | --repo https://helm.vector.dev 10 | --values opa-vector-aggregator-values.yaml 11 | --- 12 | apiVersion: v1 13 | kind: ConfigMap 14 | metadata: 15 | name: opa-vector-aggregator-discovery 16 | data: 17 | ADDRESS: opa-vector-aggregator:6123 18 | -------------------------------------------------------------------------------- /.yamllint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | ignore: | 5 | deploy/helm/**/templates 6 | 7 | rules: 8 | line-length: disable 9 | truthy: 10 | check-keys: false 11 | comments: 12 | min-spaces-from-content: 1 # Needed due to https://github.com/adrienverge/yamllint/issues/443 13 | indentation: 14 | indent-sequences: consistent 15 | comments-indentation: disable # This is generally useless and interferes with commented example values 16 | braces: 17 | max-spaces-inside: 1 18 | max-spaces-inside-empty: 0 19 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Debug operator binary", 8 | "cargo": { 9 | "args": ["build"], 10 | "filter": { 11 | "name": "stackable-{[ operator.name }]", 12 | "kind": "bin" 13 | } 14 | }, 15 | "args": ["run"], 16 | "cwd": "${workspaceFolder}" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/11-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This test checks if the containerdebug-state.json file is present and valid 3 | apiVersion: kuttl.dev/v1beta1 4 | kind: TestAssert 5 | timeout: 600 6 | commands: 7 | - script: | 8 | FIRST_OPA_POD=$(kubectl get -n $NAMESPACE pods --field-selector=status.phase=Running --selector app.kubernetes.io/instance=test-opa -o jsonpath='{.items[0].metadata.name}') 9 | kubectl exec -n $NAMESPACE --container opa $FIRST_OPA_POD -- cat /stackable/log/containerdebug-state.json | jq --exit-status '"valid JSON"' 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/30-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-regorule 6 | commands: 7 | {% if test_scenario['values']['use-tls'] == "true" %} 8 | - script: kubectl exec -n $NAMESPACE test-opa-0 -- python /tmp/30_test-regorule.py -u "https://test-opa-server.$NAMESPACE.svc.cluster.local:8443/v1/data/test" 9 | {% else %} 10 | - script: kubectl exec -n $NAMESPACE test-opa-0 -- python /tmp/30_test-regorule.py -u "http://test-opa-server.$NAMESPACE.svc.cluster.local:8081/v1/data/test" 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/operations/pod-disruptions.adoc: -------------------------------------------------------------------------------- 1 | = Allowed Pod disruptions 2 | 3 | For OPA clusters, the operator does not deploy any PodDisruptionBudgets (PDBs), as there is one instance per Kubernetes node running (Daemonset). 4 | When a Kubernetes node gets drained to gracefully shut it down, the OPA Pod get's evicted - there is no point in blocking the eviction. 5 | In case the OPA Pod terminated before the products depending on OPA (e.g. Trino coordinator) on the same node, the products can still use the OPA Service, as it routes to OPA Pods running on other Kubernetes nodes. 6 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/31-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-metrics 6 | commands: 7 | {% if test_scenario['values']['use-tls'] == "true" %} 8 | - script: kubectl exec -n $NAMESPACE test-opa-0 -- python /tmp/30_test-metrics.py -u "https://test-opa-server-default-metrics.$NAMESPACE.svc.cluster.local:8443/metrics" 9 | {% else %} 10 | - script: kubectl exec -n $NAMESPACE test-opa-0 -- python /tmp/30_test-metrics.py -u "http://test-opa-server-default-metrics.$NAMESPACE.svc.cluster.local:8081/metrics" 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /tests/interu.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | runners: 3 | amd64: 4 | platform: aks-1.32 5 | ttl: 6h 6 | node-groups: 7 | - name: default 8 | arch: amd64 9 | size: medium 10 | disk-gb: 100 11 | nodes: 3 12 | 13 | profiles: 14 | # TODO (@Techassi): This will be enabled later 15 | # schedule: 16 | # strategy: use-runner 17 | # runner: amd64 18 | # options: 19 | # beku-parallelism: 2 20 | smoke-latest: 21 | strategy: use-runner 22 | runner: amd64 23 | options: 24 | beku-parallelism: 2 25 | beku-test-suite: smoke-latest 26 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/30-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: install-opa 6 | timeout: 300 7 | commands: 8 | - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa-groupofnames-tls --timeout 301s 9 | - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa-groupofnames-notls --timeout 301s 10 | - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa-posixgroup-tls --timeout 301s 11 | -------------------------------------------------------------------------------- /rust/regorule-library/src/userinfo/v1.rego: -------------------------------------------------------------------------------- 1 | package stackable.opa.userinfo.v1 2 | 3 | # Lookup by (human-readable) username 4 | userInfoByUsername(username) := http.send({ 5 | "method": "POST", 6 | "url": "http://127.0.0.1:9476/user", 7 | "body": {"username": username}, 8 | "headers": {"Content-Type": "application/json"}, 9 | "raise_error": true 10 | }).body 11 | 12 | # Lookup by stable user identifier 13 | userInfoById(id) := http.send({ 14 | "method": "POST", 15 | "url": "http://127.0.0.1:9476/user", 16 | "body": {"id": id}, 17 | "headers": {"Content-Type": "application/json"}, 18 | "raise_error": true 19 | }).body 20 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/.helmignore: -------------------------------------------------------------------------------- 1 | # ============= 2 | # This file is automatically generated from the templates in stackabletech/operator-templating 3 | # DON'T MANUALLY EDIT THIS FILE 4 | # ============= 5 | 6 | # Patterns to ignore when building packages. 7 | # This supports shell glob matching, relative path matching, and 8 | # negation (prefixed with !). Only one pattern per line. 9 | .DS_Store 10 | # Common VCS dirs 11 | .git/ 12 | .gitignore 13 | .bzr/ 14 | .bzrignore 15 | .hg/ 16 | .hgignore 17 | .svn/ 18 | # Common backup files 19 | *.swp 20 | *.bak 21 | *.tmp 22 | *.orig 23 | *~ 24 | # Various IDEs 25 | .project 26 | .idea/ 27 | *.tmproj 28 | .vscode/ 29 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/09-install-secretclass.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if test_scenario['values']['use-tls'] == "true" %} 2 | --- 3 | apiVersion: kuttl.dev/v1beta1 4 | kind: TestStep 5 | commands: 6 | - script: | 7 | kubectl apply -n $NAMESPACE -f - << EOF 8 | --- 9 | apiVersion: secrets.stackable.tech/v1alpha1 10 | kind: SecretClass 11 | metadata: 12 | name: opa-tls-$NAMESPACE 13 | spec: 14 | backend: 15 | autoTls: 16 | ca: 17 | autoGenerate: true 18 | secret: 19 | name: opa-tls-ca 20 | namespace: $NAMESPACE 21 | EOF 22 | {% endif %} 23 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/templates/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | # Note(@sbernauer): We could also call the Service something like 7 | # "product-operator-conversion-webhook". However, in the future we will have more webhooks, and 8 | # it seems like an overkill to have a dedicated Service per webhook. 9 | name: {{ include "operator.fullname" . }} 10 | labels: 11 | {{- include "operator.labels" . | nindent 4 }} 12 | spec: 13 | selector: 14 | {{- include "operator.selectorLabels" . | nindent 6 }} 15 | ports: 16 | - name: conversion-webhook 17 | protocol: TCP 18 | port: 8443 19 | targetPort: 8443 20 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/50-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | metadata: 5 | name: test-regorule 6 | commands: 7 | - script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-groupofnames-tls-server:8081/v1/data/test' -t groupofnames-tls 8 | - script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-groupofnames-notls-server:8081/v1/data/test' -t groupofnames-notls 9 | - script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-posixgroup-tls-server:8081/v1/data/test' -t posixgroup-tls 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/normal-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Normal issue 3 | about: This is just a normal empty issue with a simple checklist 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Issue checklist 11 | 12 | This is a simple checklist of things to bear in mind when creating a new issue. 13 | 14 | - [ ] Describe the use-case, as far is possible. For instance, using the pattern "As a XXXX, I would like XXXX to be able to do XXXX" helps to identify the feature as well as the problem it is intended to address. 15 | - [ ] Indicate an approximate level of importance and urgency. 16 | - [ ] Indicate if there is a known work-around until such time as the issue has been implemented. 17 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/test_log_aggregation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # we should distinguish between cases where the variable is not set 4 | # if the service or container is not found (which we don't want), and where 5 | # grep does not find anything (which is what we *do* want). 6 | 7 | set -Eeuo pipefail 8 | 9 | # do not mask kubectl errors, but the right-hand group must always be true 10 | # so the test does not fail on the grep 11 | DECISION_LOGS=$(kubectl logs service/test-opa-server -c opa | { grep "decision_id" || true; }) 12 | 13 | if [ -n "$DECISION_LOGS" ]; 14 | then 15 | echo "Error: Decision logs printed to console"; 16 | exit 1; 17 | fi 18 | 19 | echo "Test successful!"; 20 | -------------------------------------------------------------------------------- /.readme/README.md.j2: -------------------------------------------------------------------------------- 1 | {%- set title="Stackable Operator for Open Policy Agent" -%} 2 | {%- set operator_name="opa" -%} 3 | {%- set operator_docs_slug="opa" -%} 4 | {%- set related_reading_links=[] -%} 5 | 6 | {% filter trim %} 7 | {%- include "partials/borrowed/header.md.j2" -%} 8 | {% endfilter %} 9 | 10 | {% filter trim %} 11 | {%- include "partials/borrowed/links.md.j2" -%} 12 | {% endfilter %} 13 | 14 | {% filter trim %} 15 | {%- include "partials/main.md.j2" -%} 16 | {% endfilter %} 17 | 18 | {% filter trim %} 19 | {%- include "partials/borrowed/footer.md.j2" -%} 20 | {% endfilter %} 21 | 22 | {% filter trim %} 23 | {%- include "partials/borrowed/related_reading.md.j2" -%} 24 | {% endfilter %} 25 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/listenerclass.adoc: -------------------------------------------------------------------------------- 1 | = Service exposition with ListenerClasses 2 | 3 | OPA offers an API. 4 | The operator deploys a Service called `` (where `` is the name of the OpaCluster) through which OPA can be reached. 5 | 6 | This service can have three different types: `cluster-internal`, `external-unstable` and `external-stable`. 7 | Read more about the types in the xref:concepts:service-exposition.adoc[service exposition] documentation at platform level. 8 | 9 | This is how the ListenerClass is configured: 10 | 11 | [source,yaml] 12 | ---- 13 | spec: 14 | clusterConfig: 15 | listenerClass: cluster-internal # <1> 16 | ---- 17 | <1> The default `cluster-internal` setting. 18 | -------------------------------------------------------------------------------- /.github/workflows/general_daily_security.yml: -------------------------------------------------------------------------------- 1 | # ============= 2 | # This file is automatically generated from the templates in stackabletech/operator-templating 3 | # DON'T MANUALLY EDIT THIS FILE 4 | # ============= 5 | --- 6 | name: Daily Security Audit 7 | 8 | on: 9 | schedule: 10 | - cron: '15 4 * * *' 11 | workflow_dispatch: 12 | 13 | permissions: {} 14 | 15 | jobs: 16 | audit: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 20 | with: 21 | persist-credentials: false 22 | - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0 23 | with: 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /tests/templates/kuttl/ad-user-info/20-install-test-regorule.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: StatefulSet 4 | metadata: 5 | name: test-regorule 6 | labels: 7 | app: test-regorule 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: test-regorule 13 | template: 14 | metadata: 15 | labels: 16 | app: test-regorule 17 | spec: 18 | containers: 19 | - name: test-regorule 20 | image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev 21 | stdin: true 22 | tty: true 23 | resources: 24 | requests: 25 | memory: "128Mi" 26 | cpu: "512m" 27 | limits: 28 | memory: "128Mi" 29 | cpu: "1" 30 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/templates/_maintenance.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Create a list of maintenance related env vars. 3 | */}} 4 | {{- define "maintenance.envVars" -}} 5 | {{- with .Values.maintenance }} 6 | {{- if not .endOfSupportCheck.enabled }} 7 | - name: EOS_DISABLED 8 | value: "true" 9 | {{- end }} 10 | {{- if and .endOfSupportCheck.enabled .endOfSupportCheck.mode }} 11 | - name: EOS_CHECK_MODE 12 | value: {{ .endOfSupportCheck.mode }} 13 | {{ end }} 14 | {{- if and .endOfSupportCheck.enabled .endOfSupportCheck.interval }} 15 | - name: EOS_INTERVAL 16 | value: {{ .endOfSupportCheck.interval }} 17 | {{ end }} 18 | {{- if not .customResourceDefinitions.maintain }} 19 | - name: DISABLE_CRD_MAINTENANCE 20 | value: "true" 21 | {{- end }} 22 | {{- end }} 23 | {{- end }} 24 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/20-install-test-regorule.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: StatefulSet 4 | metadata: 5 | name: test-regorule 6 | labels: 7 | app: test-regorule 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: test-regorule 13 | template: 14 | metadata: 15 | labels: 16 | app: test-regorule 17 | spec: 18 | containers: 19 | - name: test-regorule 20 | image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev 21 | stdin: true 22 | tty: true 23 | resources: 24 | requests: 25 | memory: "128Mi" 26 | cpu: "512m" 27 | limits: 28 | memory: "128Mi" 29 | cpu: "1" 30 | -------------------------------------------------------------------------------- /tests/templates/kuttl/openldap-user-info/40-install-test-regorule.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: StatefulSet 4 | metadata: 5 | name: test-regorule 6 | labels: 7 | app: test-regorule 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: test-regorule 13 | template: 14 | metadata: 15 | labels: 16 | app: test-regorule 17 | spec: 18 | containers: 19 | - name: test-regorule 20 | image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev 21 | stdin: true 22 | tty: true 23 | resources: 24 | requests: 25 | memory: "128Mi" 26 | cpu: "512m" 27 | limits: 28 | memory: "128Mi" 29 | cpu: "1" 30 | -------------------------------------------------------------------------------- /nix/README.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # Updating nix dependencies 7 | 8 | ## Run the following for an operator 9 | 10 | > [!NOTE] 11 | > We track the `master` branch of crate2nix as that is relatively up to date, but the releases are infrequent. 12 | 13 | ```shell 14 | niv update crate2nix 15 | niv update nixpkgs 16 | niv update beku.py -b X.Y.Z # Using the release tag 17 | ``` 18 | 19 | ### Test 20 | 21 | - Run make `regenerate-nix` to ensure crate2nix works 22 | - Run a smoke test to ensure beku.py works. 23 | - Run `make run-dev` to ensure nixpkgs are fine. 24 | 25 | ## Update operator-templating 26 | 27 | Do the same as above, but from `template/` 28 | -------------------------------------------------------------------------------- /.readme/partials/borrowed/documentation.md.j2: -------------------------------------------------------------------------------- 1 | 2 | ## Documentation 3 | 4 | The stable documentation for this operator can be found in our [Stackable Data Platform documentation](https://docs.stackable.tech/home/stable/{{operator_docs_slug}}). 5 | If you are interested in the most recent state of this repository, check out the [nightly docs](https://docs.stackable.tech/home/nightly/{{operator_docs_slug}}) instead. 6 | 7 | The documentation for all Stackable products can be found at [docs.stackable.tech](https://docs.stackable.tech). 8 | 9 | If you have a question about the Stackable Data Platform, contact us via our [homepage](https://stackable.tech/) or ask a public question in our [Discussions forum](https://github.com/orgs/stackabletech/discussions). 10 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/20-install-test-regorule.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: StatefulSet 4 | metadata: 5 | name: test-regorule 6 | labels: 7 | app: test-regorule 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: test-regorule 13 | template: 14 | metadata: 15 | labels: 16 | app: test-regorule 17 | spec: 18 | serviceAccountName: test-sa 19 | containers: 20 | - name: test-regorule 21 | image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev 22 | stdin: true 23 | tty: true 24 | resources: 25 | requests: 26 | memory: "128Mi" 27 | cpu: "512m" 28 | limits: 29 | memory: "128Mi" 30 | cpu: "1" 31 | -------------------------------------------------------------------------------- /.readme/partials/main.md.j2: -------------------------------------------------------------------------------- 1 | This is a Kubernetes operator to manage [Open Policy Agent](https://www.openpolicyagent.org/) servers. 2 | 3 | {% filter trim %} 4 | {%- include "partials/borrowed/overview_blurb.md.j2" -%} 5 | {% endfilter %} 6 | 7 | ## Installation 8 | 9 | You can install the operator using [stackablectl or helm](https://docs.stackable.tech/home/stable/{{operator_name}}/getting_started/installation). 10 | 11 | Read on to get started with it, or see it in action in one of our [demos](https://stackable.tech/en/demos/). 12 | 13 | ## Getting Started 14 | 15 | You can follow this [tutorial](https://docs.stackable.tech/home/stable/{{operator_name}}/getting_started/first_steps) . 16 | 17 | {% filter trim %} 18 | {%- include "partials/borrowed/documentation.md.j2" -%} 19 | {% endfilter %} 20 | -------------------------------------------------------------------------------- /scripts/generate-manifests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This script reads a Helm chart from deploy/helm/opa-operator and 3 | # generates manifest files into deploy/manifestss 4 | set -e 5 | 6 | tmp=$(mktemp -d ./manifests-XXXXX) 7 | 8 | helm template --output-dir "$tmp" \ 9 | --include-crds \ 10 | --name-template opa-operator \ 11 | deploy/helm/opa-operator 12 | 13 | for file in "$tmp"/opa-operator/*/*; do 14 | yq eval -i 'del(.. | select(has("app.kubernetes.io/managed-by")) | ."app.kubernetes.io/managed-by")' /dev/stdin < "$file" 15 | yq eval -i 'del(.. | select(has("helm.sh/chart")) | ."helm.sh/chart")' /dev/stdin < "$file" 16 | sed -i '/# Source: .*/d' "$file" 17 | done 18 | 19 | cp -r "$tmp"/opa-operator/*/* deploy/manifests/ 20 | 21 | rm -rf "$tmp" 22 | -------------------------------------------------------------------------------- /.readme/partials/borrowed/links.md.j2: -------------------------------------------------------------------------------- 1 | 2 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/stackabletech/{{operator_name}}-operator/graphs/commit-activity) 3 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](https://docs.stackable.tech/home/stable/contributor/index.html) 4 | [![License OSL3.0](https://img.shields.io/badge/license-OSL3.0-green)](./LICENSE) 5 | 6 | [Documentation](https://docs.stackable.tech/home/stable/{{operator_docs_slug}}) {% if quickstart_link %}| [Quickstart]({{quickstart_link}}) {% endif %}| [Stackable Data Platform](https://stackable.tech/) | [Platform Docs](https://docs.stackable.tech/) | [Discussions](https://github.com/orgs/stackabletech/discussions) | [Discord](https://discord.gg/7kZ3BNnCAF) 7 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/monitoring.adoc: -------------------------------------------------------------------------------- 1 | = Monitoring 2 | :description: The managed OPA instances are automatically configured to export Prometheus metrics. 3 | 4 | The managed OPA instances are automatically configured to export Prometheus metrics. 5 | See xref:operators:monitoring.adoc[] for more details. 6 | 7 | You can read the list of exported metrics in the https://www.openpolicyagent.org/docs/monitoring#prometheus[OPA monitoring documentation]. 8 | 9 | Especially worth mentioning are the https://www.openpolicyagent.org/docs/monitoring#status-metrics[Status metrics], which contain e.g. `opa_info`, `bundle_loaded_counter` and `bundle_failed_load_counter`. 10 | You can use these to create a dashboard to see any bundle loading problems at a glance. 11 | You can also set up alerts to get notified proactively. 12 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/templates/roles-opa-builder.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: {{ .Release.Name }}-opa-bundle-builder-clusterrole 5 | # This role is used for the OPA bundle builder. 6 | # It needs to read ConfigMaps and watch ConfigMaps for changes, 7 | # because the Rego rules that are used to build the bundles are 8 | # stored in the ConfigMaps. 9 | rules: 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - configmaps 14 | verbs: 15 | - get 16 | - watch 17 | - list 18 | {{ if .Capabilities.APIVersions.Has "security.openshift.io/v1" }} 19 | - apiGroups: 20 | - security.openshift.io 21 | resources: 22 | - securitycontextconstraints 23 | resourceNames: 24 | - opa-scc 25 | verbs: 26 | - use 27 | {{ end }} 28 | -------------------------------------------------------------------------------- /rust/regorule-library/README.md: -------------------------------------------------------------------------------- 1 | # Stackable library of shared regorules 2 | 3 | This contains regorules that are shipped by the Stackable Data Platform (SDP) as libraries to help simplify writing authorization rules. 4 | 5 | ## What this is not 6 | 7 | This library should *not* contain rules that only concern one SDP product. Those are the responsibility of their individual operators. 8 | 9 | ## Versioning 10 | 11 | All regorules exposed by this library should be versioned, according to Kubernetes conventions. 12 | 13 | This version covers *breaking changes to the interface*, not the implementation. If a proposed change breaks existing clients, 14 | add a new version. Otherwise, change the latest version inline. 15 | 16 | Ideally, old versions should be implemented on top of newer versions, rather than carry independent implementations. 17 | -------------------------------------------------------------------------------- /rust/operator-binary/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stackable-opa-operator" 3 | description = "Stackable Operator for OPA" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | repository.workspace = true 9 | publish = false 10 | 11 | [dependencies] 12 | product-config.workspace = true 13 | stackable-operator.workspace = true 14 | 15 | anyhow.workspace = true 16 | clap.workspace = true 17 | const_format.workspace = true 18 | fnv.workspace = true 19 | futures.workspace = true 20 | indoc.workspace = true 21 | pin-project.workspace = true 22 | semver.workspace = true 23 | serde_json.workspace = true 24 | serde.workspace = true 25 | snafu.workspace = true 26 | strum.workspace = true 27 | tokio.workspace = true 28 | tracing.workspace = true 29 | 30 | [build-dependencies] 31 | built.workspace = true 32 | -------------------------------------------------------------------------------- /rust/bundle-builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stackable-opa-bundle-builder" 3 | description = "Builds OPA bundles from Kubernetes ConfigMaps" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | repository.workspace = true 9 | publish = false 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | stackable-opa-regorule-library = { path = "../regorule-library" } 15 | stackable-operator.workspace = true 16 | 17 | axum.workspace = true 18 | clap.workspace = true 19 | flate2.workspace = true 20 | futures.workspace = true 21 | hyper.workspace = true 22 | snafu.workspace = true 23 | tar.workspace = true 24 | tokio.workspace = true 25 | tracing.workspace = true 26 | 27 | [build-dependencies] 28 | built.workspace = true 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01-normal-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Normal issue 3 | about: This is just a normal empty issue with a simple checklist 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Issue checklist 11 | 12 | This is a simple checklist of things to bear in mind when creating a new issue. 13 | 14 | - [ ] **Describe the use-case**: As far as possible, use the pattern "As a [type of user], I would like [feature/functionality] to be able to do [specific action]." This helps identify the feature and the problem it addresses. 15 | - [ ] **Indicate importance and urgency**: Use a scale (e.g., low, medium, high) to indicate the level of importance and urgency. 16 | - [ ] **Work-around**: If there is a known work-around, describe it briefly. 17 | - [ ] **Environment**: Describe the environment where the issue occurs (e.g., SDP version, K8S version, etc.). 18 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/10-install-opa.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: opa.stackable.tech/v1alpha1 3 | kind: OpaCluster 4 | metadata: 5 | name: test-opa 6 | spec: 7 | image: 8 | {% if test_scenario['values']['opa-latest'].find(",") > 0 %} 9 | custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" 10 | productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" 11 | {% else %} 12 | productVersion: "{{ test_scenario['values']['opa-latest'] }}" 13 | {% endif %} 14 | pullPolicy: IfNotPresent 15 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 16 | clusterConfig: 17 | vectorAggregatorConfigMapName: vector-aggregator-discovery 18 | {% endif %} 19 | servers: 20 | config: 21 | logging: 22 | enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} 23 | roleGroups: 24 | default: {} 25 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/40-restart-opa.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: opa.stackable.tech/v1alpha1 3 | kind: OpaCluster 4 | metadata: 5 | name: test-opa 6 | spec: 7 | image: 8 | {% if test_scenario['values']['opa-latest'].find(",") > 0 %} 9 | custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" 10 | productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" 11 | {% else %} 12 | productVersion: "{{ test_scenario['values']['opa-latest'] }}" 13 | {% endif %} 14 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 15 | clusterConfig: 16 | vectorAggregatorConfigMapName: vector-aggregator-discovery 17 | {% endif %} 18 | clusterOperation: 19 | stopped: false 20 | reconciliationPaused: false 21 | servers: 22 | config: 23 | logging: 24 | enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} 25 | roleGroups: 26 | default: {} 27 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # All defaults or options can be checked here: 3 | # https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml 4 | 5 | # Default state for all rules 6 | default: true 7 | 8 | # MD013/line-length - Line length 9 | MD013: 10 | # Number of characters 11 | line_length: 9999 12 | # Number of characters for headings 13 | heading_line_length: 9999 14 | # Number of characters for code blocks 15 | code_block_line_length: 9999 16 | 17 | # MD033/no-inline-html 18 | MD033: 19 | allowed_elements: [h1, img, p] 20 | 21 | # MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content 22 | MD024: 23 | # Only check sibling headings 24 | siblings_only: true 25 | 26 | # MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading 27 | MD041: false # Github issues and PRs already have titles, and H1 is enormous in the description box. 28 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/20-stop-opa.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: opa.stackable.tech/v1alpha1 3 | kind: OpaCluster 4 | metadata: 5 | name: test-opa 6 | spec: 7 | image: 8 | {% if test_scenario['values']['opa-latest'].find(",") > 0 %} 9 | custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" 10 | productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" 11 | {% else %} 12 | productVersion: "{{ test_scenario['values']['opa-latest'] }}" 13 | {% endif %} 14 | pullPolicy: IfNotPresent 15 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 16 | clusterConfig: 17 | vectorAggregatorConfigMapName: vector-aggregator-discovery 18 | {% endif %} 19 | clusterOperation: 20 | stopped: true 21 | reconciliationPaused: false 22 | servers: 23 | config: 24 | logging: 25 | enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} 26 | roleGroups: 27 | default: {} 28 | -------------------------------------------------------------------------------- /tests/templates/kuttl/cluster-operation/30-pause-opa.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: opa.stackable.tech/v1alpha1 3 | kind: OpaCluster 4 | metadata: 5 | name: test-opa 6 | spec: 7 | image: 8 | {% if test_scenario['values']['opa-latest'].find(",") > 0 %} 9 | custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" 10 | productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" 11 | {% else %} 12 | productVersion: "{{ test_scenario['values']['opa-latest'] }}" 13 | {% endif %} 14 | pullPolicy: IfNotPresent 15 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 16 | clusterConfig: 17 | vectorAggregatorConfigMapName: vector-aggregator-discovery 18 | {% endif %} 19 | clusterOperation: 20 | stopped: false 21 | reconciliationPaused: true 22 | servers: 23 | config: 24 | logging: 25 | enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} 26 | roleGroups: 27 | default: {} 28 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/30_test-metrics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import requests 3 | import argparse 4 | 5 | if __name__ == "__main__": 6 | all_args = argparse.ArgumentParser() 7 | all_args.add_argument("-u", "--url", required=True, help="OPA metrics url") 8 | args = vars(all_args.parse_args()) 9 | 10 | metrics_url = args["url"] 11 | 12 | # Determine verification setting based on whether TLS is used 13 | if metrics_url.startswith("http://"): 14 | verify = False 15 | protocol = "HTTP" 16 | else: 17 | verify = "/tls/ca.crt" 18 | protocol = "HTTPS" 19 | 20 | response = requests.get(metrics_url, verify=verify) 21 | 22 | assert response.status_code == 200, "Metrics endpoint must return a 200 status code" 23 | assert "bundle_loaded_counter" in response.text, ( 24 | f"Metric bundle_loaded_counter should exist in {metrics_url}" 25 | ) 26 | print(f"Metrics test ({protocol}) successful!") 27 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/00-misc-setup.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: LimitRange 4 | metadata: 5 | name: limit-request-ratio 6 | spec: 7 | limits: 8 | - type: "Container" 9 | maxLimitRequestRatio: 10 | cpu: 5 11 | memory: 1 12 | --- 13 | kind: Role 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | metadata: 16 | name: test-role 17 | rules: 18 | {% if test_scenario['values']['openshift'] == "true" %} 19 | - apiGroups: ["security.openshift.io"] 20 | resources: ["securitycontextconstraints"] 21 | resourceNames: ["privileged"] 22 | verbs: ["use"] 23 | {% endif %} 24 | --- 25 | apiVersion: v1 26 | kind: ServiceAccount 27 | metadata: 28 | name: test-sa 29 | --- 30 | kind: RoleBinding 31 | apiVersion: rbac.authorization.k8s.io/v1 32 | metadata: 33 | name: test-rb 34 | subjects: 35 | - kind: ServiceAccount 36 | name: test-sa 37 | roleRef: 38 | kind: Role 39 | name: test-role 40 | apiGroup: rbac.authorization.k8s.io 41 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Helm Chart for Stackable Operator for OpenPolicyAgent 3 | 4 | This Helm Chart can be used to install Custom Resource Definitions and the Operator for OpenPolicyAgent provided by Stackable. 5 | 6 | ## Requirements 7 | 8 | - Create a [Kubernetes Cluster](../Readme.md) 9 | - Install [Helm](https://helm.sh/docs/intro/install/) 10 | 11 | ## Install the Stackable Operator for OpenPolicyAgent 12 | 13 | ```bash 14 | # From the root of the operator repository 15 | make compile-chart 16 | 17 | helm install opa-operator deploy/helm/opa-operator 18 | ``` 19 | 20 | ## Usage of the CRDs 21 | 22 | The usage of this operator and its CRDs is described in the [documentation](https://docs.stackable.tech/opa/index.html) 23 | 24 | The operator has example requests included in the [`/examples`](https://github.com/stackabletech/opa-operator/tree/main/examples) directory. 25 | 26 | ## Links 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/getting_started/index.adoc: -------------------------------------------------------------------------------- 1 | = Getting started 2 | :description: Get started with OPA using Stackable Operator. Install, set up OPA, create a policy rule, and query it in Kubernetes. 3 | 4 | This guide gets you started with the OpenPolicyAgent (OPA) using the Stackable operator. 5 | It guides you through the installation of the operator, setting up your first OPA instance, creating a policy rule and querying it. 6 | 7 | == Prerequisites 8 | 9 | You need: 10 | 11 | * a Kubernetes cluster 12 | * kubectl 13 | * curl 14 | * optional: Helm 15 | 16 | Resource sizing depends on cluster type(s), usage and scope, but as a starting point a minimum of the following resources is recommended for this operator: 17 | 18 | * 0.2 cores (e.g. i5 or similar) 19 | * 256MB RAM 20 | 21 | == What's next 22 | 23 | The Guide is divided into two steps: 24 | 25 | * xref:getting_started/installation.adoc[Installing the operator]. 26 | * xref:getting_started/first_steps.adoc[Setting up an OPA, a policy rule and querying it]. 27 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/policies.adoc: -------------------------------------------------------------------------------- 1 | = Defining policies 2 | :description: Define OPA policies using Rego in ConfigMaps. Mark them with a bundle label and include Rego rules to deploy and manage your policies effectively. 3 | :rego-docs: https://www.openpolicyagent.org/docs/latest/policy-language/ 4 | 5 | You can define policies by using Rego, OPAs {rego-docs}[policy language]. 6 | 7 | Policy definitions are deployed as ConfigMap resources as described in xref:implementation-notes.adoc[implementation notes]. 8 | 9 | Here is an example: 10 | 11 | [source,yaml] 12 | ---- 13 | --- 14 | apiVersion: v1 15 | kind: ConfigMap 16 | metadata: 17 | name: test 18 | labels: 19 | opa.stackable.tech/bundle: "true" # <1> 20 | data: 21 | test.rego: | # <2> 22 | package test 23 | 24 | hello if { 25 | true 26 | } 27 | 28 | world if { 29 | false 30 | } 31 | ---- 32 | <1> Mark this `ConfigMap` as a bundle source. 33 | <2> `test.rego` is the file name to use inside the bundle for these rules. 34 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/10-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestAssert 3 | timeout: 300 4 | commands: 5 | - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa --timeout 301s 6 | --- 7 | apiVersion: apps/v1 8 | kind: DaemonSet 9 | metadata: 10 | name: test-opa-server-default 11 | spec: 12 | template: 13 | spec: 14 | containers: 15 | - name: opa 16 | resources: 17 | limits: 18 | cpu: 500m 19 | memory: 256Mi 20 | requests: 21 | cpu: 250m 22 | memory: 256Mi 23 | - name: bundle-builder 24 | resources: 25 | limits: 26 | cpu: 200m 27 | memory: 128Mi 28 | requests: 29 | cpu: 100m 30 | memory: 128Mi 31 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 32 | - name: vector 33 | {% endif %} 34 | terminationGracePeriodSeconds: 125 # 2 minutes + 5s safety buffer 35 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | {{ if .Values.serviceAccount.create -}} 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: {{ include "operator.fullname" . }}-serviceaccount 7 | labels: 8 | {{- include "operator.labels" . | nindent 4 }} 9 | {{- with .Values.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | --- 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | # This cluster role binding allows anyone in the "manager" group to read secrets in any namespace. 16 | kind: ClusterRoleBinding 17 | metadata: 18 | name: {{ include "operator.fullname" . }}-clusterrolebinding 19 | labels: 20 | {{- include "operator.labels" . | nindent 4 }} 21 | subjects: 22 | - kind: ServiceAccount 23 | name: {{ include "operator.fullname" . }}-serviceaccount 24 | namespace: {{ .Release.Namespace }} 25 | roleRef: 26 | kind: ClusterRole 27 | name: {{ include "operator.fullname" . }}-clusterrole 28 | apiGroup: rbac.authorization.k8s.io 29 | {{- end }} 30 | -------------------------------------------------------------------------------- /rust/user-info-fetcher/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stackable-opa-user-info-fetcher" 3 | description = "Fetches user metadata from directory services" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | repository.workspace = true 9 | publish = false 10 | 11 | [dependencies] 12 | stackable-opa-operator = { path = "../operator-binary" } 13 | stackable-operator.workspace = true 14 | krb5.workspace = true 15 | 16 | axum.workspace = true 17 | base64.workspace = true 18 | byteorder.workspace = true 19 | clap.workspace = true 20 | futures.workspace = true 21 | hyper.workspace = true 22 | ldap3.workspace = true 23 | moka.workspace = true 24 | native-tls.workspace = true 25 | pin-project.workspace = true 26 | reqwest.workspace = true 27 | rustls-pemfile.workspace = true 28 | semver.workspace = true 29 | serde.workspace = true 30 | serde_json.workspace = true 31 | snafu.workspace = true 32 | tokio.workspace = true 33 | tracing.workspace = true 34 | url.workspace = true 35 | uuid.workspace = true 36 | 37 | [build-dependencies] 38 | built.workspace = true 39 | -------------------------------------------------------------------------------- /rust/operator-binary/src/operations/graceful_shutdown.rs: -------------------------------------------------------------------------------- 1 | use snafu::{ResultExt, Snafu}; 2 | use stackable_opa_operator::crd::{SERVER_GRACEFUL_SHUTDOWN_SAFETY_OVERHEAD, v1alpha1}; 3 | use stackable_operator::builder::pod::PodBuilder; 4 | 5 | #[derive(Debug, Snafu)] 6 | pub enum Error { 7 | #[snafu(display("Failed to set terminationGracePeriod"))] 8 | SetTerminationGracePeriod { 9 | source: stackable_operator::builder::pod::Error, 10 | }, 11 | } 12 | 13 | pub fn add_graceful_shutdown_config( 14 | merged_config: &v1alpha1::OpaConfig, 15 | pod_builder: &mut PodBuilder, 16 | ) -> Result<(), Error> { 17 | // This must be always set by the merge mechanism, as we provide a default value, 18 | // users can not disable graceful shutdown. 19 | if let Some(graceful_shutdown_timeout) = merged_config.graceful_shutdown_timeout { 20 | pod_builder 21 | .termination_grace_period( 22 | &(graceful_shutdown_timeout + SERVER_GRACEFUL_SHUTDOWN_SAFETY_OVERHEAD), 23 | ) 24 | .context(SetTerminationGracePeriodSnafu)?; 25 | } 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/resources.adoc: -------------------------------------------------------------------------------- 1 | = Resource requests 2 | 3 | include::concepts:stackable_resource_requests.adoc[] 4 | 5 | A minimal OPA setup consists of 1 Pod per Node (DaemonSet) and has the following https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/[resource requirements] per scheduled Pod: 6 | 7 | * `600m` CPU request 8 | * `1200m` CPU limit 9 | * `512Mi` memory request and limit 10 | 11 | Of course, additional services, require additional resources. 12 | For Stackable components, see the corresponding documentation on further resource requirements. 13 | 14 | Corresponding to the values above, the operator uses the following resource defaults for the main app container: 15 | 16 | [source,yaml] 17 | ---- 18 | servers: 19 | roleGroups: 20 | default: 21 | config: 22 | resources: 23 | cpu: 24 | min: 250m 25 | max: 500m 26 | memory: 27 | limit: 256Mi 28 | ---- 29 | 30 | WARNING: The default values are _most likely_ not sufficient to run a proper cluster in production. 31 | Please adapt according to your requirements. 32 | -------------------------------------------------------------------------------- /scripts/docs_templating.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # Reads a file with variables to insert into templates, and templates all .*.j2 files 5 | # in the 'docs' directory. 6 | # 7 | # dependencies 8 | # pip install jinja2-cli 9 | 10 | docs_dir="$(dirname "$0")/../docs" 11 | templating_vars_file="$docs_dir/templating_vars.yaml" 12 | 13 | # Check if files need templating 14 | if [[ -z $(find "$docs_dir" -name '*.j2') ]]; 15 | then 16 | echo "No files need templating, exiting." 17 | exit 18 | fi 19 | 20 | # Check if jinja2 is there 21 | if ! command -v jinja2 &> /dev/null 22 | then 23 | echo "jinja2 could not be found. Use 'pip install jinja2-cli' to install it." 24 | exit 1 25 | fi 26 | 27 | # Check if templating vars file exists 28 | if [[ ! -f "$templating_vars_file" ]]; 29 | then 30 | echo "$templating_vars_file does not exist, cannot start templating." 31 | fi 32 | 33 | find "$docs_dir" -name '*.j2' | 34 | while read -r file 35 | do 36 | new_file_name=${file%.j2} # Remove .j2 suffix 37 | echo "templating $new_file_name" 38 | jinja2 "$file" "$templating_vars_file" -o "$new_file_name" 39 | done 40 | 41 | echo "done" 42 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/reference/commandline-parameters.adoc: -------------------------------------------------------------------------------- 1 | = Command line parameters 2 | 3 | This operator accepts the following command line parameters: 4 | 5 | == product-config 6 | 7 | *Default value*: `/etc/stackable/opa-operator/config-spec/properties.yaml` 8 | 9 | *Required*: false 10 | 11 | *Multiple values:* false 12 | 13 | [source] 14 | ---- 15 | stackable-opa-operator run --product-config /foo/bar/properties.yaml 16 | ---- 17 | 18 | == watch-namespace 19 | 20 | *Default value*: All namespaces 21 | 22 | *Required*: false 23 | 24 | *Multiple values:* false 25 | 26 | The operator **only** watches for resources in the provided namespace `test`: 27 | 28 | [source] 29 | ---- 30 | stackable-opa-operator run --watch-namespace test 31 | ---- 32 | 33 | == opa-bundle-builder-clusterrole 34 | 35 | *Default value*: `None`. A value is generated automatically by Helm. 36 | 37 | *Required*: false. When not specified, the environment variable `OPA_BUNDLE_BUILDER_CLUSTERROLE` must be present. 38 | 39 | *Multiple values:* false 40 | 41 | 42 | The name of the `ClusterRole` object that is referenced by the OPA pods. This object must exist in the Kubernetes cluster and is created by Helm. 43 | -------------------------------------------------------------------------------- /tests/kuttl-test.yaml.jinja2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestSuite 4 | testDirs: 5 | {% for testcase in testinput.tests %} 6 | - ./tests/{{ testcase.name }} 7 | {% endfor %} 8 | 9 | startKIND: false 10 | suppress: ["events"] 11 | parallel: 2 12 | 13 | # The timeout (in seconds) is used when namespaces are created or 14 | # deleted, and, if not overridden, in TestSteps, TestAsserts, and 15 | # Commands. If not set, the timeout is 30 seconds by default. 16 | # 17 | # The deletion of a namespace can take a while until all resources, 18 | # especially PersistentVolumeClaims, are gracefully shut down. If the 19 | # timeout is reached in the meantime, even a successful test case is 20 | # considered a failure. 21 | # 22 | # For instance, the termination grace period of the Vector aggregator in 23 | # the logging tests is set to 60 seconds. If there are logs entries 24 | # which could not be forwarded yet to the external aggregator defined in 25 | # the VECTOR_AGGREGATOR environment variable, then the test aggregator 26 | # uses this period of time by trying to forward the events. In this 27 | # case, deleting a namespace with several Pods takes about 90 seconds. 28 | timeout: 300 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new_version.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Version 3 | about: Request support for a new product version 4 | title: "[NEW VERSION]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Which new version of OpenPolicyAgent should we support? 11 | 12 | Please specify the version, version range or version numbers to support, please also add these to the issue title 13 | 14 | ## Additional information 15 | 16 | If possible, provide a link to release notes/changelog 17 | 18 | ## Changes required 19 | 20 | Are there any upstream changes that we need to support? 21 | e.g. new features, changed features, deprecated features etc. 22 | 23 | ## Implementation checklist 24 | 25 | 29 | 30 | - [ ] Update the Docker image 31 | - [ ] Update documentation to include supported version(s) 32 | - [ ] Update and test getting started guide with updated version(s) 33 | - [ ] Update operator to support the new version (if needed) 34 | - [ ] Update integration tests to test use the new versions (in addition or replacing old versions 35 | - [ ] Update examples to use new versions 36 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pre-release-getting-started-script.md: -------------------------------------------------------------------------------- 1 | ## Check and Update Getting Started Script 2 | 3 | 7 | 8 | 11 | 12 | Part of 13 | 14 | > [!NOTE] 15 | > During a Stackable release we need to check (and optionally update) the 16 | > getting-started scripts to ensure they still work after product and operator 17 | > updates. 18 | 19 | ```shell 20 | # Some of the scripts are in a code/ subdirectory 21 | # pushd docs/modules/superset/examples/getting_started 22 | # pushd docs/modules/superset/examples/getting_started/code 23 | pushd $(fd -td getting_started | grep examples); cd code 2>/dev/null || true 24 | 25 | # Make a fresh cluster (~12 seconds) 26 | kind delete cluster && kind create cluster 27 | ./getting_started.sh stackablectl 28 | 29 | # Make a fresh cluster (~12 seconds) 30 | kind delete cluster && kind create cluster 31 | ./getting_started.sh helm 32 | 33 | popd 34 | ``` 35 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/04-install-opa-test-runner.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: Role 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | name: logging-role 6 | rules: 7 | - apiGroups: [""] 8 | resources: ["services", "pods", "pods/log"] 9 | verbs: ["get", "list"] 10 | --- 11 | apiVersion: v1 12 | kind: ServiceAccount 13 | metadata: 14 | name: logging-sa 15 | --- 16 | kind: RoleBinding 17 | apiVersion: rbac.authorization.k8s.io/v1 18 | metadata: 19 | name: logging-rb 20 | subjects: 21 | - kind: ServiceAccount 22 | name: logging-sa 23 | roleRef: 24 | kind: Role 25 | name: logging-role 26 | apiGroup: rbac.authorization.k8s.io 27 | --- 28 | apiVersion: apps/v1 29 | kind: StatefulSet 30 | metadata: 31 | name: opa-test-runner 32 | labels: 33 | app: opa-test-runner 34 | spec: 35 | replicas: 1 36 | selector: 37 | matchLabels: 38 | app: opa-test-runner 39 | template: 40 | metadata: 41 | labels: 42 | app: opa-test-runner 43 | spec: 44 | serviceAccountName: logging-sa 45 | containers: 46 | - name: opa-test-runner 47 | image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev 48 | stdin: true 49 | tty: true 50 | -------------------------------------------------------------------------------- /docs/modules/opa/partials/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:opa:getting_started/index.adoc[] 2 | ** xref:opa:getting_started/installation.adoc[] 3 | ** xref:opa:getting_started/first_steps.adoc[] 4 | * xref:opa:usage-guide/index.adoc[] 5 | ** xref:opa:usage-guide/listenerclass.adoc[] 6 | ** xref:opa:usage-guide/policies.adoc[] 7 | ** xref:opa:usage-guide/user-info-fetcher.adoc[] 8 | ** xref:opa:usage-guide/resources.adoc[] 9 | ** xref:opa:usage-guide/logging.adoc[] 10 | ** xref:opa:usage-guide/monitoring.adoc[] 11 | ** xref:opa:usage-guide/OpenTelemetry.adoc[] 12 | ** xref:opa:usage-guide/configuration-environment-overrides.adoc[] 13 | ** xref:opa:usage-guide/tls.adoc[] 14 | ** xref:opa:usage-guide/operations/index.adoc[] 15 | *** xref:opa:usage-guide/operations/cluster-operations.adoc[] 16 | // *** xref:hdfs:usage-guide/operations/pod-placement.adoc[] Missing 17 | *** xref:opa:usage-guide/operations/pod-disruptions.adoc[] 18 | *** xref:opa:usage-guide/operations/graceful-shutdown.adoc[] 19 | * xref:opa:reference/index.adoc[] 20 | ** xref:opa:reference/crds.adoc[] 21 | *** {crd-docs}/opa.stackable.tech/opacluster/v1alpha1/[OpaCluster {external-link-icon}^] 22 | ** xref:opa:reference/discovery.adoc[] 23 | ** xref:opa:reference/commandline-parameters.adoc[] 24 | ** xref:opa:reference/environment-variables.adoc[] 25 | * xref:opa:implementation-notes.adoc[] 26 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc: -------------------------------------------------------------------------------- 1 | = Configuration & Environment Overrides 2 | :description: Configure OPA with environment variable and Pod overrides. 3 | 4 | The cluster definition also supports overriding configuration properties and environment variables, either per role or per role group, where the more specific override (role group) has precedence over the less specific one (role). 5 | 6 | IMPORTANT: Do not override port numbers. 7 | This will lead to faulty installations. 8 | 9 | == Configuration properties 10 | 11 | Currently, not supported for `config.json`. 12 | 13 | == Environment variables 14 | 15 | Environment variables can be (over)written by adding the `envOverrides` property. 16 | 17 | For example per role group: 18 | 19 | [source,yaml] 20 | ---- 21 | servers: 22 | roleGroups: 23 | default: 24 | config: {} 25 | envOverrides: 26 | MY_ENV_VAR: "MY_VALUE" 27 | ---- 28 | 29 | or per role: 30 | 31 | [source,yaml] 32 | ---- 33 | servers: 34 | envOverrides: 35 | MY_ENV_VAR: "MY_VALUE" 36 | roleGroups: 37 | default: 38 | config: {} 39 | ---- 40 | 41 | == Pod overrides 42 | 43 | The OPA operator also supports Pod overrides, allowing you to override any property that you can set on a Kubernetes Pod. 44 | Read the xref:concepts:overrides.adoc#pod-overrides[Pod overrides documentation] to learn more about this feature. 45 | -------------------------------------------------------------------------------- /.github/workflows/pr_pre-commit.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: pre-commit 3 | 4 | on: 5 | pull_request: 6 | merge_group: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | NIX_PKG_MANAGER_VERSION: "2.30.0" 11 | RUST_TOOLCHAIN_VERSION: "nightly-2025-10-23" 12 | HADOLINT_VERSION: "v2.14.0" 13 | PYTHON_VERSION: "3.14" 14 | JINJA2_CLI_VERSION: "0.8.2" 15 | 16 | jobs: 17 | pre-commit: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Install host dependencies 21 | uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0 22 | with: 23 | packages: protobuf-compiler krb5-user libkrb5-dev libclang-dev liblzma-dev libssl-dev pkg-config apt-transport-https 24 | version: ubuntu-latest 25 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 26 | with: 27 | persist-credentials: false 28 | submodules: recursive 29 | fetch-depth: 0 30 | - uses: stackabletech/actions/run-pre-commit@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 31 | with: 32 | python-version: ${{ env.PYTHON_VERSION }} 33 | rust: ${{ env.RUST_TOOLCHAIN_VERSION }} 34 | hadolint: ${{ env.HADOLINT_VERSION }} 35 | nix: ${{ env.NIX_PKG_MANAGER_VERSION }} 36 | nix-github-token: ${{ secrets.GITHUB_TOKEN }} 37 | jinja2-cli: ${{ env.JINJA2_CLI_VERSION }} 38 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pre-release-rust-deps.md: -------------------------------------------------------------------------------- 1 | ## Bump Rust Dependencies for Stackable Release YY.M.X 2 | 3 | 7 | 8 | 11 | 12 | Part of 13 | 14 | > [!NOTE] 15 | > During a Stackable release we need to update various Rust dependencies before 16 | > entering the final release period to ensure we run the latest versions of 17 | > crates. These bumps also include previously updated and released crates from 18 | > the `operator-rs` repository. 19 | 20 | ### Tasks 21 | 22 | - [ ] Bump Rust Dependencies, see below for more details. 23 | - [ ] Add changelog entry stating which important crates were bumped (including the version). 24 | 25 | > [!NOTE] 26 | > The bumping / updating of Rust dependencies is done in multiple steps: 27 | > 28 | > 1. Update the minimum Version in the root `Cargo.toml` manifest. 29 | > 2. Run the `cargo update` command, which also updates the `Cargo.lock` file. 30 | > 3. Lastly, run `make regenerate-nix` to update the `Cargo.nix` file. 31 | 32 | ### Bump Rust Dependencies 33 | 34 | - [ ] Bump `stackable-operator` and friends 35 | - [ ] Bump `product-config` 36 | - [ ] Bump all other dependencies 37 | -------------------------------------------------------------------------------- /rust/user-info-fetcher/src/http_error.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::{Json, response::IntoResponse}; 4 | use hyper::StatusCode; 5 | use serde::Serialize; 6 | 7 | pub trait Error: std::error::Error { 8 | fn status_code(&self) -> StatusCode; 9 | } 10 | impl Error for Arc { 11 | fn status_code(&self) -> StatusCode { 12 | let inner: &T = self; 13 | inner.status_code() 14 | } 15 | } 16 | 17 | pub struct JsonResponse { 18 | pub error: E, 19 | } 20 | 21 | impl From for JsonResponse { 22 | fn from(error: E) -> Self { 23 | Self { error } 24 | } 25 | } 26 | 27 | impl IntoResponse for JsonResponse { 28 | fn into_response(self) -> axum::response::Response { 29 | ( 30 | self.error.status_code(), 31 | Json(Container { 32 | error: Payload { 33 | message: self.error.to_string(), 34 | causes: std::iter::successors(self.error.source(), |err| err.source()) 35 | .map(|err| err.to_string()) 36 | .collect(), 37 | }, 38 | }), 39 | ) 40 | .into_response() 41 | } 42 | } 43 | 44 | #[derive(Serialize)] 45 | #[serde(rename_all = "camelCase")] 46 | struct Container { 47 | error: Payload, 48 | } 49 | 50 | #[derive(Serialize)] 51 | #[serde(rename_all = "camelCase")] 52 | struct Payload { 53 | message: String, 54 | causes: Vec, 55 | } 56 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/operations/graceful-shutdown.adoc: -------------------------------------------------------------------------------- 1 | = Graceful shutdown 2 | 3 | You can configure the graceful shutdown as described in xref:concepts:operations/graceful_shutdown.adoc[]. 4 | 5 | == Servers 6 | 7 | As a default, OPA servers have `2 minutes` to shut down gracefully. 8 | 9 | The OPA server process receives a `SIGTERM` signal when Kubernetes wants to terminate the Pod. 10 | It acknowledges the shutdown as shown in the log below and initiate a graceful shutdown. 11 | After the graceful shutdown timeout runs out, and the process still didn't exit, Kubernetes issues a `SIGKILL` signal. 12 | 13 | [source,text] 14 | ---- 15 | {"level":"info","msg":"Shutting down...","time":"2023-11-06T15:16:08Z"} 16 | {"level":"info","msg":"Server shutdown.","time":"2023-11-06T15:16:08Z"} 17 | {"level":"info","msg":"Stopping bundle loader.","name":"stackable","plugin":"bundle","time":"2023-11-06T15:16:08Z"} 18 | ---- 19 | 20 | == Implementation 21 | 22 | Once a server Pod is asked to terminate the following timeline occurs: 23 | 24 | 1. The server stops accepting any new queries. 25 | 2. The server waits until all running queries have finished. 26 | 3. If the graceful shutdown doesn't complete quick enough (e.g. a query runs longer than the graceful shutdown period), after ` + 5s safety overhead` the Pod gets killed, regardless if it has shut down gracefully or not. This is achieved by setting `terminationGracePeriodSeconds` on the server Pods. Running queries on the sever will fail. 27 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/OpenTelemetry.adoc: -------------------------------------------------------------------------------- 1 | = OpenTelemetry 2 | :description: Ship OPA traces and logs to OpenTelemetry 3 | :opa-docs: https://v1-4-2--opa-docs.netlify.app/configuration/#distributed-tracing 4 | 5 | Opa supports sending OpenTelemetry traces as stated in {opa-docs}[the documentation]. 6 | 7 | As of SDP 25.7, `configOverrides` are (still) not supported, we are tracking the progress in https://github.com/stackabletech/opa-operator/issues/756[this GitHub issue]. 8 | To enable traces you need to modify the config and thus xref:opa:usage-guide/operations/cluster-operations.adoc[pause the reconciliation] of your OpaCluster, so that changes to the ConfigMap aren't immediately overridden by the opa-operator. 9 | 10 | WARNING: It's not encouraged to pause the reconciliation more than just temporarily. We recommend disabling it while you debug e.g. performance problems and re-enabling it afterwards. This problem will be solved once we support configOverrides for OPA. 11 | 12 | Afterwards you can edit the `-server-default` ConfigMap and append a `distributed_tracing` section as follows. 13 | Please check the {opa-docs}[OPA documentation] to see what other settings you can configure. 14 | 15 | [source,yaml] 16 | ---- 17 | apiVersion: v1 18 | kind: ConfigMap 19 | metadata: 20 | name: opa-server-default 21 | data: 22 | config.json: |- 23 | { 24 | <<< existing JSON >>> 25 | "distributed_tracing": { 26 | "address": "jaeger-collector.default.svc.cluster.local:4317", 27 | "type": "grpc" 28 | } 29 | } 30 | ---- 31 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/prepared-bundle-builder-logs.tracing-rs.json: -------------------------------------------------------------------------------- 1 | {"timestamp":"2024-01-01T00:00:00.000000Z","level":"INFO","fields":{"message":"Valid log event","directory":"/stackable/log/prepared-logs"},"target":"TestLogger"} 2 | {"timestamp":"2024-01-01T00:00:00.000000Z","level":"INFO","fields":{"message":"Valid log event with only the message field"},"target":"TestLogger"} 3 | {"timestamp":"2024-01-01T00:00:00.000000Z","level":"INFO","fields":{"message":"Valid log event with a nested field","nested":{"array":[1,2,3]}},"target":"TestLogger"} 4 | {"level":"INFO","fields":{"message":"Invalid log event without a timestamp","directory":"/stackable/log/prepared-logs"},"target":"TestLogger"} 5 | {"timestamp":"unparsable timestamp","level":"INFO","fields":{"message":"Invalid log event with an unparsable timestamp","directory":"/stackable/log/prepared-logs"},"target":"TestLogger"} 6 | {"timestamp":"2024-01-01T00:00:00.000000Z","fields":{"message":"Invalid log event without a level","directory":"/stackable/log/prepared-logs"},"target":"TestLogger"} 7 | {"timestamp":"2024-01-01T00:00:00.000000Z","level":"INFO","fields":{"message":"Invalid log event without a logger","directory":"/stackable/log/prepared-logs"}} 8 | {"timestamp":"2024-01-01T00:00:00.000000Z","level":"CRITICAL","fields":{"message":"Invalid log event with an unknown level","directory":"/stackable/log/prepared-logs"},"target":"TestLogger"} 9 | {"timestamp":"2024-01-01T00:00:00.000000Z","level":"INFO","fields":{"directory":"/stackable/log/prepared-logs"},"target":"TestLogger"} 10 | {"timestamp":"2024-01-01T00:00:00.000000Z","level":"INFO","target":"TestLogger"} 11 | "true" 12 | -------------------------------------------------------------------------------- /crate-hashes.json: -------------------------------------------------------------------------------- 1 | { 2 | "git+https://github.com/stackabletech/krb5-rs.git?tag=v0.1.0#krb5-sys@0.1.0": "148zr0q04163hpirkrff5q7cbxqgwzzxh0091zr4g23x7l64jh39", 3 | "git+https://github.com/stackabletech/krb5-rs.git?tag=v0.1.0#krb5@0.1.0": "148zr0q04163hpirkrff5q7cbxqgwzzxh0091zr4g23x7l64jh39", 4 | "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.100.1#k8s-version@0.1.3": "1a98klljvifnc168f1wc3d6szcry1lamxgjjdq89plr99p4b953l", 5 | "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.100.1#stackable-operator-derive@0.3.1": "1a98klljvifnc168f1wc3d6szcry1lamxgjjdq89plr99p4b953l", 6 | "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.100.1#stackable-operator@0.100.1": "1a98klljvifnc168f1wc3d6szcry1lamxgjjdq89plr99p4b953l", 7 | "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.100.1#stackable-shared@0.0.3": "1a98klljvifnc168f1wc3d6szcry1lamxgjjdq89plr99p4b953l", 8 | "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.100.1#stackable-telemetry@0.6.1": "1a98klljvifnc168f1wc3d6szcry1lamxgjjdq89plr99p4b953l", 9 | "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.100.1#stackable-versioned-macros@0.8.3": "1a98klljvifnc168f1wc3d6szcry1lamxgjjdq89plr99p4b953l", 10 | "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.100.1#stackable-versioned@0.8.3": "1a98klljvifnc168f1wc3d6szcry1lamxgjjdq89plr99p4b953l", 11 | "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" 12 | } -------------------------------------------------------------------------------- /tests/templates/kuttl/resources/10-install-opa.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: opa.stackable.tech/v1alpha1 3 | kind: OpaCluster 4 | metadata: 5 | name: opa 6 | spec: 7 | image: 8 | {% if test_scenario['values']['opa-latest'].find(",") > 0 %} 9 | custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" 10 | productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" 11 | {% else %} 12 | productVersion: "{{ test_scenario['values']['opa-latest'] }}" 13 | {% endif %} 14 | pullPolicy: IfNotPresent 15 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 16 | clusterConfig: 17 | vectorAggregatorConfigMapName: vector-aggregator-discovery 18 | {% endif %} 19 | servers: 20 | config: 21 | resources: 22 | cpu: 23 | min: 50m 24 | max: 110m 25 | memory: 26 | limit: 256Mi 27 | logging: 28 | enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} 29 | roleGroups: 30 | resources-from-role: {} 31 | resources-from-role-group: 32 | config: 33 | resources: 34 | cpu: 35 | min: 60m 36 | max: 130m 37 | memory: 38 | limit: 284Mi 39 | logging: 40 | enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} 41 | resources-from-pod-overrides: 42 | podOverrides: 43 | spec: 44 | containers: 45 | - name: opa 46 | resources: 47 | requests: 48 | cpu: 70m 49 | limits: 50 | cpu: 150m 51 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/prepared-opa-logs.json: -------------------------------------------------------------------------------- 1 | {"level":"info","msg":"Valid server log event","name":"stackable","plugin":"bundle","time":"2024-01-01T00:00:00Z","logger":"server"} 2 | {"bundles":{"stackable":{}},"decision_id":"5b887ec2-80a0-4b44-9009-2e0cae3507c2","labels":{"id":"992b9dd5-f29e-47a7-aa3a-4408218c3825","version":"0.67.1"},"level":"info","metrics":{"counter_server_query_cache_hit":0,"timer_rego_input_parse_ns":5500,"timer_rego_query_compile_ns":143369,"timer_rego_query_eval_ns":41107,"timer_rego_query_parse_ns":95750,"timer_server_handler_ns":422100},"msg":"Valid decision log event","path":"test","req_id":3,"requested_by":"10.244.0.138:33268","time":"2024-01-01T00:00:00Z","timestamp":"2024-01-01T00:00:00.000000000Z","type":"openpolicyagent.org/decision_logs","logger":"decision"} 3 | {"level":"info","msg":"Invalid log event without a timestamp","name":"stackable","plugin":"bundle","logger":"server"} 4 | {"level":"info","msg":"Invalid log event with an unparsable timestamp","name":"stackable","plugin":"bundle","time":"unparsable timestamp","logger":"server"} 5 | {"level":"info","msg":"Invalid log event without a logger","name":"stackable","plugin":"bundle","time":"2024-01-01T00:00:00Z"} 6 | {"level":"critical","msg":"Invalid log event with an unknown level","name":"stackable","plugin":"bundle","time":"2024-01-01T00:00:00Z","logger":"server"} 7 | {"msg":"Invalid log event without a level","name":"stackable","plugin":"bundle","time":"2024-01-01T00:00:00Z","logger":"server"} 8 | {"level":"info","name":"stackable","plugin":"bundle","time":"2024-01-01T00:00:00Z","logger":"server"} 9 | {"level":"info","msg":"Unparsable log event",... 10 | "true" 11 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/30_test-regorule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import requests 3 | import argparse 4 | 5 | 6 | if __name__ == "__main__": 7 | all_args = argparse.ArgumentParser() 8 | all_args.add_argument("-u", "--url", required=True, help="OPA service url") 9 | args = vars(all_args.parse_args()) 10 | 11 | # rego rule to check (compare: 01-install-opa.yaml) 12 | # --- 13 | # package test 14 | # 15 | # hello { 16 | # true 17 | # } 18 | # 19 | # world { 20 | # false 21 | # } 22 | # --- 23 | # We need to query: https://:/v1/data//()+ 24 | # In our case https://:8443/v1/data/test 25 | # --> {'result': {'hello': True}} 26 | # or https://:8443/v1/data/test/hello 27 | # --> {'hello': True} 28 | # For HTTP: http://:8081/v1/data/test 29 | 30 | # Determine verification setting based on whether TLS is used 31 | if args["url"].startswith("http://"): 32 | verify = False 33 | protocol = "HTTP" 34 | else: 35 | verify = "/tls/ca.crt" 36 | protocol = "HTTPS" 37 | 38 | response = requests.post(args["url"], json={"input": {}}, verify=verify).json() 39 | 40 | if ( 41 | "result" in response 42 | and "hello" in response["result"] 43 | and response["result"]["hello"] 44 | ): 45 | print(f"Regorule test ({protocol}) successful!") 46 | exit(0) 47 | else: 48 | print( 49 | f"Error ({protocol}): received " 50 | + str(response) 51 | + " - expected: {'result': {'hello': True}}" 52 | ) 53 | exit(-1) 54 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | self = import ./. {}; 3 | inherit (self) sources pkgs meta; 4 | 5 | beku = pkgs.callPackage (sources."beku.py" + "/beku.nix") {}; 6 | cargoDependencySetOfCrate = crate: [ crate ] ++ pkgs.lib.concatMap cargoDependencySetOfCrate (crate.dependencies ++ crate.buildDependencies); 7 | cargoDependencySet = pkgs.lib.unique (pkgs.lib.flatten (pkgs.lib.mapAttrsToList (crateName: crate: cargoDependencySetOfCrate crate.build) self.cargo.workspaceMembers)); 8 | in pkgs.mkShell rec { 9 | name = meta.operator.name; 10 | 11 | packages = with pkgs; [ 12 | ## cargo et-al 13 | rustup # this breaks pkg-config if it is in the nativeBuildInputs 14 | cargo-udeps 15 | 16 | ## Extra dependencies for use in a pure env (nix-shell --pure) 17 | ## These are mosuly useful for maintainers of this shell.nix 18 | ## to ensure all the dependencies are caught. 19 | # cacert 20 | # vim nvim nano 21 | ]; 22 | 23 | # derivation runtime dependencies 24 | buildInputs = pkgs.lib.concatMap (crate: crate.buildInputs) cargoDependencySet; 25 | 26 | # build time dependencies 27 | nativeBuildInputs = pkgs.lib.concatMap (crate: crate.nativeBuildInputs) cargoDependencySet ++ (with pkgs; [ 28 | beku 29 | docker 30 | gettext # for the proper envsubst 31 | git 32 | jq 33 | kind 34 | kubectl 35 | kubernetes-helm 36 | kuttl 37 | nix # this is implied, but needed in the pure env 38 | # tilt already defined in default.nix 39 | which 40 | yq-go 41 | ]); 42 | 43 | LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; 44 | BINDGEN_EXTRA_CLANG_ARGS = "-I${pkgs.glibc.dev}/include -I${pkgs.clang}/resource-root/include"; 45 | } 46 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["rust/*"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | version = "0.0.0-dev" 7 | authors = ["Stackable GmbH "] 8 | license = "OSL-3.0" 9 | edition = "2021" 10 | repository = "https://github.com/stackabletech/opa-operator" 11 | 12 | [workspace.dependencies] 13 | product-config = { git = "https://github.com/stackabletech/product-config.git", tag = "0.8.0" } 14 | stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", features = ["telemetry", "versioned"], tag = "stackable-operator-0.100.1" } 15 | krb5 = { git = "https://github.com/stackabletech/krb5-rs.git", tag = "v0.1.0" } 16 | 17 | anyhow = "1.0" 18 | axum = "0.8" 19 | base64 = "0.22" 20 | built = { version = "0.8", features = ["chrono", "git2"] } 21 | byteorder = "1.5" 22 | clap = "4.5" 23 | const_format = "0.2" 24 | flate2 = "1.0" 25 | fnv = "1.0" 26 | futures = { version = "0.3" } 27 | hyper = "1.4" 28 | indoc = "2.0" 29 | ldap3 = { version = "0.12", features = ["gssapi", "tls"] } 30 | moka = { version = "0.12", features = ["future"] } 31 | native-tls = "0.2.12" 32 | pin-project = "1.1" 33 | reqwest = { version = "0.12", features = ["json"] } 34 | rustls-pemfile = "2.1" 35 | semver = "1.0" 36 | serde = { version = "1.0", features = ["derive"] } 37 | serde_json = "1.0" 38 | snafu = "0.8" 39 | strum = { version = "0.27", features = ["derive"] } 40 | tar = "0.4" 41 | tokio = { version = "1.40", features = ["full"] } 42 | tracing = "0.1" 43 | url = "2.5" 44 | uuid = "1.10" 45 | 46 | # [patch."https://github.com/stackabletech/operator-rs.git"] 47 | # stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } 48 | # stackable-operator = { path = "../operator-rs/crates/stackable-operator" } 49 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/10-install-opa.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: | 6 | kubectl apply -n $NAMESPACE -f - < 0 %} 30 | custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" 31 | productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" 32 | {% else %} 33 | productVersion: "{{ test_scenario['values']['opa-latest'] }}" 34 | {% endif %} 35 | pullPolicy: IfNotPresent 36 | clusterConfig: 37 | userInfo: 38 | backend: 39 | experimentalXfscAas: 40 | hostname: aas.$NAMESPACE.svc.cluster.local 41 | port: 5000 42 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 43 | vectorAggregatorConfigMapName: vector-aggregator-discovery 44 | {% endif %} 45 | servers: 46 | config: 47 | logging: 48 | enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} 49 | roleGroups: 50 | default: {} 51 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | *Please add a description here. This will become the commit message of the merge request later.* 4 | 5 | ## Definition of Done Checklist 6 | 7 | - Not all of these items are applicable to all PRs, the author should update this template to only leave the boxes in that are relevant 8 | - Please make sure all these things are done and tick the boxes 9 | 10 | ### Author 11 | 12 | - [ ] Changes are OpenShift compatible 13 | - [ ] CRD changes approved 14 | - [ ] CRD documentation for all fields, following the [style guide](https://docs.stackable.tech/home/nightly/contributor/docs/style-guide). 15 | - [ ] Helm chart can be installed and deployed operator works 16 | - [ ] Integration tests passed (for non trivial changes) 17 | - [ ] Changes need to be "offline" compatible 18 | - [ ] Links to generated (nightly) docs added 19 | - [ ] Release note snippet added 20 | 21 | ### Reviewer 22 | 23 | - [ ] Code contains useful comments 24 | - [ ] Code contains useful logging statements 25 | - [ ] (Integration-)Test cases added 26 | - [ ] Documentation added or updated. Follows the [style guide](https://docs.stackable.tech/home/nightly/contributor/docs/style-guide). 27 | - [ ] Changelog updated 28 | - [ ] Cargo.toml only contains references to git tags (not specific commits or branches) 29 | 30 | ### Acceptance 31 | 32 | - [ ] Feature Tracker has been updated 33 | - [ ] Proper release label has been added 34 | - [ ] Links to generated (nightly) docs added 35 | - [ ] Release note snippet added 36 | - [ ] Add `type/deprecation` label & add to the [deprecation schedule](https://github.com/orgs/stackabletech/projects/44/views/1) 37 | - [ ] Add `type/experimental` label & add to the [experimental features tracker](https://github.com/orgs/stackabletech/projects/47) 38 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "beku.py": { 3 | "branch": "0.0.10", 4 | "description": "Test suite expander for Stackable Kuttl tests.", 5 | "homepage": null, 6 | "owner": "stackabletech", 7 | "repo": "beku.py", 8 | "rev": "fc75202a38529a4ac6776dd8a5dfee278d927f58", 9 | "sha256": "152yary0p11h87yabv74jnwkghsal7lx16az0qlzrzdrs6n5v8id", 10 | "type": "tarball", 11 | "url": "https://github.com/stackabletech/beku.py/archive/fc75202a38529a4ac6776dd8a5dfee278d927f58.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "crate2nix": { 15 | "branch": "master", 16 | "description": "nix build file generator for rust crates", 17 | "homepage": "", 18 | "owner": "kolloch", 19 | "repo": "crate2nix", 20 | "rev": "be31feae9a82c225c0fd1bdf978565dc452a483a", 21 | "sha256": "14d0ymlrwk7dynv35qcw4xn0dylfpwjmf6f8znflbk2l6fk23l12", 22 | "type": "tarball", 23 | "url": "https://github.com/kolloch/crate2nix/archive/be31feae9a82c225c0fd1bdf978565dc452a483a.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "nixpkgs": { 27 | "branch": "nixpkgs-unstable", 28 | "description": "Nix Packages collection", 29 | "homepage": "", 30 | "owner": "NixOS", 31 | "repo": "nixpkgs", 32 | "rev": "a7fc11be66bdfb5cdde611ee5ce381c183da8386", 33 | "sha256": "0h3gvjbrlkvxhbxpy01n603ixv0pjy19n9kf73rdkchdvqcn70j2", 34 | "type": "tarball", 35 | "url": "https://github.com/NixOS/nixpkgs/archive/a7fc11be66bdfb5cdde611ee5ce381c183da8386.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/templates/kuttl/smoke/20-install-test-opa.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: | 6 | kubectl apply -n $NAMESPACE -f - < 0 %} 33 | custom: "{{ test_scenario['values']['opa'].split(',')[1] }}" 34 | productVersion: "{{ test_scenario['values']['opa'].split(',')[0] }}" 35 | {% else %} 36 | productVersion: "{{ test_scenario['values']['opa'] }}" 37 | {% endif %} 38 | pullPolicy: IfNotPresent 39 | clusterConfig: 40 | {% if test_scenario['values']['use-tls'] == "true" %} 41 | tls: 42 | serverSecretClass: opa-tls-$NAMESPACE 43 | {% endif %} 44 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 45 | vectorAggregatorConfigMapName: vector-aggregator-discovery 46 | {% endif %} 47 | servers: 48 | config: 49 | logging: 50 | enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} 51 | envOverrides: 52 | SERVER_ROLE_LEVEL_ENV_VAR: "SERVER_ROLE_LEVEL_ENV_VAR" 53 | roleGroups: 54 | default: 55 | envOverrides: 56 | SERVER_ROLE_GROUP_LEVEL_ENV_VAR: "SERVER_ROLE_GROUP_LEVEL_ENV_VAR" 57 | EOF 58 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/implementation-notes.adoc: -------------------------------------------------------------------------------- 1 | = Implementation notes 2 | 3 | These notes may be of use when trying to understand why things are implemented the way that they are, 4 | but should not be required reading for regular use. 5 | 6 | == OPA replica per node 7 | 8 | OPA runs on each Node to avoid requiring network round trips for services making policy queries (which are often chained in serial, and block other tasks in the products). 9 | 10 | Local access is ensured via an https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/[`InternalTrafficPolicy`]. 11 | This means that https://kubernetes.io/docs/concepts/workloads/pods/[Pods] accessing OPA via the service discovery are routed to the OPA Pod on the same https://kubernetes.io/docs/concepts/architecture/nodes/[Node] to reduce request latency and network traffic. 12 | 13 | == OPA Bundle Builder 14 | 15 | Users can manage policy rules by creating, updating and deleting ConfigMap resources. 16 | 17 | The responsibility of the https://github.com/stackabletech/opa-bundle-builder[OPA Bundle Builder] is to convert these resources to bundles (`tar.gz` files) and make them available via an HTTP endpoint. 18 | The OPA Bundle Builder runs in a side container of the OPA Pod as a simple HTTP server that OPA is querying regularly 19 | (every 20 to 30 seconds) for updates. 20 | 21 | NOTE: Kubernetes limits the size of ConfigMaps to 1MB. 22 | Users have to take this limit into consideration when managing policy rules. 23 | 24 | Only ConfigMaps labeled with `opa.stackable.tech/bundle: "true"` are considered by the builder when updating bundles. The name of 25 | the `data` entries in the `ConfigMap` are used as file names when storing the rules in the bundle. 26 | 27 | NOTE: Currently, it is the user's responsibility to make sure these names do not collide (as they will override each other). 28 | -------------------------------------------------------------------------------- /.readme/static/borrowed/Icon_Stackable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/getting_started/installation.adoc: -------------------------------------------------------------------------------- 1 | = Installation 2 | :description: Install the Stackable OPA operator on Kubernetes using stackablectl or Helm. Deploy OPA, create policies, and query rules easily. 3 | 4 | There are 2 ways to install Stackable operators on a Kubernetes cluster. 5 | 6 | * Using xref:management:stackablectl:index.adoc[] (recommended) 7 | * Using Helm 8 | 9 | [tabs] 10 | ==== 11 | stackablectl:: 12 | + 13 | -- 14 | `stackablectl` is the command line tool to interact with Stackable operators and the recommended way to install operators. 15 | Follow the xref:management:stackablectl:installation.adoc[installation steps] for your platform. 16 | 17 | After you have installed `stackablectl` run the following command to install the OPA operator: 18 | 19 | [source,shell] 20 | ---- 21 | include::example$getting_started/getting_started.sh[tag=stackablectl-install-operators] 22 | ---- 23 | 24 | The tool prints 25 | 26 | [source] 27 | include::example$getting_started/install_output.txt[] 28 | 29 | TIP: Consult the xref:management:stackablectl:quickstart.adoc[] to learn more about how to use `stackablectl`. 30 | For example, you can use the `--cluster kind` flag to create a Kubernetes cluster with link:https://kind.sigs.k8s.io/[kind]. 31 | -- 32 | 33 | Helm:: 34 | + 35 | -- 36 | You can also use Helm to install the operator. 37 | 38 | NOTE: `helm repo` subcommands are not supported for OCI registries. The operators are installed directly, without adding the Helm Chart repository first. 39 | 40 | Install the Stackable OPA operator: 41 | 42 | [source,shell] 43 | ---- 44 | include::example$getting_started/getting_started.sh[tag=helm-install-operators] 45 | ---- 46 | 47 | Helm deploys the operator in a Kubernetes Deployment and apply the CRDs for the OPA service. 48 | -- 49 | ==== 50 | 51 | == What's next 52 | 53 | xref:getting_started/first_steps.adoc[Deploy OPA, a policy rule and query it]. 54 | -------------------------------------------------------------------------------- /tests/templates/kuttl/resources/10-assert.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | timeout: 600 5 | commands: 6 | - script: kubectl -n $NAMESPACE rollout status daemonset opa-server-resources-from-role --timeout 600s 7 | - script: kubectl -n $NAMESPACE rollout status daemonset opa-server-resources-from-role-group --timeout 600s 8 | --- 9 | apiVersion: apps/v1 10 | kind: DaemonSet 11 | metadata: 12 | name: opa-server-resources-from-role 13 | spec: 14 | template: 15 | spec: 16 | containers: 17 | - name: opa 18 | resources: 19 | requests: 20 | cpu: 50m 21 | memory: 256Mi 22 | limits: 23 | cpu: 110m 24 | memory: 256Mi 25 | - name: bundle-builder 26 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 27 | - name: vector 28 | {% endif %} 29 | --- 30 | apiVersion: apps/v1 31 | kind: DaemonSet 32 | metadata: 33 | name: opa-server-resources-from-role-group 34 | spec: 35 | template: 36 | spec: 37 | containers: 38 | - name: opa 39 | resources: 40 | requests: 41 | cpu: 60m 42 | memory: 284Mi 43 | limits: 44 | cpu: 130m 45 | memory: 284Mi 46 | - name: bundle-builder 47 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 48 | - name: vector 49 | {% endif %} 50 | --- 51 | apiVersion: apps/v1 52 | kind: DaemonSet 53 | metadata: 54 | name: opa-server-resources-from-pod-overrides 55 | spec: 56 | template: 57 | spec: 58 | containers: 59 | - name: opa 60 | resources: 61 | requests: 62 | cpu: 70m 63 | memory: 256Mi 64 | limits: 65 | cpu: 150m 66 | memory: 256Mi 67 | - name: bundle-builder 68 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 69 | - name: vector 70 | {% endif %} 71 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/test_log_aggregation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import requests 3 | import time 4 | 5 | 6 | def send_opa_decision_request(): 7 | response = requests.post("http://test-opa-server:8081/v1/data/test/world") 8 | 9 | assert response.status_code == 200, "Cannot access the API of the opa cluster." 10 | 11 | 12 | def check_sent_events(): 13 | response = requests.post( 14 | "http://opa-vector-aggregator:8686/graphql", 15 | json={ 16 | "query": """ 17 | { 18 | transforms(first:100) { 19 | nodes { 20 | componentId 21 | metrics { 22 | sentEventsTotal { 23 | sentEventsTotal 24 | } 25 | } 26 | } 27 | } 28 | } 29 | """ 30 | }, 31 | ) 32 | 33 | assert response.status_code == 200, ( 34 | "Cannot access the API of the vector aggregator." 35 | ) 36 | 37 | result = response.json() 38 | 39 | transforms = result["data"]["transforms"]["nodes"] 40 | for transform in transforms: 41 | sentEvents = transform["metrics"]["sentEventsTotal"] 42 | componentId = transform["componentId"] 43 | 44 | if componentId == "filteredInvalidEvents": 45 | assert sentEvents is None or sentEvents["sentEventsTotal"] == 0, ( 46 | "Invalid log events were sent." 47 | ) 48 | else: 49 | assert sentEvents is not None and sentEvents["sentEventsTotal"] > 0, ( 50 | f'No events were sent in "{componentId}".' 51 | ) 52 | 53 | 54 | if __name__ == "__main__": 55 | send_opa_decision_request() 56 | time.sleep(10) 57 | check_sent_events() 58 | print("Test successful!") 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02-bug_report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | description: "If something isn't working as expected 🤔." 4 | labels: ["type/bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible. 9 | 10 | - type: input 11 | attributes: 12 | label: Affected Stackable version 13 | description: Which version of the Stackable Operator do you see this bug in? 14 | 15 | # 16 | - type: input 17 | attributes: 18 | label: Affected OpenPolicyAgent version 19 | description: Which version of OpenPolicyAgent do you see this bug in? 20 | # 21 | 22 | - type: textarea 23 | attributes: 24 | label: Current and expected behavior 25 | description: A clear and concise description of what the operator is doing and what you would expect. 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | attributes: 31 | label: Possible solution 32 | description: "If you have suggestions on a fix for the bug." 33 | 34 | - type: textarea 35 | attributes: 36 | label: Additional context 37 | description: "Add any other context about the problem here. Or a screenshot if applicable." 38 | 39 | - type: textarea 40 | attributes: 41 | label: Environment 42 | description: | 43 | What type of kubernetes cluster you are running against (k3s/eks/aks/gke/other) and any other information about your environment? 44 | placeholder: | 45 | Examples: 46 | Output of `kubectl version --short` 47 | 48 | - type: dropdown 49 | attributes: 50 | label: Would you like to work on fixing this bug? 51 | description: | 52 | **NOTE**: Let us know if you would like to submit a PR for this. We are more than happy to help you through the process. 53 | options: 54 | - "yes" 55 | - "no" 56 | - "maybe" 57 | -------------------------------------------------------------------------------- /tests/test-definition.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | dimensions: 3 | - name: opa 4 | values: 5 | # To use a custom image, add a comma and the full name after the product version 6 | # 0.67.1,oci.stackable.tech/sdp/opa:0.67.1-stackable0.0.0-dev 7 | - 1.4.2 8 | - 1.8.0 9 | - name: opa-latest 10 | values: 11 | # To use a custom image, add a comma and the full name after the product version 12 | # 0.67.1,oci.stackable.tech/sdp/opa:0.67.1-stackable0.0.0-dev 13 | - 1.8.0 14 | - name: keycloak 15 | values: 16 | - 23.0.1 17 | - name: openshift 18 | values: 19 | - "false" 20 | - name: use-tls 21 | values: 22 | - "true" 23 | - "false" 24 | tests: 25 | - name: smoke 26 | dimensions: 27 | - opa 28 | - openshift 29 | - use-tls 30 | - name: resources 31 | dimensions: 32 | - opa-latest 33 | - openshift 34 | - name: logging 35 | dimensions: 36 | - opa 37 | - openshift 38 | - name: cluster-operation 39 | dimensions: 40 | - opa-latest 41 | - openshift 42 | - name: keycloak-user-info 43 | dimensions: 44 | - opa-latest 45 | - keycloak 46 | - openshift 47 | # AD must be initialized (by running ad-init) first, 48 | # and the correct users and groups must be set up (see test-regorule.py) 49 | # name: ad-user-info 50 | # dimensions: 51 | # - opa-latest 52 | # - openshift 53 | - name: aas-user-info 54 | dimensions: 55 | - opa-latest 56 | - openshift 57 | - name: openldap-user-info 58 | dimensions: 59 | - opa-latest 60 | - openshift 61 | suites: 62 | - name: nightly 63 | patch: 64 | - dimensions: 65 | - expr: last 66 | - name: smoke-latest 67 | select: 68 | - smoke 69 | patch: 70 | - dimensions: 71 | - expr: last 72 | - name: openshift 73 | patch: 74 | - dimensions: 75 | - expr: last 76 | - dimensions: 77 | - name: openshift 78 | expr: "true" 79 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/templates/_telemetry.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Create a list of telemetry related env vars. 3 | */}} 4 | {{- define "telemetry.envVars" -}} 5 | {{- with .Values.telemetry }} 6 | {{- if not .consoleLog.enabled }} 7 | - name: CONSOLE_LOG_DISABLED 8 | value: "true" 9 | {{- end }} 10 | {{- if and .consoleLog.enabled .consoleLog.level }} 11 | - name: CONSOLE_LOG_LEVEL 12 | value: {{ .consoleLog.level }} 13 | {{ end }} 14 | {{- if and .consoleLog.enabled .consoleLog.format }} 15 | - name: CONSOLE_LOG_FORMAT 16 | value: {{ .consoleLog.format }} 17 | {{ end }} 18 | {{- if .fileLog.enabled }} 19 | - name: FILE_LOG_DIRECTORY 20 | value: /stackable/logs/{{ include "operator.appname" $ }} 21 | {{- end }} 22 | {{- if and .fileLog.enabled .fileLog.level }} 23 | - name: FILE_LOG_LEVEL 24 | value: {{ .fileLog.level }} 25 | {{- end }} 26 | {{- if and .fileLog.enabled .fileLog.rotationPeriod }} 27 | - name: FILE_LOG_ROTATION_PERIOD 28 | value: {{ .fileLog.rotationPeriod }} 29 | {{- end }} 30 | {{- if and .fileLog.enabled .fileLog.maxFiles }} 31 | - name: FILE_LOG_MAX_FILES 32 | value: {{ quote .fileLog.maxFiles }} 33 | {{- end }} 34 | {{- if .otelLogExporter.enabled }} 35 | - name: OTEL_LOG_EXPORTER_ENABLED 36 | value: "true" 37 | {{- end }} 38 | {{- if and .otelLogExporter.enabled .otelLogExporter.level }} 39 | - name: OTEL_LOG_EXPORTER_LEVEL 40 | value: {{ .otelLogExporter.level }} 41 | {{- end }} 42 | {{- if and .otelLogExporter.enabled .otelLogExporter.endpoint }} 43 | - name: OTEL_EXPORTER_OTLP_LOGS_ENDPOINT 44 | value: {{ .otelLogExporter.endpoint }} 45 | {{- end }} 46 | {{- if .otelTraceExporter.enabled }} 47 | - name: OTEL_TRACE_EXPORTER_ENABLED 48 | value: "true" 49 | {{- end }} 50 | {{- if and .otelTraceExporter.enabled .otelTraceExporter.level }} 51 | - name: OTEL_TRACE_EXPORTER_LEVEL 52 | value: {{ .otelTraceExporter.level }} 53 | {{- end }} 54 | {{- if and .otelTraceExporter.enabled .otelTraceExporter.endpoint }} 55 | - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT 56 | value: {{ .otelTraceExporter.endpoint }} 57 | {{- end }} 58 | {{- end }} 59 | {{- end }} 60 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/reference/discovery.adoc: -------------------------------------------------------------------------------- 1 | = Discovery 2 | :description: Discover OPA cluster connection strings for internal access, including URL configuration for querying policies. 3 | :page-aliases: discovery.adoc 4 | :clusterName: simple-opa 5 | :namespace: stackable 6 | :packageName: opa-test 7 | :policyName: allow 8 | 9 | The Stackable operator for OpenPolicyAgent (OPA) publishes a xref:concepts:service-discovery.adoc[discovery ConfigMap], which exposes a client configuration bundle that allows access to the OPA cluster. 10 | 11 | The bundle includes a connection string to access the OPA cluster. 12 | This string may be used by other operators or tools to configure their products with access to OPA. 13 | This is limited to internal cluster access. 14 | 15 | == Example 16 | 17 | Given the following OPA cluster: 18 | 19 | [source,yaml,subs="normal,callouts"] 20 | ---- 21 | apiVersion: opa.stackable.tech/v1alpha1 22 | kind: OpaCluster 23 | metadata: 24 | name: {clusterName} # <1> 25 | namespace: {namespace} # <2> 26 | spec: 27 | [...] 28 | ---- 29 | <1> The name of the OPA cluster, which is also the name of the created discovery ConfigMap. 30 | <2> The namespace of the discovery ConfigMap. 31 | 32 | The resulting discovery ConfigMap is `{namespace}/{clusterName}`. 33 | 34 | == Contents 35 | 36 | The `{namespace}/{clusterName}` discovery ConfigMap contains the following fields where `{clusterName}` represents the name and `{namespace}` the namespace of the cluster: 37 | 38 | `OPA`:: 39 | ==== 40 | A connection string for cluster internal OPA requests. 41 | Provided the cluster example above, the connection string is created as follows: 42 | 43 | [subs="attributes"] 44 | http://{clusterName}.{namespace}.svc.cluster.local:8081/ 45 | 46 | This connection string points to the base URL (and web UI) of the OPA cluster. 47 | In order to query policies you have to configure your product and its OPA URL as follows, given the bundle package name `{packageName}` and the policy name `{policyName}`: 48 | 49 | [subs="attributes"] 50 | http://{clusterName}.{namespace}.svc.cluster.local:8081/v1/data/{packageName}/{policyName} 51 | ==== 52 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/10-install-opa.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: | 6 | kubectl apply -n $NAMESPACE -f - < 0 %} 30 | custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" 31 | productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" 32 | {% else %} 33 | productVersion: "{{ test_scenario['values']['opa-latest'] }}" 34 | {% endif %} 35 | pullPolicy: IfNotPresent 36 | clusterConfig: 37 | userInfo: 38 | backend: 39 | keycloak: 40 | hostname: keycloak.$NAMESPACE.svc.cluster.local 41 | port: 8443 42 | tls: 43 | verification: 44 | server: 45 | caCert: 46 | secretClass: keycloak-tls-$NAMESPACE 47 | clientCredentialsSecret: user-info-fetcher-client-credentials 48 | adminRealm: my-dataspace 49 | userRealm: my-dataspace 50 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 51 | vectorAggregatorConfigMapName: vector-aggregator-discovery 52 | {% endif %} 53 | servers: 54 | config: 55 | logging: 56 | enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} 57 | roleGroups: 58 | default: {} 59 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/logging.adoc: -------------------------------------------------------------------------------- 1 | = Logging 2 | :description: Configure log aggregation for OPA with Vector, enable decision logging, and customize log levels for file and console outputs. 3 | 4 | == Log aggregation 5 | 6 | The logs can be forwarded to a Vector log aggregator by providing a discovery ConfigMap for the aggregator and by enabling the log agent: 7 | 8 | [source,yaml] 9 | ---- 10 | spec: 11 | clusterConfig: 12 | vectorAggregatorConfigMapName: vector-aggregator-discovery 13 | servers: 14 | config: 15 | logging: 16 | enableVectorAgent: true 17 | containers: 18 | opa: 19 | console: 20 | level: NONE 21 | file: 22 | level: INFO 23 | ---- 24 | 25 | The Stackable operator for OPA only supports automatic log configuration due to the lack of customization for the OPA logging. 26 | 27 | Furthermore, the only customization possible for console output for the `bundle-builder` container is `NONE`. 28 | This deactivates console logging. 29 | Other log levels for console logging in this container are overwritten by the file log level. 30 | 31 | == Decision logging 32 | 33 | The decision logging for OPA can be enabled by setting the log level of the decision logger to any other level than `NONE`, as shown by example here: 34 | 35 | [source,yaml] 36 | ---- 37 | spec: 38 | clusterConfig: 39 | vectorAggregatorConfigMapName: vector-aggregator-discovery 40 | servers: 41 | config: 42 | logging: 43 | enableVectorAgent: true 44 | containers: 45 | opa: 46 | console: 47 | level: NONE 48 | file: 49 | level: INFO 50 | loggers: 51 | decision: # <1> 52 | level: INFO 53 | ---- 54 | <1> The `decision` logger is configured here. 55 | 56 | The decision logs are still filtered by the log level set for the console and file appenders. 57 | Therefore, the configuration above would result in decision logs being present on file but not on console. 58 | 59 | Further information on how to configure logging, can be found in 60 | xref:concepts:logging.adoc[]. 61 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # This file is the source of truth for all our repos! 2 | # This includes repos not templated by operator-templating, please copy/paste the file for this repos. 3 | 4 | # TIP: Use "cargo deny check" to check if everything is fine 5 | 6 | [graph] 7 | targets = [ 8 | { triple = "x86_64-unknown-linux-gnu" }, 9 | { triple = "aarch64-unknown-linux-gnu" }, 10 | { triple = "x86_64-unknown-linux-musl" }, 11 | { triple = "aarch64-apple-darwin" }, 12 | { triple = "x86_64-apple-darwin" }, 13 | ] 14 | 15 | [advisories] 16 | yanked = "deny" 17 | ignore = [ 18 | # https://rustsec.org/advisories/RUSTSEC-2023-0071 19 | # "rsa" crate: Marvin Attack: potential key recovery through timing sidechannel 20 | # 21 | # No patch is yet available, however work is underway to migrate to a fully constant-time implementation 22 | # So we need to accept this, as of SDP 25.3 we are not using the rsa crate to create certificates used in production 23 | # setups. 24 | # 25 | # https://github.com/RustCrypto/RSA/issues/19 is the tracking issue 26 | "RUSTSEC-2023-0071", 27 | ] 28 | 29 | [bans] 30 | multiple-versions = "allow" 31 | 32 | [licenses] 33 | unused-allowed-license = "allow" 34 | confidence-threshold = 1.0 35 | allow = [ 36 | "Apache-2.0", 37 | "BSD-2-Clause", 38 | "BSD-3-Clause", 39 | "CC0-1.0", 40 | "ISC", 41 | "LicenseRef-ring", 42 | "LicenseRef-webpki", 43 | "MIT", 44 | "MPL-2.0", 45 | "OpenSSL", # Needed for the ring and/or aws-lc-sys crate. See https://github.com/stackabletech/operator-templating/pull/464 for details 46 | "Unicode-3.0", 47 | "Unicode-DFS-2016", 48 | "Zlib", 49 | "Unlicense", 50 | ] 51 | private = { ignore = true } 52 | 53 | [[licenses.clarify]] 54 | name = "ring" 55 | expression = "LicenseRef-ring" 56 | license-files = [ 57 | { path = "LICENSE", hash = 0xbd0eed23 }, 58 | ] 59 | 60 | [[licenses.clarify]] 61 | name = "webpki" 62 | expression = "LicenseRef-webpki" 63 | license-files = [ 64 | { path = "LICENSE", hash = 0x001c7e6c }, 65 | ] 66 | 67 | [sources] 68 | unknown-registry = "deny" 69 | unknown-git = "deny" 70 | 71 | [sources.allow-org] 72 | github = ["stackabletech"] 73 | -------------------------------------------------------------------------------- /Tiltfile: -------------------------------------------------------------------------------- 1 | # If tilt_options.json exists read it and load the default_registry value from it 2 | settings = read_json('tilt_options.json', default={}) 3 | registry = settings.get('default_registry', 'oci.stackable.tech/sandbox') 4 | 5 | # Configure default registry either read from config file above, or with default value of "oci.stackable.tech/sandbox" 6 | default_registry(registry) 7 | 8 | meta = read_json('nix/meta.json') 9 | operator_name = meta['operator']['name'] 10 | 11 | custom_build( 12 | registry + '/' + operator_name, 13 | 'make regenerate-nix && nix-build . -A docker --argstr dockerName "${EXPECTED_REGISTRY}/' + operator_name + '" && ./result/load-image | docker load', 14 | deps=['rust', 'Cargo.toml', 'Cargo.lock', 'default.nix', "nix", 'build.rs', 'vendor'], 15 | ignore=['*.~undo-tree~'], 16 | # ignore=['result*', 'Cargo.nix', 'target', *.yaml], 17 | outputs_image_ref_to='result/ref', 18 | ) 19 | 20 | # Load the latest CRDs from Nix 21 | watch_file('result') 22 | if os.path.exists('result'): 23 | k8s_yaml('result/crds.yaml') 24 | 25 | # We need to set the correct image annotation on the operator Deployment to use e.g. 26 | # oci.stackable.tech/sandbox/opa-operator:7y19m3d8clwxlv34v5q2x4p7v536s00g instead of 27 | # oci.stackable.tech/sandbox/opa-operator:0.0.0-dev (which does not exist) 28 | k8s_kind('Deployment', image_json_path='{.spec.template.metadata.annotations.internal\\.stackable\\.tech/image}') 29 | k8s_kind('DaemonSet', image_json_path='{.spec.template.metadata.annotations.internal\\.stackable\\.tech/image}') 30 | 31 | # Optionally specify a custom Helm values file to be passed to the Helm deployment below. 32 | # This file can for example be used to set custom telemetry options (like log level) which is not 33 | # supported by helm(set). 34 | helm_values = settings.get('helm_values', None) 35 | 36 | helm_override_image_repository = 'image.repository=' + registry + '/' + operator_name 37 | 38 | # Exclude stale CRDs from Helm chart, and apply the rest 39 | helm_crds, helm_non_crds = filter_yaml( 40 | helm( 41 | 'deploy/helm/' + operator_name, 42 | name=operator_name, 43 | namespace="stackable-operators", 44 | set=[ 45 | helm_override_image_repository, 46 | ], 47 | values=helm_values, 48 | ), 49 | api_version = "^apiextensions\\.k8s\\.io/.*$", 50 | kind = "^CustomResourceDefinition$", 51 | ) 52 | k8s_yaml(helm_non_crds) 53 | -------------------------------------------------------------------------------- /rust/user-info-fetcher/src/utils/http.rs: -------------------------------------------------------------------------------- 1 | use hyper::StatusCode; 2 | use reqwest::{RequestBuilder, Response}; 3 | use serde::de::DeserializeOwned; 4 | use snafu::{ResultExt, Snafu}; 5 | 6 | #[derive(Snafu, Debug)] 7 | pub enum Error { 8 | #[snafu(display("failed to execute request"))] 9 | HttpRequest { source: reqwest::Error }, 10 | 11 | #[snafu(display("failed to parse json response"))] 12 | ParseJson { source: reqwest::Error }, 13 | 14 | #[snafu(display("http response {status:?} for {url:?} with response body {text:?}"))] 15 | HttpErrorResponse { 16 | status: StatusCode, 17 | url: String, 18 | text: String, 19 | }, 20 | 21 | #[snafu(display("http response {status:?} for {url:?} with an undecodable response body"))] 22 | HttpErrorResponseUndecodableText { 23 | status: StatusCode, 24 | url: String, 25 | encoding_error: reqwest::Error, 26 | }, 27 | } 28 | 29 | pub async fn send_json_request(req: RequestBuilder) -> Result { 30 | // make the request 31 | let response = req.send().await.context(HttpRequestSnafu)?; 32 | // check for client or server errors 33 | let non_error_response = error_for_status(response).await?; 34 | // parse the result 35 | let result = non_error_response.json().await.context(ParseJsonSnafu)?; 36 | Ok(result) 37 | } 38 | 39 | /// Wraps a Response into a Result. If there is an HTTP Client or Server error, 40 | /// extract the HTTP body (if possible) to be used as context in the returned Err. 41 | /// This is done this because the `Response::error_for_status()` method Err variant 42 | /// does not contain this information. 43 | async fn error_for_status(response: Response) -> Result { 44 | let status = response.status(); 45 | if status.is_client_error() || status.is_server_error() { 46 | let url = response.url().to_string(); 47 | return match response.text().await { 48 | Ok(text) => HttpErrorResponseSnafu { 49 | status, 50 | url, 51 | text: text.trim(), 52 | } 53 | .fail(), 54 | Err(encoding_error) => HttpErrorResponseUndecodableTextSnafu { 55 | status, 56 | url, 57 | encoding_error, 58 | } 59 | .fail(), 60 | }; 61 | } 62 | Ok(response) 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/integration-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Integration Test 3 | 4 | on: 5 | # schedule: 6 | # # At 00:00 on Sunday. See: https://crontab.guru/#0_0_*_*_0 7 | # - cron: "0 0 * * 0" 8 | workflow_dispatch: 9 | inputs: 10 | test-mode: 11 | description: Test mode 12 | required: true 13 | type: choice 14 | options: 15 | - profile 16 | - custom 17 | test-mode-input: 18 | description: | 19 | The profile or the runner used. Eg: `smoke-latest` or `amd64` (see test/interu.yaml) 20 | required: true 21 | test-suite: 22 | description: Name of the test-suite. Only used if test-mode is `custom` 23 | test: 24 | description: Name of the test. Only used of test-mode is `custom` 25 | 26 | jobs: 27 | test: 28 | name: Run Integration Test 29 | runs-on: ubuntu-latest 30 | # services: 31 | # otel-collector: 32 | # image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-k8s:0.131.1 33 | # volumes: 34 | # - .:/mnt 35 | steps: 36 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 37 | with: 38 | persist-credentials: false 39 | submodules: recursive 40 | 41 | # TODO: Enable the scheduled runs which hard-code what profile to use 42 | - name: Run Integration Test 43 | id: test 44 | uses: stackabletech/actions/run-integration-test@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 45 | with: 46 | replicated-api-token: ${{ secrets.REPLICATED_API_TOKEN }} 47 | test-mode-input: ${{ inputs.test-mode-input }} 48 | test-suite: ${{ inputs.test-suite }} 49 | test-mode: ${{ inputs.test-mode }} 50 | test: ${{ inputs.test }} 51 | 52 | - name: Send Notification 53 | if: ${{ failure() || github.run_attempt > 1 }} 54 | uses: stackabletech/actions/send-slack-notification@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 55 | with: 56 | slack-token: ${{ secrets.SLACK_INTEGRATION_TEST_TOKEN }} 57 | failed-tests: ${{ steps.test.outputs.failed-tests }} 58 | test-health: ${{ steps.test.outputs.health }} 59 | test-result: ${{ steps.test.conclusion }} 60 | channel-id: C07UYJYSMSN # notifications-integration-tests 61 | type: integration-test 62 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "operator.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-operator" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Expand the name of the chart. 10 | */}} 11 | {{- define "operator.appname" -}} 12 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 13 | {{- end }} 14 | 15 | {{/* 16 | Create a default fully qualified app name. 17 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 18 | If release name contains chart name it will be used as a full name. 19 | */}} 20 | {{- define "operator.fullname" -}} 21 | {{- if .Values.fullnameOverride }} 22 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 23 | {{- else }} 24 | {{- $name := default .Chart.Name .Values.nameOverride }} 25 | {{- if contains $name .Release.Name }} 26 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 27 | {{- else }} 28 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 29 | {{- end }} 30 | {{- end }} 31 | {{- end }} 32 | 33 | {{/* 34 | Create chart name and version as used by the chart label. 35 | */}} 36 | {{- define "operator.chart" -}} 37 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 38 | {{- end }} 39 | 40 | {{/* 41 | Common labels 42 | */}} 43 | {{- define "operator.labels" -}} 44 | helm.sh/chart: {{ include "operator.chart" . }} 45 | {{ include "operator.selectorLabels" . }} 46 | {{- if .Chart.AppVersion }} 47 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 48 | {{- end }} 49 | app.kubernetes.io/managed-by: {{ .Release.Service }} 50 | {{- end }} 51 | 52 | {{/* 53 | Selector labels 54 | */}} 55 | {{- define "operator.selectorLabels" -}} 56 | app.kubernetes.io/name: {{ include "operator.appname" . }} 57 | app.kubernetes.io/instance: {{ .Release.Name }} 58 | {{- with .Values.labels }} 59 | {{ toYaml . }} 60 | {{- end }} 61 | {{- end }} 62 | 63 | {{/* 64 | Create the name of the service account to use 65 | */}} 66 | {{- define "operator.serviceAccountName" -}} 67 | {{- if .Values.serviceAccount.create }} 68 | {{- default (include "operator.fullname" .) .Values.serviceAccount.name }} 69 | {{- else }} 70 | {{- default "default" .Values.serviceAccount.name }} 71 | {{- end }} 72 | {{- end }} 73 | 74 | {{/* 75 | Labels for Kubernetes objects created by helm test 76 | */}} 77 | {{- define "operator.testLabels" -}} 78 | helm.sh/test: {{ include "operator.chart" . }} 79 | {{- end }} 80 | -------------------------------------------------------------------------------- /rust/operator-binary/src/product_logging.rs: -------------------------------------------------------------------------------- 1 | use snafu::Snafu; 2 | use stackable_opa_operator::crd::v1alpha1; 3 | use stackable_operator::{ 4 | builder::configmap::ConfigMapBuilder, 5 | product_logging::{ 6 | self, 7 | spec::{ContainerLogConfig, ContainerLogConfigChoice, LogLevel, Logging}, 8 | }, 9 | role_utils::RoleGroupRef, 10 | }; 11 | 12 | #[derive(Snafu, Debug)] 13 | pub enum Error { 14 | #[snafu(display("object has no namespace"))] 15 | ObjectHasNoNamespace, 16 | #[snafu(display("failed to retrieve the ConfigMap [{cm_name}]"))] 17 | ConfigMapNotFound { 18 | source: stackable_operator::client::Error, 19 | cm_name: String, 20 | }, 21 | #[snafu(display("failed to retrieve the entry [{entry}] for ConfigMap [{cm_name}]"))] 22 | MissingConfigMapEntry { 23 | entry: &'static str, 24 | cm_name: String, 25 | }, 26 | #[snafu(display("vectorAggregatorConfigMapName must be set"))] 27 | MissingVectorAggregatorAddress, 28 | } 29 | 30 | type Result = std::result::Result; 31 | 32 | #[derive(strum::Display)] 33 | #[strum(serialize_all = "UPPERCASE")] 34 | pub enum BundleBuilderLogLevel { 35 | Trace, 36 | Debug, 37 | Info, 38 | Warn, 39 | Error, 40 | } 41 | 42 | impl From for BundleBuilderLogLevel { 43 | fn from(level: LogLevel) -> Self { 44 | match level { 45 | LogLevel::TRACE => Self::Trace, 46 | LogLevel::DEBUG => Self::Debug, 47 | LogLevel::INFO => Self::Info, 48 | LogLevel::WARN => Self::Warn, 49 | LogLevel::ERROR | LogLevel::FATAL | LogLevel::NONE => Self::Error, 50 | } 51 | } 52 | } 53 | 54 | /// Extend the role group ConfigMap with logging and Vector configurations 55 | pub fn extend_role_group_config_map( 56 | rolegroup: &RoleGroupRef, 57 | logging: &Logging, 58 | cm_builder: &mut ConfigMapBuilder, 59 | ) -> Result<()> { 60 | let vector_log_config = if let Some(ContainerLogConfig { 61 | choice: Some(ContainerLogConfigChoice::Automatic(log_config)), 62 | }) = logging.containers.get(&v1alpha1::Container::Vector) 63 | { 64 | Some(log_config) 65 | } else { 66 | None 67 | }; 68 | 69 | if logging.enable_vector_agent { 70 | cm_builder.add_data( 71 | product_logging::framework::VECTOR_CONFIG_FILE, 72 | product_logging::framework::create_vector_config(rolegroup, vector_log_config), 73 | ); 74 | } 75 | 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/usage-guide/tls.adoc: -------------------------------------------------------------------------------- 1 | = Enabling TLS Encryption 2 | :description: Learn how to enable TLS encryption for your OPA cluster to secure client connections. 3 | 4 | TLS encryption for securing client connections to the OPA server can be configured in the `OpaCluster` resource. When enabled, OPA serves requests over HTTPS instead of HTTP. 5 | 6 | == Overview 7 | 8 | TLS encryption in OPA is disabled by default. To enable it, you need to: 9 | 10 | 1. Create a `SecretClass` that provides TLS certificates 11 | 2. Reference the `SecretClass` in your `OpaCluster` custom resource 12 | 13 | The operator integrates with the xref:secret-operator:index.adoc[Secret Operator] to automatically provision and mount TLS certificates into the OPA pods. 14 | 15 | == Configuration 16 | 17 | === Creating a SecretClass 18 | 19 | First, create a `SecretClass` that will provide TLS certificates. Here's an example using xref:secret-operator:secretclass.adoc#backend-autotls[autoTls]: 20 | 21 | [source,yaml] 22 | ---- 23 | apiVersion: secrets.stackable.tech/v1alpha1 24 | kind: SecretClass 25 | metadata: 26 | name: opa-tls 27 | spec: 28 | backend: 29 | autoTls: 30 | ca: 31 | autoGenerate: true 32 | secret: 33 | name: opa-tls-ca 34 | namespace: default 35 | ---- 36 | 37 | This SecretClass uses the autoTls backend, which automatically generates a Certificate Authority (CA) and signs certificates for your OPA cluster. 38 | 39 | Similarly, you can also use xref:secret-operator:secretclass.adoc#backend[other backends] supported by Secret Operator. 40 | 41 | === Enabling TLS in OpaCluster 42 | 43 | Once you have a SecretClass, enable TLS in your OpaCluster by setting the `.spec.clusterConfig.tls.serverSecretClass` field: 44 | 45 | [source,yaml] 46 | ---- 47 | kind: OpaCluster 48 | name: opa-with-tls 49 | spec: 50 | clusterConfig: 51 | tls: 52 | serverSecretClass: opa-tls # <1> 53 | ---- 54 | <1> Reference the SecretClass created above 55 | 56 | == Discovery ConfigMap 57 | 58 | The operator automatically creates a discovery ConfigMap, with the same name as the OPA cluster, that contains the connection URL for your cluster. When TLS is enabled, this ConfigMap will contain an HTTPS URL and the SecretClass name: 59 | 60 | [source,yaml] 61 | ---- 62 | apiVersion: v1 63 | kind: ConfigMap 64 | metadata: 65 | name: opa-with-tls 66 | data: 67 | OPA: "https://opa-with-tls.default.svc.cluster.local:8443/" 68 | OPA_SECRET_CLASS: "opa-tls" 69 | ---- 70 | 71 | Applications can use this ConfigMap to discover and connect to the OPA cluster automatically. 72 | -------------------------------------------------------------------------------- /tests/templates/kuttl/logging/opa-vector-aggregator-values.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | role: Aggregator 3 | service: 4 | ports: 5 | - name: api 6 | port: 8686 7 | protocol: TCP 8 | targetPort: 8686 9 | - name: vector 10 | port: 6123 11 | protocol: TCP 12 | targetPort: 6000 13 | customConfig: 14 | api: 15 | address: 0.0.0.0:8686 16 | enabled: true 17 | sources: 18 | vector: 19 | address: 0.0.0.0:6000 20 | type: vector 21 | version: "2" 22 | transforms: 23 | validEvents: 24 | type: filter 25 | inputs: [vector] 26 | condition: is_null(.errors) 27 | filteredAutomaticLogConfigServerOpaDecision: 28 | type: filter 29 | inputs: [validEvents] 30 | condition: >- 31 | starts_with(string!(.pod), "test-opa-server-automatic-log-config") && 32 | .container == "opa" && 33 | .logger == "decision" 34 | filteredAutomaticLogConfigServerOpa: 35 | type: filter 36 | inputs: [validEvents] 37 | condition: >- 38 | starts_with(string!(.pod), "test-opa-server-automatic-log-config") && 39 | .container == "opa" && 40 | .logger != "decision" 41 | filteredAutomaticLogConfigServerBundleBuilder: 42 | type: filter 43 | inputs: [validEvents] 44 | condition: >- 45 | starts_with(string!(.pod), "test-opa-server-automatic-log-config") && 46 | .container == "bundle-builder" 47 | filteredAutomaticLogConfigServerVector: 48 | type: filter 49 | inputs: [validEvents] 50 | condition: >- 51 | starts_with(string!(.pod), "test-opa-server-automatic-log-config") && 52 | .container == "vector" 53 | filteredAutomaticLogConfigServerPrepare: 54 | type: filter 55 | inputs: [validEvents] 56 | condition: >- 57 | starts_with(string!(.pod), "test-opa-server-automatic-log-config") && 58 | .container == "prepare" 59 | filteredInvalidEvents: 60 | type: filter 61 | inputs: [vector] 62 | condition: |- 63 | .timestamp == from_unix_timestamp!(0) || 64 | is_null(.level) || 65 | is_null(.logger) || 66 | is_null(.message) 67 | sinks: 68 | test: 69 | inputs: [filtered*] 70 | type: blackhole 71 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 72 | aggregator: 73 | inputs: [vector] 74 | type: vector 75 | address: {{ lookup('env', 'VECTOR_AGGREGATOR') }} 76 | buffer: 77 | # Avoid back pressure from VECTOR_AGGREGATOR. The test should 78 | # not fail if the aggregator is not available. 79 | when_full: drop_newest 80 | {% endif %} 81 | -------------------------------------------------------------------------------- /tests/templates/kuttl/ad-user-info/10-install-opa.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - script: | 6 | kubectl apply -n $NAMESPACE -f - < 0 %} 30 | custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" 31 | productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" 32 | {% else %} 33 | productVersion: "{{ test_scenario['values']['opa-latest'] }}" 34 | {% endif %} 35 | pullPolicy: IfNotPresent 36 | clusterConfig: 37 | userInfo: 38 | backend: 39 | experimentalActiveDirectory: 40 | ldapServer: sble-addc.sble.test 41 | baseDistinguishedName: DC=sble,DC=test 42 | customAttributeMappings: 43 | country: c 44 | kerberosSecretClassName: kerberos-ad 45 | tls: 46 | verification: 47 | server: 48 | caCert: 49 | secretClass: tls-ad 50 | cache: # optional, enabled by default 51 | entryTimeToLive: 60s # optional, defaults to 60s 52 | {% if lookup('env', 'VECTOR_AGGREGATOR') %} 53 | vectorAggregatorConfigMapName: vector-aggregator-discovery 54 | {% endif %} 55 | servers: 56 | config: 57 | logging: 58 | enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} 59 | roleGroups: 60 | default: 61 | podOverrides: 62 | spec: 63 | containers: 64 | - name: bundle-builder 65 | imagePullPolicy: IfNotPresent 66 | - name: user-info-fetcher 67 | imagePullPolicy: IfNotPresent 68 | env: 69 | - name: CONSOLE_LOG 70 | value: DEBUG 71 | - name: CONSOLE_LOG_LEVEL 72 | value: DEBUG 73 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/test-regorule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import requests 3 | import argparse 4 | import json 5 | 6 | 7 | def assertions( 8 | username, response, opa_attribute, expected_groups, expected_attributes={} 9 | ): 10 | assert "result" in response 11 | result = response["result"] 12 | assert opa_attribute in result, f"expected {opa_attribute} in {result}" 13 | 14 | # repeated the right hand side for better output on error 15 | assert "customAttributes" in result[opa_attribute] 16 | assert "groups" in result[opa_attribute] 17 | assert "id" in result[opa_attribute] 18 | assert "username" in result[opa_attribute] 19 | 20 | # todo: split out group assertions 21 | print(f"Testing for {username} in groups {expected_groups}") 22 | groups = sorted(result[opa_attribute]["groups"]) 23 | expected_groups = sorted(expected_groups) 24 | assert groups == expected_groups, f"got {groups}, expected: {expected_groups}" 25 | 26 | # todo: split out customAttribute assertions 27 | print(f"Testing for {username} with customAttributes {expected_attributes}") 28 | custom_attributes = result[opa_attribute]["customAttributes"] 29 | assert custom_attributes == expected_attributes, ( 30 | f"got {custom_attributes}, expected: {expected_attributes}" 31 | ) 32 | 33 | 34 | if __name__ == "__main__": 35 | all_args = argparse.ArgumentParser() 36 | all_args.add_argument("-u", "--url", required=True, help="OPA service url") 37 | args = vars(all_args.parse_args()) 38 | params = {"strict-builtin-errors": "true"} 39 | 40 | def make_request(payload): 41 | response = requests.post(args["url"], data=json.dumps(payload), params=params) 42 | expected_status_code = 200 43 | assert response.status_code == expected_status_code, ( 44 | f"got {response.status_code}, expected: {expected_status_code}" 45 | ) 46 | return response.json() 47 | 48 | for subject_id in ["alice", "bob"]: 49 | try: 50 | # todo: try this out locally until it works 51 | # url = 'http://test-opa-svc:8081/v1/data' 52 | payload = {"input": {"id": subject_id}} 53 | response = make_request(payload) 54 | assertions( 55 | subject_id, 56 | response, 57 | "currentUserInfoById", 58 | [], 59 | {"e-mail": f"{subject_id}@example.com", "company": "openid"}, 60 | ) 61 | except Exception as e: 62 | print(f"exception: {e}") 63 | if response is not None: 64 | print(f"request body: {payload}") 65 | print(f"response body: {response}") 66 | raise e 67 | 68 | print("Test successful!") 69 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/getting_started/first_steps.adoc: -------------------------------------------------------------------------------- 1 | = First steps 2 | :description: Set up the OPA with Stackable operator, deploy your first policy rule, and query it from the command line in Kubernetes. 3 | :docs-policy-language: https://www.openpolicyagent.org/docs/latest/policy-language/ 4 | 5 | After you went through the xref:getting_started/installation.adoc[], on this page you deploy OPA, deploy your first rule and query it from the command line. 6 | 7 | == Deploy OPA 8 | 9 | To deploy OPA, you create an OpaCluster resource in Kubernetes and the operator creates the OPA Stacklet. 10 | Create a file called `opa.yaml` with the following contents: 11 | 12 | [source,yaml] 13 | include::example$getting_started/opa.yaml[] 14 | 15 | and apply it: 16 | 17 | [source,bash] 18 | include::example$getting_started/getting_started.sh[tag=apply-opa-cluster] 19 | 20 | This creates an OPA cluster. 21 | The operator deploys a DaemonSet, which means that an OPA Pod is deployed on every Node of the cluster. 22 | This reduces network traffic and improves latency for decision requests, since every other Pod making decision requests only has to make its request to another port on the same Node. 23 | 24 | == Deploy a policy rule 25 | 26 | Now deploy the first {docs-policy-language}[policy rule] to OPA. 27 | Rules are deployed in ConfigMaps. 28 | Create a file `simple-rule.yaml` with the following contents: 29 | 30 | [source,yaml] 31 | ---- 32 | include::example$getting_started/simple-rule.yaml[] 33 | ---- 34 | 35 | and apply it: 36 | 37 | [source,bash] 38 | include::example$getting_started/getting_started.sh[tag=apply-rule-file] 39 | 40 | The operator reads the rule file, bundles it and publishes the bundle to all OPA Pods in the cluster. 41 | 42 | == Make policy requests 43 | 44 | Now that you have deployed the rule, you can query OPA for it. 45 | First, port-forward the service so you can query it from outside the Kubernetes cluster: 46 | 47 | [source,bash] 48 | include::example$getting_started/getting_started.sh[tag=port-forwarding] 49 | 50 | Then, request the `hello` rule: 51 | 52 | [source,bash] 53 | include::example$getting_started/getting_started.sh[tag=request-hello] 54 | 55 | As it was defined in the rule file, the response should be `true`: 56 | 57 | [source,json] 58 | include::example$getting_started/expected_response_hello.json[] 59 | 60 | You can also request the other rule, `world`: 61 | 62 | [source,bash] 63 | include::example$getting_started/getting_started.sh[tag=request-world] 64 | 65 | And see a different response: 66 | 67 | [source,json] 68 | include::example$getting_started/expected_response_world.json[] 69 | 70 | Great! You've set up OPA, deployed a rule and queried it! 71 | 72 | == What's next 73 | 74 | Have a look at the xref:usage-guide/index.adoc[] page for more configuration options of the operator. 75 | -------------------------------------------------------------------------------- /tests/README-templating.md: -------------------------------------------------------------------------------- 1 | # Test Scenario Templating 2 | 3 | ## Introduction 4 | 5 | The tests in this directory are designed to be expanded into multiple test scenarios based on test dimensions that can be defined in a dimensions file. 6 | 7 | ## Defining Test Dimensions 8 | 9 | The dimensions file currently has to be named `test-definition.yaml` and reside in the same directory as the `kuttl-test.yaml.jinja2` file. 10 | 11 | An example of a minimal folder structure will be given further down in this file. 12 | 13 | An example of the content for the test definition file is shown here: 14 | 15 | ````yaml 16 | dimensions: 17 | - name: spark 18 | values: 19 | - 3.2.1 20 | - 3.2.2 21 | - 3.2.3 22 | - name: hadoop 23 | values: 24 | - 3.1.0 25 | - 3.2.0 26 | - name: aws 27 | - abc 28 | - xyz 29 | tests: 30 | - name: spark-pi-public-s3 31 | dimensions: 32 | - spark 33 | - hadoop 34 | ```` 35 | 36 | This file defines three dimensions for this test to be considered. 37 | It also defines one test case named _spark-pi-public-s3_ and the dimensions that this test case should use. 38 | In this example the test case uses only two of the three dimensions defined, so a run of this test case would be expanded into the following test structure: 39 | 40 | ````text 41 | └── spark-pi-public-s3 42 | ├── spark-3.2.1_hadoop-3.1.0 43 | ├── spark-3.2.1_hadoop-3.2.0 44 | ├── spark-3.2.2_hadoop-3.1.0 45 | ├── spark-3.2.2_hadoop-3.2.0 46 | ├── spark-3.2.3_hadoop-3.1.0 47 | └── spark-3.2.3_hadoop-3.2.0 48 | ```` 49 | 50 | The name of a test case defined under `tests` in this file has to refer back to a directory in the `templates/kuttl` directory, which will be used to create the test scenarios. 51 | 52 | Given the example of a test-definition.yaml shown above, the following folder structure would create the test scenarios shown above. 53 | 54 | ````text 55 | tests 56 | ├── kuttl-test.yaml.j2 57 | ├── templates 58 | │ └── kuttl 59 | │ └── spark-pi-public-s3 60 | └── test-definition.yaml 61 | ```` 62 | 63 | The `kuttl-test.yaml.jinja2` cannot currently be edited, as it comes from the operator templating and any changes would be overwritten again. 64 | This should be fairly easy to solve and we can look at this as soon as it becomes necessary. 65 | 66 | ## Using 67 | 68 | ### Requirements 69 | 70 | To run tests locally you need the following things installed: 71 | 72 | - python3 (version >= 3.9) 73 | - pyyaml library installed 74 | - jq 75 | 76 | ### Running 77 | 78 | To run tests please execute the following command from the gitroot of the operator repository: 79 | 80 | `scripts/run_tests.sh --parallel 2` 81 | 82 | This will expand the test templates into all defined test scenarios and execute kuttl to test these scenarios. Any arguments are passed on to `kuttl`. 83 | -------------------------------------------------------------------------------- /docs/modules/opa/pages/reference/environment-variables.adoc: -------------------------------------------------------------------------------- 1 | = Environment variables 2 | 3 | This operator accepts the following environment variables: 4 | 5 | == KUBERNETES_CLUSTER_DOMAIN 6 | 7 | *Default value*: cluster.local 8 | 9 | *Required*: false 10 | 11 | *Multiple values*: false 12 | 13 | This instructs the operator, which value it should use for the Kubernetes `clusterDomain` setting. 14 | Make sure to keep this in sync with whatever setting your cluster uses. 15 | Please see the documentation xref:guides:kubernetes-cluster-domain.adoc[on configuring the Kubernetes cluster domain] for more information on this feature. 16 | 17 | [source] 18 | ---- 19 | export KUBERNETES_CLUSTER_DOMAIN=mycluster.local 20 | cargo run -- run 21 | ---- 22 | 23 | or via docker: 24 | 25 | [source] 26 | ---- 27 | docker run \ 28 | --name opa-operator \ 29 | --network host \ 30 | --env KUBECONFIG=/home/stackable/.kube/config \ 31 | --env KUBERNETES_CLUSTER_DOMAIN=mycluster.local \ 32 | --mount type=bind,source="$HOME/.kube/config",target="/home/stackable/.kube/config" \ 33 | oci.stackable.tech/sdp/opa-operator:0.0.0-dev 34 | ---- 35 | 36 | == PRODUCT_CONFIG 37 | 38 | *Default value*: `/etc/stackable/opa-operator/config-spec/properties.yaml` 39 | 40 | *Required*: false 41 | 42 | *Multiple values*: false 43 | 44 | [source] 45 | ---- 46 | export PRODUCT_CONFIG=/foo/bar/properties.yaml 47 | stackable-opa-operator run 48 | ---- 49 | 50 | or via docker: 51 | 52 | ---- 53 | docker run \ 54 | --name opa-operator \ 55 | --network host \ 56 | --env KUBECONFIG=/home/stackable/.kube/config \ 57 | --env PRODUCT_CONFIG=/my/product/config.yaml \ 58 | --mount type=bind,source="$HOME/.kube/config",target="/home/stackable/.kube/config" \ 59 | oci.stackable.tech/sdp/opa-operator:0.0.0-dev 60 | ---- 61 | 62 | == WATCH_NAMESPACE 63 | 64 | *Default value*: All namespaces 65 | 66 | *Required*: false 67 | 68 | *Multiple values*: false 69 | 70 | The operator **only** watches for resources in the provided namespace `test`: 71 | 72 | [source] 73 | ---- 74 | export WATCH_NAMESPACE=test 75 | stackable-opa-operator run 76 | ---- 77 | 78 | or via docker: 79 | 80 | [source] 81 | ---- 82 | docker run \ 83 | --name opa-operator \ 84 | --network host \ 85 | --env KUBECONFIG=/home/stackable/.kube/config \ 86 | --env WATCH_NAMESPACE=test \ 87 | --mount type=bind,source="$HOME/.kube/config",target="/home/stackable/.kube/config" \ 88 | oci.stackable.tech/sdp/opa-operator:0.0.0-dev 89 | ---- 90 | 91 | == OPA_BUNDLE_BUILDER_CLUSTERROLE 92 | 93 | *Default value*: None. 94 | 95 | *Required*: true 96 | 97 | *Multiple values*: false 98 | 99 | The name of the cluster role to use for the OPA pods. 100 | 101 | [source] 102 | ---- 103 | export OPA_BUNDLE_BUILDER_CLUSTERROLE=test 104 | stackable-opa-operator run 105 | ---- 106 | -------------------------------------------------------------------------------- /scripts/ensure_one_trailing_newline.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given the location of a file, trims all trailing blank lines and 3 | places a single one. Used as post-processing step for README rendering. 4 | """ 5 | 6 | import re 7 | import unittest 8 | 9 | BLANK_LINE_REGEX_PATTERN = r"^\s*$" 10 | 11 | 12 | def has_trailing_newline(line): 13 | return line[-1:] == "\n" 14 | 15 | 16 | def process_lines(lines): 17 | trim_count = 0 18 | # trim trailing blank lines 19 | for line in lines[::-1]: 20 | if re.match(BLANK_LINE_REGEX_PATTERN, line): 21 | trim_count += 1 22 | else: 23 | break 24 | 25 | cutoff_index = len(lines) - trim_count 26 | new_lines = lines[:cutoff_index] 27 | 28 | # maybe add a newline character to the last sensible line 29 | if not has_trailing_newline(new_lines[-1]): 30 | new_lines[-1] = new_lines[-1] + "\n" 31 | 32 | # add a trailing blank line without newline 33 | new_lines.append("") 34 | return new_lines 35 | 36 | 37 | class TestCoreMethods(unittest.TestCase): 38 | def test_trailing_new_line(self): 39 | self.assertTrue(has_trailing_newline("something\n")) 40 | self.assertTrue(has_trailing_newline("\n")) 41 | self.assertFalse(has_trailing_newline("nope")) 42 | 43 | def test_trailing_real_line(self): 44 | lines = ["bla\n", "useful"] 45 | processed_lines = process_lines(lines) 46 | self.assertEqual(len(processed_lines), 3) 47 | self.assertTrue(has_trailing_newline(processed_lines[0])) 48 | self.assertTrue(has_trailing_newline(processed_lines[1])) 49 | self.assertFalse(has_trailing_newline(processed_lines[2])) 50 | 51 | def test_lots_of_empties(self): 52 | lines = ["bla\n", "\n", "\n", "\n", "\n"] 53 | processed_lines = process_lines(lines) 54 | self.assertEqual(len(processed_lines), 2) 55 | self.assertEqual(processed_lines[-1], "") 56 | 57 | def test_one_trailing_new_line(self): 58 | lines = ["bla\n", "\n"] 59 | processed_lines = process_lines(lines) 60 | self.assertEqual(len(processed_lines), 2) 61 | self.assertEqual(processed_lines[-1], "") 62 | 63 | def test_one_trailing_blank_line(self): 64 | lines = ["bla\n", ""] 65 | processed_lines = process_lines(lines) 66 | self.assertEqual(len(processed_lines), 2) 67 | self.assertEqual(processed_lines[-1], "") 68 | 69 | 70 | if __name__ == "__main__": 71 | # to run tests for this script: 72 | # python3 -m unittest ensure_one_trailing_newline.py 73 | 74 | import sys 75 | 76 | if len(sys.argv) != 2: 77 | print("Usage: {} filename_to_trim".format(sys.argv[0])) 78 | exit(1) 79 | 80 | file_name = sys.argv[1] 81 | 82 | lines = [] 83 | with open(file_name, "r") as f: 84 | lines = f.readlines() 85 | 86 | lines = process_lines(lines) 87 | 88 | with open(file_name, "w") as f: 89 | f.write("".join(lines)) 90 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/test-regorule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import requests 3 | import argparse 4 | import json 5 | 6 | # todo: make the test more comprehensive to check customAttributes 7 | users_and_groups = { 8 | "alice": ["/superset-admin"], 9 | "bob": [], 10 | } 11 | 12 | 13 | def assertions( 14 | username, response, opa_attribute, expected_groups, expected_attributes={} 15 | ): 16 | assert "result" in response 17 | result = response["result"] 18 | assert opa_attribute in result, f"expected {opa_attribute} in {result}" 19 | 20 | # repeated the right hand side for better output on error 21 | assert "customAttributes" in result[opa_attribute] 22 | assert "groups" in result[opa_attribute] 23 | assert "id" in result[opa_attribute] 24 | assert "username" in result[opa_attribute] 25 | 26 | # todo: split out group assertions 27 | print(f"Testing for {username} in groups {expected_groups}") 28 | groups = sorted(result[opa_attribute]["groups"]) 29 | expected_groups = sorted(expected_groups) 30 | assert groups == expected_groups, f"got {groups}, expected: {expected_groups}" 31 | 32 | # todo: split out customAttribute assertions 33 | print(f"Testing for {username} with customAttributes {expected_attributes}") 34 | custom_attributes = result[opa_attribute]["customAttributes"] 35 | assert custom_attributes == expected_attributes, ( 36 | f"got {custom_attributes}, expected: {expected_attributes}" 37 | ) 38 | 39 | 40 | if __name__ == "__main__": 41 | all_args = argparse.ArgumentParser() 42 | all_args.add_argument("-u", "--url", required=True, help="OPA service url") 43 | args = vars(all_args.parse_args()) 44 | params = {"strict-builtin-errors": "true"} 45 | 46 | def make_request(payload): 47 | response = requests.post(args["url"], data=json.dumps(payload), params=params) 48 | expected_status_code = 200 49 | assert response.status_code == expected_status_code, ( 50 | f"got {response.status_code}, expected: {expected_status_code}" 51 | ) 52 | return response.json() 53 | 54 | for username, groups in users_and_groups.items(): 55 | try: 56 | # todo: try this out locally until it works 57 | # url = 'http://test-opa-svc:8081/v1/data' 58 | payload = {"input": {"username": username}} 59 | response = make_request(payload) 60 | assertions(username, response, "currentUserInfoByUsername", groups, {}) 61 | 62 | # do the reverse lookup 63 | user_id = response["result"]["currentUserInfoByUsername"]["id"] 64 | payload = {"input": {"id": user_id}} 65 | response = make_request(payload) 66 | assertions(username, response, "currentUserInfoById", groups, {}) 67 | except Exception as e: 68 | print(f"exception: {e}") 69 | if response is not None: 70 | print(f"request body: {payload}") 71 | print(f"response body: {response}") 72 | raise e 73 | 74 | print("Test successful!") 75 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ============= 2 | # This file is automatically generated from the templates in stackabletech/operator-templating 3 | # DO NOT MANUALLY EDIT THIS FILE 4 | # ============= 5 | 6 | # This script requires https://github.com/mikefarah/yq (not to be confused with https://github.com/kislyuk/yq) 7 | # It is available from Nixpkgs as `yq-go` (`nix shell nixpkgs#yq-go`) 8 | # This script also requires `jq` https://stedolan.github.io/jq/ 9 | 10 | .PHONY: build publish 11 | 12 | OPERATOR_NAME := opa-operator 13 | VERSION := $(shell cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="stackable-${OPERATOR_NAME}") | .version') 14 | 15 | OCI_REGISTRY_HOSTNAME := oci.stackable.tech 16 | OCI_REGISTRY_PROJECT_IMAGES := sdp 17 | 18 | SHELL=/usr/bin/env bash -euo pipefail 19 | 20 | render-readme: 21 | scripts/render_readme.sh 22 | 23 | render-docs: 24 | scripts/docs_templating.sh 25 | 26 | ## Docker related targets 27 | docker-build: 28 | docker build --force-rm --build-arg VERSION=${VERSION} -t "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}" -f docker/Dockerfile . 29 | 30 | ## Chart related targets 31 | compile-chart: version crds config 32 | 33 | chart-clean: 34 | rm -rf "deploy/helm/${OPERATOR_NAME}/configs" 35 | rm -rf "deploy/helm/${OPERATOR_NAME}/crds" 36 | 37 | version: 38 | cat "deploy/helm/${OPERATOR_NAME}/Chart.yaml" | yq ".version = \"${VERSION}\" | .appVersion = \"${VERSION}\"" > "deploy/helm/${OPERATOR_NAME}/Chart.yaml.new" 39 | mv "deploy/helm/${OPERATOR_NAME}/Chart.yaml.new" "deploy/helm/${OPERATOR_NAME}/Chart.yaml" 40 | 41 | config: 42 | if [ -d "deploy/config-spec/" ]; then\ 43 | mkdir -p "deploy/helm/${OPERATOR_NAME}/configs";\ 44 | cp -r deploy/config-spec/* "deploy/helm/${OPERATOR_NAME}/configs";\ 45 | fi 46 | 47 | crds: 48 | mkdir -p deploy/helm/"${OPERATOR_NAME}"/crds 49 | cargo run --bin stackable-"${OPERATOR_NAME}" -- crd | yq eval '.metadata.annotations["helm.sh/resource-policy"]="keep"' - > "deploy/helm/${OPERATOR_NAME}/crds/crds.yaml" 50 | 51 | chart-lint: compile-chart 52 | docker run -it -v $(shell pwd):/build/helm-charts -w /build/helm-charts quay.io/helmpack/chart-testing:v3.5.0 ct lint --config deploy/helm/ct.yaml 53 | 54 | clean: chart-clean 55 | cargo clean 56 | docker rmi --force '${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}' 57 | 58 | regenerate-charts: chart-clean compile-chart 59 | 60 | regenerate-nix: 61 | nix run --extra-experimental-features "nix-command flakes" -f . regenerateNixLockfiles 62 | 63 | build: regenerate-charts regenerate-nix docker-build 64 | 65 | check-nix: 66 | @which nix || (echo "Error: 'nix' is not installed. Please install it to proceed."; exit 1) 67 | 68 | check-kubernetes: 69 | @kubectl cluster-info > /dev/null 2>&1 || (echo "Error: Kubernetes is not running or kubectl is not properly configured."; exit 1) 70 | 71 | run-dev: check-nix check-kubernetes 72 | kubectl apply -f deploy/stackable-operators-ns.yaml 73 | nix run --extra-experimental-features "nix-command flakes" -f. tilt -- up --port 5439 --namespace stackable-operators 74 | 75 | stop-dev: check-nix check-kubernetes 76 | nix run --extra-experimental-features "nix-command flakes" -f. tilt -- down 77 | -------------------------------------------------------------------------------- /tests/templates/kuttl/aas-user-info/02-install-aas.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: aas-config 6 | data: 7 | aas.py: | 8 | from http.server import BaseHTTPRequestHandler, HTTPServer 9 | from urllib.parse import urlparse, parse_qs 10 | import json 11 | 12 | class MockServiceHandler(BaseHTTPRequestHandler): 13 | def _set_headers(self, status_code=200): 14 | self.send_response(status_code) 15 | self.send_header('Content-type', 'application/json') 16 | self.end_headers() 17 | 18 | def do_GET(self): 19 | print("received a get request: ", self.path) 20 | parsed_path = urlparse(self.path) 21 | path = parsed_path.path 22 | 23 | if path == '/cip/claims': 24 | query_components = parse_qs(parsed_path.query) 25 | sub = query_components.get('sub', [''])[0] 26 | scope = query_components.get('scope', [''])[0] 27 | 28 | if not sub or not scope: 29 | self._set_headers(400) 30 | self.wfile.write(json.dumps({'error': 'Both "sub" and "scope" parameters are required.'}).encode('utf-8')) 31 | return 32 | 33 | claims = { 34 | 'sub': sub, 35 | 'e-mail': f"{sub}@example.com", 36 | 'company': scope 37 | } 38 | 39 | self._set_headers() 40 | self.wfile.write(json.dumps(claims).encode('utf-8')) 41 | else: 42 | self._set_headers(404) 43 | self.wfile.write(json.dumps({'error': 'Endpoint not found'}).encode('utf-8')) 44 | 45 | def run(server_class=HTTPServer, handler_class=MockServiceHandler, port=5000): 46 | server_address = ('', port) 47 | httpd = server_class(server_address, handler_class) 48 | print(f"Starting mock server on port {port}...") 49 | httpd.serve_forever() 50 | 51 | if __name__ == "__main__": 52 | run() 53 | --- 54 | apiVersion: apps/v1 55 | kind: Deployment 56 | metadata: 57 | name: aas 58 | spec: 59 | replicas: 1 60 | selector: 61 | matchLabels: 62 | app: aas 63 | template: 64 | metadata: 65 | labels: 66 | app: aas 67 | spec: 68 | serviceAccountName: test-sa 69 | containers: 70 | - name: python 71 | image: python:3.9 72 | command: ["python", "/aas.py"] 73 | ports: 74 | - containerPort: 5000 75 | resources: 76 | limits: 77 | cpu: 1 78 | memory: 512Mi 79 | requests: 80 | cpu: 500m 81 | memory: 512Mi 82 | volumeMounts: 83 | - name: app-volume 84 | mountPath: /aas.py 85 | subPath: aas.py 86 | volumes: 87 | - name: app-volume 88 | configMap: 89 | name: aas-config 90 | --- 91 | apiVersion: v1 92 | kind: Service 93 | metadata: 94 | name: aas 95 | spec: 96 | selector: 97 | app: aas 98 | ports: 99 | - protocol: TCP 100 | port: 5000 101 | targetPort: 5000 102 | type: ClusterIP 103 | -------------------------------------------------------------------------------- /rust/user-info-fetcher/src/utils/tls.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Cursor, path::Path}; 2 | 3 | use snafu::{ResultExt as _, Snafu}; 4 | use stackable_operator::commons::tls_verification::TlsClientDetails; 5 | use tokio::{fs::File, io::AsyncReadExt}; 6 | 7 | #[derive(Debug, Snafu)] 8 | pub enum Error { 9 | #[snafu(display("failed to read ca certificates"))] 10 | ReadCaBundle { source: std::io::Error }, 11 | 12 | #[snafu(display("failed to parse ca certificates (via reqwest)"))] 13 | ParseCaBundleReqwest { source: reqwest::Error }, 14 | 15 | #[snafu(display("failed to split ca certificate bundle"))] 16 | SplitCaBundle { source: std::io::Error }, 17 | 18 | #[snafu(display("failed to parse ca certificate (via native_tls)"))] 19 | ParseCaCertNativeTls { source: native_tls::Error }, 20 | 21 | #[snafu(display("failed to build native_tls connector"))] 22 | BuildNativeTlsConnector { source: native_tls::Error }, 23 | } 24 | 25 | /// Configures a [`reqwest`] client according to the specified TLS configuration 26 | // NOTE: MUST be kept in sync with all configure_* functions 27 | pub async fn configure_reqwest( 28 | tls: &TlsClientDetails, 29 | builder: reqwest::ClientBuilder, 30 | ) -> Result { 31 | Ok(if tls.uses_tls() && !tls.uses_tls_verification() { 32 | builder.danger_accept_invalid_certs(true) 33 | } else if let Some(tls_ca_cert_mount_path) = tls.tls_ca_cert_mount_path() { 34 | reqwest::Certificate::from_pem_bundle( 35 | &read_file(&tls_ca_cert_mount_path) 36 | .await 37 | .context(ReadCaBundleSnafu)?, 38 | ) 39 | .context(ParseCaBundleReqwestSnafu)? 40 | .into_iter() 41 | .fold( 42 | builder.tls_built_in_root_certs(false), 43 | reqwest::ClientBuilder::add_root_certificate, 44 | ) 45 | } else { 46 | builder 47 | }) 48 | } 49 | 50 | /// Configures a [`native_tls`] connector according to the specified TLS configuration 51 | // NOTE: MUST be kept in sync with all configure_* functions 52 | pub async fn configure_native_tls( 53 | tls: &TlsClientDetails, 54 | ) -> Result { 55 | let mut builder = native_tls::TlsConnector::builder(); 56 | if tls.uses_tls() && !tls.uses_tls_verification() { 57 | builder.danger_accept_invalid_certs(true); 58 | } else if let Some(tls_ca_cert_mount_path) = tls.tls_ca_cert_mount_path() { 59 | builder.disable_built_in_roots(true); 60 | // native-tls doesn't support parsing CA *bundles*, so split them using rustls first 61 | for ca_cert in rustls_pemfile::certs(&mut Cursor::new( 62 | read_file(&tls_ca_cert_mount_path) 63 | .await 64 | .context(ReadCaBundleSnafu)?, 65 | )) { 66 | builder.add_root_certificate( 67 | native_tls::Certificate::from_der(&ca_cert.context(SplitCaBundleSnafu)?) 68 | .context(ParseCaCertNativeTlsSnafu)?, 69 | ); 70 | } 71 | } 72 | builder.build().context(BuildNativeTlsConnectorSnafu) 73 | } 74 | 75 | async fn read_file(path: &impl AsRef) -> Result, std::io::Error> { 76 | let mut buf = Vec::::new(); 77 | File::open(path).await?.read_to_end(&mut buf).await?; 78 | Ok(buf) 79 | } 80 | -------------------------------------------------------------------------------- /deploy/helm/opa-operator/templates/roles.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ include "operator.fullname" . }}-clusterrole 6 | labels: 7 | {{- include "operator.labels" . | nindent 4 }} 8 | rules: 9 | - apiGroups: 10 | - "" 11 | resources: 12 | - nodes 13 | verbs: 14 | - list 15 | - watch 16 | # For automatic cluster domain detection 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - nodes/proxy 21 | verbs: 22 | - get 23 | - apiGroups: 24 | - "" 25 | resources: 26 | - pods 27 | - configmaps 28 | - secrets 29 | - services 30 | - endpoints 31 | - serviceaccounts 32 | verbs: 33 | - create 34 | - delete 35 | - get 36 | - list 37 | - patch 38 | - update 39 | - watch 40 | - apiGroups: 41 | - rbac.authorization.k8s.io 42 | resources: 43 | - rolebindings 44 | verbs: 45 | - create 46 | - delete 47 | - get 48 | - list 49 | - patch 50 | - update 51 | - watch 52 | - apiGroups: 53 | - apps 54 | resources: 55 | - daemonsets 56 | verbs: 57 | - get 58 | - create 59 | - delete 60 | - list 61 | - patch 62 | - update 63 | - watch 64 | - apiGroups: 65 | - batch 66 | resources: 67 | - jobs 68 | verbs: 69 | - create 70 | - get 71 | - list 72 | - patch 73 | - update 74 | - watch 75 | - apiGroups: 76 | - apiextensions.k8s.io 77 | resources: 78 | - customresourcedefinitions 79 | verbs: 80 | - get 81 | - apiGroups: 82 | - events.k8s.io 83 | resources: 84 | - events 85 | verbs: 86 | - create 87 | - patch 88 | - apiGroups: 89 | - {{ include "operator.name" . }}.stackable.tech 90 | resources: 91 | - {{ include "operator.name" . }}clusters 92 | verbs: 93 | - get 94 | - list 95 | - patch 96 | - watch 97 | - apiGroups: 98 | - {{ include "operator.name" . }}.stackable.tech 99 | resources: 100 | - {{ include "operator.name" . }}clusters/status 101 | verbs: 102 | - patch 103 | - apiGroups: 104 | - rbac.authorization.k8s.io 105 | resources: 106 | - clusterroles 107 | verbs: 108 | - bind 109 | resourceNames: 110 | - {{ include "operator.name" . }}-clusterrole 111 | 112 | --- 113 | apiVersion: rbac.authorization.k8s.io/v1 114 | kind: ClusterRole 115 | metadata: 116 | name: {{ include "operator.name" . }}-clusterrole 117 | labels: 118 | {{- include "operator.labels" . | nindent 4 }} 119 | rules: 120 | - apiGroups: 121 | - "" 122 | resources: 123 | - configmaps 124 | - secrets 125 | - serviceaccounts 126 | verbs: 127 | - get 128 | - list 129 | - watch 130 | - apiGroups: 131 | - events.k8s.io 132 | resources: 133 | - events 134 | verbs: 135 | - create 136 | - patch 137 | {{ if .Capabilities.APIVersions.Has "security.openshift.io/v1" }} 138 | - apiGroups: 139 | - security.openshift.io 140 | resources: 141 | - securitycontextconstraints 142 | resourceNames: 143 | - nonroot-v2 144 | verbs: 145 | - use 146 | {{ end }} 147 | -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/getting_started.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # DO NOT EDIT THE SCRIPT 5 | # Instead, update the j2 template, and regenerate it for dev with `make render-docs`. 6 | 7 | # This script contains all the code snippets from the guide, as well as some assert tests 8 | # to test if the instructions in the guide work. The user *could* use it, but it is intended 9 | # for testing only. 10 | # The script will install the operator, create an OPA instance and briefly open a port 11 | # forward and query the OPA. 12 | # No running processes are left behind (i.e. the port-forwarding is closed at the end) 13 | 14 | if [ $# -eq 0 ] 15 | then 16 | echo "Installation method argument ('helm' or 'stackablectl') required." 17 | exit 1 18 | fi 19 | 20 | case "$1" in 21 | "helm") 22 | echo "Installing operators with Helm" 23 | # tag::helm-install-operators[] 24 | helm install --wait opa-operator oci://oci.stackable.tech/sdp-charts/opa-operator --version 0.0.0-dev 25 | # end::helm-install-operators[] 26 | ;; 27 | "stackablectl") 28 | echo "installing operators with stackablectl" 29 | # tag::stackablectl-install-operators[] 30 | stackablectl operator install opa=0.0.0-dev 31 | # end::stackablectl-install-operators[] 32 | ;; 33 | *) 34 | echo "Need to provide 'helm' or 'stackablectl' as an argument for which installation method to use!" 35 | exit 1 36 | ;; 37 | esac 38 | 39 | echo "Creating OPA cluster" 40 | # tag::apply-opa-cluster[] 41 | kubectl apply -f opa.yaml 42 | # end::apply-opa-cluster[] 43 | 44 | sleep 15 45 | 46 | echo "Waiting on rollout ..." 47 | kubectl rollout status --watch --timeout=5m daemonset/simple-opa-server-default 48 | 49 | echo "Applying the rule file" 50 | # tag::apply-rule-file[] 51 | kubectl apply -f simple-rule.yaml 52 | # end::apply-rule-file[] 53 | 54 | # The bundle builder will update the bundle almost immediately, but OPA can take up to 55 | # max_delay_seconds: 20 (see ConfigMap) 56 | # to poll the bundle 57 | sleep 21 58 | 59 | echo "Starting port-forwarding of port 8081" 60 | # tag::port-forwarding[] 61 | kubectl port-forward svc/simple-opa-server 8081 > /dev/null 2>&1 & 62 | # end::port-forwarding[] 63 | PORT_FORWARD_PID=$! 64 | # shellcheck disable=SC2064 65 | trap "kill $PORT_FORWARD_PID" EXIT 66 | sleep 5 67 | 68 | request_hello() { 69 | # tag::request-hello[] 70 | curl -s http://localhost:8081/v1/data/test/hello -d '{"input": {}}' 71 | # end::request-hello[] 72 | } 73 | 74 | echo "Checking policy decision for 'hello' rule ..." 75 | test_hello=$(request_hello) 76 | if [ "$test_hello" == "$(cat expected_response_hello.json)" ]; then 77 | echo "The 'hello' rule returned the correct response!" 78 | else 79 | echo "The 'hello' rule returned an incorrect response." 80 | echo "Received: $test_hello" 81 | echo "Expected: $(cat expected_response_hello.json)" 82 | exit 1 83 | fi 84 | 85 | request_world() { 86 | # tag::request-world[] 87 | curl -s http://localhost:8081/v1/data/test/world -d '{"input": {}}' 88 | # end::request-world[] 89 | } 90 | 91 | echo "Checking policy decision for 'world' rule ..." 92 | test_world=$(request_world) 93 | if [ "$test_world" == "$(cat expected_response_world.json)" ]; then 94 | echo "The 'world' rule returned the correct response!" 95 | else 96 | echo "The 'world' rule returned an incorrect response." 97 | echo "Received: $test_world" 98 | echo "Expected: $(cat expected_response_world.json)" 99 | exit 1 100 | fi 101 | -------------------------------------------------------------------------------- /docs/modules/opa/examples/getting_started/getting_started.sh.j2: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # DO NOT EDIT THE SCRIPT 5 | # Instead, update the j2 template, and regenerate it for dev with `make render-docs`. 6 | 7 | # This script contains all the code snippets from the guide, as well as some assert tests 8 | # to test if the instructions in the guide work. The user *could* use it, but it is intended 9 | # for testing only. 10 | # The script will install the operator, create an OPA instance and briefly open a port 11 | # forward and query the OPA. 12 | # No running processes are left behind (i.e. the port-forwarding is closed at the end) 13 | 14 | if [ $# -eq 0 ] 15 | then 16 | echo "Installation method argument ('helm' or 'stackablectl') required." 17 | exit 1 18 | fi 19 | 20 | case "$1" in 21 | "helm") 22 | echo "Installing operators with Helm" 23 | # tag::helm-install-operators[] 24 | helm install --wait opa-operator oci://{{ helm.repo_url }}/{{ helm.repo_name }}/opa-operator --version {{ versions.opa }} 25 | # end::helm-install-operators[] 26 | ;; 27 | "stackablectl") 28 | echo "installing operators with stackablectl" 29 | # tag::stackablectl-install-operators[] 30 | stackablectl operator install opa={{ versions.opa }} 31 | # end::stackablectl-install-operators[] 32 | ;; 33 | *) 34 | echo "Need to provide 'helm' or 'stackablectl' as an argument for which installation method to use!" 35 | exit 1 36 | ;; 37 | esac 38 | 39 | echo "Creating OPA cluster" 40 | # tag::apply-opa-cluster[] 41 | kubectl apply -f opa.yaml 42 | # end::apply-opa-cluster[] 43 | 44 | sleep 15 45 | 46 | echo "Waiting on rollout ..." 47 | kubectl rollout status --watch --timeout=5m daemonset/simple-opa-server-default 48 | 49 | echo "Applying the rule file" 50 | # tag::apply-rule-file[] 51 | kubectl apply -f simple-rule.yaml 52 | # end::apply-rule-file[] 53 | 54 | # The bundle builder will update the bundle almost immediately, but OPA can take up to 55 | # max_delay_seconds: 20 (see ConfigMap) 56 | # to poll the bundle 57 | sleep 21 58 | 59 | echo "Starting port-forwarding of port 8081" 60 | # tag::port-forwarding[] 61 | kubectl port-forward svc/simple-opa-server 8081 > /dev/null 2>&1 & 62 | # end::port-forwarding[] 63 | PORT_FORWARD_PID=$! 64 | # shellcheck disable=SC2064 65 | trap "kill $PORT_FORWARD_PID" EXIT 66 | sleep 5 67 | 68 | request_hello() { 69 | # tag::request-hello[] 70 | curl -s http://localhost:8081/v1/data/test/hello -d '{"input": {}}' 71 | # end::request-hello[] 72 | } 73 | 74 | echo "Checking policy decision for 'hello' rule ..." 75 | test_hello=$(request_hello) 76 | if [ "$test_hello" == "$(cat expected_response_hello.json)" ]; then 77 | echo "The 'hello' rule returned the correct response!" 78 | else 79 | echo "The 'hello' rule returned an incorrect response." 80 | echo "Received: $test_hello" 81 | echo "Expected: $(cat expected_response_hello.json)" 82 | exit 1 83 | fi 84 | 85 | request_world() { 86 | # tag::request-world[] 87 | curl -s http://localhost:8081/v1/data/test/world -d '{"input": {}}' 88 | # end::request-world[] 89 | } 90 | 91 | echo "Checking policy decision for 'world' rule ..." 92 | test_world=$(request_world) 93 | if [ "$test_world" == "$(cat expected_response_world.json)" ]; then 94 | echo "The 'world' rule returned the correct response!" 95 | else 96 | echo "The 'world' rule returned an incorrect response." 97 | echo "Received: $test_world" 98 | echo "Expected: $(cat expected_response_world.json)" 99 | exit 1 100 | fi 101 | -------------------------------------------------------------------------------- /tests/templates/kuttl/keycloak-user-info/04-keycloak-realm-cm.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: user-info-fetcher-client-credentials 6 | stringData: 7 | clientId: user-info-fetcher 8 | clientSecret: user-info-fetcher-client-secret 9 | --- 10 | apiVersion: v1 11 | kind: ConfigMap 12 | metadata: 13 | name: keycloak-my-dataspace-realm 14 | data: 15 | realm.json: | 16 | { 17 | "realm" : "my-dataspace", 18 | "enabled" : true, 19 | "groups" : [ { 20 | "name" : "superset-admin", 21 | "path" : "/superset-admin" 22 | } ], 23 | "users" : [ { 24 | "username" : "alice", 25 | "enabled" : true, 26 | "emailVerified" : true, 27 | "firstName" : "Alice", 28 | "lastName" : "Adams", 29 | "email" : "alice@example.org", 30 | "credentials" : [ { 31 | "type" : "password", 32 | "userLabel" : "My password", 33 | "secretData" : "{\"value\":\"hogrQRLTAPBws9RgxZF/d3+EvPvUc7AN1egnmMnuWBQ=\",\"salt\":\"DYkgbXwZ2uhvJ+k94Xr7lg==\",\"additionalParameters\":{}}", 34 | "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" 35 | } ], 36 | "realmRoles" : [ "default-roles-my-dataspace" ], 37 | "groups" : [ "/superset-admin" ] 38 | }, { 39 | "username" : "bob", 40 | "enabled" : true, 41 | "emailVerified" : true, 42 | "firstName" : "Bob", 43 | "lastName" : "Bush", 44 | "email" : "bob@example.org", 45 | "credentials" : [ { 46 | "type" : "password", 47 | "userLabel" : "My password", 48 | "secretData" : "{\"value\":\"FC3TRP//H5izxRRQsYnBDucCI65OVxMy4GgG3qyl/Ek=\",\"salt\":\"kcwgkKFSJ83xlwDtOACoZQ==\",\"additionalParameters\":{}}", 49 | "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" 50 | } ], 51 | "realmRoles" : [ "default-roles-my-dataspace" ], 52 | "groups" : [ ] 53 | }, { 54 | "username" : "service-account-user-info-fetcher", 55 | "enabled" : true, 56 | "totp" : false, 57 | "emailVerified" : false, 58 | "serviceAccountClientId" : "user-info-fetcher", 59 | "credentials" : [ ], 60 | "disableableCredentialTypes" : [ ], 61 | "requiredActions" : [ ], 62 | "realmRoles" : [ "default-roles-my-dataspace" ], 63 | "clientRoles" : { 64 | "realm-management" : [ 65 | "view-users" 66 | ] 67 | }, 68 | "notBefore" : 0, 69 | "groups" : [ ] 70 | } ], 71 | "clients" : [ { 72 | "clientId" : "${USER_INFO_FETCHER_CLIENT_ID}", 73 | "surrogateAuthRequired" : false, 74 | "enabled" : true, 75 | "alwaysDisplayInConsole" : false, 76 | "clientAuthenticatorType" : "client-secret", 77 | "secret" : "${USER_INFO_FETCHER_CLIENT_SECRET}", 78 | "redirectUris" : [ "/*" ], 79 | "webOrigins" : [ "/*" ], 80 | "notBefore" : 0, 81 | "bearerOnly" : false, 82 | "serviceAccountsEnabled" : true, 83 | "publicClient" : false, 84 | "frontchannelLogout" : true, 85 | "protocol" : "openid-connect", 86 | "attributes" : { 87 | "oidc.ciba.grant.enabled" : "true", 88 | "oauth2.device.authorization.grant.enabled" : "false" 89 | }, 90 | "authenticationFlowBindingOverrides" : { }, 91 | "fullScopeAllowed" : true 92 | } ] 93 | } 94 | -------------------------------------------------------------------------------- /.readme/partials/borrowed/footer.md.j2: -------------------------------------------------------------------------------- 1 | 2 | ## About The Stackable Data Platform 3 | 4 | This operator is written and maintained by [Stackable](https://stackable.tech) and it is part of a larger data platform. 5 | 6 | ![Stackable Data Platform Overview](./.readme/static/borrowed/stackable_overview.png) 7 | 8 | Stackable makes it easy to operate data applications in any Kubernetes cluster. 9 | 10 | The data platform offers many operators, new ones being added continuously. All our operators are designed and built to be easily interconnected and to be consistent to work with. 11 | 12 | The [Stackable GmbH](https://stackable.tech/) is the company behind the Stackable Data Platform. Offering professional services, paid support plans and custom development. 13 | 14 | We love open-source! 15 | 16 | ## Supported Platforms 17 | 18 | We develop and test our operators on the following cloud platforms: 19 | 20 | * AKS on Microsoft Azure 21 | * EKS on Amazon Web Services (AWS) 22 | * GKE on Google Cloud Platform (GCP) 23 | * [IONOS Cloud Managed Kubernetes](https://cloud.ionos.com/managed/kubernetes) 24 | * K3s 25 | * Kubernetes (for an up to date list of supported versions please check the release notes in our [docs](https://docs.stackable.tech)) 26 | * Red Hat OpenShift 27 | 28 | ## Other Operators 29 | 30 | These are the operators that are currently part of the Stackable Data Platform: 31 | 32 | * [Stackable Operator for Apache Airflow](https://github.com/stackabletech/airflow-operator) 33 | * [Stackable Operator for Apache Druid](https://github.com/stackabletech/druid-operator) 34 | * [Stackable Operator for Apache HBase](https://github.com/stackabletech/hbase-operator) 35 | * [Stackable Operator for Apache Hadoop HDFS](https://github.com/stackabletech/hdfs-operator) 36 | * [Stackable Operator for Apache Hive](https://github.com/stackabletech/hive-operator) 37 | * [Stackable Operator for Apache Kafka](https://github.com/stackabletech/kafka-operator) 38 | * [Stackable Operator for Apache NiFi](https://github.com/stackabletech/nifi-operator) 39 | * [Stackable Operator for OpenSearch](https://github.com/stackabletech/opensearch-operator) 40 | * [Stackable Operator for Apache Spark](https://github.com/stackabletech/spark-k8s-operator) 41 | * [Stackable Operator for Apache Superset](https://github.com/stackabletech/superset-operator) 42 | * [Stackable Operator for Trino](https://github.com/stackabletech/trino-operator) 43 | * [Stackable Operator for Apache ZooKeeper](https://github.com/stackabletech/zookeeper-operator) 44 | 45 | And our internal operators: 46 | 47 | * [Commons Operator](https://github.com/stackabletech/commons-operator) 48 | * [Listener Operator](https://github.com/stackabletech/listener-operator) 49 | * [OpenPolicyAgent Operator](https://github.com/stackabletech/opa-operator) 50 | * [Secret Operator](https://github.com/stackabletech/secret-operator) 51 | 52 | ## Contributing 53 | 54 | Contributions are welcome. 55 | Follow our [Contributors Guide](https://docs.stackable.tech/home/stable/contributor/index.html) to learn how you can contribute. 56 | All contributors will have to sign a [Contributor License Agreement](https://github.com/stackabletech/.github/blob/main/cla.md). 57 | This is enforced automatically when you submit a Pull Request where a bot will guide you through the process. 58 | 59 | ## License 60 | 61 | [Open Software License version 3.0](./LICENSE). 62 | 63 | ## Support 64 | 65 | Get started with the community edition! If you want professional support, [we offer subscription plans and custom licensing](https://stackable.tech/en/plans/). 66 | 67 | ## Sponsor 68 | 69 | If you want to support our work but don't need professional support please consider [sponsoring](https://github.com/sponsors/stackabletech) our work. 70 | --------------------------------------------------------------------------------