
Shifna Zarnaz
Automating Vault Key Management in Kubernetes with Golang
Table of Contents:
- 1. Introduction
- 2. Prerequisites
- 3. Code Overview
- Import Statements
- Vault struct
- Generating Unseal Keys
- Unsealing the Vault
- Configuring the Kubernetes Client
- Storing Keys in Kubernetes Secrets
- Retrieving Keys from Kubernetes Secrets
- Monitoring and unsealing vault automatically
- 4. Conclusion
1. Introduction
This blog post explores an automated approach to manage Vault keys in a Kubernetes environment using Golang. It covers how to generate, store, and retrieve Vault unseal keys and root tokens in Kubernetes Secrets. The sample code provided demonstrates the implementation of this approach, integrating the HashiCorp Vault API with the Kubernetes client-go library.
Prerequisites
Before proceeding with the implementation, make sure you have the following prerequisites in place:
- Basic knowledge of Go programming language
- Access to a Kubernetes cluster
- A running Vault server in the Kubernetes cluster
- Go installed on your local machine
- A running HashiCorp Vault server
-
kubectl
command-line tool installed and configured to communicate with the Kubernetes cluster - Basic understanding of Vault and its concepts
Code Overview
The code provided is written in Go and consists of several components that work together to automate the unsealing of a Vault server in a Kubernetes cluster and to store the keys in k8s secret. Let’s dive into the code and understand its key components.
Import Statements
The code starts by importing necessary packages, including the Vault API, Kubernetes API, and utility packages.
import (
"fmt"
"log"
"time"
"context"
"github.com/hashicorp/vault/api"
"path/filepath"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
Vault Struct
The vault
struct holds a client object from the Vault API and provides
methods for interacting with the Vault server.
This struct will be used to generate unseal keys, unseal the Vault, and store/retrieve the keys from
Kubernetes secrets
type vault struct {
client *api.Client
}
Generating Unseal Keys
Generates the required number of unseal keys and the root token using the Vault API. These keys are later stored in a Kubernetes Secret.
func (v vault) GenerateUnsealKeysFromVault() ([]string, string, error) {
res := &api.InitRequest{
SecretThreshold: 2,
SecretShares: 3,
}
unsealKeys := []string{}
key, err := v.client.Sys().Init(res)
if err != nil {
fmt.Println("Error while initializing ", err)
}
for _, key := range key.Keys {
fmt.Println("Key is ", key)
unsealKeys = append(unsealKeys, key)
}
rootToken := key.RootToken
fmt.Println("Root Token is ", rootToken)
fmt.Print("Unsealed keys are generated")
return unsealKeys, rootToken, err
}
Unsealing the Vault
The Unseal()
method takes a slice of unseal keys and iterates over them
to unseal the Vault server.
If any error occurs during the unsealing process, it returns the error.
func (v vault) Unseal(keys []string) error {
flag := true
for _, key := range keys {
_, err := v.client.Sys().Unseal(key)
if err != nil {
flag = false
fmt.Println("Error while unsealing", err)
return err
}
}
if flag {
fmt.Println("Unsealed")
}
return nil
}
Configuring the Kubernetes Client
The Config()
function reads the kubeconfig file and creates a
Kubernetes clientset that will be used to interact with the Kubernetes API.
func Config() *kubernetes.Clientset {
var kubeconfig string
if home := homedir.HomeDir(); home != "" {
kubeconfig = filepath.Join(home, ".kube", "config")
}
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
panic(err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
fmt.Println("Error while creating clientset", err)
}
return clientset
}
Storing Keys in Kubernetes Secrets
The Storekeys()
method generates unseal keys and the root token using
the
GenerateUnsealKeysFromVault()
method.
It then creates a Kubernetes secret with the generated keys and returns the values.
This function utilizes the Kubernetes clientset created in the Config()
function.
func (v vault) Storekeys(nameSpace string, SecretName string) []string {
clientset := Config()
var values []string
namespace := nameSpace//namespace
secretName := SecretName
// Retrieve the secret with the given name
unsealKeys, rootToken, _ := v.GenerateUnsealKeysFromVault()
stringData := make(map[string]string)
for i, value := range unsealKeys {
key := fmt.Sprintf("key%d", i+1)
stringData[key] = value
values = append(values, value)
}
key := "roottoken"
stringData[key] = rootToken
values = append(values, rootToken)
newSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
StringData: stringData,
}
createdSecret, err := clientset.CoreV1().Secrets(namespace).Create(context.TODO(), newSecret, metav1.CreateOptions{})
if err != nil {
fmt.Printf("Failed to create secret: %v\n", err)
}
fmt.Printf("Secret '%s' created in namespace '%s'\n", createdSecret.Name, createdSecret.Namespace)
return values
}
Retrieving Keys from Kubernetes Secrets
The RetrieveKeys()
function retrieves the previously stored unseal keys
and root token from the Kubernetes Secret.
It decrypts the Secret's data and returns the keys as a slice of strings.
func (v vault) RetrieveKeys(nameSpace string, SecretName string) []string {
var values []string
clientset := Config()
namespace := nameSpace // Namespace where you want to create the Secret
secretName := SecretName
secret2, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
if err != nil {
fmt.Println("Error while getting secret", err)
}
for key, value := range secret2.Data {
if key == "roottoken" {
break // Skip the last element
}
fmt.Printf("Retrieved value for %s: %s\n", key, value)
keys := string(value)
values = append(values, keys)
// fmt.Println("Key is ", keys)
}
fmt.Printf("Secret '%s' found in namespace '%s'\n", secret2.Name, secret2.Namespace)
// Use the secret as needed
return values
}
Monitoring and Unsealing Vault Automatically:
The startMonitoringService()
function continuously monitors the Vault
server's seal status.
If the server is not initialized, it generates the required keys and token and stores them in a
Kubernetes Secret.
If the server is sealed, it retrieves the keys from the Secret and unseals the Vault.
This automated process ensures the Vault remains operational without manual intervention.
func startMonitoringService() {
client, err := api.NewClient(&api.Config{
Address: "http://127.0.0.1:8200",
})
if err != nil {
fmt.Println("Error while connecting to client", err)
}
vault := vault{
client: client,
}
for {
status, err := vault.client.Sys().SealStatus()
if err != nil {
fmt.Println("Error while sealing", err)
}
if !status.Initialized {
fmt.Println("Vault server is not initialized")
// keys,_,_:=vault.GenerateUnsealKeysFromVault()
keys := vault.Storekeys("namespace-name", "secret-name")
err := vault.Unseal(keys)
if err != nil {
fmt.Println("Error is", err)
}
fmt.Printf("Key is Stored Successfully")
}
if status.Sealed {
log.Printf("Vault server is sealed")
keys := vault.RetrieveKeys("namespacename", "secret-name")
vault.Unseal(keys)
}
// Sleep for a given interval before the next check
time.Sleep(1 * time.Minute)
}
}
Here replace the namespacename and secret-name with the required namespace and secret name.
Conclusion:
In this blog post, we explored how to automate the management of Vault keys in a Kubernetes environment using Golang. We covered the process of generating, storing, and retrieving Vault unseal keys and root