Fetch certificates and private keys bundle from Azure Keyvault in Go via Azure SDK
As this required a little bit of digging around for a seemingly simple task, felt like sharing is a great idea. This code supports fetching certificates from Azure Keyvault
Azure Keyvault supports storing the certificate bundle in 2 formats, PEM and PKCS#12.
Screenshot of supported types while creating a new certificate
Go code, self explanatory
package main
import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"flag"
"fmt"
"os"
"time"
"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault"
"github.com/Azure/azure-sdk-for-go/services/keyvault/auth"
"github.com/Azure/go-autorest/autorest/azure"
"golang.org/x/crypto/pkcs12"
)
type azureKVClient struct {
ctx context.Context
vault string
client keyvault.BaseClient
authenticated bool
vaultBaseURL string
}
// initKVClient initializes and authenticates the client to interact with Azure Key Vault
func (akv *azureKVClient) initKVClient(ctx context.Context, vaultName string) (err error) {
akv.ctx = ctx
akv.vault = vaultName
akv.client = keyvault.New()
authorizer, err := auth.NewAuthorizerFromEnvironment()
// authorizer, err := auth.NewAuthorizerFromCLI()
if err != nil {
return err
}
akv.client.Authorizer = authorizer
akv.authenticated = true
akv.vaultBaseURL = fmt.Sprintf("https://%s.%s", akv.vault, azure.PublicCloud.KeyVaultDNSSuffix)
return nil
}
// getBundle returns the bundle
func (akv *azureKVClient) getBundle(certificateName string) (bundle string, err error) {
// Error if there's no authenticated client yet
if !akv.authenticated {
return
}
// Last param is empty so it fetches the latest version
ctx, cancel := context.WithTimeout(akv.ctx, 5*time.Second)
defer cancel()
secret, err := akv.client.GetSecret(ctx, akv.vaultBaseURL, certificateName, "")
if err != nil {
return
}
// Azure currently supports only PKCS#12 or PEM
switch *secret.ContentType {
case "application/x-pem-file":
bundle = *secret.Value
case "application/x-pkcs12":
pfx, err := base64.StdEncoding.DecodeString(*secret.Value)
if err != nil {
return bundle, err
}
blocks, _ := pkcs12.ToPEM(pfx, "")
for _, block := range blocks {
// No headers, we don't wait it
if block.Type == "PRIVATE KEY" {
pkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
derStream := x509.MarshalPKCS1PrivateKey(pkey)
block = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: derStream,
}
}
block.Headers = nil
bundle = bundle + string(pem.EncodeToMemory(block))
}
return bundle, nil
default:
return bundle, fmt.Errorf("unknown ContentType, %s", *secret.ContentType)
}
return
}
func main() {
var vaultName string
var certName string
flag.StringVar(&vaultName, "V", "", "Vault name")
flag.StringVar(&certName, "C", "", "Certificate name")
flag.Parse()
ctx := context.Background()
// Authenticate
client := azureKVClient{}
if err := client.initKVClient(ctx, vaultName); err != nil {
fmt.Println("Error", err)
os.Exit(1)
}
// Fetch the client and key as PEM
bundle, err := client.getBundle(certName)
if err != nil {
fmt.Println("Error: ", err)
os.Exit(1)
}
fmt.Println(bundle)
}
To Run:
$ go run . -V <AZ-Keyvault-Name> -C <cert-name-in-azure-keyvault>