Skip to content

Commit

Permalink
Allow multiple CA Certificates (kubernetes#4556)
Browse files Browse the repository at this point in the history
  • Loading branch information
aledbf authored Sep 13, 2019
1 parent fe4f178 commit 55820ef
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 46 deletions.
14 changes: 6 additions & 8 deletions internal/ingress/controller/store/backend_ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ limitations under the License.
package store

import (
"crypto/sha1"
"encoding/hex"
"fmt"
"strings"

Expand Down Expand Up @@ -107,11 +105,17 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
}

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)
if err != nil {
return nil, fmt.Errorf("error while storing certificate and key: %v", err)
}

sslCert.CACertificate = caCert
sslCert.CAFileName = path
sslCert.CASHA = file.SHA1(path)

Expand All @@ -125,7 +129,6 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
if err != nil {
return nil, fmt.Errorf("error configuring CRL certificate: %v", err)
}

}
}

Expand Down Expand Up @@ -170,11 +173,6 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
sslCert.Name = secret.Name
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
if secretName == s.defaultSSLCertificate {
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
Expand Down
2 changes: 2 additions & 0 deletions internal/ingress/sslcert.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type SSLCert struct {

Certificate *x509.Certificate `json:"-"`

CACertificate []*x509.Certificate `json:"-"`

// CAFileName contains the path to the file with the root certificate
CAFileName string `json:"caFileName"`

Expand Down
73 changes: 37 additions & 36 deletions internal/net/ssl/ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
Expand Down Expand Up @@ -63,21 +65,6 @@ func getPemFileName(fullSecretName string) (string, string) {
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
func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) {
var pemCertBuffer bytes.Buffer
Expand Down Expand Up @@ -138,8 +125,12 @@ func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) {
}
}

hasher := sha1.New()
hasher.Write(pemCert.Raw)

return &ingress.SSLCert{
Certificate: pemCert,
PemSHA: hex.EncodeToString(hasher.Sum(nil)),
CN: cn.List(),
ExpireTime: pemCert.NotAfter,
PemCertKey: pemCertBuffer.String(),
Expand All @@ -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
// parsing and validating it
func CreateCACert(ca []byte) (*ingress.SSLCert, error) {
pemCABlock, _ := pem.Decode(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)
caCert, err := CheckCACert(ca)
if err != nil {
return nil, err
}

return &ingress.SSLCert{
Certificate: pemCert,
CACertificate: caCert,
}, 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
// and sets relevant remaining fields of sslCert object
func StoreSSLCertOnDisk(name string, sslCert *ingress.SSLCert) (string, error) {
Expand All @@ -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
// and sets relevant fields in sslCert object
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

_, err = buffer.Write([]byte(sslCert.PemCertKey))
_, err := buffer.Write([]byte(sslCert.PemCertKey))
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"))
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)
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)
Expand Down
4 changes: 2 additions & 2 deletions internal/net/ssl/ssl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func TestConfigureCACert(t *testing.T) {
if sslCert.CAFileName != "" {
t.Fatalf("expected CAFileName to be empty")
}
if sslCert.Certificate == nil {
if sslCert.CACertificate == nil {
t.Fatalf("expected Certificate to be set")
}

Expand Down Expand Up @@ -221,7 +221,7 @@ fUNCdMGmr8FVF6IzTNYGmCuk/C4=
if sslCert.CRLFileName != "" {
t.Fatalf("expected CRLFileName to be empty")
}
if sslCert.Certificate == nil {
if sslCert.CACertificate == nil {
t.Fatalf("expected Certificate to be set")
}

Expand Down

0 comments on commit 55820ef

Please sign in to comment.