Skip to content

Commit

Permalink
tests: require Vault mTLS during e2e
Browse files Browse the repository at this point in the history
Signed-off-by: Rodrigo Fior Kuntzer <rodrigo@miro.com>
  • Loading branch information
rodrigorfk committed Jan 6, 2024
1 parent d30ac69 commit b120669
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 16 deletions.
12 changes: 10 additions & 2 deletions test/e2e/framework/addon/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ type AddonTransferableData = internal.AddonTransferableData

var (
// Base is a base addon containing Kubernetes clients
Base = &base.Base{}
Vault = &vault.Vault{}
Base = &base.Base{}
Vault = &vault.Vault{}
VaultEnforceMtls = &vault.Vault{}

// allAddons is populated by InitGlobals and defines the order in which
// addons will be provisioned
Expand All @@ -65,9 +66,16 @@ func InitGlobals(cfg *config.Config) {
Namespace: "e2e-vault",
Name: "vault",
}
*VaultEnforceMtls = vault.Vault{
Base: Base,
Namespace: "e2e-vault-mtls",
Name: "vault-mtls",
EnforceMtls: true,
}
allAddons = []Addon{
Base,
Vault,
VaultEnforceMtls,
}
}

Expand Down
26 changes: 25 additions & 1 deletion test/e2e/framework/addon/vault/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package vault

import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net"
Expand Down Expand Up @@ -182,6 +183,22 @@ func NewVaultKubernetesSecret(secretName, serviceAccountName string) *corev1.Sec
}
}

func NewVaultClientCertificateSecret(secretName, serviceAccountName string, certificate, key []byte) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Annotations: map[string]string{
"kubernetes.io/service-account.name": serviceAccountName,
},
},
Data: map[string][]byte{
corev1.TLSCertKey: certificate,
corev1.TLSPrivateKeyKey: key,
},
Type: corev1.SecretTypeTLS,
}
}

// Set up a new Vault client, port-forward to the Vault instance.
func (v *VaultInitializer) Init() error {
cfg := vault.DefaultConfig()
Expand All @@ -192,6 +209,13 @@ func (v *VaultInitializer) Init() error {
return fmt.Errorf("error loading Vault CA bundle: %s", v.details.VaultCA)
}
cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = caCertPool
if v.details.EnforceMtls {
clientCertificate, err := tls.X509KeyPair(v.details.VaultClientCertificate, v.details.VaultClientPrivateKey)
if err != nil {
return fmt.Errorf("unable to read vault client certificate: %s", err)
}
cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.Certificates = []tls.Certificate{clientCertificate}
}

client, err := vault.NewClient(cfg)
if err != nil {
Expand All @@ -208,7 +232,7 @@ func (v *VaultInitializer) Init() error {
return fmt.Errorf("error parsing proxy URL: %s", err.Error())
}
var lastError error
err = wait.PollUntilContextTimeout(context.TODO(), time.Second, 20*time.Second, true, func(ctx context.Context) (bool, error) {
err = wait.PollUntilContextTimeout(context.TODO(), time.Second, 45*time.Second, true, func(ctx context.Context) (bool, error) {
conn, err := net.DialTimeout("tcp", proxyUrl.Host, time.Second)
if err != nil {
lastError = err
Expand Down
63 changes: 60 additions & 3 deletions test/e2e/framework/addon/vault/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"math/big"
"net"
"os"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -63,6 +64,10 @@ type Vault struct {
// Namespace is the namespace to deploy Vault into
Namespace string

// EnforceMtls defines if mTLS is enforced in the vault server
// and clients must provide client certificates
EnforceMtls bool

// Proxy is the proxy that can be used to connect to Vault
proxy *proxy

Expand All @@ -84,6 +89,16 @@ type Details struct {

// VaultCA is the CA used to sign the vault serving certificate
VaultCA []byte

// VaultClientCertificate is the certificate used by clients when connecting to vault
VaultClientCertificate []byte

// VaultClientPrivateKey is the private key used by clients when connecting to vault
VaultClientPrivateKey []byte

// EnforceMtls defines if mTLS is enforced in the vault server
// and clients must provide client certificates
EnforceMtls bool
}

func convertInterfaceToDetails(unmarshalled interface{}) (Details, error) {
Expand Down Expand Up @@ -156,15 +171,24 @@ func (v *Vault) Setup(cfg *config.Config, leaderData ...internal.AddonTransferab
},
{
Key: "server.standalone.config",
Value: `
Value: fmt.Sprintf(`
listener "tcp" {
tls_disable = false
address = "[::]:8200"
cluster_address = "[::]:8201"
tls_disable = false
tls_client_ca_file = "/vault/tls/ca.crt"
tls_cert_file = "/vault/tls/server.crt"
tls_key_file = "/vault/tls/server.key"
}`,
tls_require_and_verify_client_cert = %s
}`, strconv.FormatBool(v.EnforceMtls)),
},
{
Key: "server.extraEnvironmentVars.VAULT_CLIENT_CERT",
Value: "/vault/tls/server.crt",
},
{
Key: "server.extraEnvironmentVars.VAULT_CLIENT_KEY",
Value: "/vault/tls/server.key",
},
{
Key: "server.volumes[0].name",
Expand Down Expand Up @@ -267,6 +291,14 @@ func (v *Vault) Setup(cfg *config.Config, leaderData ...internal.AddonTransferab
return nil, err
}

vaultClientCertificate, vaultClientPrivateKey, err := generateVaultClientCert(vaultCA, vaultCAPrivateKey)
if err != nil {
return nil, err
}
v.details.VaultClientCertificate = vaultClientCertificate
v.details.VaultClientPrivateKey = vaultClientPrivateKey
v.details.EnforceMtls = v.EnforceMtls

if cfg.Kubectl == "" {
return nil, fmt.Errorf("path to kubectl must be specified")
}
Expand Down Expand Up @@ -310,6 +342,7 @@ func (v *Vault) Provision() error {
Namespace: v.Namespace,
},
StringData: map[string]string{
"ca.crt": string(v.details.VaultCA),
"server.crt": string(v.vaultCert),
"server.key": string(v.vaultCertPrivateKey),
},
Expand Down Expand Up @@ -438,6 +471,30 @@ func generateVaultServingCert(vaultCA []byte, vaultCAPrivateKey []byte, dnsName
return encodePublicKey(certBytes), encodePrivateKey(privateKey), nil
}

func generateVaultClientCert(vaultCA []byte, vaultCAPrivateKey []byte) ([]byte, []byte, error) {
catls, _ := tls.X509KeyPair(vaultCA, vaultCAPrivateKey)
ca, _ := x509.ParseCertificate(catls.Certificate[0])

cert := &x509.Certificate{
Version: 3,
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
CommonName: "cert-manager vault client",
Organization: []string{"cert-manager"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
}

privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
certBytes, _ := x509.CreateCertificate(rand.Reader, cert, ca, &privateKey.PublicKey, catls.PrivateKey)

return encodePublicKey(certBytes), encodePrivateKey(privateKey), nil
}

func GenerateCA() ([]byte, []byte, error) {
ca := &x509.Certificate{
Version: 3,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package vault
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
"time"

. "github.com/onsi/ginkgo/v2"
Expand Down Expand Up @@ -69,19 +70,23 @@ type kubernetes struct {
// saTokenSecretName is the name of the Secret containing the service account token
saTokenSecretName string

// vaultClientCertificateSecretName is the name of the Secret containing the Vault client certificate
vaultClientCertificateSecretName string

setup *vault.VaultInitializer
}

func (k *kubernetes) createIssuer(f *framework.Framework) string {
k.initVault(f, f.Namespace.Name)
details := addon.VaultEnforceMtls.Details()
k.initVault(f, f.Namespace.Name, details)

By("Creating a VaultKubernetes Issuer")
issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "vault-issuer-",
Namespace: f.Namespace.Name,
},
Spec: k.issuerSpec(f),
Spec: k.issuerSpec(f, details),
}, metav1.CreateOptions{})
Expect(err).NotTo(HaveOccurred())

Expand All @@ -94,14 +99,15 @@ func (k *kubernetes) createIssuer(f *framework.Framework) string {
}

func (k *kubernetes) createClusterIssuer(f *framework.Framework) string {
k.initVault(f, f.Config.Addons.CertManager.ClusterResourceNamespace)
details := addon.VaultEnforceMtls.Details()
k.initVault(f, f.Config.Addons.CertManager.ClusterResourceNamespace, details)

By("Creating a VaultKubernetes ClusterIssuer")
issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "vault-issuer-",
},
Spec: k.issuerSpec(f),
Spec: k.issuerSpec(f, details),
}, metav1.CreateOptions{})
Expect(err).NotTo(HaveOccurred())

Expand All @@ -125,12 +131,12 @@ func (k *kubernetes) delete(f *framework.Framework, signerName string) {
Expect(k.setup.Clean()).NotTo(HaveOccurred(), "failed to deprovision vault initializer")
}

func (k *kubernetes) initVault(f *framework.Framework, boundNS string) {
func (k *kubernetes) initVault(f *framework.Framework, boundNS string, details *vault.Details) {
By("Configuring the VaultKubernetes server")

k.setup = vault.NewVaultInitializerKubernetes(
addon.Base.Details().KubeClient,
*addon.Vault.Details(),
*details,
k.testWithRootCA,
"https://kubernetes.default.svc.cluster.local",
)
Expand All @@ -147,15 +153,19 @@ func (k *kubernetes) initVault(f *framework.Framework, boundNS string) {
k.saTokenSecretName = "vault-sa-secret-" + rand.String(5)
_, err = f.KubeClientSet.CoreV1().Secrets(boundNS).Create(context.TODO(), vault.NewVaultKubernetesSecret(k.saTokenSecretName, boundSA), metav1.CreateOptions{})
Expect(err).NotTo(HaveOccurred())

k.vaultClientCertificateSecretName = "vault-client-cert-secret-" + rand.String(5)
_, err = f.KubeClientSet.CoreV1().Secrets(boundNS).Create(context.TODO(), vault.NewVaultClientCertificateSecret(k.vaultClientCertificateSecretName, boundSA, details.VaultClientCertificate, details.VaultClientPrivateKey), metav1.CreateOptions{})
Expect(err).NotTo(HaveOccurred())
}

func (k *kubernetes) issuerSpec(f *framework.Framework) cmapi.IssuerSpec {
return cmapi.IssuerSpec{
func (k *kubernetes) issuerSpec(f *framework.Framework, details *vault.Details) cmapi.IssuerSpec {
issuerSpec := cmapi.IssuerSpec{
IssuerConfig: cmapi.IssuerConfig{
Vault: &cmapi.VaultIssuer{
Server: addon.Vault.Details().URL,
Server: details.URL,
Path: k.setup.IntermediateSignPath(),
CABundle: addon.Vault.Details().VaultCA,
CABundle: details.VaultCA,
Auth: cmapi.VaultAuth{
Kubernetes: &cmapi.VaultKubernetesAuth{
Path: k.setup.KubernetesAuthPath(),
Expand All @@ -170,4 +180,19 @@ func (k *kubernetes) issuerSpec(f *framework.Framework) cmapi.IssuerSpec {
},
},
}
if details.EnforceMtls {
issuerSpec.IssuerConfig.Vault.ClientCertSecretRef = &cmmeta.SecretKeySelector{
LocalObjectReference: cmmeta.LocalObjectReference{
Name: k.vaultClientCertificateSecretName,
},
Key: corev1.TLSCertKey,
}
issuerSpec.IssuerConfig.Vault.ClientKeySecretRef = &cmmeta.SecretKeySelector{
LocalObjectReference: cmmeta.LocalObjectReference{
Name: k.vaultClientCertificateSecretName,
},
Key: corev1.TLSPrivateKeyKey,
}
}
return issuerSpec
}

0 comments on commit b120669

Please sign in to comment.