Workload Attestation
Workload attestation is the process of proving a process’s identity to the kernel module before it can use a workload identity. It’s the same concept as SPIFFE workload attestation: rather than trusting a workload because of where it runs (an IP, a hostname, a network zone), attestation looks at what the workload actually is and derives its identity from that.
It happens after node attestation has already established trust between the daemon and the control plane: node attestation identifies the machine, workload attestation identifies the individual processes running on it.
How It Works
Section titled “How It Works”Workload attestation happens at connection time, entirely on the node, with no round-trip to the control plane. When a process opens a new network connection, the daemon collects metadata about the process and its environment, then evaluates it against the selectors defined in WorkloadIdentity resources. A process receives an identity if it satisfies the selectors for that WorkloadIdentity.
This evaluation is not a one-time check. Every new connection triggers selector evaluation, so a process that previously matched but has since changed (for example, a different binary was exec’d into the same PID) will not pass attestation.
Selectors follow the same matching rules as a Verifier’s requiredMetadata (see Node Attestation): a WorkloadIdentity’s selectors field is a list of key-value groups, and a process matches if it satisfies all keys within any one group (AND within a group, OR across groups). A key within a group can also list multiple values; the process matches that key if it has any one of them:
selectors: - process:name: nginx k8s:pod:namespace: [production, staging] - process:name: nginx docker:label:env: productionThis matches an nginx process in either the production or staging Kubernetes namespace, or an nginx process running in a Docker container labeled env: production outside Kubernetes entirely.
Examples
Section titled “Examples”Kubernetes web frontend
Section titled “Kubernetes web frontend”apiVersion: core.riptides.io/v1alpha1kind: WorkloadIdentitymetadata: name: nginx-web namespace: riptides-systemspec: workloadID: my-app/web/nginx selectors: - process:name: nginx k8s:pod:namespace: my-app k8s:label:app: nginx connection: tls: mode: PERMISSIVEThis issues an identity to any process named nginx, running in a pod labeled app: nginx within the my-app Kubernetes namespace.
AWS EC2 instance without Kubernetes
Section titled “AWS EC2 instance without Kubernetes”A batch worker running directly on an EC2 instance, with no Kubernetes or Docker involved:
apiVersion: core.riptides.io/v1alpha1kind: WorkloadIdentitymetadata: name: batch-worker namespace: riptides-systemspec: workloadID: myapp/batch-worker selectors: - process:name: batch-worker ec2:placement:region: us-east-1 connection: tls: mode: MUTUALec2:placement:region alone would match every process on every instance in that region, so it’s combined with the process-scoped process:name selector to scope the identity to just the intended workload rather than the whole host.
Same service, Kubernetes or plain Docker
Section titled “Same service, Kubernetes or plain Docker”A service that’s mid-migration from plain Docker hosts to Kubernetes, or that simply runs on both, can keep a single WorkloadIdentity by giving it one selector group per environment:
apiVersion: core.riptides.io/v1alpha1kind: WorkloadIdentitymetadata: name: nginx-web namespace: riptides-systemspec: workloadID: my-app/web/nginx selectors: - process:name: nginx k8s:pod:namespace: [production, staging] - process:name: nginx docker:label:env: production connection: tls: mode: PERMISSIVEThis issues the same spiffe://example.com/my-app/web/nginx identity to an nginx process whether it’s running in the production/staging Kubernetes namespaces or in a plain Docker container labeled env: production, so the identity, TLS policy, and any egress/credential bindings built on top of it don’t need to be duplicated per environment.
Metadata Collectors
Section titled “Metadata Collectors”The daemon runs a set of collectors that gather metadata about a process and its host. Each collector namespaces its output under a prefix (process:, k8s:, docker:, ec2:, gcp:, azure:, linuxos:, node:, sysfsdmi:), which is what you reference in a selector key.
Collectors fall into two categories:
- Process-scoped: produce different metadata per process on the same host:
process(from/proc),kubernetes(pod/container context, via the kubelet API), anddocker(container context, via the Docker socket). These require resolving the PID to a pod or container, and are unavailable (and silently skipped) for processes that aren’t running in a pod or container. - Host-scoped: produce the same metadata for every process on a given host:
ec2,gcp,azure,linuxos,node,sysfsdmi. Each cloud collector auto-detects whether the host is actually running there (e.g.azurechecks/sys/class/dmi/id/sys_vendor) and produces no labels otherwise.
Because host-scoped collectors don’t distinguish between processes, a selector built only from host-scoped keys (e.g. just ec2:placement:region) matches every process on that host. Combine a host-scoped selector with a process-scoped one (e.g. process:name) to scope a WorkloadIdentity to a specific workload rather than the whole node.
The kubernetes and docker collectors are enabled by default but no-op safely when their dependency is absent (no kubelet reachable, no Docker socket); they don’t fail attestation for non-containerized processes.
Metadata by Collector
Section titled “Metadata by Collector”process
Section titled “process”Collected from /proc/<pid> for the connecting process.
| Key | Description |
|---|---|
process:pid | Process ID. |
process:name | Executable name. |
process:cmdline | Full command line. |
process:uid / process:uid:effective | Effective UID. |
process:uid:real | Real UID. |
process:gid / process:gid:effective | Effective GID. |
process:gid:real | Real GID. |
process:gid:additional | Supplementary group IDs (multi-valued). |
process:binary:path | Path to the executable (/proc/<pid>/exe). |
process:binary:hash | SHA-256 digest of the executable, cached by mount-namespace + device/inode. |
process:network:binding | ip:port for each socket the process holds in LISTEN state (multi-valued). |
process:env:<NAME> | Environment variable, keyed by uppercased name. Only collected if the daemon’s extractEnvs option is enabled (default: off). |
On the degraded attestation path (when the process has already exited or its PID was reused by the time the daemon re-derives its identity for evaluation), only process:pid, process:name, process:binary:path, process:uid*, and process:gid* (not :additional) are available, reconstructed from the connection-time snapshot rather than a live /proc read. process:cmdline, process:binary:hash, process:gid:additional, process:env:*, and all k8s:*/docker:* labels are unavailable on this path.
kubernetes (k8s)
Section titled “kubernetes (k8s)”Collected by resolving the process’s cgroup to a pod/container ID, then querying the kubelet (or API server) for that pod’s spec and status.
| Key | Description |
|---|---|
k8s:pod:name | Pod name. |
k8s:pod:namespace | Pod namespace. |
k8s:pod:serviceaccount | Pod’s service account name. |
k8s:pod:owner:kind | Owning controller kind (e.g. replicaset), lowercased. |
k8s:pod:owner:kind-with-version | Owning controller’s apiVersion/kind, lowercased. |
k8s:pod:owner:name | Owning controller’s name. |
k8s:node:name | Name of the node the pod is scheduled on. |
k8s:container:name | Name of the container within the pod. |
k8s:container:image:id | Image ID of that container. |
k8s:label:<key> | Value of pod label <key>. |
k8s:annotation:<key> | Value of pod annotation <key>. |
k8s:pod:image:name / k8s:pod:image:id | Image name/ID for each container in the pod (multi-valued). |
k8s:pod:image:count | Number of containers in the pod. |
k8s:pod:init-image:name / :id / :count | Same, for init containers. |
k8s:pod:ephemeral-image:name / :id / :count | Same, for ephemeral containers. |
docker
Section titled “docker”Collected by resolving the process’s cgroup to a container ID, then inspecting that container via the Docker API.
| Key | Description |
|---|---|
docker:id | Container ID. |
docker:name | Container name. |
docker:cmdline | Container entrypoint plus arguments. |
docker:label:<key> | Container label. |
docker:env:<NAME> | Container environment variable, keyed by uppercased name. |
docker:image:name | Image name/tag the container was created from. |
docker:image:hash | Image ID/digest. |
docker:network:mode | Container network mode. |
docker:network:hostname | Container hostname. |
docker:port-binding | Each published port binding (multi-valued). |
Host-level metadata from the EC2 Instance Metadata Service (IMDS). Distinct from the awsiid:* metadata produced during node attestation: this collector reads IMDS directly rather than parsing the signed identity document, and runs on every workload evaluation, not just once at daemon startup.
| Key | Description |
|---|---|
ec2:security-groups | Security groups attached to the instance. |
ec2:instance:id / :type | Instance ID and type. |
ec2:ami:id | AMI ID. |
ec2:kernel:id | Kernel ID, if set. |
ec2:network:hostname / :local-hostname / :public-hostname | Hostnames. |
ec2:network:local-ipv4 / :public-ipv4 / :local-ipv6 / :public-ipv6 | IP addresses. |
ec2:network:mac | Primary network interface MAC address. |
ec2:placement:availability-zone / :availability-zone-id | AZ. |
ec2:placement:region | Region. |
ec2:placement:group-name / :host-id / :partition-number | Placement group details. |
ec2:services:domain / :partition | AWS partition/domain (e.g. aws, amazonaws.com). |
Host-level metadata from the GCP metadata server.
| Key | Description |
|---|---|
gcp:instance:id / :name | Instance ID and name. |
gcp:instance:cpu-platform | CPU platform. |
gcp:instance:attribute:<key> | Instance metadata attribute (excludes ssh-keys). |
gcp:instance:tag | Network tag (multi-valued). |
gcp:instance:image:project / :name | Source image. |
gcp:instance:machine:project / :type | Machine type. |
gcp:instance:placement:project / :zone / :region | Zone and region. |
gcp:instance:scheduling:automatic-restart / :onHostMaintenance / :preemptible | Scheduling options. |
gcp:instance:network:mac | NIC MAC address (multi-valued). |
gcp:instance:network:private-ipv4 / :public-ipv4 / :private-ipv6 / :public-ipv6 | IP addresses (multi-valued). |
gcp:instance:serviceaccount:<name>:email | Attached service account email. |
gcp:instance:serviceaccount:<name>:alias / :scope | Service account aliases/OAuth scopes (multi-valued). |
gcp:project:id / :id:numeric | Project ID and numeric ID. |
gcp:project:attribute:<key> | Project-level metadata attribute. |
Host-level metadata from the Azure Instance Metadata Service. Distinct from the azureimds:* metadata produced during node attestation: this collector reads the IMDS instance endpoint directly rather than the signed managed-identity token.
| Key | Description |
|---|---|
azure:name | VM name. |
azure:ostype | Linux or Windows. |
azure:priority | VM priority (e.g. Spot, Regular). |
azure:provider / :sku | Compute provider and SKU. |
azure:resourcegroup:name | Resource group. |
azure:subscription:id | Subscription ID. |
azure:placement:location / :zone / :groupid | Region, zone, placement group. |
azure:vm:size / :id / :publisher / :offer / :version | VM image/size details. |
azure:vm:scaleset:name | VMSS name, if the VM is part of one. |
azure:tag:<key> | Azure resource tag. |
azure:network:mac | NIC MAC address (multi-valued). |
azure:network:private-ipv4 / :public-ipv4 / :private-ipv6 / :public-ipv6 | IP addresses (multi-valued, includes load balancer frontend IPs for public-ipv4). |
linuxos
Section titled “linuxos”Host OS metadata from local system files (e.g. /etc/os-release).
| Key | Description |
|---|---|
linuxos:name | Linux distribution name (e.g. ubuntu). |
linuxos:version | Distribution version. |
linuxos:kernel:release | Kernel name-release string. |
linuxos:kernel:version | Kernel version string. |
General host metadata (distribution, virtualization, network interfaces) collected locally, independent of the OS-specific linuxos collector.
| Key | Description |
|---|---|
node:hostname | Hostname (MDX_NODE_HOSTNAME env var, else /etc/hostname, else system hostname). |
node:uuid | Host ID. |
node:os:type / :version | OS type and platform version. |
node:platform:name / :family / :version | Platform details (e.g. ubuntu / debian / 22.04). |
node:virtualization:type / :role | Virtualization technology and role (host/guest). |
node:kernel:version / :arch | Kernel version and architecture. |
node:network:interface:count | Number of network interfaces. |
node:network:interface:name | Interface name (multi-valued). |
node:network:interface:<name>:mac_address / :mtu / :index | Per-interface details. |
node:network:interface:<name>:ip | Every IP address on that interface (multi-valued). |
sysfsdmi
Section titled “sysfsdmi”Hardware/firmware metadata from /sys/class/dmi/id.
| Key | Description |
|---|---|
sysfsdmi:bios:date / :release / :vendor / :version | BIOS details. |
sysfsdmi:chassis:type / :vendor / :version / :serial / :asset-tag | Chassis details. |
sysfsdmi:product:name / :family / :version / :serial / :sku | Product (hardware model) details. |