Skip to content

CNTRLPLANE-3363: Add KMS plugin health reporter design#2005

Open
ibihim wants to merge 7 commits into
openshift:masterfrom
ibihim:2026-05-07_kmsv2tpv2-health-monitor
Open

CNTRLPLANE-3363: Add KMS plugin health reporter design#2005
ibihim wants to merge 7 commits into
openshift:masterfrom
ibihim:2026-05-07_kmsv2tpv2-health-monitor

Conversation

@ibihim
Copy link
Copy Markdown
Contributor

@ibihim ibihim commented May 8, 2026

What

A health reporter sidecar runs alongside every API server pod replica when KMS is enabled. It probes the colocated KMS plugin(s) and writes a single advisory KMSHealthReporter_<nodeName> condition per node on the apiserver operator CR.

Why

Exposes plugin health state through the operator CRs and onward into the ClusterOperator's Degraded condition, so a misbehaving KMS plugin is visible in oc get co rather than silently waiting until KAS encryption fails.

Supports future key rotation: per-plugin keyID in the reporter's Message lets a rotation controller verify all nodes agree on the active key before initiating rotation.

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label May 8, 2026
@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented May 8, 2026

@ibihim: This pull request references CNTRLPLANE-3363 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

What

A health reporter sidecar runs alongside every API server pod replica when KMS is enabled. It probes the colocated KMS plugin(s) and writes a single advisory KMSHealthReporter_<nodeName> condition per node on the apiserver operator CR. A separate aggregator controller reads those conditions and emits a single KMSPluginsDegraded rollup; library-go's StatusSyncer propagates the _Degraded suffix into the ClusterOperator's Degraded condition. The Message field on each per-node condition is structured input for the aggregator: one key=value line per probed plugin, carrying keyID, status, lastChecked, and an optional trailing detail.

Why

Exposes plugin health state through the operator CRs and onward into the ClusterOperator's Degraded condition, so a misbehaving KMS plugin is visible in oc get co rather than silently waiting until KAS encryption fails.

Supports future key rotation: per-plugin keyID in the reporter's Message lets a rotation controller verify all nodes agree on the active key before initiating rotation.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci Bot requested review from JoelSpeed and derekwaynecarr May 8, 2026 17:57
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 8, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign benluddy for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

- Per-node health reporter sidecar publishes one advisory
  KMSHealthReporter_<nodeName> condition on the apiserver operator CR.
- Aggregator controller reads those conditions and emits a single
  KMSPluginsDegraded rollup; library-go's StatusSyncer routes the
  _Degraded suffix into the ClusterOperator's Degraded condition.
- Message format: one key=value line per probed plugin (keyID, status,
  lastChecked, optional trailing detail).
- Risks: stale reporter conditions, orphaned conditions on KMS disable,
  cold-start window.
@ibihim ibihim force-pushed the 2026-05-07_kmsv2tpv2-health-monitor branch from bb85f9a to b719627 Compare May 8, 2026 18:22

#### Health Reporter Sidecar

When KMS encryption is enabled, a health reporter sidecar runs alongside every API server pod replica. The sidecar probes the colocated KMS plugin(s) and publishes the outcome to the owning operator's CR as a per-node condition. A separate aggregator controller picks up these conditions and emits a single `KMSPluginsDegraded` rollup, which propagates to the `ClusterOperator`'s `Degraded` condition.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking there will be one health reporter sidecar per each kms plugin (i.e. health reporter a for kms plugin a, health reporter b for kms plugin b, etc.) not single health reporter plugin for all kms plugins. Is that correct?.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugin lifecycle is supposed to inject kms plugins. That means it will additionally inject one health monitor side car?.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it's a bit contradicting with the sections below. I was also under the assumption we had a 1:1 monitor:plugin relation.

Copy link
Copy Markdown
Contributor Author

@ibihim ibihim May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What

One reporter sidecar per API server pod, not one per plugin. It probes every colocated KMS socket and emits a single per-node condition whose Message carries one line per plugin (see Message format below).

Why

  • One monitor per node keeps condition cardinality constant. With one monitor per plugin, it won't (because of SSA / field ownership).
  • I'm assuming the injector can run one reporter per pod with all plugin sockets exposed to it, by passing socket paths as flags at startup.

If the above turns out to be harder than 1:1 injection in practice, I will change it.

- One per `openshift-oauth-apiserver` Deployment replica
- One per `openshift-apiserver` Deployment replica

During KMS-to-KMS migration, the same sidecar probes every active KMS plugin in its pod (see [Multiple Concurrent Sidecars](#multiple-concurrent-sidecars)) and reports their combined state in the Message field of its single per-node condition.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by checking the current EncryptionConfiguration?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The health reporter is given the UDS paths as a flag argument. Technically it could also scan for mounted UDS sockets.

│ SSA (per-node fieldManager)
operator CR (kubeapiservers.operator.openshift.io/cluster):
├─ KMSHealthReporter_<nodeName> ◄─ written by each per-pod reporter
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give an example about how kms-2.sock and kms-1.sock will be represented in operator CR?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw example below.

|---|---|---|
| All plugins healthy | True | `AsExpected` |
| Any plugin returns not-ok | False | `Unhealthy` |
| Any plugin RPC error, no explicit not-ok | Unknown | `Unreachable` |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From where the details of RPC error will be seen?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every call to the KMS plugin can create an error as per interface:

	Status(ctx context.Context) (*StatusResponse, error) // error = RPC error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can cluster admin see the RPC error details from the operator conditions reported by health monitor?. cluster admin can not send request to Status endpoint of kms plugin.

Copy link
Copy Markdown
Contributor Author

@ibihim ibihim May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cluster admin will see the aggregated status created by the condition controller.

Is this a CTA, to add this notion to the EP?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a paragraph for this: 771b367

The Message is the structured input the aggregator controller consumes. One line per probed plugin:

```
keyID=<id> status=healthy lastChecked=<RFC 3339 timestamp>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can this format be parsed from other controllers (i.e. rotation controller)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to replicate

      message: |-
        Available: The registry is ready
        NodeCADaemonAvailable: The daemon set node-ca has available replicas
        ImagePrunerAvailable: Pruner CronJob has been created

If you prefer, I can dump json encoded data into it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed to use minified JSON

status: "True"
reason: AsExpected
message: |
keyID=2 status=healthy lastChecked=2026-05-08T12:34:56Z
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This data probably should be in decodable format instead of one liner. So that it can be usable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed to use minified JSON


##### SSA mechanism

Two classes of writers share the conditions array: the operator (writing its own conditions like `NodeControllerDegraded` plus the aggregator's `KMSPluginsDegraded` rollup) and N reporter sidecars (each writing its `KMSHealthReporter_<nodeName>`). Per-entry ownership is enabled by `+listType=map +listMapKey=type` on `OperatorStatus.Conditions`. Each reporter uses a per-node fieldManager (`kms-health-reporter-<nodeName>`); all writers apply with `Force: true`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, I didn't understand anything from this paragraph :). Can we update it better clarify.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to explain the SSA dynamic. I can drop the section completely.

operator.openshift.io/v1.KubeAPIServer has a status that, where the health reporter would write conditions to. So there is a potential conflict that can be resolved by SSA with +listType=map, but this is a thing we should be cautious of.


##### Naming convention

Each reporter sidecar writes one condition per pod replica to the owning operator's CR (`kubeapiservers.operator.openshift.io/cluster`, etc.), keyed by the node:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please mention about who is going to inject this side car.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


##### Destination

The sidecar writes one advisory condition per pod replica to the owning operator's `*.operator.openshift.io/cluster` CR via Server-Side Apply. The aggregator controller reads these advisory conditions and emits the `KMSPluginsDegraded` rollup. See [KMS Plugin Health Conditions](#kms-plugin-health-conditions) for the exact naming, status mapping, and rollup behavior.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this "aggregator controller" referring to existing controller or will be implemented?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I was not sure. I would have hoped to let it handle by the conditionController.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will there be a change in condition controller?. That means there is a change in encryption controllers. I think we should reflect this what changes are expected?.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed, we will see if it works to put it into the condition controller and if not, we will create a new one.


- `State`: healthy / unhealthy / RPC error
- `KeyID`: keyID currently active in the probed plugin
- `Timestamp`: time of this check (last-check time; not the condition's `LastTransitionTime`, which records state flips only)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LastTransitionTime, which records state flips only

what does it mean ? does changing the msg counts as changing the state ?

Copy link
Copy Markdown
Contributor Author

@ibihim ibihim May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timestamp means, whenever the response from the KMS plugin arrived to the health reporter.
It explicitly is not a LTT, based on the suggestion you made earlier.

This means you can derive staleness from now > timestamp + probe interval + skew


During KMS-to-KMS migration, the encryption-configuration secret contains provider configs for all active keys. The operator creates a separate sidecar for each key, listening on its own unix domain socket (e.g., `kms-1.sock`, `kms-2.sock`).

#### Health Reporter Sidecar
Copy link
Copy Markdown
Contributor

@p0lyn0mial p0lyn0mial May 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that in general we miss:

  1. Specifying the probe interval
  2. Specifying the creds used to publish the condition
  3. Explaining what happens with orphaned conditions
  4. A plugin identity, when multiple plugins are specified - otherwise we cannot map status to a specific plugin.
  5. Listing the alternatives we discussed
  6. Specifying how the reporter connects to the apiserver. Perhaps for KAS we could go via localhost

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Yes, this is worthy to elaborate to balance detection and hammering KAS with SSA request.
  2. I assumed we would have a SA with RBAC.
  3. https://github.com/openshift/enhancements/pull/2005/changes#diff-c0e8034f61b2841cee9b2b1d07403961148c3a30f096b0d35720a57ead8d3697R660
  4. In the status of the condition each health report has its own line. I assumed that the keyID would make it unique. Does it not?
  5. Ok.
  6. See 2.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Set to 30s.
  2. legacy SA token with RBAC for now.
  3. Node + keyID
  4. oh damn. I will add that.
  5. service is slightly better, but localhost is worth mentioning.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a section in alternatives to cover the options discussed: b411a09


Each probe yields a HealthStatus carrying:

- `State`: healthy / unhealthy / RPC error
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

State or Status?

Each probe yields a HealthStatus carrying:

- `State`: healthy / unhealthy / RPC error
- `KeyID`: keyID currently active in the probed plugin
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would drop it for now. We don't know how rotation will work yet.

/cc @tjungblu

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no we should include this :) at least that makes my life easier to reference this mechanism in the other enhancement.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tjungblu work on the key rotation was the motivation to include it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talked about the possibility of using the mechanism produced by the reporter to publish information about the keys. But we still don’t know the detail, so for now I’d prefer to remove the information about the keyID until then.

I also spoke with @benluddy yesterday, and he would like to add an API instead of using conditions.

We will probably also change the health reporting later, but he agreed that initially we can use conditions for health reporting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we agreed, to show keyID and kekID for now as minified JSON in the message. Correct me if I am wrong.

keyID=1 status=healthy lastChecked=2026-05-08T12:34:56Z
- type: KMSHealthReporter_master-1
status: "False"
reason: Unhealthy
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if we should change this filed. The msg could be interpreted by the controller

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, this is a good point that would need investigation.

keyID=2 status=healthy lastChecked=2026-05-08T12:34:56Z
keyID=1 status=healthy lastChecked=2026-05-08T12:34:56Z
- type: KMSHealthReporter_master-1
status: "False"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if we should change this filed.

keyID=<id> status=unhealthy lastChecked=<RFC 3339 timestamp> detail=<healthz.detail>
```

Each line is a sequence of `key=value` pairs separated by spaces. `detail=...` is the only field whose value may contain spaces; it is always last on its line. `lastChecked` is per-plugin so partial probe failures (one plugin stuck while others probe normally) are visible.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the only field whose value may contain spaces; it is always last on its line

does it mean we need to escape spaces in the detail field ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not directly, but as I mentioned to @ardaguclu, I tried to replicate the existing format. I could use JSON if this is preferred.

@openshift-ci openshift-ci Bot requested a review from tjungblu May 11, 2026 10:27

##### Probe contract

Each sidecar probes its colocated KMS plugin(s) over the local UDS at `unix:///var/run/kmsplugin/kms-{keyID}.sock` (the same socket path scheme described in [Sidecar Injection](#sidecar-injection)).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, this states each sidecar probes its colocated KMS Plugins

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and is it the keyID though? I would assume we would allocate a unique socket path on each node like:

/var/run/kmsplugin/kms-kube-apiserver/{encryptionconfig.Name (?) }/plugin.sock

not sure whether we need to separate via folders here though

there's also no sidecar-injection section either 😄

Copy link
Copy Markdown
Contributor Author

@ibihim ibihim May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ardaguclu, is this a question? Each sidecar probes its colocated KMS Plugins. Yes, this is correct.

I added a "Naming caveat" section.

nodeName + keyID should be unique.

There is a sidecar injection section: https://github.com/ibihim/enhancements/blob/2bb10c537d05893622c0340a2b2322d6d0765abd/enhancements/kube-apiserver/kms-encryption-foundations.md#sidecar-injection

KMSHealthReporter_<nodeName>
```

The Type has no `_Available` or `_Degraded` suffix, so it stays advisory on the operator CR (library-go's `StatusSyncer` ignores it). The aggregator controller consumes these conditions and emits the `KMSPluginsDegraded` rollup separately (see [Aggregator behavior](#aggregator-behavior)).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we trying to skip StatusSyncer?

Copy link
Copy Markdown
Contributor Author

@ibihim ibihim May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on a discussion with @p0lyn0mial, a dedicated controller would create a condition that will be read by the StatusSyncer. This will reduce the amount of conditions.

So the "aggregator controller" will create a condition that will be read by the StatusSyncer.

- One per `openshift-oauth-apiserver` Deployment replica
- One per `openshift-apiserver` Deployment replica

During KMS-to-KMS migration, the same sidecar probes every active KMS plugin in its pod (see [Multiple Concurrent Sidecars](#multiple-concurrent-sidecars)) and reports their combined state in the Message field of its single per-node condition.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heh there's no multiple-concurrent-sidecars section :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hah, that was two weeks ago. I think you just forgot to push a few commits back then.


Each probe yields a HealthStatus carrying:

- `State`: healthy / unhealthy / RPC error
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the difference between RPC error and unhealthy?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The KMS Plugin can report healthy / unhealthy. An error would indicate that something is wrong with the connection itself.

reason: Unreachable
message: |
keyID=2 status=unreachable lastChecked=2026-05-08T12:34:56Z detail=connection refused
keyID=1 status=unreachable lastChecked=2026-05-08T12:34:56Z detail=connection refused
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors are usually very long (not short like connection refused). Would this formatting still work for very long error traces?

ibihim added 3 commits May 20, 2026 14:16
Reconcile terminology and arguments across the Health Reporter Sidecar
and KMS Plugin Health Conditions sections:

- Add a naming caveat distinguishing the socket-path keyID (encryption
  key secret id) from the plugin-reported kekID.
- Collapse the duplicate per-tick struct into a single
  PluginHealthCondition definition; rename KMSPluginID to KeyID.
- Drop the stale LastTransitionTime contrast (Status is now hardcoded).
- Strengthen the connection rationale with the HA-routing argument and
  a Single-Node OpenShift caveat.
- Acknowledge the legacy SA token lifetime tradeoff.
Address review nits on the health reporter design:

- Drop the deep link to AddKMSPluginSidecarToPodSpec; keep the
  pluginlifecycle package reference (function names and master URLs rot).
- Describe the socket-path keyID as a monotonically incrementing
  sequence number instead of a "revision number", which collides with
  the kas-o RevisionController concept.
- Remove the "advisory" label from the per-node condition: it is
  misleading since the aggregator does act on it. Describe the
  StatusSyncer mechanic directly instead.
- Move Auth and connection out of the Proposal into a new
  KMS Health Reporter Connectivity section under Implementation Details.
- Rename the unreachable probe status to error, since a failed Status
  RPC can fail for reasons beyond unreachability.
- Replace the GCP-style example kekIDs with short opaque values,
  decorrelated from keyID to reinforce the naming caveat.
Resolve the open probe-interval question raised in review:

- Add a Probe interval subsection under KMS Plugin Health Conditions:
  fixed 30s default passed as a sidecar flag, n UDS probes per cycle
  followed by a single SSA write of all results.
- Define emission as unconditional and best-effort: the reporter writes
  every tick so lastChecked keeps advancing, and discards a result it
  cannot deliver rather than queuing it, since the next interval
  produces a fresher one.
- Derive the aggregator staleness threshold as 4 x probe interval and
  reference it from the Stale Reporter Conditions risk.

#### KMS Health Reporter Connectivity

**Auth**: the reporter uses a **legacy ServiceAccount token** (mounted from a Secret) bound to a minimal Role that only permits applying its single per-node condition entry on the operator CR. The projected SA tokens available in API-server-adjacent namespaces are admin-grade, as are the auth client certificates on disk; both would over-privilege a sidecar whose only job is one SSA apply. The legacy SA token keeps the blast radius minimal if the sidecar is compromised. The tradeoff is lifetime: a legacy token does not expire, whereas a projected token rotates. We accept this, since a token scoped to one verb on one resource is a far smaller prize than an admin-grade token, expiring or not.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the justification here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, same here


**Risk: Orphaned Conditions on Mode Switch**
- **Impact:** When KMS is disabled (e.g., switching to `aescbc`), reporter sidecars are removed. Without explicit cleanup, `KMSHealthReporter_<nodeName>` and `KMSPluginsDegraded` entries remain stale on the operator CR.
- **Mitigation:** The aggregator controller owns cleanup. It removes orphaned `KMSHealthReporter_<nodeName>` entries (when their owning sidecar is no longer present) and removes its own `KMSPluginsDegraded` entry on KMS disable.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In SSA, will there be a fight between health reporter that tries to update the condition and aggregator controller that tries to cleanup the condition?.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's going to be a big problem when they stomp on each other, we might have some flapping during non-ready/deleted states though.

I wonder if we rather have top level condition of the KMSHealthReporter_<nodeName> owned by the aggregator controller.

Kinda similar to how the node controller works:
https://github.com/openshift/library-go/blob/3a6f949c22c3ffc0a2b28ee50e78173086e7adae/pkg/operator/staticpod/controller/node/node_controller.go

the sidecar would only ever write to it when the condition for said node exists. Sounds like too much coordination for a few conflicts between node state transitions...

for the later API, we could just use a map and key it off the nodename directly https://github.com/openshift/api/pull/2850/changes#diff-616d67895c3421c2d091662d30ed47b4b6f0b57db9411e29618d22b964ddb9efR362

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know better than me those conditions. In SSA, it is always better a controller fully owns the field.

Copy link
Copy Markdown
Contributor Author

@ibihim ibihim May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The *Degraded, *Available and so on are owned by the aggregator controller.
The aggregator controller itself would remove KMSHealthReporter_ that are owned by the KMS health reporters, not write into them. So there is some flapping, but it shouldn't be for too long / it shouldn't be a problem. Because the removal happens during garbage collection.

We could tear down the health reporter down before we tear down the KMS plugin, so we don't have wrong error conditions.

@ardaguclu
Copy link
Copy Markdown
Member

I have just one question. Other than that changes look good to me.

- **Impact:** A reporter that hangs leaves its last `KMSHealthReporter_<nodeName>` condition in etcd unchanged.
- **Mitigation:** Per-plugin `lastChecked` timestamps in Message expose staleness. The aggregator controller treats a condition whose `lastChecked` exceeds the freshness threshold (`4 × probe interval`; see [Probe interval](#probe-interval)) as effectively `Unknown`.

**Risk: Orphaned Conditions on Mode Switch**
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
**Risk: Orphaned Conditions on Mode Switch**
**Risk: Orphaned Conditions on Mode Switch**
**Risk: Orphaned Conditions on encryption type change and node replacement**

Just wanted to ask who is cleaning up the old orphaned nodes when a node goes away, seems it is just buried in the Mitigation here :) Let's make the title clearer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Encryption type change is more accurate, but "node replacement", is this something that is a major concern? I mean we can add it, it is correct.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can scale the control plane up and down anytime, so yes, this is kinda important

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change it. Can't apply your commit as it contains the previous line as well.

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 26, 2026

@ibihim: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants