Merge pull request #911 from vdesjardins/vault-ca-bundle

vault ca bundle support
This commit is contained in:
jetstack-bot 2018-10-12 15:06:39 +01:00 committed by GitHub
commit 54d8ef7e8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 259 additions and 24 deletions

View File

@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: vault-config
labels:
app: vault
data:
config.json: |
{{ .Values.vault.config | toJson }}

View File

@ -17,6 +17,8 @@ spec:
containers:
- name: vault
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["vault", "server", "-dev", "-dev-listen-address=[::]:8202", "-config", "/vault/config/config.json"]
# command: ["/bin/sh", "-c", "sleep 9999"]
ports:
- containerPort: 8200
name: vaultport
@ -32,3 +34,16 @@ spec:
httpGet:
path: /v1/sys/health
port: 8200
scheme: HTTPS
volumeMounts:
- name: vault-config
mountPath: /vault/config
- name: vault-tls
mountPath: /vault/tls
volumes:
- name: vault-config
configMap:
name: vault-config
- name: vault-tls
secret:
secretName: vault-tls

View File

@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: vault-tls
type: Opaque
data:
server.crt: {{ .Values.vault.publicKey | b64enc }}
server.key: {{ .Values.vault.privateKey | b64enc }}

View File

@ -1,4 +1,18 @@
image:
repository: vault
tag: "0.9.3"
pullPolicy: IfNotPresent
vault:
publicKey:
privateKey:
config:
listener:
tcp:
address: '[::]:8200'
cluster_address: '[::]:8201'
tls_disable: false
tls_prefer_server_cipher_suites: true
tls_cipher_suites: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA
tls_cert_file: /vault/tls/server.crt
tls_key_file: /vault/tls/server.key

View File

@ -1641,6 +1641,13 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
Format: "",
},
},
"caBundle": {
SchemaProps: spec.SchemaProps{
Description: "Base64 encoded CA bundle to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection.",
Type: []string{"string"},
Format: "byte",
},
},
},
Required: []string{"auth", "server", "path"},
},

View File

@ -1930,6 +1930,10 @@ Appears In:
<td>Vault authentication</td>
</tr>
<tr>
<td><code>caBundle</code><br /> <em>string</em></td>
<td>Base64 encoded CA bundle to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection.</td>
</tr>
<tr>
<td><code>path</code><br /> <em>string</em></td>
<td>Vault URL path to the certificate role</td>
</tr>

View File

@ -54,6 +54,7 @@ We can now create a cluster issuer referencing this secret:
vault:
path: pki_int/sign/example-dot-com
server: https://vault
caBundle: <base64 encoded caBundle PEM file>
auth:
appRole:
path: approle
@ -72,6 +73,11 @@ key value that store the *secretId*. The optional attribute *path* specifies
where the AppRole authentication is mounted in Vault. The attribute *path* default
value is *approle*.
An optional base64 encoded *caBundle* in PEM format can be provided to validate
the TLS connection to the Vault Server. When *caBundle* is set it replaces the CA
bundle inside the container running cert-manager. This parameter as no effect if the
connection used is in plain HTTP.
Once we have created the above Issuer we can use it to obtain a certificate.
.. code-block:: yaml
@ -153,12 +159,18 @@ We can now create an issuer referencing this secret:
key: token
path: pki_int/sign/example-dot-com
server: https://vault
caBundle: <base64 encoded caBundle PEM file>
Where *path* is the Vault role path of the PKI backend and *server* is
the Vault server base URL. The secret created previously is referenced in the issuer
with its *name* and *key* corresponding to the name of the Kubernetes secret and the
property name containing the token value respectively.
An optional base64 encoded *caBundle* in PEM format can be provided to validate
the TLS connection to the Vault Server. When *caBundle* is set it replaces the CA
bundle inside the container running cert-manager. This parameter as no effect if the
connection used is in plain HTTP.
Once we have created the above Issuer we can use it to obtain a certificate.
.. code-block:: yaml

View File

@ -91,6 +91,11 @@ type VaultIssuer struct {
Server string `json:"server"`
// Vault URL path to the certificate role
Path string `json:"path"`
// Base64 encoded CA bundle to validate Vault server certificate. Only used
// if the Server URL is using HTTPS protocol. This parameter is ignored for
// plain HTTP protocol connection. If not set the system root certificates
// are used to validate the TLS connection.
CABundle []byte `json:"caBundle,omitempty"`
}
// Vault authentication can be configured:

View File

@ -796,7 +796,7 @@ func (in *IssuerConfig) DeepCopyInto(out *IssuerConfig) {
*out = nil
} else {
*out = new(VaultIssuer)
**out = **in
(*in).DeepCopyInto(*out)
}
}
if in.SelfSigned != nil {
@ -1169,6 +1169,11 @@ func (in *VaultAuth) DeepCopy() *VaultAuth {
func (in *VaultIssuer) DeepCopyInto(out *VaultIssuer) {
*out = *in
out.Auth = in.Auth
if in.CABundle != nil {
in, out := &in.CABundle, &out.CABundle
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package validation
import (
"crypto/x509"
"fmt"
"strings"
@ -122,6 +123,17 @@ func ValidateVaultIssuerConfig(iss *v1alpha1.VaultIssuer, fldPath *field.Path) f
if len(iss.Path) == 0 {
el = append(el, field.Required(fldPath.Child("path"), ""))
}
// check if caBundle is valid
certs := iss.CABundle
if len(certs) > 0 {
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(certs)
if !ok {
el = append(el, field.Invalid(fldPath.Child("caBundle"), "", "Specified CA bundle is invalid"))
}
}
return el
// TODO: add validation for Vault authentication types
}

View File

@ -72,6 +72,16 @@ func TestValidateVaultIssuerConfig(t *testing.T) {
field.Required(fldPath.Child("path"), ""),
},
},
"vault issuer with invalid fields": {
spec: &v1alpha1.VaultIssuer{
Server: "something",
Path: "a/b/c",
CABundle: []byte("invalid"),
},
errs: []*field.Error{
field.Invalid(fldPath.Child("caBundle"), "", "Specified CA bundle is invalid"),
},
},
}
for n, s := range scenarios {
t.Run(n, func(t *testing.T) {
@ -619,7 +629,7 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) {
}
func TestValidateSecretKeySelector(t *testing.T) {
validName := v1alpha1.LocalObjectReference{"name"}
validName := v1alpha1.LocalObjectReference{Name: "name"}
validKey := "key"
// invalidName := v1alpha1.LocalObjectReference{"-name-"}
// invalidKey := "-key-"

View File

@ -19,8 +19,10 @@ package vault
import (
"bytes"
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"net/http"
"path"
"strings"
"time"
@ -109,14 +111,36 @@ func (v *Vault) obtainCertificate(ctx context.Context, crt *v1alpha1.Certificate
return keyBytes, crtBytes, caBytes, nil
}
func (v *Vault) configureCertPool(cfg *vault.Config) error {
certs := v.issuer.GetSpec().Vault.CABundle
if len(certs) == 0 {
return nil
}
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(certs)
if ok == false {
return fmt.Errorf("error loading Vault CA bundle")
}
cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = caCertPool
return nil
}
func (v *Vault) initVaultClient() (*vault.Client, error) {
client, err := vault.NewClient(nil)
vaultCfg := vault.DefaultConfig()
vaultCfg.Address = v.issuer.GetSpec().Vault.Server
err := v.configureCertPool(vaultCfg)
if err != nil {
return nil, err
}
client, err := vault.NewClient(vaultCfg)
if err != nil {
return nil, fmt.Errorf("error initializing Vault client: %s", err.Error())
}
client.SetAddress(v.issuer.GetSpec().Vault.Server)
tokenRef := v.issuer.GetSpec().Vault.Auth.TokenSecretRef
if tokenRef.Name != "" {
token, err := v.vaultTokenRef(tokenRef.Name, tokenRef.Key)

View File

@ -23,6 +23,7 @@ go_library(
"//test/e2e/clusterissuer:go_default_library",
"//test/e2e/framework:go_default_library",
"//test/e2e/issuer:go_default_library",
"//test/util/vault:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/config:go_default_library",

View File

@ -69,14 +69,14 @@ var _ = framework.CertManagerDescribe("Vault Certificate (AppRole)", func() {
vaultInit.Clean()
})
vaultURL := "http://vault.vault:8200"
vaultURL := "https://vault.vault:8200"
It("should generate a new valid certificate", func() {
By("Creating an Issuer")
certClient := f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name)
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath))
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath, vault.VaultCA))
Expect(err).NotTo(HaveOccurred())
By("Waiting for Issuer to become Ready")
@ -137,14 +137,14 @@ var _ = framework.CertManagerDescribe("Vault Certificate (AppRole with a custom
vaultInit.Clean()
})
vaultURL := "http://vault.vault:8200"
vaultURL := "https://vault.vault:8200"
It("should generate a new valid certificate", func() {
By("Creating an Issuer")
certClient := f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name)
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath))
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath, vault.VaultCA))
Expect(err).NotTo(HaveOccurred())
By("Waiting for Issuer to become Ready")

View File

@ -35,6 +35,7 @@ import (
_ "github.com/jetstack/cert-manager/test/e2e/clusterissuer"
"github.com/jetstack/cert-manager/test/e2e/framework"
_ "github.com/jetstack/cert-manager/test/e2e/issuer"
"github.com/jetstack/cert-manager/test/util/vault"
)
const certManagerDeploymentNamespace = "cert-manager"
@ -69,7 +70,8 @@ func RunE2ETests(t *testing.T) {
}
InstallHelmChart(t, "pebble", "./contrib/charts/pebble", "pebble", "./test/fixtures/pebble-values.yaml", extraArgs...)
InstallHelmChart(t, "vault", "./contrib/charts/vault", "vault", "./test/fixtures/vault-values.yaml")
vaultExtraArgs := []string{"--set", "vault.privateKey=" + string(vault.VaultCertPrivateKey), "--set", "vault.publicKey=" + string(vault.VaultCert)}
InstallHelmChart(t, "vault", "./contrib/charts/vault", "vault", "./test/fixtures/vault-values.yaml", vaultExtraArgs...)
glog.Infof("Starting e2e run %q on Ginkgo node %d", framework.RunId, config.GinkgoConfig.ParallelNode)

View File

@ -67,13 +67,13 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() {
const vaultDefaultDuration = time.Hour * 24 * 90
vaultURL := "http://vault.vault:8200"
vaultURL := "https://vault.vault:8200"
It("should be ready with a valid AppRole", func() {
_, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(vault.NewVaultAppRoleSecret(vaultSecretAppRoleName, secretId))
Expect(err).NotTo(HaveOccurred())
By("Creating an Issuer")
_, err = f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath))
_, err = f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath, vault.VaultCA))
Expect(err).NotTo(HaveOccurred())
By("Waiting for Issuer to become Ready")
@ -88,7 +88,7 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() {
It("should fail to init with missing Vault AppRole", func() {
By("Creating an Issuer")
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath))
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath, vault.VaultCA))
Expect(err).NotTo(HaveOccurred())
By("Waiting for Issuer to become Ready")
@ -103,7 +103,7 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() {
It("should fail to init with missing Vault Token", func() {
By("Creating an Issuer")
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerToken(issuerName, vaultURL, vaultPath, vaultSecretTokenName))
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerToken(issuerName, vaultURL, vaultPath, vaultSecretTokenName, authPath, vault.VaultCA))
Expect(err).NotTo(HaveOccurred())
By("Waiting for Issuer to become Ready")

View File

@ -473,7 +473,7 @@ func NewCertManagerSelfSignedIssuer(name string) *v1alpha1.Issuer {
}
}
func NewCertManagerVaultIssuerToken(name, vaultURL, vaultPath, vaultSecretToken string) *v1alpha1.Issuer {
func NewCertManagerVaultIssuerToken(name, vaultURL, vaultPath, vaultSecretToken, authPath string, caBundle []byte) *v1alpha1.Issuer {
return &v1alpha1.Issuer{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@ -481,8 +481,9 @@ func NewCertManagerVaultIssuerToken(name, vaultURL, vaultPath, vaultSecretToken
Spec: v1alpha1.IssuerSpec{
IssuerConfig: v1alpha1.IssuerConfig{
Vault: &v1alpha1.VaultIssuer{
Server: vaultURL,
Path: vaultPath,
Server: vaultURL,
Path: vaultPath,
CABundle: caBundle,
Auth: v1alpha1.VaultAuth{
TokenSecretRef: v1alpha1.SecretKeySelector{
Key: "secretkey",
@ -497,7 +498,7 @@ func NewCertManagerVaultIssuerToken(name, vaultURL, vaultPath, vaultSecretToken
}
}
func NewCertManagerVaultIssuerAppRole(name, vaultURL, vaultPath, roleId, vaultSecretAppRole, authPath string) *v1alpha1.Issuer {
func NewCertManagerVaultIssuerAppRole(name, vaultURL, vaultPath, roleId, vaultSecretAppRole string, authPath string, caBundle []byte) *v1alpha1.Issuer {
return &v1alpha1.Issuer{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@ -505,8 +506,9 @@ func NewCertManagerVaultIssuerAppRole(name, vaultURL, vaultPath, roleId, vaultSe
Spec: v1alpha1.IssuerSpec{
IssuerConfig: v1alpha1.IssuerConfig{
Vault: &v1alpha1.VaultIssuer{
Server: vaultURL,
Path: vaultPath,
Server: vaultURL,
Path: vaultPath,
CABundle: caBundle,
Auth: v1alpha1.VaultAuth{
AppRole: v1alpha1.VaultAppRole{
Path: authPath,

View File

@ -17,7 +17,16 @@ limitations under the License.
package vault
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"net/http"
"os/exec"
"path"
"time"
@ -30,6 +39,18 @@ import (
const vaultToken = "vault-root-token"
var (
VaultCA []byte
VaultCAPrivateKey []byte
VaultCert []byte
VaultCertPrivateKey []byte
)
func init() {
generateCA()
generateCert()
}
func NewVaultTokenSecret(name string) *v1.Secret {
return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
@ -72,7 +93,16 @@ func NewVaultInitializer(container, rootMount, intermediateMount, role, authPath
time.Sleep(3 * time.Second)
cfg := vault.DefaultConfig()
cfg.Address = "http://127.0.0.1:8200"
cfg.Address = "https://127.0.0.1:8200"
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(VaultCA)
if ok == false {
glog.Fatal("error loading Vault CA bundle")
}
cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = caCertPool
client, err := vault.NewClient(cfg)
if err != nil {
return nil, fmt.Errorf("Unable to initialize vault client: %s", err.Error())
@ -284,8 +314,8 @@ func (v *VaultInitializer) importSignIntermediate(intermediateCa, rootCa, interm
func (v *VaultInitializer) configureCert(mount string) error {
params := map[string]string{
"issuing_certificates": fmt.Sprintf("http://vault.vault:8200/v1/%s/ca", mount),
"crl_distribution_points": fmt.Sprintf("http://vault.vault:8200/v1/%s/crl", mount),
"issuing_certificates": fmt.Sprintf("https://vault.vault:8200/v1/%s/ca", mount),
"crl_distribution_points": fmt.Sprintf("https://vault.vault:8200/v1/%s/crl", mount),
}
url := path.Join("/v1", mount, "config", "urls")
@ -353,3 +383,78 @@ func (v *VaultInitializer) callVault(method, url, field string, params map[strin
return fieldData, err
}
func generateCA() {
ca := &x509.Certificate{
SerialNumber: big.NewInt(1653),
Subject: pkix.Name{
Organization: []string{"cert-manager test"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
pubKey := &privateKey.PublicKey
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, pubKey, privateKey)
if err != nil {
glog.Fatalf("create ca failed: %s", err.Error())
}
VaultCA = encodePublicKey(caBytes)
VaultCAPrivateKey = encodePrivateKey(privateKey)
}
func generateCert() {
catls, err := tls.X509KeyPair(VaultCA, VaultCAPrivateKey)
if err != nil {
glog.Fatalf("parsing ca key pair failed: %s", err.Error())
}
ca, err := x509.ParseCertificate(catls.Certificate[0])
if err != nil {
glog.Fatalf("parsing ca failed: %s", err.Error())
}
cert := &x509.Certificate{
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
CommonName: "vault.vault",
Organization: []string{"cert-manager vault server"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)},
DNSNames: []string{"vault.vault"},
}
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
glog.Fatalf("private key generation failed: %s", err.Error())
}
publicKey := &privateKey.PublicKey
certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, publicKey, catls.PrivateKey)
if err != nil {
glog.Fatal(err)
}
VaultCert = encodePublicKey(certBytes)
VaultCertPrivateKey = encodePrivateKey(privateKey)
}
func encodePublicKey(pub []byte) []byte {
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: pub})
}
func encodePrivateKey(priv *rsa.PrivateKey) []byte {
block := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}
return pem.EncodeToMemory(block)
}