From 9fa9190454100f1cf82fe7ae05cb0c9250169b1d Mon Sep 17 00:00:00 2001 From: David Hadley Date: Tue, 2 Jun 2026 15:57:32 +0100 Subject: [PATCH] feat(identity-mapper): tool to apply user groups following incoming cluster admin policies --- .../workflows/_identity_mapper_container.yaml | 54 +++ .github/workflows/_kyverno_policy.yaml | 3 + .github/workflows/ci.yaml | 24 ++ backend/identity-mapper/.gitignore | 1 + backend/identity-mapper/.python-version | 1 + backend/identity-mapper/Dockerfile | 25 ++ backend/identity-mapper/README.md | 5 + backend/identity-mapper/pyproject.toml | 35 ++ .../src/identity_mapper/__init__.py | 1 + .../src/identity_mapper/__main__.py | 40 ++ .../src/identity_mapper/_identity.py | 13 + .../_lookup_identities_in_kubernetes.py | 22 + .../_lookup_identities_in_ldap.py | 81 ++++ .../_sync_ldap_to_kubernetes.py | 71 ++++ .../identity-mapper/tests/test_kubernetes.py | 57 +++ backend/identity-mapper/tests/test_ldap.py | 61 +++ backend/identity-mapper/tests/test_sync.py | 72 ++++ backend/identity-mapper/uv.lock | 387 ++++++++++++++++++ charts/apps/Chart.yaml | 2 +- charts/apps/dev-values.yaml | 6 + charts/apps/staging-values.yaml | 7 + .../identity-mapper-application.yaml | 35 ++ charts/apps/values.yaml | 4 + charts/identity-mapper/Chart.lock | 6 + charts/identity-mapper/Chart.yaml | 10 + charts/identity-mapper/crds/useridentity.yaml | 32 ++ charts/identity-mapper/staging-values.yaml | 0 charts/identity-mapper/templates/cronjob.yaml | 20 + .../identity-mapper/templates/namespace.yaml | 4 + charts/identity-mapper/templates/policy.yaml | 149 +++++++ charts/identity-mapper/templates/rbac.yaml | 41 ++ .../chainsaw-test.yaml | 137 +++++++ charts/identity-mapper/values.yaml | 11 + 33 files changed, 1416 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/_identity_mapper_container.yaml create mode 100644 backend/identity-mapper/.gitignore create mode 100644 backend/identity-mapper/.python-version create mode 100644 backend/identity-mapper/Dockerfile create mode 100644 backend/identity-mapper/README.md create mode 100644 backend/identity-mapper/pyproject.toml create mode 100644 backend/identity-mapper/src/identity_mapper/__init__.py create mode 100644 backend/identity-mapper/src/identity_mapper/__main__.py create mode 100644 backend/identity-mapper/src/identity_mapper/_identity.py create mode 100644 backend/identity-mapper/src/identity_mapper/_lookup_identities_in_kubernetes.py create mode 100644 backend/identity-mapper/src/identity_mapper/_lookup_identities_in_ldap.py create mode 100644 backend/identity-mapper/src/identity_mapper/_sync_ldap_to_kubernetes.py create mode 100644 backend/identity-mapper/tests/test_kubernetes.py create mode 100644 backend/identity-mapper/tests/test_ldap.py create mode 100644 backend/identity-mapper/tests/test_sync.py create mode 100644 backend/identity-mapper/uv.lock create mode 100644 charts/apps/templates/identity-mapper-application.yaml create mode 100644 charts/identity-mapper/Chart.lock create mode 100644 charts/identity-mapper/Chart.yaml create mode 100644 charts/identity-mapper/crds/useridentity.yaml create mode 100644 charts/identity-mapper/staging-values.yaml create mode 100644 charts/identity-mapper/templates/cronjob.yaml create mode 100644 charts/identity-mapper/templates/namespace.yaml create mode 100644 charts/identity-mapper/templates/policy.yaml create mode 100644 charts/identity-mapper/templates/rbac.yaml create mode 100644 charts/identity-mapper/test-policy/mutate-argo-workflow-security-context/chainsaw-test.yaml create mode 100644 charts/identity-mapper/values.yaml diff --git a/.github/workflows/_identity_mapper_container.yaml b/.github/workflows/_identity_mapper_container.yaml new file mode 100644 index 000000000..92b06d7d5 --- /dev/null +++ b/.github/workflows/_identity_mapper_container.yaml @@ -0,0 +1,54 @@ +name: Identity Mapper Container + +on: + workflow_call: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Generate Image Name + run: echo IMAGE_REPOSITORY=ghcr.io/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]' | tr '[_]' '[\-]')-identity-mapper >> $GITHUB_ENV + + - name: Log in to GitHub Docker Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v4.1.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Version from Tag + id: tags + run: echo version=$(echo "${{ github.ref }}" | awk -F '[@v]' '{print $3}') >> $GITHUB_OUTPUT + + - name: Docker Metadata + id: meta + uses: docker/metadata-action@v5.10.0 + with: + images: ${{ env.IMAGE_REPOSITORY }} + tags: | + type=ref,event=branch + type=raw,value=latest,enable={{is_default_branch}} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4.0.0 + with: + driver-opts: network=host + + - name: Build Image + uses: docker/build-push-action@v6.18.0 + with: + context: backend/identity-mapper + push: ${{ github.event_name == 'push' }} + load: false + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/_kyverno_policy.yaml b/.github/workflows/_kyverno_policy.yaml index e60be3371..c7a9ed7f9 100644 --- a/.github/workflows/_kyverno_policy.yaml +++ b/.github/workflows/_kyverno_policy.yaml @@ -30,6 +30,7 @@ jobs: helm dep build charts/workflows helm dep build charts/sessionspaces helm dep build charts/sessionspaces/charts/database + helm dep build charts/identity-mapper - name: Install Kyverno run: | @@ -39,6 +40,7 @@ jobs: - name: Install Workflows CRDs run: | helm template workflows charts/workflows --namespace workflows --create-namespace | yq e 'select(.kind == "CustomResourceDefinition")' | tee -a /tmp/crds.yaml | kubectl apply -f - + kubectl apply -f charts/identity-mapper/crds/useridentity.yaml - name: Wait for CRDs run: | @@ -60,6 +62,7 @@ jobs: # To make testing work with this policy in place, it will be required to emulate the existence # of a POSIX uid label as part of request.userInfo.extra helm template workflows charts/workflows | yq e '. | select(.kind == "Policy" or .kind == "ClusterPolicy" or .kind == "GeneratingPolicy" or .kind == "ClusterRole" or .kind == "ClusterRoleBinding") | select(.metadata.name != "workflows-posix-uid-label")' | tee -a /tmp/policies.yaml | kubectl apply -f - + helm template identity-mapper charts/identity-mapper | yq e '. | select(.kind == "Policy" or .kind == "ClusterPolicy" or .kind == "GeneratingPolicy" or .kind == "ClusterRole" or .kind == "ClusterRoleBinding")' | tee -a /tmp/policies.yaml | kubectl apply -f - cat /tmp/policies.yaml | yq e '. | select(.kind == "Policy" or .kind == "ClusterPolicy")' | kubectl wait --for=condition=Ready --timeout=120s -f - - name: Wait for Kyverno diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3d787e4ae..3f149208b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,6 +5,22 @@ on: pull_request: jobs: + + changes: + runs-on: ubuntu-latest + outputs: + identity_mapper: ${{ steps.filter.outputs.identity_mapper }} + steps: + - uses: actions/checkout@v6 + + - name: Detect changes + id: filter + uses: dorny/paths-filter@v3 + with: + filters: | + identity_mapper: + - 'backend/identity-mapper/**' + helm_lint: # Deduplicate jobs from pull requests and branch pushes within the same repo. if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -77,6 +93,14 @@ jobs: contents: read packages: write + identity_mapper_container: + needs: changes + if: needs.changes.outputs.identity_mapper == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository) + uses: ./.github/workflows/_identity_mapper_container.yaml + permissions: + contents: read + packages: write + auth_core_code: # Deduplicate jobs from pull requests and branch pushes within the same repo. if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository diff --git a/backend/identity-mapper/.gitignore b/backend/identity-mapper/.gitignore new file mode 100644 index 000000000..ba0430d26 --- /dev/null +++ b/backend/identity-mapper/.gitignore @@ -0,0 +1 @@ +__pycache__/ \ No newline at end of file diff --git a/backend/identity-mapper/.python-version b/backend/identity-mapper/.python-version new file mode 100644 index 000000000..24ee5b1be --- /dev/null +++ b/backend/identity-mapper/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/backend/identity-mapper/Dockerfile b/backend/identity-mapper/Dockerfile new file mode 100644 index 000000000..f09ac9dd7 --- /dev/null +++ b/backend/identity-mapper/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.13-slim-trixie + +# The installer requires curl (and certificates) to download the release archive +RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates + +# Download the latest installer +ADD https://astral.sh/uv/0.11.16/install.sh /uv-installer.sh + +# Run the installer then remove it +RUN sh /uv-installer.sh && rm /uv-installer.sh + +# Ensure the installed binary is on the `PATH` +ENV PATH="/root/.local/bin/:$PATH" + +# Copy the project into the image +COPY . /app + +# Disable development dependencies +ENV UV_NO_DEV=1 + +# Sync the project into a new environment, asserting the lockfile is up to date +WORKDIR /app +RUN uv sync --locked + +CMD ["uv", "run", "--no-sync", "identity-mapper"] \ No newline at end of file diff --git a/backend/identity-mapper/README.md b/backend/identity-mapper/README.md new file mode 100644 index 000000000..53a41a225 --- /dev/null +++ b/backend/identity-mapper/README.md @@ -0,0 +1,5 @@ +# identity-mapper + +This is an MVP to collect user group information to patch Argo Worflows and Pod securityContext based on LDAP information. + +TODO: replace with a rust implementation. \ No newline at end of file diff --git a/backend/identity-mapper/pyproject.toml b/backend/identity-mapper/pyproject.toml new file mode 100644 index 000000000..a14e281f5 --- /dev/null +++ b/backend/identity-mapper/pyproject.toml @@ -0,0 +1,35 @@ +[project] +name = "identity-mapper" +version = "0.1.0" +description = "Determines pod security context for Diamond Workflows" +readme = "README.md" +authors = [ + { name = "David Hadley", email = "davehadley@users.noreply.github.com" } +] +requires-python = ">=3.13" +dependencies = [ + "kubernetes==35.0.0", + "ldap3>=2.9.1", + "structlog>=25.5.0", + "jsonpatch>=1.33", +] + +[project.scripts] +identity-mapper = "identity_mapper:__main__._main" + +[build-system] +requires = ["uv_build>=0.11.15,<0.12.0"] +build-backend = "uv_build" + +[dependency-groups] +dev = [ + "pytest>=9.0.3", + "ruff>=0.15.13", +] + +[tool.ruff.lint] +select = ["ALL"] +ignore = ["N806", "D203", "D213", "D106", "S104", "D101", "D103", "D102", "COM812", "RET504", "C901"] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["S101", "D103", "D100", "INP001", "PLR2004", ] diff --git a/backend/identity-mapper/src/identity_mapper/__init__.py b/backend/identity-mapper/src/identity_mapper/__init__.py new file mode 100644 index 000000000..6d0b8b0cd --- /dev/null +++ b/backend/identity-mapper/src/identity_mapper/__init__.py @@ -0,0 +1 @@ +"""Tool to synchronize Analysis Platform user information with LDAP.""" diff --git a/backend/identity-mapper/src/identity_mapper/__main__.py b/backend/identity-mapper/src/identity_mapper/__main__.py new file mode 100644 index 000000000..50970b501 --- /dev/null +++ b/backend/identity-mapper/src/identity_mapper/__main__.py @@ -0,0 +1,40 @@ +"""Synchronize LDAP and Kubernetes user IDs and groups.""" + +import logging + +import kubernetes +import ldap3 + +from ._lookup_identities_in_kubernetes import lookup_identities_in_kubernetes +from ._lookup_identities_in_ldap import lookup_identities_in_ldap +from ._sync_ldap_to_kubernetes import sync_ldap_to_kubernetes + +_logger = logging.getLogger(__name__) + + +def _get_kubernetes_client() -> kubernetes.client.CustomObjectsApi: + try: + kubernetes.config.load_incluster_config() + except kubernetes.config.ConfigException: + kubernetes.config.load_kube_config() + return kubernetes.client.CustomObjectsApi() + + +def _main() -> None: + _logger.info("Connecting to LDAP") + ldap_server: str = "ldap://ldapmaster.diamond.ac.uk" + server = ldap3.Server(ldap_server) + ldap = ldap3.Connection(server, auto_bind=True) + _logger.info("Initializing kubernetes client") + kubectl = _get_kubernetes_client() + _logger.info("Looking up identities in LDAP") + ldap_identities = lookup_identities_in_ldap(ldap) + _logger.info("Looking up identities in Kubernetes") + kubernetes_identities = lookup_identities_in_kubernetes(kubectl) + _logger.info("Syncronizing identities") + sync_ldap_to_kubernetes(kubectl, ldap_identities, kubernetes_identities) + _logger.info("Complete.") + + +if __name__ == "__main__": + _main() diff --git a/backend/identity-mapper/src/identity_mapper/_identity.py b/backend/identity-mapper/src/identity_mapper/_identity.py new file mode 100644 index 000000000..08d17297b --- /dev/null +++ b/backend/identity-mapper/src/identity_mapper/_identity.py @@ -0,0 +1,13 @@ +from typing import TypedDict + + +class Identity(TypedDict): + uid: int + gid: int + supplementalGroups: list[int] + + +class IdentityCrd: + GROUP = "workflows.internal.diamond.ac.uk" + VERSION = "v1" + PLURAL = "useridentities" diff --git a/backend/identity-mapper/src/identity_mapper/_lookup_identities_in_kubernetes.py b/backend/identity-mapper/src/identity_mapper/_lookup_identities_in_kubernetes.py new file mode 100644 index 000000000..ea4e42eb0 --- /dev/null +++ b/backend/identity-mapper/src/identity_mapper/_lookup_identities_in_kubernetes.py @@ -0,0 +1,22 @@ +import kubernetes + +from ._identity import Identity, IdentityCrd + + +def lookup_identities_in_kubernetes( + kubectl: kubernetes.client.CustomObjectsApi, +) -> dict[int, Identity]: + current_crds = kubectl.list_cluster_custom_object( + group=IdentityCrd.GROUP, version=IdentityCrd.VERSION, plural=IdentityCrd.PLURAL + ) + current_state = { + int(item["spec"].get("uid")): { + "uid": int(item["spec"].get("uid")), + "gid": int(item["spec"].get("gid")), + "supplementalGroups": list( + map(int, item["spec"].get("supplementalGroups", [])) + ), + } + for item in current_crds.get("items", []) + } + return current_state diff --git a/backend/identity-mapper/src/identity_mapper/_lookup_identities_in_ldap.py b/backend/identity-mapper/src/identity_mapper/_lookup_identities_in_ldap.py new file mode 100644 index 000000000..43589794a --- /dev/null +++ b/backend/identity-mapper/src/identity_mapper/_lookup_identities_in_ldap.py @@ -0,0 +1,81 @@ +import ldap3 + +from ._identity import Identity + +_BASE_DN = "dc=diamond,dc=ac,dc=uk" + + +def lookup_identities_in_ldap( + ldap: ldap3.Connection, +) -> dict[int, Identity]: + people_base_dn = _BASE_DN + group_base_dn = _BASE_DN + + user_filter = "(objectClass=posixAccount)" + + ldap.search( + people_base_dn, + user_filter, + attributes=["uid", "uidNumber", "gidNumber"], + ) + + users: list[tuple[int, str, int]] = [] + usernames: set[str] = set() + primary_gids: set[int] = set() + + for e in ldap.entries: + if not (e.uid.value and e.uidNumber.value and e.gidNumber.value): + continue + uid_num = int(e.uidNumber.value) + username = str(e.uid.value) + gid_num = int(e.gidNumber.value) + + users.append((uid_num, username, gid_num)) + usernames.add(username) + primary_gids.add(gid_num) + + if not users: + return {} + + ldap.search( + group_base_dn, + "(objectClass=posixGroup)", + attributes=["cn", "gidNumber", "memberUid"], + ) + + gid_to_cn: dict[int, str] = {} + user_to_groups: dict[str, list[int]] = {u: [] for u in usernames} + + for g in ldap.entries: + if not (g.cn.value and g.gidNumber.value): + continue + + cn = str(g.cn.value) + gid = int(g.gidNumber.value) + + gid_to_cn.setdefault(gid, cn) + + member_uid = getattr(g, "memberUid", None) + values = getattr(member_uid, "values", None) + members = list(values) if values else [] + + for m in members: + mu = str(m) + if mu in user_to_groups: + user_to_groups[mu].append({"name": cn, "gid": gid}) + + out = {} + for uid_num, username, primary_gid in users: + supplementary = [ + grp["gid"] + for grp in user_to_groups.get(username, []) + if grp["gid"] != primary_gid + ] + supplementary.sort() + out[uid_num] = { + "uid": uid_num, + "gid": primary_gid, + "supplementalGroups": supplementary, + } + + return out diff --git a/backend/identity-mapper/src/identity_mapper/_sync_ldap_to_kubernetes.py b/backend/identity-mapper/src/identity_mapper/_sync_ldap_to_kubernetes.py new file mode 100644 index 000000000..88be89dbf --- /dev/null +++ b/backend/identity-mapper/src/identity_mapper/_sync_ldap_to_kubernetes.py @@ -0,0 +1,71 @@ +import logging + +import jsonpatch +import kubernetes + +from ._identity import Identity, IdentityCrd + +_logger = logging.getLogger(__name__) + + +def sync_ldap_to_kubernetes( + kubectl: kubernetes.client.CustomObjectsApi, + ldap_data: dict[int, Identity], + kubernetes_data: dict[int, Identity], +) -> None: + desired_state = { + str(uid_num): { + "uid": uid_num, + "gid": data["gid"], + "supplementalGroups": data["supplementalGroups"], + } + for uid_num, data in ldap_data.items() + } + + patch = jsonpatch.JsonPatch.from_diff(kubernetes_data, desired_state) + + if not patch: + _logger.info("Cluster is fully in-sync with LDAP. No changes needed.") + return + + _logger.info( + "Applying %d updates to match desired LDAP state...", + len(patch.patch), + ) + + for operation in patch.patch: + op_type = operation["op"] # 'add', 'remove', or 'replace' + path_parts = operation["path"].strip("/").split("/") + username = path_parts[0] # The user ID string (e.g., "10023") + + if op_type == "add" and len(path_parts) == 1: + body = { + "apiVersion": f"{IdentityCrd.GROUP}/{IdentityCrd.VERSION}", + "kind": "UserIdentity", + "metadata": {"name": username}, + "spec": operation["value"], + } + kubectl.create_cluster_custom_object( + IdentityCrd.GROUP, IdentityCrd.VERSION, IdentityCrd.PLURAL, body + ) + + elif op_type == "remove" and len(path_parts) == 1: + kubectl.delete_cluster_custom_object( + IdentityCrd.GROUP, IdentityCrd.VERSION, IdentityCrd.PLURAL, username + ) + + elif op_type in ("replace", "add"): + # Update the entire spec for the user to keep API calls clean + body = { + "apiVersion": f"{IdentityCrd.GROUP}/{IdentityCrd.VERSION}", + "kind": "UserIdentity", + "metadata": {"name": username}, + "spec": desired_state[username], + } + kubectl.patch_cluster_custom_object( + IdentityCrd.GROUP, + IdentityCrd.VERSION, + IdentityCrd.PLURAL, + username, + body, + ) diff --git a/backend/identity-mapper/tests/test_kubernetes.py b/backend/identity-mapper/tests/test_kubernetes.py new file mode 100644 index 000000000..c06a5e95a --- /dev/null +++ b/backend/identity-mapper/tests/test_kubernetes.py @@ -0,0 +1,57 @@ +from unittest.mock import Mock + +import kubernetes.client + +from identity_mapper._identity import IdentityCrd +from identity_mapper._lookup_identities_in_kubernetes import ( + lookup_identities_in_kubernetes, +) + + +def test_lookup_identities_in_kubernetes() -> None: + mock_kubectl = Mock(spec=kubernetes.client.CustomObjectsApi) + mock_kubectl.list_cluster_custom_object.return_value = { + "items": [ + { + "metadata": {"name": "1001"}, + "spec": { + "uid": 1001, + "gid": 2001, + "supplementalGroups": [3001, 3002], + }, + }, + { + "metadata": {"name": "1002"}, + "spec": { + "uid": 1002, + "gid": 2002, + # intentionally omit supplementalGroups to test default + }, + }, + ] + } + + # Call function + actual = lookup_identities_in_kubernetes(mock_kubectl) + + expected = { + 1001: { + "uid": 1001, + "gid": 2001, + "supplementalGroups": [3001, 3002], + }, + 1002: { + "uid": 1002, + "gid": 2002, + "supplementalGroups": [], + }, + } + + assert actual == expected + + # Optional: verify API was called correctly + mock_kubectl.list_cluster_custom_object.assert_called_once_with( + group=IdentityCrd.GROUP, + version=IdentityCrd.VERSION, + plural=IdentityCrd.PLURAL, + ) diff --git a/backend/identity-mapper/tests/test_ldap.py b/backend/identity-mapper/tests/test_ldap.py new file mode 100644 index 000000000..cff45300c --- /dev/null +++ b/backend/identity-mapper/tests/test_ldap.py @@ -0,0 +1,61 @@ +from ldap3 import MOCK_SYNC, Connection, Server + +from identity_mapper._lookup_identities_in_ldap import lookup_identities_in_ldap + + +def _mock_ldap_connection() -> Connection: + server = Server("mock") + + conn = Connection(server, client_strategy=MOCK_SYNC) + conn.bind() + + base = "dc=diamond,dc=ac,dc=uk" + + conn.strategy.add_entry( + "uid=mok12345,ou=people," + base, + { + "objectClass": ["posixAccount"], + "uid": "mok12345", + "uidNumber": "1", + "gidNumber": "2", + }, + ) + + conn.strategy.add_entry( + "cn=mok12345,ou=groups," + base, + { + "objectClass": ["posixGroup"], + "cn": "mok12345", + "gidNumber": "2", + }, + ) + + conn.strategy.add_entry( + "cn=dls_dasc,ou=groups," + base, + { + "objectClass": ["posixGroup"], + "cn": "sup_group_3", + "gidNumber": "3", + "memberUid": ["mok12345"], + }, + ) + + conn.strategy.add_entry( + "cn=dls_staff,ou=groups," + base, + { + "objectClass": ["posixGroup"], + "cn": "sup_group_4", + "gidNumber": "4", + "memberUid": ["mok12345"], + }, + ) + + return conn + + +def test_lookup_identities_in_ldap() -> None: + ldap = _mock_ldap_connection() + actual = lookup_identities_in_ldap(ldap) + + expected = {1: {"uid": 1, "gid": 2, "supplementalGroups": [3, 4]}} + assert actual == expected diff --git a/backend/identity-mapper/tests/test_sync.py b/backend/identity-mapper/tests/test_sync.py new file mode 100644 index 000000000..ac0914ca2 --- /dev/null +++ b/backend/identity-mapper/tests/test_sync.py @@ -0,0 +1,72 @@ +from unittest.mock import Mock + +import kubernetes.client + +from identity_mapper._identity import IdentityCrd +from identity_mapper._sync_ldap_to_kubernetes import sync_ldap_to_kubernetes + + +def test_sync_add_user() -> None: + mock_kubectl = Mock(spec=kubernetes.client.CustomObjectsApi) + + ldap_data = {1001: {"uid": 1001, "gid": 2001, "supplementalGroups": [3001]}} + + kubernetes_data = {} # user does not exist yet + + sync_ldap_to_kubernetes(mock_kubectl, ldap_data, kubernetes_data) + + mock_kubectl.create_cluster_custom_object.assert_called_once() + args = mock_kubectl.create_cluster_custom_object.call_args[0] + + assert args[0] == IdentityCrd.GROUP + assert args[1] == IdentityCrd.VERSION + assert args[2] == IdentityCrd.PLURAL + assert args[3]["metadata"]["name"] == "1001" + + +def test_sync_remove_user() -> None: + mock_kubectl = Mock(spec=kubernetes.client.CustomObjectsApi) + + ldap_data = {} # no users in LDAP + + kubernetes_data = {1001: {"uid": 1001, "gid": 2001, "supplementalGroups": [3001]}} + + sync_ldap_to_kubernetes(mock_kubectl, ldap_data, kubernetes_data) + + mock_kubectl.delete_cluster_custom_object.assert_called_once_with( + IdentityCrd.GROUP, IdentityCrd.VERSION, IdentityCrd.PLURAL, "1001" + ) + + +def test_sync_update_user() -> None: + mock_kubectl = Mock(spec=kubernetes.client.CustomObjectsApi) + + ldap_data = {1001: {"uid": 1001, "gid": 9999, "supplementalGroups": [4000]}} + + kubernetes_data = {1001: {"uid": 1001, "gid": 2001, "supplementalGroups": [3001]}} + + sync_ldap_to_kubernetes(mock_kubectl, ldap_data, kubernetes_data) + + mock_kubectl.patch_cluster_custom_object.assert_called_once() + + args = mock_kubectl.patch_cluster_custom_object.call_args[0] + + assert args[0] == IdentityCrd.GROUP + assert args[1] == IdentityCrd.VERSION + assert args[2] == IdentityCrd.PLURAL + assert args[3] == "1001" + assert args[4]["spec"]["gid"] == 9999 + + +def test_sync_no_changes() -> None: + mock_kubectl = Mock(spec=kubernetes.client.CustomObjectsApi) + + ldap_data = {1001: {"uid": 1001, "gid": 2001, "supplementalGroups": [3001]}} + + kubernetes_data = {"1001": {"uid": 1001, "gid": 2001, "supplementalGroups": [3001]}} + + sync_ldap_to_kubernetes(mock_kubectl, ldap_data, kubernetes_data) + + mock_kubectl.create_cluster_custom_object.assert_not_called() + mock_kubectl.delete_cluster_custom_object.assert_not_called() + mock_kubectl.patch_cluster_custom_object.assert_not_called() diff --git a/backend/identity-mapper/uv.lock b/backend/identity-mapper/uv.lock new file mode 100644 index 000000000..fe9728a12 --- /dev/null +++ b/backend/identity-mapper/uv.lock @@ -0,0 +1,387 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "certifi" +version = "2026.5.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "durationpy" +version = "0.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335, upload-time = "2025-05-17T13:52:37.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, +] + +[[package]] +name = "identity-mapper" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "jsonpatch" }, + { name = "kubernetes" }, + { name = "ldap3" }, + { name = "structlog" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "jsonpatch", specifier = ">=1.33" }, + { name = "kubernetes", specifier = "==35.0.0" }, + { name = "ldap3", specifier = ">=2.9.1" }, + { name = "structlog", specifier = ">=25.5.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.0.3" }, + { name = "ruff", specifier = ">=0.15.13" }, +] + +[[package]] +name = "idna" +version = "3.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, +] + +[[package]] +name = "kubernetes" +version = "35.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "durationpy" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "requests-oauthlib" }, + { name = "six" }, + { name = "urllib3" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/8f/85bf51ad4150f64e8c665daf0d9dfe9787ae92005efb9a4d1cba592bd79d/kubernetes-35.0.0.tar.gz", hash = "sha256:3d00d344944239821458b9efd484d6df9f011da367ecb155dadf9513f05f09ee", size = 1094642, upload-time = "2026-01-16T01:05:27.76Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/70/05b685ea2dffcb2adbf3cdcea5d8865b7bc66f67249084cf845012a0ff13/kubernetes-35.0.0-py2.py3-none-any.whl", hash = "sha256:39e2b33b46e5834ef6c3985ebfe2047ab39135d41de51ce7641a7ca5b372a13d", size = 2017602, upload-time = "2026-01-16T01:05:25.991Z" }, +] + +[[package]] +name = "ldap3" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/ac/96bd5464e3edbc61595d0d69989f5d9969ae411866427b2500a8e5b812c0/ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f", size = 398830, upload-time = "2021-07-18T06:34:21.786Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/f6/71d6ec9f18da0b2201287ce9db6afb1a1f637dedb3f0703409558981c723/ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70", size = 432192, upload-time = "2021-07-18T06:34:12.905Z" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, + { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, + { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "structlog" +version = "25.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/52/9ba0f43b686e7f3ddfeaa78ac3af750292662284b3661e91ad5494f21dbc/structlog-25.5.0.tar.gz", hash = "sha256:098522a3bebed9153d4570c6d0288abf80a031dfdb2048d59a49e9dc2190fc98", size = 1460830, upload-time = "2025-10-27T08:28:23.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/45/a132b9074aa18e799b891b91ad72133c98d8042c70f6240e4c5f9dabee2f/structlog-25.5.0-py3-none-any.whl", hash = "sha256:a8453e9b9e636ec59bd9e79bbd4a72f025981b3ba0f5837aebf48f02f37a7f9f", size = 72510, upload-time = "2025-10-27T08:28:21.535Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] diff --git a/charts/apps/Chart.yaml b/charts/apps/Chart.yaml index d5ce3bfef..e43b53a87 100644 --- a/charts/apps/Chart.yaml +++ b/charts/apps/Chart.yaml @@ -2,4 +2,4 @@ apiVersion: v2 name: apps description: An argocd app to deploy apps inside the virtual cluster type: application -version: 0.5.5 +version: 0.5.6 diff --git a/charts/apps/dev-values.yaml b/charts/apps/dev-values.yaml index 67c647bc8..ca7f2a833 100644 --- a/charts/apps/dev-values.yaml +++ b/charts/apps/dev-values.yaml @@ -70,3 +70,9 @@ dashboard: targetRevision: HEAD extraValueFiles: - dev-values.yaml + +identityMapper: + enabled: false + targetRevision: HEAD + extraValueFiles: + - dev-values.yaml \ No newline at end of file diff --git a/charts/apps/staging-values.yaml b/charts/apps/staging-values.yaml index 2cd031e22..0b6a8bd98 100644 --- a/charts/apps/staging-values.yaml +++ b/charts/apps/staging-values.yaml @@ -92,3 +92,10 @@ k6Operator: extraValueFiles: - staging-values.yaml valuesObject: {} + +identityMapper: + enabled: true + targetRevision: HEAD + extraValueFiles: + - staging-values.yaml + valuesObject: {} \ No newline at end of file diff --git a/charts/apps/templates/identity-mapper-application.yaml b/charts/apps/templates/identity-mapper-application.yaml new file mode 100644 index 000000000..9700eed7a --- /dev/null +++ b/charts/apps/templates/identity-mapper-application.yaml @@ -0,0 +1,35 @@ +{{- if .Values.identityMapper.enabled }} +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: identity-mapper + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "2" +spec: + destination: + namespace: {{ .Values.identityMapper.namespace }} + server: {{ .Values.destination.server }} + project: default + source: + repoURL: https://github.com/DiamondLightSource/workflows.git + path: charts/identity-mapper + targetRevision: {{ .Values.identityMapper.targetRevision }} + helm: + valueFiles: + - values.yaml + {{- if .Values.identityMapper.extraValueFiles }} + {{- .Values.identityMapper.extraValueFiles | toYaml | nindent 8 }} + {{- end }} + {{- if .Values.identityMapper.valuesObject }} + valuesObject: + {{- .Values.identityMapper.valuesObject | toYaml | nindent 8 }} + {{- end }} + includeCRDs: true + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true +{{- end }} diff --git a/charts/apps/values.yaml b/charts/apps/values.yaml index a171ac0d5..de246ab7d 100644 --- a/charts/apps/values.yaml +++ b/charts/apps/values.yaml @@ -162,3 +162,7 @@ k6Operator: targetRevision: HEAD extraValueFiles: [] valuesObject: {} + +identityMapper: + enabled: false + targetRevision: HEAD diff --git a/charts/identity-mapper/Chart.lock b/charts/identity-mapper/Chart.lock new file mode 100644 index 000000000..37d8ea788 --- /dev/null +++ b/charts/identity-mapper/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: oci://docker.io/bitnamicharts + version: 2.23.0 +digest: sha256:c6a6a1cd877a7776095f62977d2fe441ee8b1145d624b6a57bc08dd52aa2611b +generated: "2026-05-27T07:53:54.112180005+01:00" diff --git a/charts/identity-mapper/Chart.yaml b/charts/identity-mapper/Chart.yaml new file mode 100644 index 000000000..6754e948d --- /dev/null +++ b/charts/identity-mapper/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: identity-mapper +description: Identity Mapper +type: application +version: 0.1.0 +appVersion: 0.1.0 +dependencies: + - name: common + version: 2.23.0 + repository: oci://docker.io/bitnamicharts diff --git a/charts/identity-mapper/crds/useridentity.yaml b/charts/identity-mapper/crds/useridentity.yaml new file mode 100644 index 000000000..42c089e7c --- /dev/null +++ b/charts/identity-mapper/crds/useridentity.yaml @@ -0,0 +1,32 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: useridentities.workflows.internal.diamond.ac.uk + annotations: + argocd.argoproj.io/sync-wave: "-1" +spec: + group: workflows.internal.diamond.ac.uk + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + uid: + type: integer + gid: + type: integer + supplementalGroups: + type: array + items: + type: integer + scope: Cluster + names: + plural: useridentities + singular: useridentity + kind: UserIdentity diff --git a/charts/identity-mapper/staging-values.yaml b/charts/identity-mapper/staging-values.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/charts/identity-mapper/templates/cronjob.yaml b/charts/identity-mapper/templates/cronjob.yaml new file mode 100644 index 000000000..e6a92e071 --- /dev/null +++ b/charts/identity-mapper/templates/cronjob.yaml @@ -0,0 +1,20 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: identity-mapper + namespace: identity-mapper +spec: + schedule: "*/30 * * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + template: + spec: + serviceAccountName: identity-mapper + restartPolicy: OnFailure + containers: + - name: identity-mapper + image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}{{- if .Values.image.digest }}@{{ .Values.image.digest }}{{- else }}:{{ .Values.image.tag }}{{- end }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" diff --git a/charts/identity-mapper/templates/namespace.yaml b/charts/identity-mapper/templates/namespace.yaml new file mode 100644 index 000000000..6d42cdfb0 --- /dev/null +++ b/charts/identity-mapper/templates/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: "identity-mapper" diff --git a/charts/identity-mapper/templates/policy.yaml b/charts/identity-mapper/templates/policy.yaml new file mode 100644 index 000000000..b1e17f5c9 --- /dev/null +++ b/charts/identity-mapper/templates/policy.yaml @@ -0,0 +1,149 @@ +{{- if .Values.policy.enable }} +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: {{ include "common.names.fullname" $ }}-mutate-argo-workflow-security-context +spec: + background: false + rules: + - name: inject-posix-ids + match: + any: + - resources: + kinds: + - argoproj.io/v1alpha1/Workflow + operations: + - CREATE + context: + - name: posixUidString + variable: + jmesPath: "request.userInfo.extra.\"workflows.diamond.ac.uk/posixuid\"[0] || \"\"" + default: "" + + - name: userProfile + apiCall: + urlPath: "/apis/workflows.internal.diamond.ac.uk/v1/useridentities/{{`{{ posixUidString }}`}}" + jmesPath: "spec" + default: {} + + + preconditions: + all: + - key: "{{`{{ posixUidString }}`}}" + operator: NotEquals + value: "" + - key: "{{`{{ userProfile.uid || '' }}`}}" + operator: NotEquals + value: "" + - key: "{{`{{ userProfile.gid || '' }}`}}" + operator: NotEquals + value: "" + + + mutate: + patchStrategicMerge: + spec: + securityContext: + runAsUser: "{{`{{ userProfile.uid }}`}}" + runAsGroup: "{{`{{ userProfile.gid }}`}}" + supplementalGroups: "{{`{{ userProfile.supplementalGroups }}`}}" +--- +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: {{ include "common.names.fullname" $ }}-pod-security-context + annotations: + pod-policies.kyverno.io/autogen-controllers: none +spec: + background: false + validationFailureAction: Enforce + schemaValidation: false + rules: + - name: pod-securitycontext-propagate-flags + match: + any: + - resources: + kinds: + - Pod + operations: + - CREATE + namespaceSelector: + matchLabels: + app.kubernetes.io/managed-by: sessionspaces + mutate: + foreach: + - list: "request.object.spec.containers || `[]`" + patchStrategicMerge: + spec: + containers: + - (name): "{{`{{ element.name }}`}}" + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + + - list: "request.object.spec.initContainers || `[]`" + patchStrategicMerge: + spec: + initContainers: + - (name): "{{`{{ element.name }}`}}" + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + + - list: "request.object.spec.ephemeralContainers || `[]`" + patchStrategicMerge: + spec: + ephemeralContainers: + - (name): "{{`{{ element.name }}`}}" + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + - name: pod-securitycontext-propagate-uid-and-gid + match: + any: + - resources: + kinds: + - Pod + operations: + - CREATE + namespaceSelector: + matchLabels: + app.kubernetes.io/managed-by: sessionspaces + preconditions: + all: + - key: "{{`{{ request.object.spec.securityContext.runAsUser || '' }}`}}" + operator: NotEquals + value: "" + - key: "{{`{{ request.object.spec.securityContext.runAsGroup || '' }}`}}" + operator: NotEquals + value: "" + + mutate: + foreach: + - list: "request.object.spec.containers || `[]`" + patchStrategicMerge: + spec: + containers: + - (name): "{{`{{ element.name }}`}}" + securityContext: + runAsUser: "{{`{{ request.object.spec.securityContext.runAsUser }}`}}" + runAsGroup: "{{`{{ request.object.spec.securityContext.runAsGroup }}`}}" + + - list: "request.object.spec.initContainers || `[]`" + patchStrategicMerge: + spec: + initContainers: + - (name): "{{`{{ element.name }}`}}" + securityContext: + runAsUser: "{{`{{ request.object.spec.securityContext.runAsUser }}`}}" + runAsGroup: "{{`{{ request.object.spec.securityContext.runAsGroup }}`}}" + + - list: "request.object.spec.ephemeralContainers || `[]`" + patchStrategicMerge: + spec: + ephemeralContainers: + - (name): "{{`{{ element.name }}`}}" + securityContext: + runAsUser: "{{`{{ request.object.spec.securityContext.runAsUser }}`}}" + runAsGroup: "{{`{{ request.object.spec.securityContext.runAsGroup }}`}}" +{{- end }} \ No newline at end of file diff --git a/charts/identity-mapper/templates/rbac.yaml b/charts/identity-mapper/templates/rbac.yaml new file mode 100644 index 000000000..1add4be12 --- /dev/null +++ b/charts/identity-mapper/templates/rbac.yaml @@ -0,0 +1,41 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: kyverno:manage-user-identities +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kyverno:manage-user-identities +subjects: + - kind: ServiceAccount + name: kyverno-background-controller + namespace: kyverno + - kind: ServiceAccount + name: kyverno-admission-controller + namespace: kyverno + - kind: ServiceAccount + name: identity-mapper + namespace: identity-mapper +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kyverno:manage-user-identities +rules: + - apiGroups: + - "workflows.internal.diamond.ac.uk" + resources: + - useridentities + verbs: + - get + - list + - create + - update + - patch + - delete +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: identity-mapper + namespace: identity-mapper diff --git a/charts/identity-mapper/test-policy/mutate-argo-workflow-security-context/chainsaw-test.yaml b/charts/identity-mapper/test-policy/mutate-argo-workflow-security-context/chainsaw-test.yaml new file mode 100644 index 000000000..0df115d0a --- /dev/null +++ b/charts/identity-mapper/test-policy/mutate-argo-workflow-security-context/chainsaw-test.yaml @@ -0,0 +1,137 @@ +# TODO: these tests clash with existing podSecurityContext tests +# They should be reinstated on migration from the old policies to the new policies +# apiVersion: chainsaw.kyverno.io/v1alpha1 +# kind: Test +# metadata: +# name: mutate-argo-workflow-security-context +# spec: +# namespaceTemplate: +# metadata: +# labels: +# app.kubernetes.io/managed-by: sessionspaces +# steps: +# - try: +# - create: +# resource: +# apiVersion: workflows.internal.diamond.ac.uk/v1 +# kind: UserIdentity +# metadata: +# name: "12" +# spec: +# uid: 12 +# gid: 34 +# supplementalGroups: +# - 56 +# - 78 +# - create: +# resource: +# apiVersion: rbac.authorization.k8s.io/v1 +# kind: Role +# metadata: +# name: workflow-creator +# namespace: ($namespace) +# rules: +# - apiGroups: ["argoproj.io"] +# resources: ["workflows"] +# verbs: ["create"] +# - create: +# resource: +# apiVersion: rbac.authorization.k8s.io/v1 +# kind: RoleBinding +# metadata: +# name: workflow-creator +# namespace: ($namespace) +# subjects: +# - kind: User +# name: oidc:test-user +# roleRef: +# apiGroup: rbac.authorization.k8s.io +# kind: Role +# name: workflow-creator +# - command: +# env: +# - name: namespace +# value: ($namespace) +# entrypoint: sh +# args: +# - -c +# - | +# cat <<'EOF' | kubectl create --namespace="$namespace" --as=oidc:test-user --as-user-extra=workflows.diamond.ac.uk/posixuid=12 -f - +# apiVersion: argoproj.io/v1alpha1 +# kind: Workflow +# metadata: +# name: test-workflow +# spec: {} +# EOF +# - assert: +# resource: +# apiVersion: argoproj.io/v1alpha1 +# kind: Workflow +# metadata: +# name: test-workflow +# spec: +# securityContext: +# runAsUser: 12 +# runAsGroup: 34 +# supplementalGroups: [56, 78] +# --- +# apiVersion: chainsaw.kyverno.io/v1alpha1 +# kind: Test +# metadata: +# name: pod-securitycontext-propagation +# spec: +# namespaceTemplate: +# metadata: +# labels: +# app.kubernetes.io/managed-by: sessionspaces +# steps: +# - try: +# - create: +# resource: +# apiVersion: v1 +# kind: Pod +# metadata: +# name: test-pod +# spec: +# securityContext: +# runAsGroup: 1234 +# runAsUser: 4321 +# containers: +# - name: test-container +# image: docker.io/library/busybox:latest +# initContainers: +# - name: test-init-container +# image: docker.io/library/busybox:latest +# - assert: +# resource: +# apiVersion: v1 +# kind: Pod +# metadata: +# name: test-pod +# spec: +# securityContext: +# runAsGroup: 1234 +# runAsUser: 4321 +# containers: +# - name: test-container +# image: docker.io/library/busybox:latest +# securityContext: +# runAsGroup: 1234 +# runAsUser: 4321 +# allowPrivilegeEscalation: false +# readOnlyRootFilesystem: true +# initContainers: +# - name: test-init-container +# image: docker.io/library/busybox:latest +# securityContext: +# runAsGroup: 1234 +# runAsUser: 4321 +# allowPrivilegeEscalation: false +# readOnlyRootFilesystem: true +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: noop +spec: + steps: + - try: \ No newline at end of file diff --git a/charts/identity-mapper/values.yaml b/charts/identity-mapper/values.yaml new file mode 100644 index 000000000..9f2875f1e --- /dev/null +++ b/charts/identity-mapper/values.yaml @@ -0,0 +1,11 @@ +image: + registry: ghcr.io + repository: diamondlightsource/workflows-identity-mapper + tag: latest + digest: "sha256:afd5e882dc4c0b2477e2b9d00bd53a155e17985f6dd4d97fdeb0c7f4e8c3c87c" + +policy: + # WARNING: this policy sets podSecurityContext following post 2026-05 cloud team policies + # Conflicts with the policy in charts/sessionspaces/templates/pod-clusterpolicy.yaml + # Do not enable both at the same time + enable: false