Security

Security in regulated environments

A CSI driver runs as root on every node in the cluster, which makes it worth reviewing carefully. Most of what follows is a list of things this one does not have: No Kubernetes API access, no mount(2), no shell in the image, and in two of its four credential modes, no S3 credentials on the node at all.

What is not there#

Most of this driver's security story is subtraction. Each row is a thing an auditor would otherwise have to assess, and the reason it is absent.

Not presentWhy it is not needed
Any Kubernetes API access. The node plugin's ServiceAccount has no Role, no RoleBinding and no ClusterRole.kubelet pushes pod metadata and per-volume secrets into the CSI calls. The driver never needs to ask the API server anything.
privileged: true, added capabilities, and bidirectional mount propagation.The volume is a directory, not a mount. There is no mount(2) anywhere in the driver, so there is nothing to be privileged for.
A writable root filesystem.readOnlyRootFilesystem: true. The driver writes only to the volume directories and its own state directory.
A shell, a package manager, or a libc in the runtime image.FROM scratch with a statically linked binary and a CA bundle. There is nothing to exec even after a compromise.
S3 credentials on the node, in presigned and POST-policy modes.A signer holds them elsewhere and issues per-object grants, or a policy authorises a prefix for a bounded time.
Inbound network listeners, by default.The CSI socket is a Unix domain socket. Metrics and the statistics page are off unless a port is set.

The driver runs as root but not privileged. Root is needed for exactly one thing: Creating and removing directories under /var/lib/kubelet/pods, which are root-owned. That is a smaller grant than it sounds, and it is the same one every CSI node plugin holds.

Credential handling#

Four modes, in decreasing order of what the node is trusted with.

ModeWhat the node holdsSuits
Default provider chainNothing. IRSA, EKS Pod Identity or an instance profile supplies short-lived credentials.AWS clusters with an identity story already in place
Presigned (signer)Nothing but a bearer token for the signer. Each upload gets a URL for one object key, valid for minutes.Regulated environments: Every grant is auditable at the signer, and revocation is immediate
Presigned POST policyOne signed policy, prefix-scoped, valid at most 7 days. No signer service to run.Environments that want scoped grants without another deployment
Static keysAn access key and secret, per volume or driver-wide.Non-AWS S3 endpoints; the fallback when nothing else fits

Per-volume secrets arrive through nodePublishSecretRef, which kubelet reads from the pod's own namespace. A namespace can therefore only use credentials it already has, and the driver is not a way to escalate across namespaces.

Supply chain#

ControlDetail
Keyless signaturesEvery published image and every architecture-specific tag is signed with cosign using the workflow's OIDC identity. No private key exists to leak or rotate.
Verifiable provenanceThe signature covers the digest, not the tag, so moving a tag afterwards does not carry the signature with it.
Reproducible installThe release attaches the rendered YAML pinned to the released digest, so what you applied is what was signed.
Four runtime dependenciesThe AWS SDK, grpc-java, Jackson streaming, and slf4j. No dependency-injection container, no reflective frameworks, no plugin loading.
Nothing loaded at runtimeA native image has no classpath to add to. There is no plugin mechanism, no scripting engine and no deserialisation of untrusted input.
verify before you deploy
cosign verify \
  --certificate-identity-regexp "^https://github.com/.*/csi-s3-archiver/" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/trion-development/csi-s3-archiver:1.2.3

The data path#

Archiving is one-way and append-only for an ephemeral volume: Files go to S3 and objects are never deleted. That is a property worth stating plainly in a compliance context, because it means the driver cannot be used to destroy evidence it has already archived. Two deliberate exceptions:

  • Segment compaction deletes the .parts/ fragments it has just assembled into the canonical object. Nothing a user wrote is removed.
  • Durable volumes mirror deletions, because a volume that resurrects files its workload deleted is not durable. This is opt-in per StorageClass, and the durable class defaults to reclaimPolicy: Retain so deleting a claim cannot delete the archive.

Other boundaries the driver holds:

BoundaryEnforcement
Symbolic links are never followedA symlink inside a volume is skipped and logged. The driver runs as root, so following one would exfiltrate node files into the workload's bucket.
Target paths are validatedAn absolute path of at least two segments, refusing anything close to the filesystem root, and refusing a symlinked target.
Object keys cannot escape the prefixA restored key that resolves outside the volume is refused, which matters if the bucket is writable by anyone else.
Over-long keys are skipped, never truncatedA truncated key would silently overwrite an unrelated object.

Reporting a vulnerability#

Report privately first, and give us a chance to ship a fix before the details are public. That applies to anything that lets a workload reach files outside its own volume, read credentials it was not given, or make the driver act on another namespace's behalf.

ChannelUse it for
security.txtThe address to send the report to, machine-readable and per RFC 9116. It is kept there rather than printed on this page, because an address in a web page is harvested within days and then the reports arrive buried in spam.
Bluesky, @triondev or @everfluxGetting our attention, or asking how to reach us, when the address above bounces. Say that you have something to report; do not put the details in a public post or a DM.
The contact form at TRION DevelopmentEverything else, including whether commercial support exists for your cluster.

What helps. The driver version or image digest, the Kubernetes distribution and version, the volumeAttributes of the volume involved, and the smallest pod spec that shows the behaviour. Driver logs at S3A_LOG_LEVEL=debug are usually the fastest way to a diagnosis; they are designed to carry no secret material, and there is a test enforcing that, so they are safe to attach.

What happens next. You get a human reply confirming whether we can reproduce it. A fix is released as a normal tagged version and named in the release history, with credit if you want it and without if you do not. There is no bounty programme.

Your responsibilities#

The driver covers what is listed above. These remain yours.

  • The statistics page has no authentication. It is read-only and carries no secret material, but it does reveal object keys inside failure messages and per-node volume counts. It is off by default; keep it on a port-forward or behind an authenticating ingress.
  • The reference signer is a reference. One static bearer token, no TLS termination, no rate limiting, no audit log. It exists so presigned mode is testable and so you have a readable starting point, and the code says so where it matters.
  • Bucket policy is yours. The driver needs PutObject, and ListBucket plus DeleteObject only for segment compaction and durable volumes. Grant the narrowest set your configuration actually uses.
  • Encryption is a bucket decision. The driver can request SSE-S3 or SSE-KMS per volume, but a bucket default is what protects you from a volume that forgot to ask.
  • No release has been tagged yet. Until one is, there is no signed image to verify, and everything above describes the pipeline rather than an artefact you can download today.