Skip to content

Add OCI Vault: vaults, keys, secrets - #425

Open
arunesh-j wants to merge 2 commits into
developmentfrom
feat/oci-vault
Open

Add OCI Vault: vaults, keys, secrets#425
arunesh-j wants to merge 2 commits into
developmentfrom
feat/oci-vault

Conversation

@arunesh-j

Copy link
Copy Markdown
Collaborator

Summary

  • Implements OCI Vault against the existing portable secrets driver.
  • OCI Vault is a superset of that driver — it puts KMS key management behind the secrets service — so most of the surface lives in a consumer-side Extras interface. Nothing added to services/secrets/driver, per Move OCI-only capabilities out of shared driver packages #393.

Closes #413. Part of #376.

Changes

  • providers/oci/vault/Mock over memstore implementing driver.Secrets, one sync.RWMutex guarding all four stores: vaults, master encryption keys and key versions, OCI-shaped secrets, secret versions and stages.
  • server/oci/vault//20180608 management and /20190301 retrieval, claimed separately in Matches.
  • Wiring is one line each in providers/oci/oci.go and server/oci/oci.go.

How DeleteSecret maps onto scheduled deletion

The interesting design question in this service. DeleteSecret(name) takes the same path as ScheduleSecretDeletion, with timeOfDeletion at OCI's soonest permitted value — one day out — rather than the 30-day default an unspecified wire call receives.

The secret moves to PENDING_DELETION, keeping its OCID and every version. All seven portable operations then treat it as gone (NotFound from Get/Put/GetValue/ListVersions/Delete; absent from ListSecrets), while the OCI surface still lists it as PENDING_DELETION and CancelSecretDeletion restores it — the same soft delete the AWS Secrets Manager mock exposes. Nothing reaps; a scheduled resource stays pending until cancelled.

One deliberate consequence: a pending secret releases its name, so a portable delete→recreate works. CancelSecretDeletion then fails with AlreadyExists if the name was retaken, rather than producing two live secrets with the same name.

Deletion windows are enforced — 7–30 days for vaults and keys, 1–30 for secrets; out-of-window or unparseable timeOfDeletion is a 400.

Judgement calls

  • Portable creates need a vault. OCI has no secret outside one, so the first portable CreateSecret mints cloudemu-default-vault and an AES-256 key. Tested that a second create reuses them, and that 16 concurrent creates mint exactly one vault.
  • Crypto endpoints are claimed, not faked. encrypt/decrypt/sign/verify/generateDataEncryptionKey/exportKey answer 501 naming the gap, because CloudEmu stores no key material. A fake ciphertext would be worse than an honest refusal.
  • Compartments: RequireCompartmentID on /vaults, /keys, /secrets; deliberately not on keyVersions, secrets/{id}/versions, getByName or any /20190301 path — the real API takes none there and the parent resource already names the compartment.
  • Status codes follow the SDK response shapes: 200 + body for vault/key mutations (ScheduleVaultDeletionResponse carries a Vault), 204 headers-only for secret and version deletions (those responses carry no body), 202 for changeCompartment.
  • Never accept-and-ignore — 400 naming the field for definedTags, restoreFromFile, restoreFromObjectStore, externalKeyManagerMetadata, autoKeyRotationDetails, externalKeyReference, desiredState, secretRules, rotationConfig, secretGenerationContext, a non-BASE64 contentType, and reshaping a key after create.

Provider Coverage

  • AWS
  • Azure
  • GCP
  • OCI

Checklist

  • All tests pass (go test ./...) — exit 0, 272 packages
  • Linter passes (golangci-lint run --timeout=9m) — 0 issues
  • Every provider the change applies to implements the same behavior — OCI-only, additive
  • Integration tests added to cloudemu_test.go — driver + handler tests instead
  • Unit tests added to provider test files

Test Plan

go build ./...                                              clean
go test ./...                                               exit 0, 272 packages
go test -race ./providers/oci/... ./server/oci/...          11/11 ok
golangci-lint run --timeout=9m ./providers/oci/... ./server/oci/...   0 issues
go generate ./...                                           docs/coverage committed

Coverage leak check clean: no OCI operation in docs/coverage/{aws,azure,gcp}/*.md; git diff development -- services/ empty. docs/coverage/oci/vault.md lists exactly the 7 portable operations, as expected with capabilities consumer-side.

Lock sweep over all 63 *Mock methods: every one of the 42 exported methods takes m.mu exactly once, no locking method calls another locking method, no unexported helper touches m.mu, and there is no store.Update anywhere — so no closure can re-enter its store.

End-to-end on a running server (port 4614):

create vault -> create key -> create secret        -> opc-work-request-id stamped
write version 2                                    -> 200
read CURRENT bundle                                -> "hunter3"
read PREVIOUS stage                                -> "hunter2"
version stages                                     -> [PREVIOUS] / [CURRENT,LATEST]
schedule secret deletion                           -> 204, PENDING_DELETION, timeOfDeletion 2026-09-20
cancel deletion                                    -> 204, ACTIVE
list without compartmentId                         -> 400
poll work request                                  -> CREATE_VAULT, SUCCEEDED
rotate key (new key version)                       -> 200
encrypt                                            -> 501, names the gap

OCIDs came out as ocid1.{vault,key,keyversion,vaultsecret}.oc1.iad.….

A note on parallel worktrees

The session first reported cmd/cloudemu TestServeOutOfProcess as a pre-existing failure. That was wrong and I have re-checked it: with nothing else running, the test passes on clean development in under 6s. The failure is contention on the shared ~/.cloudemu daemon lock between the six Wave 2 worktrees running suites in parallel. This branch's full suite is exit 0.

Left out

KMS crypto operations (disclosed as 501), OCI secret rules and rotation targets, tag namespaces, and vault backup/restore — each rejected by name rather than silently dropped.

Implements OCI Vault against the portable secrets driver.

providers/oci/vault holds a Mock over memstore.Store carrying vaults,
master encryption keys and their versions, secrets, and secret versions
with OCI's CURRENT/PENDING/PREVIOUS/DEPRECATED stages. Deletion is
scheduled rather than immediate throughout: a vault, key, secret or
version moves to PENDING_DELETION with a timeOfDeletion inside OCI's
window and stays there until the deletion is cancelled.

The portable DeleteSecret maps onto ScheduleSecretDeletion at the
soonest OCI permits, one day out. The portable operations then treat the
secret as gone while the OCI surface still lists it and can restore it —
the same soft delete the AWS Secrets Manager mock exposes. OCI has no
secret outside a vault, so the portable create mints one vault and one
AES key on first use.

server/oci/vault claims /20180608 for KMS and secret management and
/20190301 for the secret-retrieval data plane. Vaults, keys and rotation
are OCI-only, so they reach the driver through a consumer-side Extras
interface rather than the portable one; a driver that does not satisfy
it is served 501. Mutations OCI runs asynchronously record a work
request and stamp opc-work-request-id. The KMS crypto endpoint shares
the management prefix and is claimed only to report that CloudEmu stores
no key material.
Collapses the three changeCompartment handlers and the per-resource
mutation tails onto shared helpers, names the path segment positions
rather than indexing by number, passes the create specs by pointer, and
splits the secret router so the version sub-collection routes on its
own.

docs/coverage/oci/vault.md lists the seven portable operations and
nothing more: vaults, keys and rotation reach the driver through the
handler's Extras interface, so they stay out of the AWS, Azure and GCP
coverage docs.
@arunesh-j arunesh-j added the oci Oracle Cloud Infrastructure label Aug 21, 2026

@NitinKumar004 NitinKumar004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review notes

Real data-plane engine (per #427): N/A — OCI Vault is a secrets/KMS service.

CI: the Build failure is a flaky runner I/O error (fork/exec .../link: input/output error), not a code defect — a re-run should clear it. Findings below are the real items.

Findings

Medium · real-engine — Secret name uniqueness enforced globally across all vaults instead of per-vault
providers/oci/vault/secret.go:117
If a user creates a secret named db-password in vault A, then calls CreateSecret (CreateOCISecret) for db-password in vault B -> the handler returns 409 AlreadyExists, but real OCI creates it, because validateSecretSpecLocked (secret.go:117) calls secretByNameLocked (secret.go:398) which scans every secret in every vault and never compares spec.VaultID (OCI scopes secret-name uniqueness to the vault). CancelOCISecretDeletion (secret.go:339) reuses the same global check and can likewise wrongly refuse a restore when an unrelated vault reused the name.

Medium · docs — docs/services.md not updated for OCI Vault (Definition-of-done miss)
docs/services.md:1389
If a user or contributor consults docs/services.md (the human service reference) for OCI secrets -> the ~30-op Vault/KMS surface is invisible: the Secrets header (line 1389) still lists only AWS/Azure/GCP and there is no OCI Vault section, unlike every sibling OCI service (VCN line 407, Monitoring 623, Identity 749). oci-conventions.md Definition-of-done explicitly requires 'Operations added to docs/services.md'; only the generated coverage pages (7 portable ops) were updated, and those cannot describe the OCI-only Extras surface.

Low · docs — Garbled doc comment on VaultManagement interface
server/oci/vault/extras.go:7
If a maintainer reads the VaultManagement doc comment (extras.go:7-11) -> they get an out-of-order, incoherent sentence (a dangling 'container above a secret at all...' fragment and a //nolint:revive directive that runs on into prose), because the comment lines were reordered incorrectly in a tidy edit. The nolint still functions; cosmetic only.

Low · coverage — server/oci/vault wire handler coverage 79.7% (below 90% pillar)
server/oci/vault/handler.go:246
If a client sends an unsupported verb (e.g. DELETE /vaults/{id}) or walks a multi-page list -> those paths ship untested (notFound 0%, listKeys 37.5%, listSecretVersions 50%, getSecretVersion 54.5%, paginate next-page 70%), so a regression in the negative-verb or pagination branch would pass the suite silently.

Low · coverage — No oci-go-sdk SDK-compat test
server/oci/vault/handler_test.go:31
If the wire envelope subtly diverges from what the real oci-go-sdk KMS/Vaults/Secrets client expects (field casing, work-request waiter behavior) -> the hand-rolled map[string]any assertions may still pass while the actual SDK client would fail, because no test round-trips through the real SDK marshaller/waiter (convention calls this the strongest evidence, framed as 'ideally').

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

Labels

oci Oracle Cloud Infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OCI Vault: vaults, keys, secrets

2 participants