0
votes

Sorry for my bad English but I don't know how to solve my problem. So... Introduction: I have 2 microservices (I called them gRPCClient and gRPCServer, although it doesn’t matter what exactly). They need to communicate via TLS. Without Kubernets, everything is quite simple. I create my CA via cfssl in a docker container, then I get the root certificate from CA and I put it in trust for my grpc applications (I do this in Dockerfile), so that any certificate signed by my CA passes the test.

Now Kubernetes is included in the game. I'm playing locally with minikube. I create local cluster "minikube start" on mac (maybe this is important, I don’t know ...)

Problem: How will this flow work with the Kubernetes? As I understand it, there is already a CA inside the Kubernetes (correct me if this is not so). I read many articles, but I really didn’t understand anything. I tried the examples from this article https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/ Step by step:

  1. Create a signature request
    cat <<EOF | cfssl genkey - | cfssljson -bare server
    {
      "hosts": [
        "my-svc.my-namespace.svc.cluster.local",
        "my-pod.my-namespace.pod.cluster.local",
        "192.0.2.24",
        "10.0.34.2"
      ],
      "CN": "my-pod.my-namespace.pod.cluster.local",
      "key": {
        "algo": "ecdsa",
        "size": 256
      }
    }
    EOF

The first thing I did not understand was the hosts. For example, my-svc.my-namespace.svc.cluster.local is the full name of my service? (I mean the service in Kubernetes as kind: Service). I have it in the namespace "dev" and its name is user-app-sesrvice. Should I specify user-app-sesrvice.dev.svc.cluster.local then? or it just user-app-sesrvice. Or is there some kind of command to get the full name of the service? 192.0.2.24 - as I understand it, is the IP of service, it is also unclear whether it is mandatory to specify it or is it possible only the name of the service? What if I have clusterIP: None installed, then I don't have IP for it. my-pod.my-namespace.pod.cluster.local - Should I specify this? If I have several pods, should I list them all? Then the problem is in the dynamics, because the pods are recreated, deleted and added, and I need to send a new request for signature each time. The same questions that I asked about service including some parts "my-pod" and "namespace"? Is it possible to see the full name of the pod with all this data. 10.0.34.2 - pods' IP. The same question about pods' IP.

I tried to specify the host and CN as name of my service name "user-app-service" (as if I was working without a Kubernetes). I created a signature and a key. Then all the steps, created a request object for signature in the Kubernetes

    cat <<EOF | kubectl apply -f -
    apiVersion: certificates.k8s.io/v1beta1
    kind: CertificateSigningRequest
    metadata:
      name: my-svc.my-namespace
    spec:
      request: $(cat server.csr | base64 | tr -d '\n')
      usages:
      - digital signature
      - key encipherment
      - server auth
    EOF

Then I made it and I received a certificate Further, based on security, I need to store the key and a certificate in secrets and then get it in the container (for the purposes of the test, I just put them in the container in the docker file, hard-coded), this is in the gRPC server. I run the deployment and created a client on golang, specifying config: = &tls.Config{} in the code so that it would pull the trusted certificates from the system itself, I thought that the Kubernetes has a CA, but did not find how to get its certificate in the docs. I thought the Kubernetes adds them to all the containers himself. But I got the error Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate signed by unknown authority". How should all this work? Where can I get a CA certificate from a Kubernetes? And then, do I need to add it to each container with my hands in dockerfile? or is this not the right tactic and is there some kind of automation from the Kubernetes?

I found another way, this is to try to deploy cfssl https://hub.docker.com/r/cfssl/cfssl/ on the Kubernetes and already work with it, like there was no Kubernetes (I have not tried this method yet)

How to put all this into a working system, what options to use and why? Maybe there are some full articles. I wrote a lot, but I hope it’s clear. I really need the help.

2

2 Answers

3
votes

There is a lot of boilerplate work that you need to do with this bespoke approach. If you have an option I would suggest exploring service mesh such as istio or linkerd to secure communication between micro-services using TLS in kubernetes.

3
votes

I am going to break down my answer into a couple of parts:

Kubernetes Services and DNS Discovery

In general, it is recommended to put a Service in front of a Deployment that manages pods in Kubernetes. The Service creates a stable DNS and IP endpoint for pods that may be deleted and be assigned a different IP address when recreated. DNS service discovery is automatically enabled with a ClusterIP type service and is in the format: <service name>.<kubernetes namespace>.svc.<cluster domain> where cluster domain is usually cluster.local. This means that we can use the autocreated DNS and assigned ClusterIP in our altnames for our certificate.

Kubernetes Internal CA

Kubernetes does have an internal CA along with API methods to post CSRs and have those CSRs signed by the CA however I would not use the internal CA for securing microservices. The internal CA is primarily used by the kubelet and other internal cluster processes to authenticate to the Kubernetes API server. There is no functionality for autorenewal and I think the cert will always be signed for 30 days.

Kubernetes-native Certificate Management

You can install and use cert-manager to have the cluster automatically create and manage certificates for you using custom resources. They have excellent examples on their website so I would encourage you to check that out if it is of interest. You should be able to use the CA Issuer Type and create Certificate Resources that will create a certificate as a Kubernetes Secret. For the altnames, refer to the below certificate generation steps in the manual section of my response.

Manually Create and Deploy Certificates

You should be able to achieve they same result using your "without Kubernetes" approach using cfssl:

  1. generate CA using cfssl

  2. add CA as trusted in image (using your Dockerfile approach)

  3. create Kubernetes Service (for example purposes I will use kubectl create)

    $ kubectl create service clusterip grpcserver --tcp=8000
    
  4. describe the created Kubernetes Service, note IP will most likely be different in your case

    $ kubectl describe service/grpcserver
    Name:              grpcserver
    Namespace:         default
    Labels:            app=grpcserver
    Annotations:       <none>
    Selector:          app=grpcserver
    Type:              ClusterIP
    IP:                10.108.125.158
    Port:              8000  8000/TCP
    TargetPort:        8000/TCP
    Endpoints:         <none>
    Session Affinity:  None
    Events:            <none>
    
  5. generate certificate for gRPCServer with a CN of grpcserver.default.svc.cluster.local the following altnames:

    • grpcserver
    • grpcserver.default.svc
    • grpcserver.default.svc.cluster.local
    • 10.108.125.158
  6. generate the client certificate with cfssl

  7. put both certificates into Secret objects

    kubectl create secret tls server --cert=server.pem --key=server.key
    kubectl create secret tls client --cert=client.pem --key=client.key
    
  8. mount the secret into the podspec