Implementation Owner:
Status: deferred
A typical workflow of an operator that is written using the Operator-SDK involves watching a Custom Resource (CR), which specifies the desired state of the cluster, and making changes to the current state of the cluster to achieve the desired state.
Each user specified CR represents a self contained system. Based on this, we can assume that any TLS assets generated are specific to each system which then is also specific to the corresponding CR.
Based on the above assumption, we want to design a TLS utility pkg that helps generate TLS assets and ties them to a specific CR.
To provide TLS utilities for operator developers to create self signed Certificate Authority (CA) and the TLS encryption keys along with the signed certs. This will enable the operator to setup TLS assets for the application so that all the communication within it is secure.
We will break down the TLS workflow into the following steps:
-
Generate a Certificate Authority (CA): A CA is a centralized trusted third party that signs the certificates. We need to generate a CA key and CA Certificate to get started. If the users want to provide their own CA, we can add an option to do so. The generated CA certificate can be saved in a ConfigMap as follows:
Inputs:
- CR-kind
- CR-name
- namespace
Outputs:
* A ConfigMap that contains the CA Cert:
kind: ConfigMap apiVersion: v1 metadata: name: <crd-kind>-<crd-name>-ca namespace: <ns> data: ca.crt: ... ``` * A secret that contains the CA key: ``` kind: Secret apiVersion: v1 metadata: name: <cr-kind>-<cr-name>-ca namespace: <ns> data: ca.key: ...
-
Verify the CA certificate: The newly generated CA certificate in ConfigMap should be validated i.e. the digital signature should be checked, the expiry, activation dates and validity period should be checked, etc.
-
Generate Server or Client Certificate: We assume that the components inside an application communicate through a service. Therefore, we generate a TLS private key and certificate for that service.
Inputs:
- CR-kind
- CR-name
- Namespace
- Previously generated CA key and Certificate
Options:
-
Key usage: (Server, Client or Both) If Server, needs svc obj (cluster.local can be different). If client, needs CN and possible org.
-
Option to specify signature configuration policy i.e Signing Profile for the certificate.
Outputs:
-
A Secret containing Private Key and Server/Client Certificate signed by the CA:
kind: Secret apiVersion: v1 metadata: name: <cr-kind>-<cr-name>-<CertName> namespace: <ns> data: tls.crt: ... tls.key: … type: kubernetes.io/tls
-
Verify Server or Client Certificate: The newly generated client/server certificate in should be validated i.e. the digital signature should be checked, the expiry, activation dates and validity period should be checked, certificate chain should be verified etc
-
Use CA configmap and TLS secret in the necessary deployment/pod object.
Have a certificate configuration object which will contain all the input arguments required to generate a server/client certificate.
// CertConfig configures the Cert generation.
type CertConfig struct {
// CertName is the name of the cert.
CertName string
// Optional CertType. Server, client or both; defaults to both.
CertType CertType
// CommonName is the common name of the cert
CommonName string
// Organization is Organization of the cert
Organization []string
// Optional CA Key, if user wants to provide custom CA
CAKey string
// Optional CA Certificate, if user wants to provide custom CA
CACert string
}
Have a method which will generate a secret that contains a newly created TLS Private key and Certificate for the given service. A unique CA is also generated by default to sign the Certificate if a prior CA certificate is not preset for the user specified CR. If the user wants to use a custom CA, it can be specified in the CertConfig
object. The function signature of the method is as follows:
func GenerateCert(cr runtime.Object, service *v1.Service, config *CertConfig) (*v1.Secret, *v1.ConfigMap, *v1.Secret, error)
Please note that the GenerateCert
method cannot be used to generate TLS assets for a service of type ExternalName
. This is because in the current design the fully qualified domain name (FQDN) of the Service object is used as the SAN for the certificate by default. Currently we do not have a way to customize the SAN value, we will add a field in CertConfig
for this purpose later on.
The GenerateCert
method can be used in the operator handler function to set up TLS for the desired application
// ../pkg/stub/handler.go
func (h *Handler) Handle(ctx types.Context, event types.Event) error {
switch crd := event.Object.(type) {
case *api.AppService:
cfg := CertConfig{
CertName: "server1",
}
s := v1.Service{...}
tlsCertSecret, caCertConfigMap, caKeySecret, err := GenerateCert(crd, s, cfg)
if err != nil {
return err
}
// use the newly generated TLS secret in a deployment manifest to mount it as volume.
...
}
}
To understand the implementation details more clearly let us run through an example operator and how it can make use of the TLS utility package:
Let us take the Vault Operator as an example. The Vault operator deploys and manages Vault clusters on Kubernetes. The following workflow is a step by step guide of how the vault operator can make use of the TLS utility package described above:
- Create the Vault CR using
kubectl create
- Modify the operator handler to generate the necessary vault server TLS assets:
- Populate the struct
CertConfig
with required values which specify the server certificate. - Call the
GenerateCert
method which will generate and return a secret that contains a newly created TLS Private key and Certificate for the Vault server. This Certificate will be signed by a custom CA or a newly generated CA depending on the configuration inCertConfig
.
- Populate the struct
- The operator is responsible for creating a service and deployment manifests for the application the user wishes to run. The deployment manifests will contain the TLS assets generated in step 2 as Volume mounts.
- What happens after the CA certificate expires? Once the CA certificate expires, all certificates signed by the CA become invalid.
-
Maintenance and rotation of these TLS certificates: Setting a certificate rotation policy from the start will protect you against the usual key mismanagement or leaking that is bound to happen over long periods of time. This is often overlooked and never-expiring tokens are shared between administrators for convenience reasons. To start off simple, we can provide a simple command line tool
--rotate-certificates
that will perform a rotation for a specific certificate. -
Consider having a signer server and a client agent in our TLS utilities package. The server will handle HTTP requests and responses. When it receives a CSR approval requests from the client agent it attempts to sign it. If successful, the approved CSR, which contains the signed certificate, is returned to the agent. This server-client mechanism will eliminate the need to copy around certs.