Allow multiple CA Certificates (#4556)
This commit is contained in:
parent
fe4f178db1
commit
55820ef1e8
4 changed files with 47 additions and 46 deletions
|
@ -17,8 +17,6 @@ limitations under the License.
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -107,11 +105,17 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ca) > 0 {
|
if len(ca) > 0 {
|
||||||
|
caCert, err := ssl.CheckCACert(ca)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing CA certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
|
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error while storing certificate and key: %v", err)
|
return nil, fmt.Errorf("error while storing certificate and key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sslCert.CACertificate = caCert
|
||||||
sslCert.CAFileName = path
|
sslCert.CAFileName = path
|
||||||
sslCert.CASHA = file.SHA1(path)
|
sslCert.CASHA = file.SHA1(path)
|
||||||
|
|
||||||
|
@ -125,7 +129,6 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error configuring CRL certificate: %v", err)
|
return nil, fmt.Errorf("error configuring CRL certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,11 +173,6 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
||||||
sslCert.Name = secret.Name
|
sslCert.Name = secret.Name
|
||||||
sslCert.Namespace = secret.Namespace
|
sslCert.Namespace = secret.Namespace
|
||||||
|
|
||||||
hasher := sha1.New()
|
|
||||||
hasher.Write(sslCert.Certificate.Raw)
|
|
||||||
|
|
||||||
sslCert.PemSHA = hex.EncodeToString(hasher.Sum(nil))
|
|
||||||
|
|
||||||
// the default SSL certificate needs to be present on disk
|
// the default SSL certificate needs to be present on disk
|
||||||
if secretName == s.defaultSSLCertificate {
|
if secretName == s.defaultSSLCertificate {
|
||||||
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
|
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
|
||||||
|
|
|
@ -30,6 +30,8 @@ type SSLCert struct {
|
||||||
|
|
||||||
Certificate *x509.Certificate `json:"-"`
|
Certificate *x509.Certificate `json:"-"`
|
||||||
|
|
||||||
|
CACertificate []*x509.Certificate `json:"-"`
|
||||||
|
|
||||||
// CAFileName contains the path to the file with the root certificate
|
// CAFileName contains the path to the file with the root certificate
|
||||||
CAFileName string `json:"caFileName"`
|
CAFileName string `json:"caFileName"`
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,12 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -63,21 +65,6 @@ func getPemFileName(fullSecretName string) (string, string) {
|
||||||
return fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, pemName), pemName
|
return fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, pemName), pemName
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyPemCertAgainstRootCA(pemCert *x509.Certificate, ca []byte) error {
|
|
||||||
bundle := x509.NewCertPool()
|
|
||||||
bundle.AppendCertsFromPEM(ca)
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
Roots: bundle,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := pemCert.Verify(opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateSSLCert validates cert and key, extracts common names and returns corresponding SSLCert object
|
// CreateSSLCert validates cert and key, extracts common names and returns corresponding SSLCert object
|
||||||
func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) {
|
func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) {
|
||||||
var pemCertBuffer bytes.Buffer
|
var pemCertBuffer bytes.Buffer
|
||||||
|
@ -138,8 +125,12 @@ func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasher := sha1.New()
|
||||||
|
hasher.Write(pemCert.Raw)
|
||||||
|
|
||||||
return &ingress.SSLCert{
|
return &ingress.SSLCert{
|
||||||
Certificate: pemCert,
|
Certificate: pemCert,
|
||||||
|
PemSHA: hex.EncodeToString(hasher.Sum(nil)),
|
||||||
CN: cn.List(),
|
CN: cn.List(),
|
||||||
ExpireTime: pemCert.NotAfter,
|
ExpireTime: pemCert.NotAfter,
|
||||||
PemCertKey: pemCertBuffer.String(),
|
PemCertKey: pemCertBuffer.String(),
|
||||||
|
@ -150,25 +141,41 @@ func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) {
|
||||||
// CreateCACert is similar to CreateSSLCert but it creates instance of SSLCert only based on given ca after
|
// CreateCACert is similar to CreateSSLCert but it creates instance of SSLCert only based on given ca after
|
||||||
// parsing and validating it
|
// parsing and validating it
|
||||||
func CreateCACert(ca []byte) (*ingress.SSLCert, error) {
|
func CreateCACert(ca []byte) (*ingress.SSLCert, error) {
|
||||||
pemCABlock, _ := pem.Decode(ca)
|
caCert, err := CheckCACert(ca)
|
||||||
if pemCABlock == nil {
|
|
||||||
return nil, fmt.Errorf("no valid PEM formatted block found")
|
|
||||||
}
|
|
||||||
// If the first certificate does not start with 'BEGIN CERTIFICATE' it's invalid and must not be used.
|
|
||||||
if pemCABlock.Type != "CERTIFICATE" {
|
|
||||||
return nil, fmt.Errorf("no certificate PEM data found, make sure certificate content starts with 'BEGIN CERTIFICATE'")
|
|
||||||
}
|
|
||||||
|
|
||||||
pemCert, err := x509.ParseCertificate(pemCABlock.Bytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ingress.SSLCert{
|
return &ingress.SSLCert{
|
||||||
Certificate: pemCert,
|
CACertificate: caCert,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckCACert validates a byte array containing one or more CA certificate/s
|
||||||
|
func CheckCACert(caBytes []byte) ([]*x509.Certificate, error) {
|
||||||
|
certs := []*x509.Certificate{}
|
||||||
|
|
||||||
|
var block *pem.Block
|
||||||
|
for {
|
||||||
|
block, caBytes = pem.Decode(caBytes)
|
||||||
|
if block == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certs = append(certs, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(certs) == 0 {
|
||||||
|
return nil, fmt.Errorf("error decoding CA certificate/s")
|
||||||
|
}
|
||||||
|
|
||||||
|
return certs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// StoreSSLCertOnDisk creates a .pem file with content PemCertKey from the given sslCert
|
// StoreSSLCertOnDisk creates a .pem file with content PemCertKey from the given sslCert
|
||||||
// and sets relevant remaining fields of sslCert object
|
// and sets relevant remaining fields of sslCert object
|
||||||
func StoreSSLCertOnDisk(name string, sslCert *ingress.SSLCert) (string, error) {
|
func StoreSSLCertOnDisk(name string, sslCert *ingress.SSLCert) (string, error) {
|
||||||
|
@ -185,27 +192,21 @@ func StoreSSLCertOnDisk(name string, sslCert *ingress.SSLCert) (string, error) {
|
||||||
// ConfigureCACertWithCertAndKey appends ca into existing PEM file consisting of cert and key
|
// ConfigureCACertWithCertAndKey appends ca into existing PEM file consisting of cert and key
|
||||||
// and sets relevant fields in sslCert object
|
// and sets relevant fields in sslCert object
|
||||||
func ConfigureCACertWithCertAndKey(name string, ca []byte, sslCert *ingress.SSLCert) error {
|
func ConfigureCACertWithCertAndKey(name string, ca []byte, sslCert *ingress.SSLCert) error {
|
||||||
err := verifyPemCertAgainstRootCA(sslCert.Certificate, ca)
|
|
||||||
if err != nil {
|
|
||||||
oe := fmt.Sprintf("failed to verify certificate chain: \n\t%s\n", err)
|
|
||||||
return errors.New(oe)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
_, err = buffer.Write([]byte(sslCert.PemCertKey))
|
_, err := buffer.Write([]byte(sslCert.PemCertKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.PemFileName, err)
|
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.CAFileName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = buffer.Write([]byte("\n"))
|
_, err = buffer.Write([]byte("\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.PemFileName, err)
|
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.CAFileName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = buffer.Write(ca)
|
_, err = buffer.Write(ca)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not write ca data to cert file %v: %v", sslCert.PemFileName, err)
|
return fmt.Errorf("could not write ca data to cert file %v: %v", sslCert.CAFileName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(sslCert.CAFileName, buffer.Bytes(), 0644)
|
return ioutil.WriteFile(sslCert.CAFileName, buffer.Bytes(), 0644)
|
||||||
|
|
|
@ -176,7 +176,7 @@ func TestConfigureCACert(t *testing.T) {
|
||||||
if sslCert.CAFileName != "" {
|
if sslCert.CAFileName != "" {
|
||||||
t.Fatalf("expected CAFileName to be empty")
|
t.Fatalf("expected CAFileName to be empty")
|
||||||
}
|
}
|
||||||
if sslCert.Certificate == nil {
|
if sslCert.CACertificate == nil {
|
||||||
t.Fatalf("expected Certificate to be set")
|
t.Fatalf("expected Certificate to be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ fUNCdMGmr8FVF6IzTNYGmCuk/C4=
|
||||||
if sslCert.CRLFileName != "" {
|
if sslCert.CRLFileName != "" {
|
||||||
t.Fatalf("expected CRLFileName to be empty")
|
t.Fatalf("expected CRLFileName to be empty")
|
||||||
}
|
}
|
||||||
if sslCert.Certificate == nil {
|
if sslCert.CACertificate == nil {
|
||||||
t.Fatalf("expected Certificate to be set")
|
t.Fatalf("expected Certificate to be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue