Creating tls secrets in Kubernetes from Azure Vault Certificates in Go

385 Views Asked by At

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...

  1. 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
  2. I use base64 package to decode what Azure sends me (a PFX containing private and public keys
  3. I use pkcs12 package pkcs12.toPEM function to break it apart into an array of pem.Block.
  4. Each block has a value that contains headers in the value
  5. 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
}
,,,
0

There are 0 best solutions below