Workload Identity API & Workload Attestation
The Workload Identity API service (workload-identity-api
) is a configurable
tbot
service that allows workloads to request JWT and X509 workload identity
credentials on-the-fly.
It's a more secure alternative to writing credentials to disk and supports performing a process known as workload attestation to determine attributes of the workload before issuing credentials.
The Workload Identity API is compatible with two standards:
In addition to issuing credentials to workloads, the Workload Identity API can also provide the trust bundle necessary for workloads to validate the credentials of other workloads.
Configuration
# type specifies the type of the service. For the Workload Identity API service,
# this will always be `workload-identity-api`.
type: workload-identity-api
# listen specifies the address that the service should listen on.
#
# Two types of listener are supported:
# - TCP: `tcp://<address>:<port>`
# - Unix socket: `unix:///<path>`
listen: unix:///opt/machine-id/workload.sock
# attestors allows Workload Attestation to be configured for this Workload
# API.
attestors:
# kubernetes is configuration for the Kubernetes Workload Attestor. See
# the Kubernetes Workload Attestor section for more information.
kubernetes:
# enabled specifies whether the Kubernetes Workload Attestor should be
# enabled. If unspecified, this defaults to false.
enabled: true
# kubelet holds configuration relevant to the Kubernetes Workload Attestors
# interaction with the Kubelet API.
kubelet:
# read_only_port is the port on which the Kubelet API is exposed for
# read-only operations. Since Kubernetes 1.16, the read-only port is
# typically disabled by default and secure_port should be used instead.
read_only_port: 10255
# secure_port is the port on which the attestor should connect to the
# Kubelet secure API. If unspecified, this defaults to `10250`. This is
# mutually exclusive with ReadOnlyPort.
secure_port: 10250
# token_path is the path to the token file that the Kubelet API client
# should use to authenticate with the Kubelet API. If unspecified, this
# defaults to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
token_path: "/var/run/secrets/kubernetes.io/serviceaccount/token"
# ca_path is the path to the CA file that the Kubelet API client should
# use to validate the Kubelet API server's certificate. If unspecified,
# this defaults to `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`.
ca_path: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
# skip_verify is used to disable verification of the Kubelet API server's
# certificate. If unspecified, this defaults to false.
#
# If specified, the value specified in ca_path is ignored.
#
# This is useful in cases where the Kubelet API server has not been issued
# with a certificate signed by the Kubernetes cluster's CA. This is fairly
# common with a number of Kubernetes distributions.
skip_verify: true
# anonymous is used to disable authentication with the Kubelet API. If
# unspecified, this defaults to false. If set, the token_path field is
# ignored.
anonymous: false
# Selector is used to control which WorkloadIdentity resource will be used to
# issue the workload identity credential. The selector can either be the name of
# a specific WorkloadIdentity resource or a label selector that can match
# multiple WorkloadIdentity resources.
#
# The selector must be set to either a name or labels, but not both.
selector:
# Name is used to select a specific WorkloadIdentity resource by its name.
name: foo
# Labels is used to select multiple WorkloadIdentity resources by their labels.
labels:
app: [foo, bar]
SPIFFE Workload API
The Workload Identity API implements the SPIFFE Workload API, a standardized API for workloads to request workload identity credentials and trust bundles.
Via this API, both JWT and X509 workload identity credentials can be issued.
Workload Attestation
Workload Attestation is the process completed by tbot
to assert the identity
of a workload that has connected to the Workload API and requested credentials.
Workload Attestors are the individual components that perform this attestation. They use the process ID of the workload to gather information about the workload from platform-specific APIs. For example, the Kubernetes Workload Attestor queries the local Kubelet API to determine which Kubernetes pod the process belongs to.
The result of this attestation process is known as attestation metadata. This attestation metadata can be included in the rules or templates you configure as part of a WorkloadIdentity resource.
Unix
The Unix Workload Attestor is the most basic attestor and allows you to restrict the issuance of workload identities to specific Unix processes based on a range of criteria.
Support for non-standard procfs mounting
To resolve information about a process from the PID, the Unix Workload Attestor
reads information from the procfs filesystem. By default, it expects procfs to
be mounted at /proc
.
If procfs is mounted at a different location, you must configure the Unix
Workload Attestor to read from that alternative location by setting the
HOST_PROC
environment variable.
This is a sensitive configuration option, and you should ensure that it is set correctly or not set at all. If misconfigured, an attacker could provide falsified information about processes, and this could lead to the issuance of SVIDs to unauthorized workloads.
Kubernetes
The Kubernetes Workload Attestor allows you to restrict the issuance of workload identities to specific Kubernetes workloads based on a range of criteria.
It works by first determining the pod ID for a given process ID and then by querying the local kubelet API for details about that pod.
Deployment Guidance
To use Kubernetes Workload Attestation, tbot
must be deployed as a daemon
set. This is because the unix domain socket can only be accessed by pods on the
same node as the agent. Additionally, the daemon set must have the hostPID
property set to true
to allow the agent to access information about
processes within other containers.
The daemon set must also have a service account assigned that allows it to query the Kubelet API. This is an example role with the required RBAC:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: tbot
rules:
- resources: ["pods","nodes","nodes/proxy"]
apiGroups: [""]
verbs: ["get"]
Mapping the Workload API Unix domain socket into the containers of workloads can be done in two ways:
- Directly configuring a hostPath volume for the
tbot
daemonset and workloads which will need to connect to it. - Using spiffe-csi-driver.
Example manifests for required Kubernetes resources:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: tbot
rules:
- resources: ["pods","nodes","nodes/proxy"]
apiGroups: [""]
verbs: ["get"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: tbot
subjects:
- kind: ServiceAccount
name: tbot
namespace: default
roleRef:
kind: ClusterRole
name: tbot
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: tbot
namespace: default
---
apiVersion: v1
kind: ConfigMap
metadata:
name: tbot-config
namespace: default
data:
tbot.yaml: |
version: v2
onboarding:
join_method: kubernetes
# replace with the name of a join token you have created.
token: example-token
storage:
type: memory
# ensure this is configured to the address of your Teleport Proxy Service.
proxy_server: example.teleport.sh:443
services:
- type: workload-identity-api
listen: unix:///run/tbot/sockets/workload.sock
attestor:
kubernetes:
enabled: true
kubelet:
# skip verification of the Kubelet API certificate as this is not
# usually issued by the cluster CA.
skip_verify: true
selector:
name: example-workload-identity
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: tbot
spec:
selector:
matchLabels:
app: tbot
template:
metadata:
labels:
app: tbot
spec:
securityContext:
runAsUser: 0
runAsGroup: 0
hostPID: true
containers:
- name: tbot
image: public.ecr.aws/gravitational/tbot-distroless:17.0.0-dev
imagePullPolicy: IfNotPresent
securityContext:
privileged: true
args:
- start
- -c
- /config/tbot.yaml
- --log-format
- json
volumeMounts:
- mountPath: /config
name: config
- mountPath: /var/run/secrets/tokens
name: join-sa-token
- name: tbot-sockets
mountPath: /run/tbot/sockets
readOnly: false
env:
- name: TELEPORT_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: KUBERNETES_TOKEN_PATH
value: /var/run/secrets/tokens/join-sa-token
serviceAccountName: tbot
volumes:
- name: tbot-sockets
hostPath:
path: /run/tbot/sockets
type: DirectoryOrCreate
- name: config
configMap:
name: tbot-config
- name: join-sa-token
projected:
sources:
- serviceAccountToken:
path: join-sa-token
# 600 seconds is the minimum that Kubernetes supports. We
# recommend this value is used.
expirationSeconds: 600
# `example.teleport.sh` must be replaced with the name of
# your Teleport cluster.
audience: example.teleport.sh
Envoy SDS
The workload-identity-api
service endpoint also implements the Envoy SDS API.
This allows it to act as a source of certificates and certificate authorities
for the Envoy proxy.
As a forward proxy, Envoy can be used to attach an X.509 SVID to an outgoing connection from a workload that is not SPIFFE-enabled.
As a reverse proxy, Envoy can be used to terminate mTLS connections from SPIFFE-enabled clients. Envoy can validate that the client has presented a valid X.509 SVID and perform enforcement of authorization policies based on the SPIFFE ID contained within the SVID.
When acting as a reverse proxy for certain protocols, Envoy can be configured to attach a header indicating the identity of the client to a request before forwarding it to the service. This can then be used by the service to make authorization decisions based on the client's identity.
When configuring Envoy to use the SDS API exposed by the workload-identity-api
service, three additional special names can be used to aid configuration:
default
:tbot
will return the default SVID for the workload.ROOTCA
:tbot
will return the trust bundle for the trust domain that the workload is a member of.ALL
:tbot
will return the trust bundle for the trust domain that the workload is a member of, as well as the trust bundles of any trust domain that the trust domain is federated with.
The following is an example Envoy configuration that sources a certificate
and trust bundle from the workload-identity-api
service listening on
unix:///opt/machine-id/workload.sock
. It requires that a connecting client
presents a valid SPIFFE SVID and forwards this information to the backend
service in the x-forwarded-client-cert
header.
node:
id: "my-envoy-proxy"
cluster: "my-cluster"
static_resources:
listeners:
- name: test_listener
enable_reuse_port: false
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
common_http_protocol_options:
idle_timeout: 1s
forward_client_cert_details: sanitize_set
set_current_client_cert_details:
uri: true
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: my_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: my_service
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
# configure the certificate that the reverse proxy should present.
tls_certificate_sds_secret_configs:
# `name` can be replaced with the desired SPIFFE ID if multiple
# SVIDs are available.
- name: "default"
sds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
envoy_grpc:
cluster_name: tbot_agent
# combined validation context "melds" two validation contexts
# together. This is handy for extending the validation context
# from the SDS source.
combined_validation_context:
default_validation_context:
# You can use match_typed_subject_alt_names to configure
# rules that only allow connections from specific SPIFFE IDs.
match_typed_subject_alt_names: []
validation_context_sds_secret_config:
name: "ALL" # This can also be replaced with the trust domain name
sds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
envoy_grpc:
cluster_name: tbot_agent
clusters:
# my_service is the example service that Envoy will forward traffic to.
- name: my_service
type: strict_dns
load_assignment:
cluster_name: my_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8090
- name: tbot_agent
http2_protocol_options: {}
load_assignment:
cluster_name: tbot_agent
endpoints:
- lb_endpoints:
- endpoint:
address:
pipe:
# Configure the path to the socket that `tbot` is
# listening on.
path: /opt/machine-id/workload.sock