Status: Draft for Implementation
Version: 2.0.0
Date: 2026-05-19
Audience: SDK implementers, security engineers, operator authors, NF teams
This RFC defines the OpenPacketCore security substrate: workload identity, transport security, authorization, key management, secret handling, audit integrity, and runtime security administration. It integrates SPIFFE/SPIRE, gNSI, NACM, AEAD envelope encryption, and tenant-aware policy into a coherent boundary suitable for carrier-grade cloud-native network functions.
The initial draft correctly selected SPIFFE and gNSI, but it did not define a strong enough multi-tenant carrier boundary, key lifecycle, replay controls, or break-glass governance. This version makes those contracts explicit.
- Authenticate every workload and operator action with cryptographic identity.
- Authorize every operation by tenant, role, transport, method, and YANG path.
- Encrypt all sensitive persistent configuration and session state.
- Keep secret material out of logs, telemetry, panic messages, and ordinary gNMI reads.
- Provide tamper-evident audit and durable security event trails.
- Fail closed on invalid identity, unknown issuer, expired certificate, failed authorization, key lookup failure, or audit integrity failure.
- TLS rotation must not drop established data-plane sessions unless policy requires it.
- Authorization decisions must be cacheable and bounded.
- Crypto operations must use the RFC 001 crypto pool or equivalent offload so they do not starve async or data-plane workers.
- Security checks on high-rate paths must avoid heap allocation in the common case.
- Identity parsing, authorization, key lookup, and redaction must be separate modules with narrow APIs.
- Policy documents must be versioned, validated, and testable offline.
- Security defaults must live in one profile file, not scattered constants.
- The same security metadata must drive NACM, audit, and evidence generation.
- Support SPIFFE X.509-SVID identity.
- Support trust domain federation.
- Support gNSI certificate and authorization services.
- Support break-glass with strict governance.
- Support tenant-aware policy.
- Support key rotation and historical decryption.
The SDK assumes attackers may:
- Control an unprivileged pod in the same Kubernetes cluster.
- Control another tenant namespace.
- Replay old management-plane requests.
- Attempt confused-deputy attacks through the operator.
- Read persistent volumes or backend snapshots offline.
- Corrupt local database files.
- Delay, drop, or reorder network packets.
- Trigger malformed gNMI, NETCONF, gNSI, or protocol inputs.
- Observe timing, status codes, and logs.
- Compromise a single NF replica.
The SDK does not claim to survive:
- Total compromise of the root trust domain signing keys.
- Compromise of the active KMS/HSM root keys without detection.
- Kernel-level compromise of the node running the NF.
- Malicious code compiled into the NF binary.
These residual risks MUST be documented in RFC 006 known gaps.
Every NF replica MUST obtain an X.509-SVID from the local SPIRE Workload API.
Default SPIFFE ID format:
spiffe://<trust-domain>/tenant/<tenant-id>/ns/<namespace>/sa/<service-account>/nf/<nf-kind>/instance/<instance-id>
The original namespace/service-account pattern is insufficient for
multi-tenant carrier isolation because namespaces are often operational
boundaries, not contractual tenant boundaries. tenant-id MUST be explicit
unless the deployment uses one trust domain per tenant.
The SDK MUST parse the SVID into:
pub struct WorkloadIdentity {
pub trust_domain: TrustDomain,
pub tenant: TenantId,
pub namespace: Namespace,
pub service_account: ServiceAccount,
pub nf_kind: NetworkFunctionKind,
pub instance: InstanceId,
pub spiffe_id: SpiffeId,
pub expires_at: Timestamp,
}Identity parsing MUST reject:
- Unknown path formats.
- Missing tenant.
- Invalid NF kind.
- Expired SVID.
- SVIDs with trust domains not present in the active bundle set.
SPIRE registration entries MUST bind identity to Kubernetes selectors such as:
- namespace
- service account
- pod label set
- node attestation policy
- image digest, when available through the attestor
The SDK MUST document the required SPIRE registration pattern. Relying only on service account name is not sufficient for production carrier profiles.
Federation MUST be explicit. The SDK MUST load and validate trust bundles for:
- local workload trust domain
- management/operator trust domain
- optional peer-region trust domains
Federation policy MUST define which remote trust domains may perform which actions. Accepting a federated bundle MUST NOT automatically grant management privileges.
Example:
[[federation]]
trust_domain = "operator.openpacketcore.example"
allowed_tenants = ["tenant-a"]
allowed_roles = ["config-admin", "security-admin"]
allowed_transports = ["gnmi", "gnsi"]The SDK MUST watch SVID and bundle updates and hot-reload TLS acceptors and clients without process restart.
Rotation requirements:
- After the controller accepts and publishes a new epoch, new handshakes use that coherent snapshot.
- Existing connections are cooperatively retired after a material change, explicit reauthentication request, or configurable maximum connection age; replacements complete a full handshake.
- Expired identities are not accepted.
- Trust-anchor removal cuts over future handshakes: every chain that depends on the removed anchor is rejected.
- Rotation failures emit critical telemetry.
The bounded response to compromise of a certificate/key under an issuer that remains trusted MUST be short-lived SVID expiry, not rotation or reauthentication. Replacing material moves cooperative workloads to the new SVID, but the old certificate/key can establish another full handshake until the earliest expiry across every certificate in its presented SVID chain while its issuer remains trusted. The TLS substrate does not implement immediate generic CRL, OCSP, certificate/identity denylist, or other selective same-issuer revocation. Removing a root is instead a trust-anchor cutover for all chains that depend on it; it is not a certificate-expiry deadline.
For Kubernetes projected Secrets, production consumers MUST resolve one
relative ..data target and read the leaf chain, key, intermediates, and trust
bundles directly from that immutable generation directory. Independently
following each user-facing file symlink is forbidden because an atomic
..data replacement can otherwise produce mixed material. A source MUST check
the generation after every read, discard a candidate if it changes, and stop
after a fixed retry and work budget.
ProjectedSvidSource implements this boundary with public exact limits: 1 MiB
for the chain file, 64 KiB for the key, 1 MiB per trust-bundle file, 4 MiB
total, 16 bundle files, 16 chain certificates, 128 trust anchors, and three
retries after the initial attempt. Each attempt has a five-second deadline.
Polling cannot be configured below 100 milliseconds. Paths must be normalized
relative paths below the projected generation. The source rejects non-regular
material files and never places paths, PEM, SPIFFE IDs, keys, or parser text in
status or events.
A validated candidate is published with a process-local monotonic generation.
Rollback is another publication and therefore advances that generation. An
invalid candidate retains the exact last-known-good identity, but never beyond
the leaf's expiry; its ongoing expiry monitor schedules clearing from that leaf
expiry and is not the authority for an earlier intermediate expiry. Expiry
clears the source identity and reports a typed unavailable state. This
source-level publication contract precedes #162's coherent per-handshake TLS
epoch and #163's bounded connection reauthentication. Source Ready alone is
not TLS readiness; consumers MUST gate on the controller status described
below.
Because a rejected projected candidate deliberately leaves the identity-state
watch unchanged, ProjectedSvidSource MUST synchronously record its fixed
rejection outcome under the publication lock before notifying observers. This
producer accounting MUST use the recorder selected when constructing the
source, and MUST remain independent of watch delivery and controller lifetime;
burst, coalescing, scheduler lag, recovery before controller construction, and
source closure therefore cannot lose an outcome. There is no public outcome
cursor or separately droppable monitor.
TLS consumers MUST construct the sole projected controller through
TlsMaterialController::new_from_projected_source or
new_pinned_from_projected_source. That one-time claim carries the source's
exact identity channel and recorder into the controller, and rejects a second
authority before it can split or duplicate telemetry. Generic controller
constructors remain valid for independent-file, socket, and custom sources, but
subscribing a projected source through them does not establish this production
observability pairing.
opc-tls::TlsMaterialController MUST revalidate each identity state under fixed
certificate, trust-anchor, private-key, and aggregate byte bounds before it can
become handshake authority. It MUST pin the explicit local SPIFFE identity or
the first accepted identity. It MUST pre-scan every certificate configured in
the presented SVID chain and retain an invalid candidate's predecessor only
until the earliest expiry in that chain. A redundantly presented root therefore
bounds the controller lifetime; a root appearing only in a trust bundle is not
independently scanned for this deadline. Production SVID chains SHOULD omit the
trust anchor. Every accepted update or rollback receives a new opaque
process-local epoch. Status and errors MUST contain only closed reason codes,
epoch, availability, leaf expiry, and effective presented-chain expiry;
identity text, paths, PEM, keys, and parser/application error text are
forbidden.
Every production handshake MUST freeze one controller snapshot before rustls construction so certificate resolution and peer verification use the same leaf/key/chain/trust material. After mutual TLS and application negotiation, the caller MUST verify that epoch is still current before admitting the connection. A changed epoch MUST discard the connection and retry within the fixed SDK retry/concurrency limits. Tickets, resumption, early data, half-RTT data, and 0-RTT MUST remain disabled. This admission record carries the exact epoch, local leaf expiry, and effective local configured/presented-chain expiry; #163 separately combines the local and peer presented-chain expiries when retiring connections after admission.
These reload, admission, and retirement mechanisms now have single-host three- and five-process trust overlap/removal, root cutover/rollback, and one bounded short-lived-SVID-expiry regression slice. They are not deployed fleet qualification. Real network/storage faults, active-mutator restart, deployed reconnect/resource/soak bounds, remote-HKMS and signed independent evidence, plus the explicit unsupported generic-revocation limitation, remain open under #164/#143.
gNMI, gNSI, and internal gRPC APIs MUST use mTLS with SPIFFE identity verification.
Requirements:
- TLS 1.3 required by default.
- TLS 1.2 disabled by default and only allowed by explicit compatibility profile.
- Peer certificate SAN MUST contain a valid SPIFFE URI.
- Common Name MUST NOT be used for authorization.
- ALPN and service/method authorization MUST be enforced.
- Certificates MUST be validated against active SPIFFE bundles, not system web PKI.
Default modern profile:
TLS_AES_256_GCM_SHA384TLS_CHACHA20_POLY1305_SHA256
FIPS profile:
- MUST use a FIPS 140-3 validated module and only approved algorithms.
- MUST document any difference from the modern profile.
- MUST disable algorithms not available through the validated boundary.
The SDK MUST expose the selected security profile in metrics and evidence.
If NETCONF/SSH is enabled:
- SSH host keys MUST be generated or provisioned through the security substrate.
- Client identity MUST map to a
TrustedPrincipal. - Password authentication MUST be disabled by default.
- SSH certificate authorities SHOULD be used when SPIFFE-native SSH identity is unavailable.
- SSH authorization MUST flow through the same NACM engine as gNMI.
pub struct TrustedPrincipal {
pub identity: WorkloadIdentity,
pub tenant: TenantId,
pub roles: Vec<Role>,
pub groups: Vec<Group>,
pub auth_strength: AuthStrength,
}Roles and groups MUST come from signed policy or trusted identity attributes. They MUST NOT be accepted from unsigned client metadata.
Authorization is evaluated in this order:
- Transport and peer authentication.
- Trust domain allowlist.
- Tenant boundary check.
- gRPC service/method authorization.
- NACM/YANG path authorization.
- Operation-specific guardrails, such as break-glass or key export denial.
Any deny at any layer is final unless a governed break-glass flow applies.
NACM MUST authorize:
readcreateupdatereplacedeleteexecsubscribesecurity-admin
The engine MUST evaluate all changed paths after patch expansion. It is not enough to authorize the request's root path.
Authorization decisions SHOULD be cached by:
- principal digest
- tenant
- policy version
- normalized path
- action
Cache entries MUST be invalidated on policy updates and SVID rotation.
Cross-tenant access is denied by default. A principal from tenant A MUST NOT
read or mutate tenant B config, session state, keys, or audit records unless a
federated policy explicitly grants a scoped operation.
The tenant boundary MUST be enforced in:
- identity parsing
- authorization
- persistence key namespace
- session key namespace
- audit query filters
- telemetry labels, with cardinality controls
- operator reconciliation
The SDK MUST provide server-side support for:
| Service | Purpose | SDK Component |
|---|---|---|
gnsi.certz.v1 |
Certificate and trust material distribution | opc-gnsi-server |
gnsi.pathz.v1 |
Path authorization policy | opc-nacm |
gnsi.authz.v1 |
gRPC service/method authorization | opc-nacm |
gNSI endpoints are security-critical. Access MUST require security-admin or a
more specific role. gNSI mutations MUST be audited and persisted through the
shadow-security store from RFC 001.
Security material pushed through gNSI is stored in shadow-security.
Rules:
- Not visible through ordinary gNMI
Get. - Exportable only through explicitly authorized security APIs.
- Encrypted at rest with a distinct key purpose from normal config.
- Included in backup only when backup policy allows secret material.
- Redacted in audit and telemetry.
Authorization policy updates MUST support validate-only and staged apply. A policy that would lock out all security administrators MUST be rejected unless a break-glass recovery policy exists.
Break-glass is dangerous and MUST be treated as an exceptional workflow, not a convenience override.
Requirements:
- Disabled by default in production profiles unless explicitly enabled.
- Requires a high-assurance principal.
- Requires reason, ticket/reference, requested scope, and duration.
- Maximum default duration: 15 minutes.
- Requires dual authorization or an externally signed emergency token in carrier profiles.
- Cannot bypass cryptographic verification, tenant boundary, or audit logging.
- Cannot export raw key material unless a separate key recovery policy allows it.
- Emits critical audit events at start, use, and expiry.
- Emits high-priority telemetry.
Break-glass must grant the narrowest possible action set and path set.
The SDK uses purpose-separated keys:
| Purpose | Example Use |
|---|---|
config |
RFC 001 encrypted config blobs |
shadow-security |
gNSI security material |
session |
RFC 004 session store data |
audit |
HMAC hash chains |
backup |
encrypted export bundles |
Keys MUST be separated by KMS key ID or HKDF info labels. Reusing one raw key
for multiple purposes is forbidden.
Production profiles MUST obtain root or wrapping keys from one of:
- KMS plugin.
- HSM plugin.
- Kubernetes Secret encrypted by a cluster KMS provider, only for lower assurance profiles.
- SPIRE/SVID-authenticated key service.
Environment variables are forbidden for production key material.
#[async_trait::async_trait]
pub trait KeyProvider: Send + Sync {
async fn get_active_key(&self, purpose: KeyPurpose, tenant: &TenantId)
-> Result<KeyHandle, KeyError>;
async fn get_key_by_id(&self, key_id: &KeyId)
-> Result<KeyHandle, KeyError>;
async fn rotate_key(&self, purpose: KeyPurpose, tenant: &TenantId)
-> Result<KeyId, KeyError>;
}
#[async_trait::async_trait]
pub trait RemoteSealProvider: Send + Sync {
async fn seal(&self, aad: &EnvelopeAad, plaintext: &[u8])
-> Result<EncryptedPayload, KeyError>;
async fn unseal(&self, key_id: &KeyId, aad: &EnvelopeAad,
ciphertext_and_tag: &[u8]) -> Result<Zeroizing<Vec<u8>>, KeyError>;
}KeyHandle MUST avoid exposing raw bytes unless required by the crypto module.
If raw bytes are materialized, they MUST be zeroized after use where the crypto
backend permits.
Deployments that require sealing through a provider that declares
non-exportable custody can install one process-level KeyCustodyModule. The
composite object MUST supply both CryptoModule evidence and
RemoteSealProvider operations; evidence from one object MUST NOT authorize
operations on another. Admission requires the module to declare, self-test, and
service the explicit sealed_key_storage and zeroization capabilities and
returns a bounded CapabilityReport. The SDK does not independently certify
those declarations. The process slot is immutable after success, and the
opaque AdmittedKeyCustody adapter has no public constructor or fallback.
The recorded self-test outcome is admission-time evidence; seal and unseal do
not rerun an asynchronous power-on self-test. Before every operation, the SDK
synchronously verifies that the module identity and validation declaration
still match admission and that the complete frozen grant remains both
advertised and serviceable. A module whose subsequent self-test or health state
becomes invalid MUST withdraw the affected readiness capability. Provider
NotFound and Unavailable classifications retain their public meaning;
other provider context is collapsed to a fieldless redaction-safe error.
Successful provider-returned bound AAD MUST be rejected before parsing when it
exceeds 64 KiB. Within that bound it MUST decode as the exact canonical SDK AAD
shape and reserialize byte-for-byte from the caller's EnvelopeAad plus the
returned KeyId. Oversized, malformed, non-canonical, or context-mismatched
provider output fails closed.
The existing KeyProvider, KeyHandle, and direct RemoteSealProvider
interfaces remain available for ordinary non-validated compatibility. Those
values cannot construct AdmittedKeyCustody and MUST NOT inherit or advertise
its admission evidence merely because another process component installed a
module.
For remote sealing, key_id MUST come from a canonical, validated envelope.
It selects the exact historical remote key and MUST NOT be replaced by the
provider's current active key. KmsRemoteSealProvider snapshots one coherent
RemoteSealMaterialController epoch before each encrypt request. Active-key
publication affects only future seals; in-flight requests keep their snapshot.
The controller retains only the current ID and opaque process-local epoch. It
does not cache historical key material or authorization decisions, persist its
epoch, watch a source, coordinate pods, or produce a fleet-comparable epoch.
Each unseal calls the remote provider for the exact envelope key ID.
Key rotation MUST support:
- New writes using the active key.
- Old reads using key ID from the envelope.
- Optional background re-encryption.
- Retention windows.
- Emergency key revocation.
If a key is unavailable, the SDK MUST fail closed for writes and for reads that require the missing key.
For remote-seal rotation, KMS/HKMS is the authority for historical retention, revocation, and physical retirement. The SDK supplies exact historical-key selection and bounded live-state scan inputs, but it has no rewrap campaign, dependency-proof object, retirement API, or enforcement gate and cannot block an external KMS retirement. Operators MUST provision the new key before publishing it active, retain every old key while any artifact can reference it, and enforce retirement externally only after a composite proof:
- a separately implemented rewrap has completed and a bounded, snapshot-bound, write-fenced scan verifies the resulting live state;
- retained Raft logs and snapshots have been compacted, expired, or inspected and verified independently; and
- backups, restore inputs, rollback checkpoints, and other offline sources have been inspected and then rewrapped, deleted, or retained with the old key.
A deployment-specific finite retention/TTL proof MAY replace rewrap only when it covers every live and replayable source and no record is unbounded. A restore scan alone does not prove logs, snapshots, backups, restore sources, or rollback artifacts. A partial or stale scan, concurrent writes, an unavailable source, or an ambiguous result blocks the operator's retirement decision. Emergency KMS revocation remains fail closed and may intentionally make dependent records unreadable.
RemoteSealProvider::unseal's historical KeyId argument is a breaking source
API change. Provider implementations and callers MUST be upgraded together.
It does not change the durable envelope or consensus/session wire format; the
KMS request framing/schema is unchanged, but decrypt request contents now use
the historical envelope ID. A code rollout MUST keep the old ID active until
every reader, writer, and custom provider has stopped or upgraded, passed
readiness, and can unseal by exact ID. Only then may the fleet publish a new
active ID; upgraded pods may temporarily seal under different IDs because all
upgraded reads select the envelope key.
Material rollback MUST first verify that KMS can encrypt/decrypt with the old ID and decrypt with the new ID, then republish the old ID on every upgraded process and verify new writes use it while both epochs remain readable. The new ID MUST remain retained while any artifact depends on it. Rolling back to a pre-change binary is safe only before a new ID is published, or after a complete rewrap/artifact proof has returned all dependencies to one key; otherwise use a coherent pre-publication checkpoint restore.
Default persistent encryption uses AES-256-GCM-SIV for misuse resistance.
Nonce reuse is still a bug and MUST be monitored.
Some FIPS validated modules may not expose AES-GCM-SIV. A FIPS profile MAY use
AES-256-GCM only when:
- Nonces are generated by a validated DRBG or deterministic counter scheme.
- Nonce uniqueness is guaranteed per key.
- The uniqueness state is crash-safe.
- Tests prove duplicate nonce detection.
The active AEAD algorithm MUST be recorded in each envelope and in RFC 006 evidence.
AAD MUST bind ciphertext to:
- tenant
- purpose
- tx/session identifier
- schema digest or state type
- key ID
- version
- principal, for config commits
AAD mismatch MUST produce a generic integrity error without exposing which field failed.
Encryption alone does not prevent replay of an old valid blob. The management store MUST enforce monotonic config versions as specified in RFC 001. Session store backends MUST use generation numbers or lease fencing as specified in RFC 004.
Audit records MUST include:
entry_hmac = HMAC(audit_key, tenant || sequence || canonical_entry || previous_hash)
The hash chain MUST be tenant-scoped and purpose-separated. Startup MUST verify the local audit chain unless the operator explicitly configures degraded recovery mode.
Carrier profiles SHOULD stream audit events to an external append-only system. Local SQLite audit is necessary for recovery and debugging but is not sufficient against host-level compromise.
Audit timestamps MUST use UTC. The SDK SHOULD record both wall-clock timestamp and monotonic sequence number. Security decisions MUST NOT rely only on wall clock when monotonic ordering is required.
The redaction subsystem consumes metadata generated by RFC 002.
Redaction MUST apply to:
Debug- structured logs
- audit records
- metrics labels
- error messages
- traces
- panic hooks where possible
- gNMI read responses after NACM filtering
Redaction MUST preserve enough information for debugging, such as value presence, length class, or stable digest when explicitly allowed by policy.
Required metrics:
opc_security_authn_total{outcome,reason,transport}opc_security_authz_total{outcome,reason,action}opc_security_svid_expires_secondsopc_security_bundle_versionopc_security_rotation_total{kind,outcome}opc_security_key_lookup_total{purpose,outcome}opc_security_breakglass_activeopc_security_breakglass_total{outcome}opc_security_audit_chain_verify_total{outcome}opc_security_redactions_total{source}
For TLS readiness and lifecycle reporting, SVID expiry means the controller's effective earliest configured/presented-chain expiry, not an assumption that the leaf always expires first. Certificates present only in trust bundles are not independently included in that expiry value.
opc_security_svid_expires_seconds is that expiry as a Unix timestamp and is
zero when no coherent unexpired controller snapshot is available.
opc_security_bundle_version is the opaque process-local coherent material
epoch; it is not a Kubernetes generation name, path, material hash, cluster
identity, or value that may be compared across process restarts or replicas.
The fixed opc_security_rotation_total label space is the Cartesian product of
kind={tls_material,svid,trust_bundle} and
outcome={success,retained_last_good,rejected,expired}. A source reason is
classified as svid or trust_bundle only when its closed enum proves that
component; ambiguous failures remain tls_material. Reload rejection with an
unexpired predecessor (retained_last_good), rejection without one
(rejected), and observed lifecycle expiry of a coherent source publication
(expired) are distinct from peer authentication/trust failure. Expiry can be
observed before pairing, while controller-active, or after controller rejection;
only expiry of the active accepted ticket may clear the expiry gauge, and
supersession alone does not synthesize an outcome. Controller private-key
mismatch, local
identity-pin, temporal-validity, and expiry reasons are provably SVID outcomes.
Chain/workload-identity validation, source acquisition, material-limit, closure,
and epoch failures do not prove one changed component and remain
tls_material. Expiry does not suppress a later malformed-candidate rejection,
and that later rejection MUST NOT increment expiry again.
Fleet rotation alerts and evidence MUST use the mechanically derived hard span in the consensus operator runbook, not a fixed sample duration. Evidence MUST bind exactly one invocation, non-secret live-lease binding, monotonic operation/nonce, member/checkpoint, phase/step, and fresh timestamp, and MUST be published with no-replace and crash-durable filesystem semantics. The lease token MUST travel only through a private descriptor and MUST NOT be logged or persisted. Emergency serving withdrawal MUST execute independently of evidence storage. A deliberate old-chain negative probe MUST remain visible to the authentication/trust alert and fail if its isolated delta is not exact.
Metrics MUST control label cardinality. Raw SPIFFE IDs SHOULD be exposed through logs, not high-cardinality metrics, unless explicitly enabled.
| Module | Responsibility |
|---|---|
opc-identity |
SPIFFE ID parsing, SVID watch, trust bundle watch |
opc-tls |
TLS acceptor/client reload and peer extraction |
opc-authz |
Principal, roles, method policy, decision cache |
opc-nacm |
YANG path authorization and RFC 8341 semantics |
opc-gnsi-server |
gNSI service handlers and staged policy apply |
opc-key |
KeyProvider trait and KMS/HSM adapters |
opc-crypto |
AEAD envelopes and key derivation |
opc-redaction |
Secret metadata and safe rendering |
opc-audit |
HMAC chain, external sink adapter |
opc-security-testkit |
fake SPIRE, fake KMS, policy fixtures |
Agents must not mix transport identity parsing with NACM path logic. Each module should have deterministic test fixtures and no hidden global state.
- SPIFFE ID parser accepts valid pattern and rejects malformed identities.
- Federation allowlist denies unknown trust domains.
- Authorization cache invalidates on policy version change.
- NACM denies missing rules.
- Redaction covers generated secret fields.
- AEAD envelope rejects wrong AAD, wrong key, corrupted tag, and wrong tenant.
- Break-glass scope and TTL enforcement.
- SVID rotation without process restart.
- Kubernetes
..datareplacement during every projected-material read phase, proving that no mixed generation is published. - Projected-material exact-limit/one-over, last-good retention, expiry, rollback-generation, and redaction tests.
- TLS material rotation during every handshake/application phase, exact epoch/effective-chain-expiry admission, identity continuity, rollback, repeated-rotation retry exhaustion, concurrent-operation bounds, cancellation, and redaction.
- Trust-anchor cutover rejects every future handshake whose chain depends on the removed anchor.
- gNSI policy staging and rollback.
- Management commit rejected after NACM policy update removes permission.
- Shadow-security store not visible through ordinary gNMI
Get. - Key rotation reads old commits and writes new commits.
- External audit sink outage does not drop local audit.
- SPIRE socket unavailable.
- Expired SVID.
- Corrupt trust bundle.
- KMS timeout.
- Missing historical key.
- Duplicate AEAD nonce detector trigger, when applicable.
- Audit HMAC mismatch.
- Break-glass token replay.
- Authorization decision cache p99 under 50 microseconds for hot entries.
- TLS reload completes without blocking new accepts longer than 100 milliseconds on reference hardware.
- Key lookup cache hit p99 under 25 microseconds.
- Redaction of a 10 MiB config audit diff completes within configured commit budget.
This RFC is implemented when:
- Every management connection is authenticated with SPIFFE-aware mTLS or an explicitly configured SSH identity profile.
- Tenant identity is explicit and enforced across authz, persistence, audit, and telemetry.
- gNSI services can stage, validate, apply, audit, and roll back security policy.
- Config, shadow-security, session, and audit keys are purpose-separated and rotatable.
- AEAD envelopes bind ciphertext to tenant, purpose, version, and schema/state metadata.
- Break-glass is scoped, time-limited, audited, and disabled by default in production unless carrier policy enables it.
- Security failure modes fail closed and are covered by fault injection tests.