Commit dfac7ba9 authored by Konstantinos Togias's avatar Konstantinos Togias Committed by Dimitrios Gogos
Browse files

Deploy the single SRM bootstrap with validated credential mounts

Ported from 38e96ef3 (fix/srm-control-path-bootstrap) onto the flattened
2.0 values layout: srm.* instead of srm.srmcontroller.*.

Co-Authored-By: default avatarClaude Opus 5.5 <noreply@anthropic.com>
parent faf02dd8
Loading
Loading
Loading
Loading
+4 −0
Changes for .gitignore: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Secret values files — never commit real secrets
environments/**/secrets.values.yaml
oop-platform-chart/charts/*.tgz

# Offline chart-test artifacts
__pycache__/
.pytest_cache/
+1 −1
Changes for README.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -279,7 +279,7 @@ the same `global.publicHost` covers them; they fetch signing keys from `http://k
- Only Keycloak and the Portal get NodePorts by default. To reach OEG, SRM or Federation Manager from
  outside, set their `service.type` and `service.nodePort` (as `environments/kind/values.yaml` does).
- SRM has no control paths or topology by default, so it can't deploy anything until you set
  `srm.controlPaths`, `topology` and `workloads.namespaces`; see the
  the complete `srm.topology` bootstrap and `workloads.namespaces`; see the
  [SRM README](oop-platform-chart/charts/srm/README.md).
- Federation partners must reach FM's callback links: set
  `federation-manager.publicUrl`; see the
+1 −5
Changes for environments/kind/values.yaml: 1 added line, 5 removed lines.
Original line number Diff line number Diff line
@@ -14,11 +14,6 @@ srm:
    nodePort: 32415
  workloads:
    namespaces: [oop-apps]
  controlPaths:
    - control_path_ref: k8s-local
      adapter: kubernetes
      config:
        namespace: oop-apps
  topology: |
    version: openop.srm.bootstrap/v1
    platform_instance_id: kind-local
@@ -28,6 +23,7 @@ srm:
        adapter_type: kubernetes
        adapter_contract_version: v1
        infrastructure:
          in_cluster: true
          namespace: oop-apps
    zones:
      - ref: kind-local
+64 −59
Changes for oop-platform-chart/charts/srm/README.md: 64 added lines, 59 removed lines.
Original line number Diff line number Diff line
@@ -18,16 +18,13 @@ The chart creates:

It is meant to be installed through the umbrella chart `oop-platform`.

## Status
## Release compatibility

This chart targets SRM branch `refactor/tf-sdk-wiring`. Two things are still pending in the SRM
repository before it works end to end:

- **The image doesn't build as-is**: that branch pulls tf-sdk from a local path (`../tf-sdk`),
  which the official Dockerfile can't resolve.
- **The image has no `helm` binary**, which the `kubernetes` adapter runs to install workloads.

The chart is written for the image you get once both are fixed.
Use the SRM single-bootstrap change with SDK 2.0.0rc3 and its matching image.
The current release integration base is `refactor/tf-sdk-srm-bug-fixes`; its
Dockerfile already installs Helm 3.18.6 and uses the SDK package index. The new
candidate package/image must be available before deployment. Upgrade or roll
back SDK, SRM, chart and configuration together.

## What SRM needs

@@ -75,69 +72,77 @@ All keys sit under `srm:` in the umbrella chart's values.
| `federationManager.url` | string | `""` | Federation Manager internal API address; empty = `http://<global.federationManager.host>:<port>` (`http://federation-manager-internal:8083`) |
| `federationManager.timeout` | number | `5` | Seconds per Federation Manager call |
| `zoneProviderName` | string | `""` | Provider name SRM reports for its zones; empty = SRM's default (`oop-provider`) |
| `controlPaths` | list | `[]` | tf-sdk adapters SRM can use (see [Control paths](#control-paths)) |
| `controlPaths` | list | `[]` | Retired; nonempty input fails with a migration error |
| `controlPathCredentials` | list | `[]` | Generated references to existing credential Secret mounts |
| `topology` | string | `""` | Bootstrap topology YAML (see [Bootstrap topology](#bootstrap-topology)) |
| `workloads.namespaces` | list | `[]` | Namespaces SRM may deploy workloads into (kind: `[oop-apps]`) |
| `workloads.createNamespaces` | bool | `true` | Create those namespaces |
| `serviceAccount.create` | bool | `true` | Create SRM's ServiceAccount |
| `serviceAccount.name` | string | `""` | ServiceAccount name; empty = `name` |

## Control paths

A control path is one configured tf-sdk adapter, known by a name you choose
(`control_path_ref`). SRM builds its adapters from this list at startup; topology bindings (below)
point at them by that name. The chart passes the list to SRM as JSON in
`CONTROL_PATH_SETTINGS__ENTRIES`.
## One bootstrap for control paths and topology

Each entry has three fields:

| Field | Meaning |
|---|---|
| `control_path_ref` | Name of this control path |
| `adapter` | tf-sdk adapter type: `kubernetes`, `oai`, `open5gs`, `open5gcore`, `i2cat_ric` or `i2edge` |
| `config` | Adapter settings, different per adapter |
Put the complete SDK bootstrap in `topology`. SRM loads one
revision and derives discovery and its execution registry from it. Remove the
old independent `controlPaths` list. Instance identity is derived from each
`control_paths[].ref`; do not repeat it in infrastructure configuration.

A Kubernetes cluster (the one SRM runs in) and a 5G core:
For the cluster SRM runs in:

```yaml
srm:
  topology: |
    version: openop.srm.bootstrap/v1
    control_paths:
      - ref: local-workloads
        adapter_type: kubernetes
        infrastructure:
          in_cluster: true
          namespace: applications
    zones: []  # Add the declared zones/domains/capabilities/bindings for execution.
  workloads:
    namespaces: [oop-apps]
  controlPaths:
    - control_path_ref: k8s-local
      adapter: kubernetes
      config:
        namespace: oop-apps
    - control_path_ref: open5gs-lab
      adapter: open5gs
      config:
        base_url: http://nef.core.example:8000
        scs_as_id: oop
    namespaces: [applications]
```

- `kubernetes`: `namespace` is where workloads go. The adapter runs the `helm` CLI with the pod's
  own ServiceAccount, so the namespace must also be listed in `workloads.namespaces` for SRM to
  have permission there.
- Network adapters (`oai`, `open5gs`, `open5gcore`, `i2cat_ric`): `base_url` and `scs_as_id`,
  plus an optional `notification_url`.
- `i2edge`: `base_url` and `flavour_id`.

## Bootstrap topology

The topology file tells SRM which zones, domains and capabilities the platform has, and which
control path serves each capability. Put the whole YAML in `topology` as a string; the chart
mounts it and sets `BOOTSTRAP_TOPOLOGY_PATH`. With `topology` empty SRM discovers no topology.

- Format: `version: openop.srm.bootstrap/v1`. A full example with every field is tf-sdk's
  [`tests/contract/bootstrap_example.yaml`](https://labs.etsi.org/rep/oop/code/tf-sdk/-/blob/refactor/tfsdk-architecture-alignment/tests/contract/bootstrap_example.yaml);
  `environments/kind/values.yaml` has a small working one (one zone, one Kubernetes domain).
- **Refs must match.** Every `control_path_bindings[].control_path_ref` in the topology must be a
  `control_path_ref` in `controlPaths`. SRM only logs a warning when one isn't, and requests routed
  through that binding then fail.
- At startup SRM **only adds** zones, domains, capabilities and bindings that aren't in its
  database yet; it never updates or deletes existing ones. Changing an existing entry in the file
  has no effect on a running installation.
- Changing `topology` restarts the pod (the Deployment carries a checksum of it).
For a remote cluster, declare `kubeconfig_path`, `kube_context` and namespace
in the bootstrap. Supply matching mount metadata:

```yaml
controlPathCredentials:
  - controlPathRef: athens
    existingSecret: athens-credentials
    key: config
    mountPath: /etc/openop/kubeconfigs/athens
```

The matching bootstrap path is `/etc/openop/kubeconfigs/athens/config`. The
Secret must already exist in SRM's namespace. Its other files are available
alongside the config for relative certificate/token paths. The directory is
mounted read-only without `subPath`, allowing projected token files to rotate.
Kubeconfig changes require a new SDK instance/Pod; the SDK pins the selected
profile on first use. The SDK does not resolve Secret URIs.

The chart rejects missing, duplicate or mismatched credential mounts and
retired registry values before deployment. The existing Ansible `oop-platform`
role can generate bootstrap and mount metadata from one operator declaration,
avoiding manual duplication of file paths. Chart rendering validates mapping;
it does not establish that remote credentials or backends work.

A disk-backed `emptyDir` at `/var/run/openop-tfsdk` holds private SDK execution
files. This chart supports that runtime directory. Credential-provider plugins,
ambient connection defaults and independent `api_server` overrides are
unsupported by the paired SDK candidate. `endpoint_host`, if needed, addresses
the deployed application rather than the Kubernetes API server.

Bootstrap is declared topology, not backend health. SRM keeps create-missing
seeding: existing DB entries are not updated or deleted by this change.
Changing `topology` restarts the Pod through its existing checksum annotation.

Offline chart verification:

```sh
HELM_BINARY=/path/to/helm python -m pytest tests/test_srm_bootstrap.py
```

## Workload namespaces and RBAC

@@ -181,5 +186,5 @@ kubectl logs deployment/srmcontroller -n oop -c wait-for-database
- Pod stuck in `Init`: the `srm` database or role isn't there yet, or the password in
  `postgres-credentials` → `srm-password` doesn't match.
- `bootstrap_binding_unknown_control_path` in the logs: a topology binding names a control path
  that isn't in `controlPaths`.
  that isn't declared in bootstrap `control_paths`.
- Deploys fail with "forbidden": the adapter's namespace isn't in `workloads.namespaces`.
+39 −0
Changes for oop-platform-chart/charts/srm/templates/_helpers.tpl: 39 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -96,3 +96,42 @@ Returns "true" or empty (falsy in `if`).
true
{{- end -}}
{{- end }}

{{/* Validate mount references only; SDK owns its operational configuration. */}}
{{- define "srm.validateBootstrapMounts" -}}
{{- $c := .Values -}}
{{- if $c.controlPaths -}}
{{- fail "srm.controlPaths is retired; declare control_paths in topology" -}}
{{- end -}}
{{- $paths := dict -}}
{{- if $c.topology -}}
{{- $bootstrap := fromYaml $c.topology -}}
{{- if $bootstrap.Error }}{{ fail "srm.topology must contain valid bootstrap YAML" }}{{ end -}}
{{- range $entry := $bootstrap.control_paths -}}
{{- $ref := required "Each bootstrap control path requires a ref" $entry.ref -}}
{{- if hasKey $paths $ref }}{{ fail "Duplicate bootstrap control-path ref" }}{{ end -}}
{{- $_ := set $paths $ref ($entry.infrastructure | default dict) -}}
{{- end -}}
{{- end -}}
{{- $mounted := dict -}}
{{- $directories := dict -}}
{{- range $mount := $c.controlPathCredentials -}}
{{- $ref := required "Credential mount requires controlPathRef" $mount.controlPathRef -}}
{{- if not (hasKey $paths $ref) }}{{ fail "Credential mount names an undeclared control path" }}{{ end -}}
{{- if hasKey $mounted $ref }}{{ fail "Duplicate credential mount for a control path" }}{{ end -}}
{{- $directory := required "Credential mount requires mountPath" $mount.mountPath -}}
{{- $key := required "Credential mount requires key" $mount.key -}}
{{- if hasKey $directories $directory }}{{ fail "Credential mount directories must be unique" }}{{ end -}}
{{- $_ := set $directories $directory true -}}
{{- if or (not (hasPrefix "/etc/openop/kubeconfigs/" $directory)) (ne (clean $directory) $directory) }}{{ fail "Credential mountPath must be a directory under /etc/openop/kubeconfigs/" }}{{ end -}}
{{- if or (not (regexMatch "^[A-Za-z0-9._-]+$" $key)) (hasPrefix ".." $key) (eq $key ".") }}{{ fail "Credential key must be a safe Secret file key" }}{{ end -}}
{{- if not $mount.existingSecret }}{{ fail "Credential mount requires existingSecret" }}{{ end -}}
{{- $config := get $paths $ref -}}
{{- if or $config.in_cluster (ne (default "" $config.kubeconfig_path) (printf "%s/%s" $directory $key)) }}{{ fail "Credential mount does not match its bootstrap kubeconfig_path" }}{{ end -}}
{{- $_ := set $mounted $ref true -}}
{{- end -}}
{{- range $ref, $config := $paths -}}
{{- if and $config.kubeconfig_path (not (hasKey $mounted $ref)) }}{{ fail "Bootstrap kubeconfig_path requires a matching credential mount" }}{{ end -}}
{{- if and $config.runtime_directory (ne $config.runtime_directory "/var/run/openop-tfsdk") }}{{ fail "This chart mounts runtime_directory at /var/run/openop-tfsdk" }}{{ end -}}
{{- end -}}
{{- end -}}
Loading