Add support to CRL (#3164)
Signed-off-by: Ricardo Pchevuzinske Katz <ricardo.katz@serpro.gov.br> Add support to CRL Signed-off-by: Ricardo Pchevuzinske Katz <ricardo.katz@serpro.gov.br>
This commit is contained in:
parent
48c89cbe3c
commit
9c51676f17
8 changed files with 140 additions and 8 deletions
|
@ -44,6 +44,12 @@ Authentication to work properly.
|
||||||
kubectl create secret generic ca-secret --from-file=tls.crt=server.crt --from-file=tls.key=server.key --from-file=ca.crt=ca.crt
|
kubectl create secret generic ca-secret --from-file=tls.crt=server.crt --from-file=tls.key=server.key --from-file=ca.crt=ca.crt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. If you want to also enable Certificate Revocation List verification you can
|
||||||
|
create the secret also containing the CRL file in PEM format:
|
||||||
|
```bash
|
||||||
|
kubectl create secret generic ca-secret --from-file=ca.crt=ca.crt --from-file=ca.crl=ca.crl
|
||||||
|
```
|
||||||
|
|
||||||
Note: The CA Certificate must contain the trusted certificate authority chain to verify client certificates.
|
Note: The CA Certificate must contain the trusted certificate authority chain to verify client certificates.
|
||||||
|
|
||||||
## Setup Instructions
|
## Setup Instructions
|
||||||
|
|
|
@ -84,6 +84,8 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
||||||
key, okkey := secret.Data[apiv1.TLSPrivateKeyKey]
|
key, okkey := secret.Data[apiv1.TLSPrivateKeyKey]
|
||||||
ca := secret.Data["ca.crt"]
|
ca := secret.Data["ca.crt"]
|
||||||
|
|
||||||
|
crl := secret.Data["ca.crl"]
|
||||||
|
|
||||||
auth := secret.Data["auth"]
|
auth := secret.Data["auth"]
|
||||||
|
|
||||||
// namespace/secretName -> namespace-secretName
|
// namespace/secretName -> namespace-secretName
|
||||||
|
@ -117,12 +119,25 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error configuring CA certificate: %v", err)
|
return nil, fmt.Errorf("error configuring CA certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(crl) > 0 {
|
||||||
|
err = ssl.ConfigureCRL(nsSecName, crl, sslCert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error configuring CRL certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := fmt.Sprintf("Configuring Secret %q for TLS encryption (CN: %v)", secretName, sslCert.CN)
|
msg := fmt.Sprintf("Configuring Secret %q for TLS encryption (CN: %v)", secretName, sslCert.CN)
|
||||||
if ca != nil {
|
if ca != nil {
|
||||||
msg += " and authentication"
|
msg += " and authentication"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if crl != nil {
|
||||||
|
msg += " and CRL"
|
||||||
|
}
|
||||||
|
|
||||||
klog.V(3).Info(msg)
|
klog.V(3).Info(msg)
|
||||||
} else if len(ca) > 0 {
|
} else if len(ca) > 0 {
|
||||||
sslCert, err = ssl.CreateCACert(ca)
|
sslCert, err = ssl.CreateCACert(ca)
|
||||||
|
@ -135,6 +150,12 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
||||||
return nil, fmt.Errorf("error configuring CA certificate: %v", err)
|
return nil, fmt.Errorf("error configuring CA certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(crl) > 0 {
|
||||||
|
err = ssl.ConfigureCRL(nsSecName, crl, sslCert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
// makes this secret in 'syncSecret' to be used for Certificate Authentication
|
// makes this secret in 'syncSecret' to be used for Certificate Authentication
|
||||||
// this does not enable Certificate Authentication
|
// this does not enable Certificate Authentication
|
||||||
klog.V(3).Infof("Configuring Secret %q for TLS authentication", secretName)
|
klog.V(3).Infof("Configuring Secret %q for TLS authentication", secretName)
|
||||||
|
|
|
@ -816,9 +816,11 @@ func (s *k8sStore) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error
|
||||||
}
|
}
|
||||||
|
|
||||||
return &resolver.AuthSSLCert{
|
return &resolver.AuthSSLCert{
|
||||||
Secret: name,
|
Secret: name,
|
||||||
CAFileName: cert.CAFileName,
|
CAFileName: cert.CAFileName,
|
||||||
CASHA: cert.CASHA,
|
CASHA: cert.CASHA,
|
||||||
|
CRLFileName: cert.CRLFileName,
|
||||||
|
CRLSHA: cert.CRLSHA,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,11 @@ type Resolver interface {
|
||||||
// GetSecret searches for secrets containing the namespace and name using a the character /
|
// GetSecret searches for secrets containing the namespace and name using a the character /
|
||||||
GetSecret(string) (*apiv1.Secret, error)
|
GetSecret(string) (*apiv1.Secret, error)
|
||||||
|
|
||||||
// GetAuthCertificate resolves a given secret name into an SSL certificate.
|
// GetAuthCertificate resolves a given secret name into an SSL certificate and CRL.
|
||||||
// The secret must contain 3 keys named:
|
// The secret must contain 2 keys named:
|
||||||
|
|
||||||
// ca.crt: contains the certificate chain used for authentication
|
// ca.crt: contains the certificate chain used for authentication
|
||||||
// tls.crt: contains the server certificate
|
// ca.crl: contains the revocation list used for authentication
|
||||||
// tls.key: contains the server key
|
|
||||||
GetAuthCertificate(string) (*AuthSSLCert, error)
|
GetAuthCertificate(string) (*AuthSSLCert, error)
|
||||||
|
|
||||||
// GetService searches for services containing the namespace and name using a the character /
|
// GetService searches for services containing the namespace and name using a the character /
|
||||||
|
@ -53,6 +52,10 @@ type AuthSSLCert struct {
|
||||||
CAFileName string `json:"caFilename"`
|
CAFileName string `json:"caFilename"`
|
||||||
// CASHA contains the SHA1 hash of the 'ca.crt' or combinations of (tls.crt, tls.key, tls.crt) depending on certs in secret
|
// CASHA contains the SHA1 hash of the 'ca.crt' or combinations of (tls.crt, tls.key, tls.crt) depending on certs in secret
|
||||||
CASHA string `json:"caSha"`
|
CASHA string `json:"caSha"`
|
||||||
|
// CRLFileName contains the path to the secrets 'ca.crl'
|
||||||
|
CRLFileName string `json:"crlFileName"`
|
||||||
|
// CRLSHA contains the SHA1 hash of the 'ca.crl' file
|
||||||
|
CRLSHA string `json:"crlSha"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal tests for equality between two AuthSSLCert types
|
// Equal tests for equality between two AuthSSLCert types
|
||||||
|
@ -74,5 +77,12 @@ func (asslc1 *AuthSSLCert) Equal(assl2 *AuthSSLCert) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if asslc1.CRLFileName != assl2.CRLFileName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if asslc1.CRLSHA != assl2.CRLSHA {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,11 @@ type SSLCert struct {
|
||||||
// This is used to detect changes in the secret that contains certificates
|
// This is used to detect changes in the secret that contains certificates
|
||||||
CASHA string `json:"caSha"`
|
CASHA string `json:"caSha"`
|
||||||
|
|
||||||
|
// CRLFileName contains the path to the file with the Certificate Revocation List
|
||||||
|
CRLFileName string `json:"crlFileName"`
|
||||||
|
// CRLSHA contains the sha1 of the pem file.
|
||||||
|
CRLSHA string `json:"crlSha"`
|
||||||
|
|
||||||
// PemFileName contains the path to the file with the certificate and key concatenated
|
// PemFileName contains the path to the file with the certificate and key concatenated
|
||||||
PemFileName string `json:"pemFileName"`
|
PemFileName string `json:"pemFileName"`
|
||||||
|
|
||||||
|
|
|
@ -211,6 +211,38 @@ func ConfigureCACertWithCertAndKey(name string, ca []byte, sslCert *ingress.SSLC
|
||||||
return ioutil.WriteFile(sslCert.CAFileName, buffer.Bytes(), 0644)
|
return ioutil.WriteFile(sslCert.CAFileName, buffer.Bytes(), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigureCRL creates a CRL file and append it into the SSLCert
|
||||||
|
func ConfigureCRL(name string, crl []byte, sslCert *ingress.SSLCert) error {
|
||||||
|
|
||||||
|
crlName := fmt.Sprintf("crl-%v.pem", name)
|
||||||
|
crlFileName := fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, crlName)
|
||||||
|
|
||||||
|
pemCRLBlock, _ := pem.Decode(crl)
|
||||||
|
if pemCRLBlock == nil {
|
||||||
|
return fmt.Errorf("no valid PEM formatted block found in CRL %v", name)
|
||||||
|
}
|
||||||
|
// If the first certificate does not start with 'X509 CRL' it's invalid and must not be used.
|
||||||
|
if pemCRLBlock.Type != "X509 CRL" {
|
||||||
|
return fmt.Errorf("CRL file %v contains invalid data, and must be created only with PEM formatted certificates", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := x509.ParseCRL(pemCRLBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(crlFileName, crl, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not write CRL file %v: %v", crlFileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sslCert.CRLFileName = crlFileName
|
||||||
|
sslCert.CRLSHA = file.SHA1(crlFileName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// ConfigureCACert is similar to ConfigureCACertWithCertAndKey but it creates a separate file
|
// ConfigureCACert is similar to ConfigureCACertWithCertAndKey but it creates a separate file
|
||||||
// for CA cert and writes only ca into it and then sets relevant fields in sslCert
|
// for CA cert and writes only ca into it and then sets relevant fields in sslCert
|
||||||
func ConfigureCACert(name string, ca []byte, sslCert *ingress.SSLCert) error {
|
func ConfigureCACert(name string, ca []byte, sslCert *ingress.SSLCert) error {
|
||||||
|
|
|
@ -39,6 +39,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
certutil "k8s.io/client-go/util/cert"
|
certutil "k8s.io/client-go/util/cert"
|
||||||
|
"k8s.io/ingress-nginx/internal/file"
|
||||||
)
|
)
|
||||||
|
|
||||||
// generateRSACerts generates a self signed certificate using a self generated ca
|
// generateRSACerts generates a self signed certificate using a self generated ca
|
||||||
|
@ -183,11 +184,60 @@ func TestConfigureCACert(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||||
}
|
}
|
||||||
if sslCert.CAFileName == "" {
|
|
||||||
|
caFilename := fmt.Sprintf("%v/ca-%v.pem", file.DefaultSSLDirectory, cn)
|
||||||
|
|
||||||
|
if sslCert.CAFileName != caFilename {
|
||||||
t.Fatalf("expected a valid CA file name")
|
t.Fatalf("expected a valid CA file name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigureCRL(t *testing.T) {
|
||||||
|
// Demo CRL from https://csrc.nist.gov/projects/pki-testing/sample-certificates-and-crls
|
||||||
|
// Converted to PEM to be tested
|
||||||
|
// SHA: ef21f9c97ec2ef84ba3b2ab007c858a6f760d813
|
||||||
|
var crl = []byte(`-----BEGIN X509 CRL-----
|
||||||
|
MIIBYDCBygIBATANBgkqhkiG9w0BAQUFADBDMRMwEQYKCZImiZPyLGQBGRYDY29t
|
||||||
|
MRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBDQRcN
|
||||||
|
MDUwMjA1MTIwMDAwWhcNMDUwMjA2MTIwMDAwWjAiMCACARIXDTA0MTExOTE1NTcw
|
||||||
|
M1owDDAKBgNVHRUEAwoBAaAvMC0wHwYDVR0jBBgwFoAUCGivhTPIOUp6+IKTjnBq
|
||||||
|
SiCELDIwCgYDVR0UBAMCAQwwDQYJKoZIhvcNAQEFBQADgYEAItwYffcIzsx10NBq
|
||||||
|
m60Q9HYjtIFutW2+DvsVFGzIF20f7pAXom9g5L2qjFXejoRvkvifEBInr0rUL4Xi
|
||||||
|
NkR9qqNMJTgV/wD9Pn7uPSYS69jnK2LiK8NGgO94gtEVxtCccmrLznrtZ5mLbnCB
|
||||||
|
fUNCdMGmr8FVF6IzTNYGmCuk/C4=
|
||||||
|
-----END X509 CRL-----`)
|
||||||
|
|
||||||
|
cn := "demo-crl"
|
||||||
|
_, ca, err := generateRSACerts(cn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||||
|
}
|
||||||
|
c := encodeCertPEM(ca.Cert)
|
||||||
|
|
||||||
|
sslCert, err := CreateCACert(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||||
|
}
|
||||||
|
if sslCert.CRLFileName != "" {
|
||||||
|
t.Fatalf("expected CRLFileName to be empty")
|
||||||
|
}
|
||||||
|
if sslCert.Certificate == nil {
|
||||||
|
t.Fatalf("expected Certificate to be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ConfigureCRL(cn, crl, sslCert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error creating CRL file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crlFilename := fmt.Sprintf("%v/crl-%v.pem", file.DefaultSSLDirectory, cn)
|
||||||
|
if sslCert.CRLFileName != crlFilename {
|
||||||
|
t.Fatalf("expected a valid CRL file name")
|
||||||
|
}
|
||||||
|
if sslCert.CRLSHA != "ef21f9c97ec2ef84ba3b2ab007c858a6f760d813" {
|
||||||
|
t.Fatalf("the expected CRL SHA wasn't found")
|
||||||
|
}
|
||||||
|
}
|
||||||
func TestCreateSSLCert(t *testing.T) {
|
func TestCreateSSLCert(t *testing.T) {
|
||||||
cert, _, err := generateRSACerts("echoheaders")
|
cert, _, err := generateRSACerts("echoheaders")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -811,6 +811,12 @@ stream {
|
||||||
ssl_client_certificate {{ $server.CertificateAuth.CAFileName }};
|
ssl_client_certificate {{ $server.CertificateAuth.CAFileName }};
|
||||||
ssl_verify_client {{ $server.CertificateAuth.VerifyClient }};
|
ssl_verify_client {{ $server.CertificateAuth.VerifyClient }};
|
||||||
ssl_verify_depth {{ $server.CertificateAuth.ValidationDepth }};
|
ssl_verify_depth {{ $server.CertificateAuth.ValidationDepth }};
|
||||||
|
|
||||||
|
{{ if not (empty $server.CertificateAuth.CRLFileName) }}
|
||||||
|
# PEM sha: {{ $server.CertificateAuth.CRLSHA }}
|
||||||
|
ssl_crl {{ $server.CertificateAuth.CRLFileName }};
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
{{ if not (empty $server.CertificateAuth.ErrorPage)}}
|
{{ if not (empty $server.CertificateAuth.ErrorPage)}}
|
||||||
error_page 495 496 = {{ $server.CertificateAuth.ErrorPage }};
|
error_page 495 496 = {{ $server.CertificateAuth.ErrorPage }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
Loading…
Reference in a new issue