From 74b821ab3a0f84dfe9b7e13258935bb8ad9dc732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 19 Mar 2025 10:43:02 +0100 Subject: [PATCH 1/7] Use service scope for user-info-fetcher AD kerberos keytab Fixes #680 --- rust/operator-binary/src/controller.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 1f1d9458..b47ee8c6 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -747,6 +747,7 @@ fn build_server_rolegroup_daemonset( user_info_fetcher_image: &str, service_account: &ServiceAccount, ) -> Result { + let opa_name = opa.metadata.name.as_deref().context(NoNameSnafu)?; let role = opa.role(opa_role); let role_group = opa .rolegroup(rolegroup_ref) @@ -979,9 +980,9 @@ fn build_server_rolegroup_daemonset( SecretClassVolume::new( ad.kerberos_secret_class_name.clone(), Some(SecretClassVolumeScope { - pod: true, - node: true, - services: Vec::new(), + pod: false, + node: false, + services: vec![opa_name.to_string()], listener_volumes: Vec::new(), }), ) From 63e223a7a242567be5ce435bd3f20107354ae921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 19 Mar 2025 10:47:29 +0100 Subject: [PATCH 2/7] Add ad-user-info test suite that I apparently forgot to commit before Disabled for now, since it still requires some manual setup, but at least it's a starting point... --- .../kuttl/ad-user-info/00-limit-range.yaml | 11 +++ .../kuttl/ad-user-info/00-patch-ns.yaml.j2 | 9 +++ .../kuttl/ad-user-info/01-assert.yaml.j2 | 10 +++ ...tor-aggregator-discovery-configmap.yaml.j2 | 9 +++ .../kuttl/ad-user-info/10-assert.yaml | 8 ++ .../kuttl/ad-user-info/10-install-opa.yaml.j2 | 69 +++++++++++++++++ .../kuttl/ad-user-info/20-assert.yaml | 14 ++++ .../20-install-test-regorule.yaml | 29 +++++++ .../kuttl/ad-user-info/30-assert.yaml | 7 ++ .../30-prepare-test-regorule.yaml | 5 ++ .../kuttl/ad-user-info/test-regorule.py | 75 +++++++++++++++++++ tests/test-definition.yaml | 6 ++ 12 files changed, 252 insertions(+) create mode 100644 tests/templates/kuttl/ad-user-info/00-limit-range.yaml create mode 100644 tests/templates/kuttl/ad-user-info/00-patch-ns.yaml.j2 create mode 100644 tests/templates/kuttl/ad-user-info/01-assert.yaml.j2 create mode 100644 tests/templates/kuttl/ad-user-info/01-install-vector-aggregator-discovery-configmap.yaml.j2 create mode 100644 tests/templates/kuttl/ad-user-info/10-assert.yaml create mode 100644 tests/templates/kuttl/ad-user-info/10-install-opa.yaml.j2 create mode 100644 tests/templates/kuttl/ad-user-info/20-assert.yaml create mode 100644 tests/templates/kuttl/ad-user-info/20-install-test-regorule.yaml create mode 100644 tests/templates/kuttl/ad-user-info/30-assert.yaml create mode 100644 tests/templates/kuttl/ad-user-info/30-prepare-test-regorule.yaml create mode 100755 tests/templates/kuttl/ad-user-info/test-regorule.py diff --git a/tests/templates/kuttl/ad-user-info/00-limit-range.yaml b/tests/templates/kuttl/ad-user-info/00-limit-range.yaml new file mode 100644 index 00000000..7b6cb30e --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/00-limit-range.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: LimitRange +metadata: + name: limit-request-ratio +spec: + limits: + - type: "Container" + maxLimitRequestRatio: + cpu: 5 + memory: 1 diff --git a/tests/templates/kuttl/ad-user-info/00-patch-ns.yaml.j2 b/tests/templates/kuttl/ad-user-info/00-patch-ns.yaml.j2 new file mode 100644 index 00000000..67185acf --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/00-patch-ns.yaml.j2 @@ -0,0 +1,9 @@ +{% if test_scenario['values']['openshift'] == 'true' %} +# see https://github.com/stackabletech/issues/issues/566 +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' + timeout: 120 +{% endif %} diff --git a/tests/templates/kuttl/ad-user-info/01-assert.yaml.j2 b/tests/templates/kuttl/ad-user-info/01-assert.yaml.j2 new file mode 100644 index 00000000..50b1d4c3 --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/01-assert.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +{% endif %} diff --git a/tests/templates/kuttl/ad-user-info/01-install-vector-aggregator-discovery-configmap.yaml.j2 b/tests/templates/kuttl/ad-user-info/01-install-vector-aggregator-discovery-configmap.yaml.j2 new file mode 100644 index 00000000..2d6a0df5 --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/01-install-vector-aggregator-discovery-configmap.yaml.j2 @@ -0,0 +1,9 @@ +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +data: + ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} +{% endif %} diff --git a/tests/templates/kuttl/ad-user-info/10-assert.yaml b/tests/templates/kuttl/ad-user-info/10-assert.yaml new file mode 100644 index 00000000..819abc51 --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/10-assert.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +metadata: + name: install-opa +timeout: 300 +commands: + - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa --timeout 301s diff --git a/tests/templates/kuttl/ad-user-info/10-install-opa.yaml.j2 b/tests/templates/kuttl/ad-user-info/10-install-opa.yaml.j2 new file mode 100644 index 00000000..c0ef848a --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/10-install-opa.yaml.j2 @@ -0,0 +1,69 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + kubectl apply -n $NAMESPACE -f - < 0 %} + custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['opa-latest'] }}" +{% endif %} + pullPolicy: IfNotPresent + clusterConfig: + userInfo: + backend: + experimentalActiveDirectory: + ldapServer: sble-addc.sble.test + baseDistinguishedName: DC=sble,DC=test + customAttributeMappings: + country: c + kerberosSecretClassName: kerberos-ad + tls: + verification: + server: + caCert: + secretClass: tls-ad + cache: # optional, enabled by default + entryTimeToLive: 60s # optional, defaults to 60s +{% if lookup('env', 'VECTOR_AGGREGATOR') %} + vectorAggregatorConfigMapName: vector-aggregator-discovery +{% endif %} + servers: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + podOverrides: + spec: + volumes: + - name: kerberos + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/scope: service=opa + roleGroups: + default: {} diff --git a/tests/templates/kuttl/ad-user-info/20-assert.yaml b/tests/templates/kuttl/ad-user-info/20-assert.yaml new file mode 100644 index 00000000..7912d1c5 --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/20-assert.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +metadata: + name: test-regorule +timeout: 300 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: test-regorule +status: + readyReplicas: 1 + replicas: 1 diff --git a/tests/templates/kuttl/ad-user-info/20-install-test-regorule.yaml b/tests/templates/kuttl/ad-user-info/20-install-test-regorule.yaml new file mode 100644 index 00000000..f6770ed2 --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/20-install-test-regorule.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: test-regorule + labels: + app: test-regorule +spec: + replicas: 1 + selector: + matchLabels: + app: test-regorule + template: + metadata: + labels: + app: test-regorule + spec: + containers: + - name: test-regorule + image: docker.stackable.tech/stackable/testing-tools:0.2.0-stackable0.0.0-dev + stdin: true + tty: true + resources: + requests: + memory: "128Mi" + cpu: "512m" + limits: + memory: "128Mi" + cpu: "1" diff --git a/tests/templates/kuttl/ad-user-info/30-assert.yaml b/tests/templates/kuttl/ad-user-info/30-assert.yaml new file mode 100644 index 00000000..0d0ef28f --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/30-assert.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +metadata: + name: test-regorule +commands: + - script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-server-default:8081/v1/data/test' diff --git a/tests/templates/kuttl/ad-user-info/30-prepare-test-regorule.yaml b/tests/templates/kuttl/ad-user-info/30-prepare-test-regorule.yaml new file mode 100644 index 00000000..4cf60f6a --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/30-prepare-test-regorule.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: +- script: kubectl cp -n $NAMESPACE ./test-regorule.py test-regorule-0:/tmp diff --git a/tests/templates/kuttl/ad-user-info/test-regorule.py b/tests/templates/kuttl/ad-user-info/test-regorule.py new file mode 100755 index 00000000..2bb3d2f3 --- /dev/null +++ b/tests/templates/kuttl/ad-user-info/test-regorule.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +import requests +import argparse +import json + +# todo: make the test more comprehensive to check customAttributes +users_and_groups = { + "alice@sble.test": ["CN=Superset Admins,CN=Users,DC=sble,DC=test", "CN=Domain Users,CN=Users,DC=sble,DC=test", "CN=Users,CN=Builtin,DC=sble,DC=test"], + "bob@sble.test": ["CN=Domain Users,CN=Users,DC=sble,DC=test", "CN=Users,CN=Builtin,DC=sble,DC=test"], +} + + +def assertions( + username, response, opa_attribute, expected_groups, expected_attributes={} +): + assert "result" in response + result = response["result"] + print(result) + assert opa_attribute in result, f"expected {opa_attribute} in {result}" + + # repeated the right hand side for better output on error + assert "customAttributes" in result[opa_attribute] + assert "groups" in result[opa_attribute] + assert "id" in result[opa_attribute] + assert "username" in result[opa_attribute] + + # todo: split out group assertions + print(f"Testing for {username} in groups {expected_groups}") + groups = sorted(result[opa_attribute]["groups"]) + expected_groups = sorted(expected_groups) + assert groups == expected_groups, f"got {groups}, expected: {expected_groups}" + + # todo: split out customAttribute assertions + print(f"Testing for {username} with customAttributes {expected_attributes}") + custom_attributes = result[opa_attribute]["customAttributes"] + assert ( + custom_attributes == expected_attributes + ), f"got {custom_attributes}, expected: {expected_attributes}" + + +if __name__ == "__main__": + all_args = argparse.ArgumentParser() + all_args.add_argument("-u", "--url", required=True, help="OPA service url") + args = vars(all_args.parse_args()) + params = {"strict-builtin-errors": "true"} + + def make_request(payload): + response = requests.post(args["url"], data=json.dumps(payload), params=params) + expected_status_code = 200 + assert ( + response.status_code == expected_status_code + ), f"got {response.status_code}, expected: {expected_status_code}" + return response.json() + + for username, groups in users_and_groups.items(): + try: + # todo: try this out locally until it works + # url = 'http://test-opa-svc:8081/v1/data' + payload = {"input": {"username": username}} + response = make_request(payload) + assertions(username, response, "currentUserInfoByUsername", groups, {}) + + # do the reverse lookup + user_id = response["result"]["currentUserInfoByUsername"]["id"] + payload = {"input": {"id": user_id}} + response = make_request(payload) + assertions(username, response, "currentUserInfoById", groups, {}) + except Exception as e: + print(f"exception: {e}") + if response is not None: + print(f"request body: {payload}") + print(f"response body: {response}") + raise e + + print("Test successful!") diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index 2a3d90c3..26cd8de0 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -39,6 +39,12 @@ tests: - opa-latest - keycloak - openshift + # AD must be initialized (by running ad-init) first, + # and the correct users and groups must be set up (see test-regorule.py) + # name: ad-user-info + # dimensions: + # - opa-latest + # - openshift - name: aas-user-info dimensions: - opa-latest From 3a74209a615e48b8a48a11655477f9b2f1c85fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 19 Mar 2025 10:49:09 +0100 Subject: [PATCH 3/7] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e86fba..24fb3fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file. - Bump `stackable-operator` to 0.87.0 and `stackable-versioned` to 0.6.0 ([#696]). - Default to OCI for image metadata and product image selection ([#671]). +- Active Directory backend for user-info-fetcher now uses the `service={opacluster}` scope rather than `pod,node` ([#698]). [#666]: https://github.com/stackabletech/opa-operator/pull/666 [#671]: https://github.com/stackabletech/opa-operator/pull/671 @@ -27,6 +28,7 @@ All notable changes to this project will be documented in this file. [#687]: https://github.com/stackabletech/opa-operator/pull/687 [#693]: https://github.com/stackabletech/opa-operator/pull/693 [#696]: https://github.com/stackabletech/opa-operator/pull/696 +[#698]: https://github.com/stackabletech/opa-operator/pull/698 ## [24.11.1] - 2025-01-10 From 20d19d0d31f2ff68e41685377336fb3c96faa37d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 18 Mar 2025 04:37:13 +0100 Subject: [PATCH 4/7] Ignore RUSTSEC-2025-0012 for now --- deny.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/deny.toml b/deny.toml index 2c0138d0..b256f323 100644 --- a/deny.toml +++ b/deny.toml @@ -29,6 +29,15 @@ ignore = [ # # TODO: Remove after https://github.com/kube-rs/kube/pull/1652 is merged "RUSTSEC-2024-0384", + + # https://rustsec.org/advisories/RUSTSEC-2025-0012 + # "backoff" is unmainted. + # + # Upstream (kube) has switched to backon in 0.99.0, and an upgrade is scheduled on our end. In the meantime, + # this is a very low-severity problem. + # + # TODO: Remove after upgrading to kube 0.99. + "RUSTSEC-2025-0012", ] [bans] From c582dd2cbafb083b789e9343f122de464c0ee5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 19 Mar 2025 11:02:14 +0100 Subject: [PATCH 5/7] Update ring to satisfy cargo deny --- Cargo.lock | 4 ++-- Cargo.nix | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d499672..dbfb9df7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2452,9 +2452,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.11" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", diff --git a/Cargo.nix b/Cargo.nix index ec3e1108..a87b87b6 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -7995,10 +7995,10 @@ rec { }; "ring" = rec { crateName = "ring"; - version = "0.17.11"; + version = "0.17.14"; edition = "2021"; - links = "ring_core_0_17_11_"; - sha256 = "0wzyhdbf71ndd14kkpyj2a6nvczvli2mndzv2al7r26k4yp4jlys"; + links = "ring_core_0_17_14_"; + sha256 = "1dw32gv19ccq4hsx3ribhpdzri1vnrlcfqb2vj41xn4l49n9ws54"; dependencies = [ { name = "cfg-if"; From c493329960e69d2895f6fcce17ef89cf8a44590c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 20 Mar 2025 12:16:47 +0100 Subject: [PATCH 6/7] Don't use a podoverride for testing the service scope --- .../templates/kuttl/ad-user-info/10-install-opa.yaml.j2 | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/templates/kuttl/ad-user-info/10-install-opa.yaml.j2 b/tests/templates/kuttl/ad-user-info/10-install-opa.yaml.j2 index c0ef848a..6522968f 100644 --- a/tests/templates/kuttl/ad-user-info/10-install-opa.yaml.j2 +++ b/tests/templates/kuttl/ad-user-info/10-install-opa.yaml.j2 @@ -56,14 +56,5 @@ commands: config: logging: enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} - podOverrides: - spec: - volumes: - - name: kerberos - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: service=opa roleGroups: default: {} From 98b06e8126fd9686a2838c9450ae3071c9bf90aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 20 Mar 2025 13:36:43 +0100 Subject: [PATCH 7/7] Formatting --- .../kuttl/ad-user-info/test-regorule.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/templates/kuttl/ad-user-info/test-regorule.py b/tests/templates/kuttl/ad-user-info/test-regorule.py index 2bb3d2f3..9d5361eb 100755 --- a/tests/templates/kuttl/ad-user-info/test-regorule.py +++ b/tests/templates/kuttl/ad-user-info/test-regorule.py @@ -5,8 +5,15 @@ # todo: make the test more comprehensive to check customAttributes users_and_groups = { - "alice@sble.test": ["CN=Superset Admins,CN=Users,DC=sble,DC=test", "CN=Domain Users,CN=Users,DC=sble,DC=test", "CN=Users,CN=Builtin,DC=sble,DC=test"], - "bob@sble.test": ["CN=Domain Users,CN=Users,DC=sble,DC=test", "CN=Users,CN=Builtin,DC=sble,DC=test"], + "alice@sble.test": [ + "CN=Superset Admins,CN=Users,DC=sble,DC=test", + "CN=Domain Users,CN=Users,DC=sble,DC=test", + "CN=Users,CN=Builtin,DC=sble,DC=test", + ], + "bob@sble.test": [ + "CN=Domain Users,CN=Users,DC=sble,DC=test", + "CN=Users,CN=Builtin,DC=sble,DC=test", + ], } @@ -33,9 +40,9 @@ def assertions( # todo: split out customAttribute assertions print(f"Testing for {username} with customAttributes {expected_attributes}") custom_attributes = result[opa_attribute]["customAttributes"] - assert ( - custom_attributes == expected_attributes - ), f"got {custom_attributes}, expected: {expected_attributes}" + assert custom_attributes == expected_attributes, ( + f"got {custom_attributes}, expected: {expected_attributes}" + ) if __name__ == "__main__": @@ -47,9 +54,9 @@ def assertions( def make_request(payload): response = requests.post(args["url"], data=json.dumps(payload), params=params) expected_status_code = 200 - assert ( - response.status_code == expected_status_code - ), f"got {response.status_code}, expected: {expected_status_code}" + assert response.status_code == expected_status_code, ( + f"got {response.status_code}, expected: {expected_status_code}" + ) return response.json() for username, groups in users_and_groups.items():