I could use the Azure Key Vault provider for Secrets Store CSI driver (https://learn.microsoft.com/en-us/azure/aks/csi-secrets-store-driver)
but I need to have a bit more control over the certificates via a custom k8 management API am developing for my use case. I had some challenges with this.
I think I have a good enough solution but want to put it out in the world for comment from others that may have done this in the past.
here is what I did...
- When one creates the certificates (or uploads them) in Azure vault the values are stored in secrets. I used the Azure sdk for Go to get them
- I use base64 package to decode what Azure sends me (a PFX containing private and public keys
- I use pkcs12 package pkcs12.toPEM function to break it apart into an array of pem.Block.
- Each block has a value that contains headers in the value
- I need to strip the headers out before I use them in the kubernetes secret
package secretcreator
import (
"context"
"encoding/base64"
"encoding/pem"
"log"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
"golang.org/x/crypto/pkcs12"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
type MyStruct struct {
kubeClientSet *kubernetes.Clientset
azureVaultSecrets *azsecrets.Client
azureVaultCertificates *azcertificates.Client
beNamespace *v1.Namespace
identitySecrests *v1.Secret
}
func (t *MyStruct) createSecrets() error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
//tlsSecret.value is a base64 encoded PEM/PFX containing PRIVATE KEY and CERTIFICATES blocks
tlsSecret, err := t.azureVaultSecrets.GetSecret(ctx, `myCertName`, ``, nil)
if err != nil {
log.Printf(`Failed to retrieve cert :%s`, err.Error())
return err
}
//pfxBytes is the Byte representation of the string stored in tlsSecret.Value
pfxBytes, err := base64.StdEncoding.Strict().DecodeString(*tlsSecret.Value)
if err != nil {
log.Printf(`Failed to decode cert PFX:%s`, err.Error())
return err
}
// pemBlocks is a array of *pem.Block - in my case I have 1 PRIVATE KEY Block and three CERTIFICATE Blocks
//the blocks have headers too that look something like this on the PRIVATE KEY, after encoding the encoded string looks like this
//
// -----BEGIN PRIVATE KEY-----
// Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0
// friendlyName: {D06B3D5A-98D1-45F1-A8C2-DC091232D6A7}
// localKeyId: 01000000
//
// MIIEAI.......ymfjsQ==
// -----END PRIVATE KEY-----
pemBlocks, err := pkcs12.ToPEM(pfxBytes, "")
if err != nil {
log.Printf(`Failed to decode key or cert from PEM Blocks:%s`, err.Error())
return err
}
// k8 needs to store TLS in a secret with key value pairs in the data - one for tls.key and the other for tls.crt
// this code iterates through the blocks, looks at the type of data stored and breaks it up into tls.key and tls.crt
// k8.nginx does not like the headers for some reason - so i strip them out using base64.StdEncoding.Encode to string and
// then adding "-----BEGIN..." and "-----END..." strings"
var tlsKey []byte
var tlsCert []byte
for _, b := range pemBlocks {
if b.Type == "PRIVATE KEY" {
block, _ := pem.Decode(pem.EncodeToMemory(b))
tlsKey = append(tlsKey, "-----BEGIN PRIVATE KEY-----\n"...)
tlsKey = append(tlsKey, base64.StdEncoding.EncodeToString(block.Bytes)...)
tlsKey = append(tlsKey, "-----END PRIVATE KEY-----\n"...)
}
if b.Type == "CERTIFICATE" {
block, _ := pem.Decode(pem.EncodeToMemory(b))
tlsCert = append(tlsCert, "-----BEGIN CERTIFICATE-----\n"...)
tlsCert = append(tlsCert, base64.StdEncoding.EncodeToString(block.Bytes)...)
tlsCert = append(tlsCert, "-----END CERTIFICATE-----\n"...)
}
}
// this following code uses client-go package to create a TLS secret in kubernetes
tlsData := make(map[string][]byte)
tlsData[`tls.key`] = tlsKey
tlsData[`tls.crt`] = tlsCert
beTls := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: `mycertname`,
Namespace: t.beNamespace.Name,
},
Data: tlsData,
Type: v1.SecretTypeTLS,
}
_, err = t.kubeClientSet.CoreV1().Secrets(t.beNamespace.Name).Create(ctx, beTls, metav1.CreateOptions{})
if err != nil {
log.Printf(`Failed to create TLS secret :%s`, err.Error())
return err
}
return nil
}
,,,