Clients TLS setup
TLS based auth certs via secret
Reloading supported by only updating secret
Routes TLS setup
Advertising public IP per NATS server for external access
{"CN": "nats.io","key": {"algo": "rsa","size": 2048},"names": [{"OU": "nats.io"}]}
(cd certs​# CA certscfssl gencert -initca ca-csr.json | cfssljson -bare ca -)
Setup the profiles for the Root CA, we will have 3 main profiles: one for the clients connecting, one for the servers, and another one for the full mesh routing connections between the servers.
{"signing": {"default": {"expiry": "43800h"},"profiles": {"server": {"expiry": "43800h","usages": ["signing","key encipherment","server auth","client auth"]},"client": {"expiry": "43800h","usages": ["signing","key encipherment","client auth"]},"route": {"expiry": "43800h","usages": ["signing","key encipherment","server auth","client auth"]}}}}
First we generate the certificates for the server.
{"CN": "nats.io","hosts": ["localhost","*.nats-cluster.default.svc","*.nats-cluster-mgmt.default.svc","nats-cluster","nats-cluster-mgmt","nats-cluster.default.svc","nats-cluster-mgmt.default.svc","nats-cluster.default.svc.cluster.local","nats-cluster-mgmt.default.svc.cluster.local","*.nats-cluster.default.svc.cluster.local","*.nats-cluster-mgmt.default.svc.cluster.local"],"key": {"algo": "rsa","size": 2048},"names": [{"OU": "Operator"}]}
(# Generating the peer certificatescd certscfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server.json | cfssljson -bare server)
We will also be setting up TLS for the full mesh routes.
{"CN": "nats.io","hosts": ["localhost","*.nats-cluster.default.svc","*.nats-cluster-mgmt.default.svc","nats-cluster","nats-cluster-mgmt","nats-cluster.default.svc","nats-cluster-mgmt.default.svc","nats-cluster.default.svc.cluster.local","nats-cluster-mgmt.default.svc.cluster.local","*.nats-cluster.default.svc.cluster.local","*.nats-cluster-mgmt.default.svc.cluster.local"],"key": {"algo": "rsa","size": 2048},"names": [{"OU": "Operator"}]}
# Generating the peer certificates(cd certscfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=route route.json | cfssljson -bare route)
{"CN": "nats.io","hosts": [""],"key": {"algo": "rsa","size": 2048},"names": [{"OU": "CNCF"}]}
(cd certs# Generating NATS client certscfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client)
cd certs kubectl create secret generic nats-tls-example --from-file=ca.pem --from-file=server-key.pem --from-file=server.pem kubectl create secret generic nats-tls-routes-example --from-file=ca.pem --from-file=route-key.pem --from-file=route.pem kubectl create secret generic nats-tls-client-example --from-file=ca.pem --from-file=client-key.pem --from-file=client.pem
{"users": [{ "username": "CN=nats.io,OU=ACME" },{ "username": "CN=nats.io,OU=CNCF","permissions": {"publish": ["hello.*"],"subscribe": ["hello.world"]}}],"default_permissions": {"publish": ["SANDBOX.*"],"subscribe": ["PUBLIC.>"]}}
kubectl create secret generic nats-tls-users --from-file=users.json
echo 'apiVersion: "nats.io/v1alpha2"kind: "NatsCluster"metadata:name: "nats-cluster"spec:size: 3​# Using custom edge nats server image for TLS verify and map support.serverImage: "wallyqs/nats-server"version: "edge-2.0.0-RC5"​tls:enableHttps: true​# Certificates to secure the NATS client connections:serverSecret: "nats-tls-example"​# Certificates to secure the routes.routesSecret: "nats-tls-routes-example"​auth:tlsVerifyAndMap: trueclientsAuthSecret: "nats-tls-users"​# How long to wait for authenticationclientsAuthTimeout: 5​pod:# To be able to reload the secret changesenableConfigReload: truereloaderImage: connecteverything/nats-server-config-reloader​# Bind the port 4222 as the host port to allow external access.enableClientsHostPort: true​# Initializer container that resolves the external IP from the# container where it is running.advertiseExternalIP: true​# Image of container that resolves external IP from K8S APIbootconfigImage: "wallyqs/nats-boot-config"bootconfigImageTag: "0.5.0"​# Service account required to be able to find the external IPtemplate:spec:serviceAccountName: "nats-server"' | kubectl apply -f -
Development
package main​import ("encoding/json""flag""fmt""log""time"​"github.com/nats-io/go-nats""github.com/nats-io/nuid")​func main() {var (serverList stringrootCACertFile stringclientCertFile stringclientKeyFile string)flag.StringVar(&serverList, "s", "tls://nats-1.nats-cluster.default.svc:4222", "List of NATS of servers available")flag.StringVar(&rootCACertFile, "cacert", "./certs/ca.pem", "Root CA Certificate File")flag.StringVar(&clientCertFile, "cert", "./certs/client.pem", "Client Certificate File")flag.StringVar(&clientKeyFile, "key", "./certs/client-key.pem", "Client Private key")flag.Parse()​log.Println("NATS endpoint:", serverList)log.Println("Root CA:", rootCACertFile)log.Println("Client Cert:", clientCertFile)log.Println("Client Key:", clientKeyFile)​// Connect optionsrootCA := nats.RootCAs(rootCACertFile)clientCert := nats.ClientCert(clientCertFile, clientKeyFile)alwaysReconnect := nats.MaxReconnects(-1)​var nc *nats.Connvar err errorfor {nc, err = nats.Connect(serverList, rootCA, clientCert, alwaysReconnect)if err != nil {log.Printf("Error while connecting to NATS, backing off for a sec... (error: %s)", err)time.Sleep(1 * time.Second)continue}break}​nc.Subscribe("discovery.*.status", func(m *nats.Msg) {log.Printf("[Received on %q] %s", m.Subject, string(m.Data))})​discoverySubject := fmt.Sprintf("discovery.%s.status", nuid.Next())info := struct {InMsgs uint64 `json:"in_msgs"`OutMsgs uint64 `json:"out_msgs"`Reconnects uint64 `json:"reconnects"`CurrentServer string `json:"current_server"`Servers []string `json:"servers"`}{}​for range time.NewTicker(1 * time.Second).C {stats := nc.Stats()info.InMsgs = stats.InMsgsinfo.OutMsgs = stats.OutMsgsinfo.Reconnects = stats.Reconnectsinfo.CurrentServer = nc.ConnectedUrl()info.Servers = nc.Servers()payload, err := json.Marshal(info)if err != nil {log.Printf("Error marshalling data: %s", err)}err = nc.Publish(discoverySubject, payload)if err != nil {log.Printf("Error during publishing: %s", err)}nc.Flush()}}
FROM golang:1.11.0-alpine3.8 AS builderCOPY . /go/src/github.com/nats-io/nats-kubernetes/examples/nats-cluster-routes-tls/appWORKDIR /go/src/github.com/nats-io/nats-kubernetes/examples/nats-cluster-routes-tls/appRUN apk add --update gitRUN go get -u github.com/nats-io/go-natsRUN go get -u github.com/nats-io/nuidRUN CGO_ENABLED=0 go build -o nats-client-app -v -a ./client.go​FROM scratchCOPY --from=builder /go/src/github.com/nats-io/nats-kubernetes/examples/nats-cluster-routes-tls/app/nats-client-app /nats-client-appENTRYPOINT ["/nats-client-app"]
docker build . -t wallyqs/nats-client-appdocker run wallyqs/nats-client-appdocker push wallyqs/nats-client-app
echo ' apiVersion: apps/v1beta2 kind: Deployment​## The name of the deployment​metadata: name: nats-client-app​spec:​## This selector has to match the template.metadata.labels section​## which is below in the PodSpec​selector: matchLabels: name: nats-client-app​## Number of instances​replicas: 1​## PodSpec​template: metadata: labels: name: nats-client-app spec: volumes:​* name: "client-tls-certs"​secret:​secretName: "nats-tls-client-example"​containers:​* name: nats-client-app​command: \["/nats-client-app", "-s", "tls://nats-cluster.default.svc:4222", "-cacert", '/etc/nats-client-tls-certs/ca.pem', '-cert', '/etc/nats-client-tls-certs/client.pem', '-key', '/etc/nats-client-tls-certs/client-key.pem'\]​image: wallyqs/nats-client-app:latest​imagePullPolicy: Always​volumeMounts:​* name: "client-tls-certs"​mountPath: "/etc/nats-client-tls-certs/"​' \| kubectl apply -f -