Here's a basic Go app, running in k8s with siffe / spire. It's basically a stripped down version of github.com/spiffe/go-spiffe/v2/examples/spiffe-http/client.
Install spire server, and agent.
Register a workload for spire agent:
1$ kubectl exec -n spire spire-server-0 -- \
2 /opt/spire/bin/spire-server entry create \
3 -spiffeID spiffe://liao.dev/ns/spire/sa/spire-agent \
4 -selector k8s_psat:cluster:ihwa \
5 -selector k8s_psat:agent_ns:spire \
6 -selector k8s_psat:agent_sa:spire-agent \
7 -node
Register workloads for our test server / client
1$ kubectl exec -n spire spire-server-0 -- \
2 /opt/spire/bin/spire-server entry create \
3 -spiffeID spiffe://ihwa.liao.dev/ns/default/sa/test-server \
4 -parentID spiffe://ihwa.liao.dev/ns/spire/sa/spire-agent \
5 -selector k8s:ns:default \
6 -selector k8s:sa:test-server
7$ kubectl exec -n spire spire-server-0 -- \
8 /opt/spire/bin/spire-server entry create \
9 -spiffeID spiffe://ihwa.liao.dev/ns/default/sa/test-client \
10 -parentID spiffe://ihwa.liao.dev/ns/spire/sa/spire-agent \
11 -selector k8s:ns:default \
12 -selector k8s:sa:test-client
go-spiffe has a bunch of packages, but the one we're interested in is workloadapi which can talk to the socket from the agent.
The cert from the client is in PeerCertificates as the only URI entry. VerifiedChains is not populated.
1package main
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "log"
8 "net/http"
9
10 "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
11 "github.com/spiffe/go-spiffe/v2/workloadapi"
12)
13
14func main() {
15 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
16 fmt.Println("peer", r.TLS.PeerCertificates[0].URIs[0].String()) // peer spiffe://ihwa.liao.dev/ns/default/sa/test-client
17 io.WriteString(w, "hello world")
18 })
19
20 ctx := context.Background()
21 // uses SPIFFE_ENDPOINT_SOCKET by default
22 source, err := workloadapi.NewX509Source(ctx)
23 if err != nil {
24 log.Fatalln("spiffe source:", err)
25 }
26 defer source.Close()
27 server := &http.Server{
28 Addr: ":8443",
29 TLSConfig: tlsconfig.MTLSServerConfig(source, source, tlsconfig.AuthorizeAny()),
30 }
31
32 log.Println("starting")
33 log.Fatalln(server.ListenAndServeTLS("", ""))
34}
manifest:
1apiVersion: v1
2kind: Service
3metadata:
4 name: test-server
5spec:
6 ports:
7 - name: https
8 port: 443
9 targetPort: https
10 selector:
11 app: test-server
12---
13apiVersion: v1
14kind: ServiceAccount
15metadata:
16 name: test-server
17---
18apiVersion: apps/v1
19kind: Deployment
20metadata:
21 name: test-server
22spec:
23 selector:
24 matchLabels:
25 app: test-server
26 template:
27 metadata:
28 labels:
29 app: test-server
30 spec:
31 serviceAccountName: test-server
32 containers:
33 - name: app
34 image: ko://go.seankhliao.com/testrepo0477/server
35 env:
36 - name: SPIFFE_ENDPOINT_SOCKET
37 value: unix:///run/spire/sockets/agent.sock
38 ports:
39 - name: https
40 containerPort: 8443
41 volumeMounts:
42 - name: spire-agent-socket
43 mountPath: /run/spire/sockets
44 readOnly: true
45 volumes:
46 - name: spire-agent-socket
47 hostPath:
48 path: /run/spire/sockets
49 type: Directory
Almost a copy of server
1package main
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "log"
8 "net/http"
9 "os"
10 "time"
11
12 "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
13 "github.com/spiffe/go-spiffe/v2/workloadapi"
14)
15
16func main() {
17 ctx := context.Background()
18 source, err := workloadapi.NewX509Source(ctx)
19 if err != nil {
20 log.Fatalln("spiffe source:", err)
21 }
22 defer source.Close()
23
24 tlsConfig := tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeAny())
25 client := &http.Client{
26 Transport: &http.Transport{
27 TLSClientConfig: tlsConfig,
28 },
29 }
30
31 log.Println("starting client loop")
32 for range time.NewTicker(5 * time.Second).C {
33 func() {
34 r, err := client.Get("https://test-server")
35 if err != nil {
36 log.Fatalln("get from server", err)
37 }
38 defer r.Body.Close()
39 fmt.Println("\npeer", r.TLS.PeerCertificates[0].URIs[0].String()) // peer spiffe://ihwa.liao.dev/ns/default/sa/test-server
40 io.Copy(os.Stdout, r.Body)
41 }()
42 }
43}
manifest:
1apiVersion: v1
2kind: ServiceAccount
3metadata:
4 name: test-client
5---
6apiVersion: apps/v1
7kind: Deployment
8metadata:
9 name: test-client
10spec:
11 selector:
12 matchLabels:
13 app: test-client
14 template:
15 metadata:
16 labels:
17 app: test-client
18 spec:
19 serviceAccountName: test-client
20 containers:
21 - name: app
22 image: ko://go.seankhliao.com/testrepo0477/client
23 env:
24 - name: SPIFFE_ENDPOINT_SOCKET
25 value: unix:///run/spire/sockets/agent.sock
26 volumeMounts:
27 - name: spire-agent-socket
28 mountPath: /run/spire/sockets
29 readOnly: true
30 volumes:
31 - name: spire-agent-socket
32 hostPath:
33 path: /run/spire/sockets
34 type: Directory