# Cloud secrets, KMS and rotation

Separate encoding, encryption, access and rotation. Complete new-secret creation with safe consumer migration and revocation of old authority.

Original AI-assisted SecNet educational content. Fictional local examples; not a standards provider publication, endorsement or compliance evidence. Page exercises do not create account progress.

Sources checked: 2026-10-21

## Encoding is not encryption

Base64 values in Kubernetes Secrets are easily decoded, not confidentiality evidence. Storage encryption, API authorization and in-app use are separate layers. Do not copy values into logs, crash reports or CI output. Review who can decrypt through KMS; ciphertext plus broadly granted decrypt authority is not a meaningful boundary. No real Secret is uploaded here; models use fictional evidence fields only.

## Treat rotation end to end

Creating a new version is only the first step. Test consumer refresh, restart requirements, old-credential revocation and failure behavior during transition together. Check that old values are not logged. A compatible transition window may be necessary before revocation; assign an owner and expiry. If the old credential still authorizes actions, the security purpose of rotation is incomplete.

## Keys and backup dependencies

Encrypted backups cannot be recovered without keys. Include key access, a separate recovery identity and secret-provider dependencies in recovery planning. Verify provider-specific key recovery rules; do not assume every KMS key is exportable. Assess whether recovery authority shares the application administrator’s compromise domain. Preserve controlled restoration observations rather than plan text alone.

## Worked example: a new version, old token

The Harbor API receives a new token, but the prior token remains valid in another running Pod. newVersionCreated=true does not establish completed rotation. The model separately checks consumerRefreshed and oldCredentialRevoked. Record synthetic-identity outcomes instead of token values in actual evidence. Falling back to plaintext storage or disabling validation after refresh failure is not an acceptable failure policy. Link revocation and impact investigation to the rotation record when responding to an incident.

## Working sequence

1. Inventory secret, consumer and key relationships.
2. Distinguish encoding, encryption and access.
3. Migrate consumers to the new version under control.
4. Revoke old authority and run a negative test.
5. Test key and secret recovery together.

## 1. Does base64 protect it?

Encoding is neither storage encryption nor scoped access. A log copy still requires rejection under this profile even when other controls exist; no real secret is modeled.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.base64
```

### Corrected implementation
```js
s=>s.base64&&s.atRestEncrypted&&s.rbacScoped&&!s.logged
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "controlled-secret",
      "input": {
        "base64": true,
        "atRestEncrypted": true,
        "rbacScoped": true,
        "logged": false
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "encoding-only",
      "input": {
        "base64": true,
        "atRestEncrypted": false,
        "rbacScoped": false,
        "logged": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "log-copy",
      "input": {
        "base64": true,
        "atRestEncrypted": true,
        "rbacScoped": true,
        "logged": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "controlled-secret",
      "input": {
        "base64": true,
        "atRestEncrypted": true,
        "rbacScoped": true,
        "logged": false
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "encoding-only",
      "input": {
        "base64": true,
        "atRestEncrypted": false,
        "rbacScoped": false,
        "logged": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "log-copy",
      "input": {
        "base64": true,
        "atRestEncrypted": true,
        "rbacScoped": true,
        "logged": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 2. Old authority still works

Verify creation, consumer migration and old-authority revocation in one process. Also require safe denial on refresh failure; incrementing a version is insufficient.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.newVersionCreated
```

### Corrected implementation
```js
s=>s.newVersionCreated&&s.consumerRefreshed&&s.oldCredentialRevoked&&s.failedRefreshDenied
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "end-to-end-rotation",
      "input": {
        "newVersionCreated": true,
        "consumerRefreshed": true,
        "oldCredentialRevoked": true,
        "failedRefreshDenied": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "old-still-valid",
      "input": {
        "newVersionCreated": true,
        "consumerRefreshed": true,
        "oldCredentialRevoked": false,
        "failedRefreshDenied": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "stale-consumer",
      "input": {
        "newVersionCreated": true,
        "consumerRefreshed": false,
        "oldCredentialRevoked": true,
        "failedRefreshDenied": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "end-to-end-rotation",
      "input": {
        "newVersionCreated": true,
        "consumerRefreshed": true,
        "oldCredentialRevoked": true,
        "failedRefreshDenied": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "old-still-valid",
      "input": {
        "newVersionCreated": true,
        "consumerRefreshed": true,
        "oldCredentialRevoked": false,
        "failedRefreshDenied": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "stale-consumer",
      "input": {
        "newVersionCreated": true,
        "consumerRefreshed": false,
        "oldCredentialRevoked": true,
        "failedRefreshDenied": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 3. Can the key be recovered?

An encrypted backup does not establish recoverable keys or separate recovery authority. A recoverable claim needs an actual restoration test.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.backupEncrypted
```

### Corrected implementation
```js
s=>s.backupEncrypted&&s.keyRecoverable&&s.independentIdentity&&s.restoreTested
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "recoverable",
      "input": {
        "backupEncrypted": true,
        "keyRecoverable": true,
        "independentIdentity": true,
        "restoreTested": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "lost-key",
      "input": {
        "backupEncrypted": true,
        "keyRecoverable": false,
        "independentIdentity": true,
        "restoreTested": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "same-compromise",
      "input": {
        "backupEncrypted": true,
        "keyRecoverable": true,
        "independentIdentity": false,
        "restoreTested": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "recoverable",
      "input": {
        "backupEncrypted": true,
        "keyRecoverable": true,
        "independentIdentity": true,
        "restoreTested": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "lost-key",
      "input": {
        "backupEncrypted": true,
        "keyRecoverable": false,
        "independentIdentity": true,
        "restoreTested": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "same-compromise",
      "input": {
        "backupEncrypted": true,
        "keyRecoverable": true,
        "independentIdentity": false,
        "restoreTested": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## Exit evidence

- Secret-consumer-key inventory without values
- Old-credential denial result and restore record

## References

- [Kubernetes · Secrets](https://kubernetes.io/docs/concepts/configuration/secret/)
- [Kubernetes · At-rest encryption](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)
