Conversation
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR introduces secure handling of secret references in
Configresources. Config blobs cannow embed
secret::<secretName>::<keyName>placeholders that are resolved against KubernetesSecrets at apply time. Resolved values are AES-256-GCM encrypted and never stored in plaintext
or cached in the informer cache.
Two new CRDs are introduced (
SensitiveConfig,TargetSnapshot), one new controller(
Resolver), and the existingTargetConfigandTargetRecoverycontrollers are refactored touse the encrypted resolved state as their source of truth.
Motivation
Previously, Config resources had no mechanism for referencing sensitive values such as passwords
or tokens. Any credentials embedded in a Config blob were stored in plaintext in the API server
and visible to any controller or process with Config read access. This PR closes that gap.
Secret Reference Format
Placeholders are embedded as string values anywhere inside the Config blob JSON:
Example Config:
The original
Config.Spec.Configalways retains thesecret::placeholder. The resolved valueexists only in the encrypted
SensitiveConfig.Spec.Payload.Dataand is never written back tothe Config resource.
Architecture
Controller Responsibilities
New CRDs
SensitiveConfig— one per Config, same name and namespace. Carries the encrypted resolvedblobs and the metadata needed for change detection:
TargetSnapshot— one per Target, same name and namespace. Records the exact resolved andencrypted blobs that were last confirmed to the datastore. Used as the sole source of truth for
crash recovery —
Config.Status.AppliedConfighas been removed.Resolver Controller
The Resolver watches
Configresources and produces aSensitiveConfigfor each one.Secret access: Uses
mgr.GetAPIReader()exclusively — a direct API call that bypasses theinformer cache. Raw secret values are never held in memory beyond the scope of a single reconcile.
Always encrypts: Configs with no secret references are still encrypted. This keeps the
TargetSnapshot self-contained — recovery never needs to fall back to
Config.Spec.Config, whichmay have changed since the last confirmed transaction.
Change detection evaluates three criteria on every reconcile, without early exit, so that
per-dimension annotations accurately reflect what changed:
keyringChanged— whether the payload was encrypted with a non-primary key (free, no API call)configChanged—sha256(cfg.Spec.Config)vs the storedconfigHashsecretChanged— per-keysha256(secret.Data[key])vs storedsecretKeyHashesBased on the result:
On resolution failure (secret missing, key not found), the Resolver sets
ConfigResolverFailedon the Config and preserves the last known good SensitiveConfig so the TargetConfig controller
continues operating. It requeues after 30 seconds.
Deletion: The Resolver only removes its own finalizer. It does not delete the
SensitiveConfig. The SC must remain alive as the record of what was applied and as the failure
surface if the datastore delete fails. The TargetConfig controller deletes the SC only after
the datastore deletion is confirmed.
TargetConfig Controller
Change detection compares the current SensitiveConfig against the TargetSnapshot entry:
Transaction flow is split into pure gRPC steps and pure Kubernetes steps with an explicit
cancel on failure:
Deletion flow: When a Config gets a
deletionTimestamp, the TargetConfig controller istriggered via its Config watch (since the Resolver keeps the SC alive, no SC deletion event
fires). The delete intent is built from the Config alone — the SC is not needed for the gRPC
call. After
TransactionConfirm,ProcessSuccessexplicitly deletes the SC.path not foundon delete: When the datastore returns"path not found in tree"for adelete intent, it is treated as idempotent success — the path was already absent, the desired
state is achieved. The Config finalizer, deviation, and SC are cleaned up normally.
TargetRecovery Controller
After a restart, the TargetRecovery controller replays the last confirmed state from the
TargetSnapshot. For each snapshot entry with a matching live Config, it decrypts the payload and
sends it to the datastore with
PreviouslyApplied: true. Configs not present in the snapshotwere never successfully applied and are skipped — the normal reconcile handles them after
recovery completes.
Config.Status.AppliedConfighas been removed. The TargetSnapshot is the only recovery source.KeyRing
Encryption keys are stored in a Kubernetes Secret (identified by the label
config.sdcio.dev/keyring: "true") as a JSON blob:{ "primary": "key-1", "keys": { "key-1": "<base64 of 32 random bytes>" } }The Resolver loads the KeyRing at startup via
mgr.GetAPIReader()and reloads it in-placewhenever the Secret changes. All encrypt/decrypt operations are safe for concurrent use.
Key rotation: Add a new key ID and bytes to
keys, updateprimary. The Resolver detectsNeedsReencryptionon each SensitiveConfig and re-encrypts them without refetching secrets.Once all SensitiveConfigs carry the new
keyID, the old key entry can be removed from theSecret.
Generating key material:
The base64-encoded result goes into
data["keyring.json"]in the Secret.