- An AMD, Intel, or ARM 64-bit Linux environment.
- Familiarity with the
kubectl
command-line interface (CLI) is helpful.
Coastal Containers Ltd., a leading container shipping company, is looking to evolve their security practices by
implementing mutual authentication (via mTLS) between their Port
Authority server
and incoming vessel clients
. In order to do so, they have decided to leverage
go-spiffe, a popular and convenient Golang
library built to work with SPIFFE. By leveraging the go-spiffe
library,
Coastal Containers hopes to create SPIFFE-aware workloads and establish mTLS connections with assigned SPIFFE IDs.
Ultimately, the Port Authority server
and vessel client
workloads should communicate securely by validating each
other's SPIFFE ID's.
This hands-on exercise is based on the go-spiffe tls example.
- Understand how to implement SPIFFE-aware workloads using the
go-spiffe
library. - Understand the
go-spiffe
listening and dialoging logic for SPIFFE-aware mTLS communication. - Learn how
go-spiffe
interacts with the Workload API to manage SVIDs.
To set sail, spin up the Kubernetes demo cluster using Kind by issuing the following make
command while in the root
lab directory:
make cluster-up
Initialize the SPIRE setup to your Kubernetes cluster:
make deploy-spire spire-wait-for-agent
The SPIRE configuration is identical to the previous lab. We will also create SPIRE registration entries for the SPIRE agent, the server workload, and the client workload. These are the same as in previous labs so for convenience run the following command:
make create-registration-entries
Once finished, you should see the output:
Entry ID : 01de1c15-ef71-4834-b702-455d70586212
SPIFFE ID : spiffe://coastal-containers.example/agent/spire-agent
Parent ID : spiffe://coastal-containers.example/spire/server
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : k8s_psat:agent_ns:spire
Selector : k8s_psat:agent_sa:spire-agent
Selector : k8s_psat:cluster:kind-kind
Entry ID : 05b21cc2-4e90-4aad-8b48-419fd9938678
SPIFFE ID : spiffe://coastal-containers.example/workload/server
Parent ID : spiffe://coastal-containers.example/agent/spire-agent
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : k8s:ns:default
Selector : k8s:sa:server
Entry ID : f7f36f9c-c73e-42ef-bb15-3d0f12a43f1d
SPIFFE ID : spiffe://coastal-containers.example/workload/client
Parent ID : spiffe://coastal-containers.example/agent/spire-agent
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : k8s:ns:default
Selector : k8s:sa:client
You can see the go-spiffe
logic within the main.go file that the server uses to interact
with the Workload API and listen for incoming client connections, only accepting client(s) that present a valid
X509-SVID with a matching SPIFFE ID.
Configuration items of note include:
Function Invocation:
- The server uses the
spiffetls.Listen
function to start listening for incoming connections as shown below.
listener, err := spiffetls.Listen(ctx, "tcp", serverAddress, tlsconfig.AuthorizeID(clientID))
Server Address:
serverAddress
constant specifies where the server will listen for client connections (0.0.0.0:8443
).
Authentication & Authorization:
-
clientID
is the SPIFFE ID which is used to authenticate incoming client connections (spiffe://coastal-containers.example/workload/client
). -
tlsconfig.AuthorizeID(clientID)
ensures the server accepts connections only from clients that present an X509-SVID with a matching SPIFFE ID (clientID
).
SPIFFE_ENDPOINT_SOCKET:
- The
spiffetls.Listen
function uses theSPIFFE_ENDPOINT_SOCKET
environment variable to locate the Workload API address, obtaining the SVIDs needed for establishing secure communication. This environment variable is set within the deploy-server.yaml manifest.
📝Note: Detailed explanations about the underlying logic are provided in the go-spiffe tls example and can be found within the associated API documentation.
You can see the go-spiffe
logic within the main.go file that the client uses to dial and
establish a connection with the server, only accepting server(s) that present a valid X509-SVID with a matching SPIFFE
ID.
Configuration items of note include:
Function Invocation:
- The client uses the
spiffetls.Dial
function to establish a connection with the server.
listener, err := spiffetls.Dial(ctx, "tcp", serverAddress, tlsconfig.AuthorizeID(serverID))
Server Address:
serverAddress
constant is the address of the server (server:443
) to which the client is connecting.- This is set to the
server
service name to work in a containerized Kubernetes environment.
Authentication & Authorization:
-
serverID
is the SPIFFE ID which is used to authenticate the server (spiffe://coastal-containers.example/workload/server
). -
tlsconfig.AuthorizeID(serverID)
ensures the client establishes a connection only with a server that presents a X509-SVID with the expected SPIFFE ID (serverID
).
SPIFFE_ENDPOINT_SOCKET:
- The
spiffetls.Dial
function uses theSPIFFE_ENDPOINT_SOCKET
environment variable to locate the Workload API address, obtaining the SVIDs needed for establishing secure communication to the server. This environment variable is set within the client manifest.
📝Note: Detailed explanations about the underlying logic are provided in the go-spiffe tls example and can be found within the associated API documentation.
That's all! By leveraging the SPIFFE_ENDPOINT_SOCKET
environment variable, which can be set within your Kubernetes
deployment manifests, your application can utilize the Workload API without the need of hard coding the socket path.
The go-spiffe
library will then take care of the rest as it manages the automatic fetching and renewing of your
X509-SVIDs, thus simplifying the setup of mutual authentication and secure communication between your workloads.
Build and load the server
and client
workload image by running the make command:
make workload-images
While still within the root lab-05-go-spiffe directory, deploy the server workload to your kind cluster:
make deploy-workloads
Check the workloads by running the following command:
kubectl get pods
If everything was successful, you should see the running server
workload and see the running (or completed) client
workload.
client
pod will likely show the status of Completed
as due to the nature of containers, they are meant
to run a process or task and quit thereafter. In this case, the client
will restart a number of times as it
successfully sends the intended message to the server
workload, receives a reply, and exits. This is expected behavior.
Now, observe the logs for the server
and client
, ensuring the client
sends the intended message, and the server
responds back.
kubectl logs deployments/server
If everything ran successfully, you should see for the server
logs:
SPIFFE_ENDPOINT_SOCKET: unix:///spire-agent-socket/agent.sock
Starting server on 0.0.0.0:8443
Server listening on 0.0.0.0:8443
Incoming vessel says: "This is SS Coastal Carrier hailing the port authority for Coastal Containers Ltd.\n"
And for the client
logs:
kubectl logs deployments/client
SPIFFE_ENDPOINT_SOCKET: unix:///spire-agent-socket/agent.sock
Connecting to server:443
Client connected to server:443
Port Authority says: "Request received SS Coastal Carrier. You are cleared to dock.\n"
To tear down the Kind cluster, run:
make cluster-down
Congratulations! You've successfully implemented SPIFFE-aware workloads using the go-spiffe
library, enabling them to
communicate securely using SPIFFE IDs and mutual authentication. Feel free to explore further by setting up different
workloads or SPIFFE ID setups, observing how SPIFFE secures communication in dynamic and containerized environments.
You are highly encouraged to explore the
go-spiffe API documentation and the rich set of standalone examples
provided within the go-spiffe examples repository which
showcases different use-cases for go-spiffe
. Happy coding!