Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ Annotations:
cert-manager-sync.lestak.sh/vault-role: "role-name" # HashiCorp Vault role name
cert-manager-sync.lestak.sh/vault-auth-method: "auth-method" # HashiCorp Vault auth method name
cert-manager-sync.lestak.sh/vault-path: "kv-name/path/to/secret" # HashiCorp Vault path to store cert
cert-manager-sync.lestak.sh/vault-secret-name: "secret-name" # The name to store
cert-manager-sync.lestak.sh/vault-version: "kvv2" # Hashicorp Vault KV version. "kvv1" or "kvv2". Default is "kvv2"
cert-manager-sync.lestak.sh/vault-base64-decode: "true" # base64 decode the cert before storing in Vault. Default is "false"
cert-manager-sync.lestak.sh/vault-pkcs12: "true" # convert the cert to PKCS#12 format before storing in Vault. Default is "false"
cert-manager-sync.lestak.sh/vault-pkcs12-password-secret: "secret-name" # name of the secret containing the password (if not specified, a random password will be generated and stored in Vault)
Expand Down
84 changes: 45 additions & 39 deletions stores/vault/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import (
"encoding/pem"
"errors"
"fmt"
"os"
"strings"

"github.com/hashicorp/vault/api"
"github.com/robertlestak/cert-manager-sync/pkg/state"
"github.com/robertlestak/cert-manager-sync/pkg/tlssecret"
log "github.com/sirupsen/logrus"
"software.sslmate.com/src/go-pkcs12"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"os"
"path/filepath"
"software.sslmate.com/src/go-pkcs12"
)

type VaultStore struct {
Expand All @@ -25,6 +24,8 @@ type VaultStore struct {
Role string
AuthMethod string
Path string
SecretName string
KvVersion string
Base64Decode bool
PKCS12 bool
PKCS12PassSecret string // Name of the secret containing the password
Expand Down Expand Up @@ -139,27 +140,24 @@ func (s *VaultStore) WriteSecret(sec map[string]interface{}) (map[string]interfa
})
l.Debugf("vault.WriteSecret")
var secrets map[string]interface{}
pp := strings.Split(s.Path, "/")
if len(pp) < 2 {
return secrets, errors.New("secret path must be in kv/path/to/secret format")
}
pp = insertSliceString(pp, 1, "data")
if len(pp) == 0 {

if s.Path == "" {
return secrets, errors.New("secret path required")
}
if pp == nil {
s.Path = "/"
} else {
s.Path = strings.Join(pp, "/")
if s.SecretName == "" {
return secrets, errors.New("secret name required")
}
l.Debugf("vault.WriteSecret writing to %s", s.Path)
if s.Path == "" {
return secrets, errors.New("secret path required")

fullPath := s.Path
if s.KvVersion == "kvv2" {
fullPath = filepath.Join(fullPath, "data")
}
fullPath = filepath.Join(fullPath, s.SecretName)

vd := map[string]interface{}{
"data": sec,
}
_, err := s.Client.Logical().Write(s.Path, vd)
_, err := s.Client.Logical().Write(fullPath, vd)
if err != nil {
l.WithError(err).Errorf("vault.WriteSecret error")
return secrets, err
Expand All @@ -175,6 +173,14 @@ func (s *VaultStore) FromConfig(c tlssecret.GenericSecretSyncConfig) error {
if c.Config["path"] != "" {
s.Path = c.Config["path"]
}
if c.Config["version"] != "" {
s.KvVersion = c.Config["version"]
} else {
s.KvVersion = "kvv2"
}
if c.Config["secret-name"] != "" {
s.SecretName = c.Config["secret-name"]
}
if c.Config["addr"] != "" {
s.Addr = c.Config["addr"]
}
Expand Down Expand Up @@ -222,37 +228,37 @@ func writeSecretValue(value []byte, asString bool) any {
// getPasswordFromSecret retrieves the PKCS#12 password from a Kubernetes secret
func (s *VaultStore) getPasswordFromSecret(c *tlssecret.Certificate) (string, error) {
l := log.WithFields(log.Fields{
"action": "getPasswordFromSecret",
"secret": s.PKCS12PassSecret,
"action": "getPasswordFromSecret",
"secret": s.PKCS12PassSecret,
"namespace": s.PKCS12PassSecretNamespace,
})
l.Debug("Retrieving PKCS#12 password from secret")

// If no secret is specified, return empty string
if s.PKCS12PassSecret == "" {
return "", nil
}

// Get the secret from Kubernetes
secret, err := state.KubeClient.CoreV1().Secrets(s.PKCS12PassSecretNamespace).Get(
context.Background(),
s.PKCS12PassSecret,
context.Background(),
s.PKCS12PassSecret,
metav1.GetOptions{},
)
if err != nil {
l.WithError(err).Error("Failed to get secret containing PKCS#12 password")
return "", err
}

// Get the password from the secret
passwordBytes, ok := secret.Data[s.PKCS12PassSecretKey]
if !ok {
err := fmt.Errorf("key %s not found in secret %s/%s",
err := fmt.Errorf("key %s not found in secret %s/%s",
s.PKCS12PassSecretKey, s.PKCS12PassSecretNamespace, s.PKCS12PassSecret)
l.WithError(err).Error("Failed to get PKCS#12 password from secret")
return "", err
}

return string(passwordBytes), nil
}

Expand All @@ -279,10 +285,10 @@ func (s *VaultStore) convertToPKCS12WithPassword(cert []byte, key []byte, ca []b
if keyBlock == nil {
return nil, "", fmt.Errorf("failed to decode key PEM")
}

var privateKey interface{}
var parseErr error

// Try different key formats
if keyBlock.Type == "EC PRIVATE KEY" {
privateKey, parseErr = x509.ParseECPrivateKey(keyBlock.Bytes)
Expand All @@ -292,7 +298,7 @@ func (s *VaultStore) convertToPKCS12WithPassword(cert []byte, key []byte, ca []b
// Try PKCS8 as a fallback
privateKey, parseErr = x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
}

if parseErr != nil {
return nil, "", fmt.Errorf("failed to parse private key: %v", parseErr)
}
Expand All @@ -315,7 +321,7 @@ func (s *VaultStore) convertToPKCS12WithPassword(cert []byte, key []byte, ca []b
caCerts = append(caCerts, caCert)
}
}

// If no password provided, generate a random one
if password == "" {
// Generate a random password
Expand Down Expand Up @@ -348,7 +354,7 @@ func (s *VaultStore) convertToPKCS12(cert []byte, key []byte, ca []byte, c *tlss
l.WithError(err).Error("Failed to get password from secret")
return nil, "", fmt.Errorf("failed to get password from secret: %v", err)
}

// Convert to PKCS#12 with the password (or generate a random one if empty)
return s.convertToPKCS12WithPassword(cert, key, ca, password)
}
Expand All @@ -375,13 +381,13 @@ func (s *VaultStore) Sync(c *tlssecret.Certificate) (map[string]string, error) {
"vaultAuthMethod": s.AuthMethod,
"id": vid,
})

// If PKCS12 is enabled and we need to use the certificate namespace for the password secret
if s.PKCS12 && s.PKCS12PassSecret != "" && s.PKCS12PassSecretNamespace == "" {
// Set the namespace to the certificate namespace
s.PKCS12PassSecretNamespace = c.Namespace
}

_, cerr := s.NewClient()
if cerr != nil {
l.WithError(cerr).Errorf("vault.NewClient error")
Expand All @@ -392,16 +398,16 @@ func (s *VaultStore) Sync(c *tlssecret.Certificate) (map[string]string, error) {
l.WithError(err).Errorf("vault.NewToken error")
return nil, err
}

cd := map[string]interface{}{}

// Always store the original PEM files
cd["tls.crt"] = writeSecretValue(c.Certificate, s.Base64Decode)
cd["tls.key"] = writeSecretValue(c.Key, s.Base64Decode)
if len(c.Ca) > 0 {
cd["ca.crt"] = writeSecretValue(c.Ca, s.Base64Decode)
}

// If PKCS#12 is enabled, convert and store the certificate in PKCS#12 format
if s.PKCS12 {
l.Debug("Converting certificate to PKCS#12 format")
Expand All @@ -410,9 +416,9 @@ func (s *VaultStore) Sync(c *tlssecret.Certificate) (map[string]string, error) {
l.WithError(err).Errorf("PKCS#12 conversion error")
return nil, err
}

cd["pkcs12"] = writeSecretValue(pkcs12Data, s.Base64Decode)

// Store the password if it was generated (not provided in secret)
if s.PKCS12PassSecret == "" {
cd["pkcs12-password"] = password
Expand Down
Loading