Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
303 changes: 303 additions & 0 deletions enhancements/kube-apiserver/kms-encryption-foundations.md
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,309 @@ These rollup conditions (`KMSPluginsDegraded`, and any future `KMSPluginsAvailab

The plan is to extend the existing [`conditionController`](https://github.com/openshift/library-go/blob/master/pkg/operator/encryption/controllers/condition_controller.go) in library-go's encryption controllers, which already emits the `Encrypted` condition on the same operator CR. It sits in the right call path (operator CR → ClusterOperator) and runs on the informer set the rollup needs. If extending it turns out to be a poor fit (conflicting sync triggers, unrelated dependencies that make the rollup hard to reason about), a dedicated controller will be introduced instead.

#### KMS Plugin Image Verification and Certification

Vendors must sign their images using cosign, and run the certification test suite described in this document before Red Hat can officially support the vendor's KMS plugin. In order to provide official support for KMS plugin configuration, we will include a pointer to officially vendor supported and signed images in the OCP payload. This will ensure that only plugin images that can prove their validity and are officially supported can run on the OCP platform.

##### Image Signature and Verification

We advise vendors to sign their images with cosign using public key encryption. OpenShift must be able to verify image signatures in air-gapped clusters, and public key encryption is best suited for this scenario.

###### Signing Images with cosign v3

cosign v3 defaults to storing signatures as OCI 1.1 referrers (which are not visible as tags). To create signatures compatible with OpenShift, vendors must use the `--new-bundle-format=false` and `--use-signing-config=false` flags to force creation of legacy `.sig` tags.

**Step-by-step signing process:**

1. **Generate cosign key pair**:
```bash
cosign generate-key-pair
# Creates cosign.key (private, keep secure) and cosign.pub (public, distribute to customers)
```

2. **Build and push your KMS plugin image**:
```bash
# Build the image
podman build -t registry.example.com/kms-plugin:v1.0.0 .

# Push to registry
podman push registry.example.com/kms-plugin:v1.0.0
```

3. **Get the image digest**:
```bash
DIGEST=$(skopeo inspect docker://registry.example.com/kms-plugin:v1.0.0 | jq -r '.Digest')
echo "Image digest: $DIGEST"
```

4. **Sign the image with compatibility flags**:
```bash
COSIGN_PASSWORD=<your-password> cosign sign \
--key cosign.key \
--new-bundle-format=false \
--use-signing-config=false \
registry.example.com/kms-plugin@${DIGEST} \
--yes
```

5. **Verify the .sig tag was created**:
```bash
# Check for signature tag (should show sha256-<digest>.sig)
skopeo list-tags docker://registry.example.com/kms-plugin | jq -r '.Tags[]' | grep ".sig"
```

To generate the base64-encoded public key for the `keyData` field that the KMS encryption API in OCP will need:
```bash
cat cosign.pub | base64 -w 0
```

**Why these flags are required:**
- `--new-bundle-format=false`: Forces cosign to use the legacy tag-based signature format instead of OCI 1.1 referrers
- `--use-signing-config=false`: Required when using `--new-bundle-format=false` to disable TUF-provided signing configuration

**Note:** These flags will be removed in cosign v4. When containers/image adds OCI 1.1 referrers support in future OpenShift versions, vendors can use cosign v3+ default behavior without compatibility flags.

###### ImagePolicy Cluster Configuration

The cluster operators will ensure that only verified images can be pulled and run from the cluster. To ensure that, when the KMS plugin is configured, the operators will check the cluster for a scoped ClusterImagePolicy with a well formed name, scope and policy.

To do this, when the controllers detect that KMS mode is configured and the operator is configuring and deploying the plugin, it will first detect which supported KMS plugin is being rolled out. It will then fetch the correct well known ClusterImagePolicy that matches the metadata for the plugin it expects. If the ClusterImagePolicy match the criteria that is embedded in the OCP payload, installation of the KMS plugin will continue.

**Note:** All supported plugin metadata, including registry image path and public key data, will be hardcoded and included in the OpenShift payload. That data will be provided by KMS plugin vendors to OCP Engineering.

###### Customer Configuration

Users must create a `ClusterImagePolicy` resource to configure OpenShift to verify the image signature every time CRI-O pulls a plugin image. Users should obtain the vendor's public key from the vendor's official documentation (e.g., HashiCorp's documentation for the Vault KMS plugin).

`ClusterImagePolicy` example:
```yaml
apiVersion: config.openshift.io/v1
kind: ClusterImagePolicy
metadata:
name: vault-kms-plugin
labels:
config.openshift.io/type: kms
spec:
scopes:
- docker.io/hashicorp/vault-plugin-kms # this must match the kmsPluginImage in the APIServer config
policy:
rootOfTrust:
policyType: PublicKey
publicKey:
# base64-encoded cosign public key (cosign.pub)
keyData: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFLi4uCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=
signedIdentity:
matchPolicy: MatchRepository
```

In order for the API Server Operators to validate that the installed KMS plugin is certified and supported by OCP, it must be defined in a well known way. The fields that match must include the following:

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 get that we want to also validate that this has been created, it's certainly better than "trust me bro"...

We would need 1:n support (for the transition case below), so we would need to hardcode a set of policy names (?) we expect? Or is this just structural - in which case any image policy would match?

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.

Can an image have more that one .sig tag ? If yes, then maybe key rotation could be backward compatible.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We just need to validate that there is a policy specified, not the specific policy type. As long as we are verifying that there is a signature required to pull the image we should satisfy the problem. The only other requirement is that the scope is set to a well known path so that we restrict the location that we are pulling the image from just to a place that we know is supported. That field we will compare to a well defined value.

@tjungblu tjungblu Jul 10, 2026

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.

just in plain terms:

  • admin configures a vault kms image in the encryption cfg
  • aggregate apiserver operator(s) list all cluster image policies
  • ... and verify that there is a policy.scope that is matching the configured image

is that it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes


```
metadata.name
spec.scopes
spec.policy.rootOfTrust.policyType

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.

do i understand correctly that the operator will also verify keyData against the pub key from the release payload ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, the operator just needs to know that a policy is specified and that a key is required. We will rely on the policy itself to enforce that it is valid.

spec.policy.matchPolicy
```

As part of our KMS documentation, we will publish a mapping of the fields that are required for each KMS plugin.

Additionally, users must set the `spec.policy.rootOfTrust.publicKey.keyData` field to the base64 encoded public key that the image was signed with. Users should refer to the vendor's official documentation for the most current public key to use in their `ClusterImagePolicy`.

When vendors release images signed with a new key, users must create a new `ClusterImagePolicy` resource with the updated public key. During the transition period, both old and new `ClusterImagePolicy` resources must exist in the cluster until the previous `ClusterImagePolicy` is no longer needed (i.e when migration to the new plugin image is finished).

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 got the impression that the operator will ensure that the proper key is used in the policy, which comes from the release payload.

However, this paragraph suggests that there will be cases where the operator does not actually validate the origin of a plugin.

Is that correct?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We don't really need to validate the key matches what we expect, we just need to validate that there is a key and that it is used for a specific image repository. Previously we discussed the idea that we would have to package it into the operator, but that was for mechanical reasons (we wanted the UX of this getting generated automatically so a user doesn't have to deal with it). Since that can't happen either way because of the problems that poses to disconnected workflows, we might as well let a user specify it at runtime (or, eventually, by a mirroring tool automatically).

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.

See here for my concerns about simultanous-ClusterImagePolicies as a key-rotation process. High level summary is that my current read of the code is that you'd need the signer to backsign existing images with the new key and to continue to sign new images with the old key for however long it took the fleet to transition to the new images. I think a convenient solution would be to propose an extension to the (Cluster)ImagePolicy API to support "signed by any key from a set" semantics, which RHEL/CRI-O already support since RHEL-22610 / containers/image#2526.

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.

Can an image have more that one .sig tag ? If yes, then maybe key rotation could be backward compatible.

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.

Since the metadata.name is unique we cannot have two policies with the same name.


###### Overriding Configuration

There are cases where a user needs the ability to override this configuration in order to be able to use an unsupported plugin image -- for example if there is a hotfix that needs to be handed to a user urgently or even in certain testing scenarios. We will enable this by adding an override field to the UnsupportedConfigOverrides cluster API that will disable the deployment of the ClusterImagePolicy. This will put the cluster in an unsupported state that will need to be resolved before upgrading the cluster, but it will allow a user a temporary workaround to running unsupported or unverified plugin images.

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.

if the customer manages the ClusterImagePolicy, they also have two options:

  • remove the ClusterImagePolicy
  • update the public key to match the hotfix version

Not sure we would need to create an override in this case


`UnsupportedConfigOverrides` in cluster config API example:
```yaml
apiVersion: operator.openshift.io/v1
kind: KubeAPIServer
metadata:
name: cluster
spec:
unsupportedConfigOverrides:
encryption:
kms:
ignoreImagePolicy: true
```

When that override is set, users will be able to apply KMS plugin configuration to the cluster without image verification.

###### Disconnected Concerns

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.

it seems like the operator could read the IDMS/ITMS resources to discover the mirror path, which would allow the operator to fully own the policy.

isn't IDMS/ITMS the standard way to configure image mirroring in OpenShift?

do users already create these resources as part of a disconnected installation?

@sherine-k maybe you could advice on ^

if we require IDMS/ITMS and have the operator read them, then the operator has everything it needs to fully manage the ClusterImagePolicy.


When a cluster is disconnected from external networks or airgapped, the ClusterImagePolicy defined by the cluster will not be sufficient because the cluster will not have access to an external registry to pull the image. OpenShift has tools and APIs that enable core component images and layered operators to be mirrored into disconnected environments in mirror registries (oc-mirror, IDMS, ITMS). In a future iteration, we will update oc-mirror to understand how to mirror KMS plugins and provide the correct set of manifests to apply to the cluster so that plugins can be run out of the box in a seamless experience. For now, we are putting that work out of scope of this initial enhancement.

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.

@sherine-k sorry to tag you directly and I hope you're still working on oc-mirror - is this something that is feasible from your end?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hi @tjungblu, I'm from the oc-mirror team. Are KMS plugins packaged as container images? Are they components of an Openshift release or are they part of operator catalog or helm charts?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hey Alex! In the current version of this proposal, they are container images that are configured to run alongside the API Servers. That data would be packaged somehow as part of the OpenShift payload. Part of this proposal is about defining where and how that metadata is stored / configured.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If this is going to follow the same standard as the other components and it will be referenced in the image-references, oc-mirror will be capable of recognizing it and mirror its container images.

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.

@aguidirh AFAIK, oc-mirror already generates IDMS/ITMS and is capable of mirroring signatures alongside their images. Right?
Does oc-mirror generate ClusterImagePolicy for disconnected clusters already? or is this the gap that probably will be needed?

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.

oc-mirror does not generate ClusterImagePolicies today, but an openshift ClusterImagePolicy is baked into the OCP payload and applied by the cluster-version operator today, to cover scopes relevant to OCP payloads. The mechanics are discussed in an existing enhancement. The hard-coded canonical pullspecs in the openshift ClusterImagePolicy and in the Sigstore signatures themselves work in disconnected/restricted-network environments, because the image is retrieved via its canonical pullspec, and CRI-O consumes the ImageDigestMirrorSets to find those images in the local mirrors (which must also host the Sigstore signatures). oc-mirror creates those ImageDigestMirrorSets, and helps ensure that the local mirrors have both the OCP-payload images and the Sigstore images that sign those OCP-payload images.


To manually configure the mirror, the ClusterImagePolicy API has the `spec.policy.signedIdentity.remapIdentity` field that allows users to point the signed image to a mirrored registry.

`ClusterImagePolicy` example for disconnected:
```yaml
apiVersion: config.openshift.io/v1
kind: ClusterImagePolicy
metadata:
name: vault-kms-plugin
labels:
config.openshift.io/type: kms
spec:
scopes:
- mirror.internal.registry.com:5000/internal/mirrored-vault-plugin-kms # mirrored KMS plugin image

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.

Question: Here we're not talking about fetching the publicKey for the rootOfTrust from any KMS or anything, correct?
And we are talking about fetching images for the KMS plugin only, right?
We're not talking about online signature verification (i.e. providing a way to reach the sigstore components (Rekor, TUF, Fulcio, etc...) for signature verification: by default the signature verification is offline and doesn't require connections to sigstore.)

policy:
rootOfTrust:
policyType: PublicKey
publicKey:
# base64-encoded cosign public key (cosign.pub)
keyData: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFLi4uCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=
signedIdentity:
matchPolicy: MatchRepository
remapIdentity:
prefix: mirror.internal.registry.com:5000/internal/mirrored-vault-plugin-kms # mirrored KMS plugin image
signedPrefix: docker.io/hashicorp/vault-plugin-kms # this must match the kmsPluginImage in the APIServer config
```

Once a user has applied the ClusterImagePolicy the KMS plugin installation can continue.

##### Certification

KMS plugin certification follows the CSI driver certification model. A dedicated test suite will be added to the `openshift-tests` binary, which vendors can extract from an OpenShift release payload and run in a test cluster with their KMS plugin installed.

The certification test suite validates:
- KMS plugin compliance with the Kubernetes KMS v2 API
- Encrypt/decrypt operations under various conditions
- Plugin behavior during upgrades and migrations

###### Extracting the Test Binary and Running the Test Suite

The KMS encryption test suite may take several hours to finish and is marked as disruptive (it may affect cluster stability during testing). Vendors are advised to run it in a dedicated test cluster within a CI/CD environment where test output is persisted. The test output is required to validate KMS plugin compatibility with OpenShift.

**Prerequisites:**
- An OpenShift cluster (version matching the target certification version)
- KMS plugin configured via OpenShift APIServer configuration API
- `oc` CLI tool installed and authenticated to the cluster
- At least 1GB of disk space for test binary extraction and test output

**Step 1: Extract the openshift-tests binary from the release payload**

```bash
# Set the release image you want to certify against
RELEASE_IMAGE="quay.io/openshift-release-dev/ocp-release:4.22.0-x86_64"

# Get the tests image reference from the release payload
TESTS_IMAGE=$(oc adm release info ${RELEASE_IMAGE} --image-for=tests)
echo "Tests image: ${TESTS_IMAGE}"

# Extract the openshift-tests binary
mkdir -p ./kms-certification
oc image extract ${TESTS_IMAGE} \
--path /usr/bin/openshift-tests:./kms-certification \
--confirm

# Make the binary executable
chmod +x ./kms-certification/openshift-tests

# Verify extraction
./kms-certification/openshift-tests version
```

**Step 2: Run the KMS certification test suite**

```bash
# Set your kubeconfig (if not already set)
export KUBECONFIG=/path/to/your/kubeconfig

# Run the full KMS test suite
# Note: This may take several hours and is disruptive to the cluster
./kms-certification/openshift-tests run openshift/kms \
--junit-dir=./kms-certification/junit \
-o ./kms-certification/kms-test-output.log

# The exit code indicates success (0) or failure (non-zero)
echo "Test run exit code: $?"
```

**Step 3: Review test results**

```bash
# Check the test output log
less ./kms-certification/kms-test-output.log

# Review JUnit XML results (for CI integration)
ls -lh ./kms-certification/junit/

# Count passed/failed tests
grep -c "PASS:" ./kms-certification/kms-test-output.log
grep -c "FAIL:" ./kms-certification/kms-test-output.log
```

**Test Suite Configuration:**

The `openshift/kms` test suite is configured with the following parameters:
- **Parallelism:** 1 (tests run sequentially to avoid conflicts)
- **Timeout:** 4 hours
- **Stability:** Disruptive (may affect cluster operations)
- **Scope:** Aggregates all KMS encryption tests across kube-apiserver, openshift-apiserver, and oauth-apiserver
- **Exclusions:** Automatically excludes tests marked as `[Flaky]` or `[Disabled:]`

**What the Test Suite Validates:**

The test suite performs comprehensive validation including:

1. **KMS v2 API Compliance:**
- Status, Encrypt, and Decrypt gRPC method correctness
- Proper error handling and timeout behavior
- Health check responses

2. **Encryption Operations:**
- Successful encryption and decryption of secrets and configmaps
- Concurrent encrypt/decrypt operations under load
- Round-trip data integrity verification

3. **Migration Scenarios:**
- identity → KMS migration
- KMS → identity migration
- KMS → KMS migration
- aesgcm/aescbc ↔ KMS migrations

4. **Operational Behaviors:**
- Plugin startup and initialization
- Graceful handling of KMS service unavailability
- API server restart with KMS encryption enabled
- Multi-plugin scenarios during migration

5. **OpenShift Integration:**
- Sidecar container lifecycle
- Credential management from Secrets
- ConfigMap integration for CA bundles
- Pre-flight checker validation

**Submitting Certification Results:**

After successful completion, vendors should provide:
1. Full test output log (`kms-test-output.log`)
2. JUnit XML files from the `junit/` directory
3. OpenShift version and KMS plugin version tested
4. Any test failures with root cause analysis and remediation plans
Comment on lines +1003 to +1007

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.

talking about "trust me bro", how do we prevent folks from just submitting all junit tests as PASSED?


**Troubleshooting Common Issues:**

```bash
# If tests fail to connect to the cluster
export KUBECONFIG=/path/to/correct/kubeconfig
oc whoami # Verify authentication

# If tests timeout
# Increase timeout with: --timeout=6h
./kms-certification/openshift-tests run openshift/kms --timeout=6h

# To run a subset of tests for debugging
# First, list the tests with --dry-run, then filter specific ones
./kms-certification/openshift-tests run openshift/kms \
--dry-run | grep "specific-test-name" | \
./kms-certification/openshift-tests run -f -

# For verbose output during test run
./kms-certification/openshift-tests run openshift/kms -v=4
```

### Risks and Mitigations

**Risk: KMS Plugin Unavailable During Controller Sync**
Expand Down