-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow mTLS for mysql secrets engine (#9181)
* Extract certificate helpers for use in non-mongodb packages * Created mTLS/X509 test for MySQL secrets engine. * Ensure mysql username and passwords aren't url encoded * Skip mTLS test for circleCI
- Loading branch information
Showing
7 changed files
with
826 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
package certhelpers | ||
|
||
import ( | ||
"bytes" | ||
"crypto" | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/sha1" | ||
"crypto/tls" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/pem" | ||
"math/big" | ||
"net" | ||
"strings" | ||
"testing" | ||
"time" | ||
) | ||
|
||
type CertBuilder struct { | ||
tmpl *x509.Certificate | ||
parentTmpl *x509.Certificate | ||
|
||
selfSign bool | ||
parentKey *rsa.PrivateKey | ||
|
||
isCA bool | ||
} | ||
|
||
type CertOpt func(*CertBuilder) error | ||
|
||
func CommonName(cn string) CertOpt { | ||
return func(builder *CertBuilder) error { | ||
builder.tmpl.Subject.CommonName = cn | ||
return nil | ||
} | ||
} | ||
|
||
func Parent(parent Certificate) CertOpt { | ||
return func(builder *CertBuilder) error { | ||
builder.parentKey = parent.PrivKey.PrivKey | ||
builder.parentTmpl = parent.Template | ||
return nil | ||
} | ||
} | ||
|
||
func IsCA(isCA bool) CertOpt { | ||
return func(builder *CertBuilder) error { | ||
builder.isCA = isCA | ||
return nil | ||
} | ||
} | ||
|
||
func SelfSign() CertOpt { | ||
return func(builder *CertBuilder) error { | ||
builder.selfSign = true | ||
return nil | ||
} | ||
} | ||
|
||
func IP(ip ...string) CertOpt { | ||
return func(builder *CertBuilder) error { | ||
for _, addr := range ip { | ||
if ipAddr := net.ParseIP(addr); ipAddr != nil { | ||
builder.tmpl.IPAddresses = append(builder.tmpl.IPAddresses, ipAddr) | ||
} | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
func DNS(dns ...string) CertOpt { | ||
return func(builder *CertBuilder) error { | ||
builder.tmpl.DNSNames = dns | ||
return nil | ||
} | ||
} | ||
|
||
func NewCert(t *testing.T, opts ...CertOpt) (cert Certificate) { | ||
t.Helper() | ||
|
||
builder := CertBuilder{ | ||
tmpl: &x509.Certificate{ | ||
SerialNumber: makeSerial(t), | ||
Subject: pkix.Name{ | ||
CommonName: makeCommonName(), | ||
}, | ||
NotBefore: time.Now().Add(-1 * time.Hour), | ||
NotAfter: time.Now().Add(1 * time.Hour), | ||
IsCA: false, | ||
KeyUsage: x509.KeyUsageDigitalSignature | | ||
x509.KeyUsageKeyEncipherment | | ||
x509.KeyUsageKeyAgreement, | ||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, | ||
BasicConstraintsValid: true, | ||
}, | ||
} | ||
|
||
for _, opt := range opts { | ||
err := opt(&builder) | ||
if err != nil { | ||
t.Fatalf("Failed to set up certificate builder: %s", err) | ||
} | ||
} | ||
|
||
key := NewPrivateKey(t) | ||
|
||
builder.tmpl.SubjectKeyId = getSubjKeyID(t, key.PrivKey) | ||
|
||
tmpl := builder.tmpl | ||
parent := builder.parentTmpl | ||
publicKey := key.PrivKey.Public() | ||
signingKey := builder.parentKey | ||
|
||
if builder.selfSign { | ||
parent = tmpl | ||
signingKey = key.PrivKey | ||
} | ||
|
||
if builder.isCA { | ||
tmpl.IsCA = true | ||
tmpl.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | ||
tmpl.ExtKeyUsage = nil | ||
} else { | ||
tmpl.KeyUsage = x509.KeyUsageDigitalSignature | | ||
x509.KeyUsageKeyEncipherment | | ||
x509.KeyUsageKeyAgreement | ||
tmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} | ||
} | ||
|
||
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, parent, publicKey, signingKey) | ||
if err != nil { | ||
t.Fatalf("Unable to generate certificate: %s", err) | ||
} | ||
certPem := pem.EncodeToMemory(&pem.Block{ | ||
Type: "CERTIFICATE", | ||
Bytes: certBytes, | ||
}) | ||
|
||
tlsCert, err := tls.X509KeyPair(certPem, key.Pem) | ||
if err != nil { | ||
t.Fatalf("Unable to parse X509 key pair: %s", err) | ||
} | ||
|
||
return Certificate{ | ||
Template: tmpl, | ||
PrivKey: key, | ||
TLSCert: tlsCert, | ||
RawCert: certBytes, | ||
Pem: certPem, | ||
IsCA: builder.isCA, | ||
} | ||
} | ||
|
||
// //////////////////////////////////////////////////////////////////////////// | ||
// Private Key | ||
// //////////////////////////////////////////////////////////////////////////// | ||
type KeyWrapper struct { | ||
PrivKey *rsa.PrivateKey | ||
Pem []byte | ||
} | ||
|
||
func NewPrivateKey(t *testing.T) (key KeyWrapper) { | ||
t.Helper() | ||
|
||
privKey, err := rsa.GenerateKey(rand.Reader, 2048) | ||
if err != nil { | ||
t.Fatalf("Unable to generate key for cert: %s", err) | ||
} | ||
|
||
privKeyPem := pem.EncodeToMemory( | ||
&pem.Block{ | ||
Type: "RSA PRIVATE KEY", | ||
Bytes: x509.MarshalPKCS1PrivateKey(privKey), | ||
}, | ||
) | ||
|
||
key = KeyWrapper{ | ||
PrivKey: privKey, | ||
Pem: privKeyPem, | ||
} | ||
|
||
return key | ||
} | ||
|
||
// //////////////////////////////////////////////////////////////////////////// | ||
// Certificate | ||
// //////////////////////////////////////////////////////////////////////////// | ||
type Certificate struct { | ||
PrivKey KeyWrapper | ||
Template *x509.Certificate | ||
TLSCert tls.Certificate | ||
RawCert []byte | ||
Pem []byte | ||
IsCA bool | ||
} | ||
|
||
func (cert Certificate) CombinedPEM() []byte { | ||
if cert.IsCA { | ||
return cert.Pem | ||
} | ||
return bytes.Join([][]byte{cert.PrivKey.Pem, cert.Pem}, []byte{'\n'}) | ||
} | ||
|
||
func (cert Certificate) PrivateKeyPEM() []byte { | ||
return cert.PrivKey.Pem | ||
} | ||
|
||
// //////////////////////////////////////////////////////////////////////////// | ||
// Helpers | ||
// //////////////////////////////////////////////////////////////////////////// | ||
func makeSerial(t *testing.T) *big.Int { | ||
t.Helper() | ||
|
||
v := &big.Int{} | ||
serialNumberLimit := v.Lsh(big.NewInt(1), 128) | ||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) | ||
if err != nil { | ||
t.Fatalf("Unable to generate serial number: %s", err) | ||
} | ||
return serialNumber | ||
} | ||
|
||
// Pulled from sdk/helper/certutil & slightly modified for test usage | ||
func getSubjKeyID(t *testing.T, privateKey crypto.Signer) []byte { | ||
t.Helper() | ||
|
||
if privateKey == nil { | ||
t.Fatalf("passed-in private key is nil") | ||
} | ||
|
||
marshaledKey, err := x509.MarshalPKIXPublicKey(privateKey.Public()) | ||
if err != nil { | ||
t.Fatalf("error marshalling public key: %s", err) | ||
} | ||
|
||
subjKeyID := sha1.Sum(marshaledKey) | ||
|
||
return subjKeyID[:] | ||
} | ||
|
||
func makeCommonName() (cn string) { | ||
return strings.ReplaceAll(time.Now().Format("20060102T150405.000"), ".", "") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.