2016-11-10 22:56:29 +00:00
|
|
|
/*
|
|
|
|
Copyright 2015 The Kubernetes Authors.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package ssl
|
|
|
|
|
|
|
|
import (
|
2018-06-04 21:48:30 +00:00
|
|
|
"bytes"
|
2017-03-01 00:11:16 +00:00
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
2020-12-04 12:40:42 +00:00
|
|
|
"crypto/sha1" // #nosec
|
2017-06-13 13:11:49 +00:00
|
|
|
"crypto/tls"
|
2016-11-10 22:56:29 +00:00
|
|
|
"crypto/x509"
|
2017-03-01 00:11:16 +00:00
|
|
|
"crypto/x509/pkix"
|
2017-06-20 23:47:06 +00:00
|
|
|
"encoding/asn1"
|
2019-09-13 12:22:24 +00:00
|
|
|
"encoding/hex"
|
2016-11-10 22:56:29 +00:00
|
|
|
"encoding/pem"
|
2016-11-16 18:24:26 +00:00
|
|
|
"errors"
|
2016-11-10 22:56:29 +00:00
|
|
|
"fmt"
|
2017-03-01 00:11:16 +00:00
|
|
|
"math/big"
|
2017-06-20 23:47:06 +00:00
|
|
|
"net"
|
2019-08-13 21:14:55 +00:00
|
|
|
"os"
|
2017-06-20 23:47:06 +00:00
|
|
|
"strconv"
|
2019-01-09 03:33:16 +00:00
|
|
|
"strings"
|
2019-02-21 19:45:21 +00:00
|
|
|
"sync"
|
2017-03-01 00:11:16 +00:00
|
|
|
"time"
|
2016-11-10 22:56:29 +00:00
|
|
|
|
2017-10-04 20:11:03 +00:00
|
|
|
"github.com/zakjan/cert-chain-resolver/certUtil"
|
2017-07-12 17:31:15 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
2022-07-20 18:53:44 +00:00
|
|
|
|
2022-07-22 00:32:48 +00:00
|
|
|
"k8s.io/ingress-nginx/pkg/apis/ingress"
|
2022-07-20 18:53:44 +00:00
|
|
|
|
2019-07-04 22:06:55 +00:00
|
|
|
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
2022-07-20 18:53:44 +00:00
|
|
|
"k8s.io/ingress-nginx/pkg/util/file"
|
2022-07-20 21:15:03 +00:00
|
|
|
|
2022-07-20 18:53:44 +00:00
|
|
|
klog "k8s.io/klog/v2"
|
2016-11-10 22:56:29 +00:00
|
|
|
)
|
|
|
|
|
2019-08-26 14:58:44 +00:00
|
|
|
// FakeSSLCertificateUID defines the default UID to use for the fake SSL
|
|
|
|
// certificate generated by the ingress controller
|
|
|
|
var FakeSSLCertificateUID = "00000000-0000-0000-0000-000000000000"
|
|
|
|
|
2023-08-31 07:36:48 +00:00
|
|
|
var oidExtensionSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
|
2017-06-20 23:47:06 +00:00
|
|
|
|
2019-04-13 21:31:28 +00:00
|
|
|
const (
|
2023-09-11 03:02:10 +00:00
|
|
|
fakeCertificateName = "default-fake-certificate" //#nosec G101
|
2019-04-13 21:31:28 +00:00
|
|
|
)
|
|
|
|
|
2019-03-08 22:01:08 +00:00
|
|
|
// getPemFileName returns absolute file path and file name of pem cert related to given fullSecretName
|
2023-08-31 07:36:48 +00:00
|
|
|
func getPemFileName(fullSecretName string) (filePath, pemName string) {
|
|
|
|
pemName = fmt.Sprintf("%v.pem", fullSecretName)
|
2019-03-08 22:01:08 +00:00
|
|
|
return fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, pemName), pemName
|
|
|
|
}
|
|
|
|
|
2019-03-11 03:25:31 +00:00
|
|
|
// CreateSSLCert validates cert and key, extracts common names and returns corresponding SSLCert object
|
2019-08-26 14:58:44 +00:00
|
|
|
func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) {
|
2019-03-11 03:25:31 +00:00
|
|
|
var pemCertBuffer bytes.Buffer
|
|
|
|
pemCertBuffer.Write(cert)
|
2019-07-04 22:06:55 +00:00
|
|
|
|
|
|
|
if ngx_config.EnableSSLChainCompletion {
|
|
|
|
data, err := fullChainCert(cert)
|
|
|
|
if err != nil {
|
2020-09-27 20:32:40 +00:00
|
|
|
klog.ErrorS(err, "Error generating certificate chain for Secret")
|
2019-07-04 22:06:55 +00:00
|
|
|
} else {
|
|
|
|
pemCertBuffer.Reset()
|
|
|
|
pemCertBuffer.Write(data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-01 14:29:47 +00:00
|
|
|
pemCertBuffer.WriteString("\n")
|
2019-03-11 03:25:31 +00:00
|
|
|
pemCertBuffer.Write(key)
|
2016-11-10 22:56:29 +00:00
|
|
|
|
2019-03-11 03:25:31 +00:00
|
|
|
pemBlock, _ := pem.Decode(pemCertBuffer.Bytes())
|
2017-02-06 18:16:36 +00:00
|
|
|
if pemBlock == nil {
|
2017-01-06 08:12:25 +00:00
|
|
|
return nil, fmt.Errorf("no valid PEM formatted block found")
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
2017-03-01 18:44:39 +00:00
|
|
|
if pemBlock.Type != "CERTIFICATE" {
|
2019-03-11 03:25:31 +00:00
|
|
|
return nil, fmt.Errorf("no certificate PEM data found, make sure certificate content starts with 'BEGIN CERTIFICATE'")
|
2017-03-01 18:44:39 +00:00
|
|
|
}
|
|
|
|
|
2017-02-06 18:16:36 +00:00
|
|
|
pemCert, err := x509.ParseCertificate(pemBlock.Bytes)
|
2016-11-10 22:56:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-06-13 13:11:49 +00:00
|
|
|
if _, err := tls.X509KeyPair(cert, key); err != nil {
|
2019-03-11 03:25:31 +00:00
|
|
|
return nil, fmt.Errorf("certificate and private key does not have a matching public key: %v", err)
|
2017-06-13 13:11:49 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 17:31:15 +00:00
|
|
|
cn := sets.NewString(pemCert.Subject.CommonName)
|
|
|
|
for _, dns := range pemCert.DNSNames {
|
|
|
|
if !cn.Has(dns) {
|
|
|
|
cn.Insert(dns)
|
|
|
|
}
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
2017-06-20 23:47:06 +00:00
|
|
|
if len(pemCert.Extensions) > 0 {
|
2020-09-27 20:32:40 +00:00
|
|
|
klog.V(3).InfoS("parsing ssl certificate extensions")
|
2017-06-20 23:47:06 +00:00
|
|
|
for _, ext := range getExtension(pemCert, oidExtensionSubjectAltName) {
|
|
|
|
dns, _, _, err := parseSANExtension(ext.Value)
|
|
|
|
if err != nil {
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Warningf("unexpected error parsing certificate extensions: %v", err)
|
2017-06-20 23:47:06 +00:00
|
|
|
continue
|
|
|
|
}
|
2017-07-12 17:31:15 +00:00
|
|
|
|
|
|
|
for _, dns := range dns {
|
|
|
|
if !cn.Has(dns) {
|
|
|
|
cn.Insert(dns)
|
|
|
|
}
|
|
|
|
}
|
2017-06-20 23:47:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-04 12:40:42 +00:00
|
|
|
hasher := sha1.New() // #nosec
|
2019-09-13 12:22:24 +00:00
|
|
|
hasher.Write(pemCert.Raw)
|
|
|
|
|
2019-03-11 03:25:31 +00:00
|
|
|
return &ingress.SSLCert{
|
|
|
|
Certificate: pemCert,
|
2019-09-13 12:22:24 +00:00
|
|
|
PemSHA: hex.EncodeToString(hasher.Sum(nil)),
|
2019-03-11 03:25:31 +00:00
|
|
|
CN: cn.List(),
|
|
|
|
ExpireTime: pemCert.NotAfter,
|
|
|
|
PemCertKey: pemCertBuffer.String(),
|
2019-08-26 14:58:44 +00:00
|
|
|
UID: uid,
|
2019-03-11 03:25:31 +00:00
|
|
|
}, nil
|
|
|
|
}
|
2016-11-10 22:56:29 +00:00
|
|
|
|
2019-03-11 03:25:31 +00:00
|
|
|
// 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) {
|
2019-09-13 12:22:24 +00:00
|
|
|
caCert, err := CheckCACert(ca)
|
2019-03-11 03:25:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-02-06 18:16:36 +00:00
|
|
|
|
2019-03-11 03:25:31 +00:00
|
|
|
return &ingress.SSLCert{
|
2019-09-13 12:22:24 +00:00
|
|
|
CACertificate: caCert,
|
2019-03-11 03:25:31 +00:00
|
|
|
}, nil
|
|
|
|
}
|
2018-07-07 17:46:18 +00:00
|
|
|
|
2019-09-13 12:22:24 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-03-11 04:52:53 +00:00
|
|
|
// StoreSSLCertOnDisk creates a .pem file with content PemCertKey from the given sslCert
|
2019-03-11 03:25:31 +00:00
|
|
|
// and sets relevant remaining fields of sslCert object
|
2019-08-13 21:14:55 +00:00
|
|
|
func StoreSSLCertOnDisk(name string, sslCert *ingress.SSLCert) (string, error) {
|
2019-03-11 03:25:31 +00:00
|
|
|
pemFileName, _ := getPemFileName(name)
|
2018-01-18 19:14:42 +00:00
|
|
|
|
2021-08-06 14:18:17 +00:00
|
|
|
err := os.WriteFile(pemFileName, []byte(sslCert.PemCertKey), file.ReadWriteByUser)
|
2019-03-11 03:25:31 +00:00
|
|
|
if err != nil {
|
2019-08-13 21:14:55 +00:00
|
|
|
return "", fmt.Errorf("could not create PEM certificate file %v: %v", pemFileName, err)
|
2017-10-04 20:11:03 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 21:14:55 +00:00
|
|
|
return pemFileName, nil
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
2019-03-11 03:25:31 +00:00
|
|
|
// ConfigureCACertWithCertAndKey appends ca into existing PEM file consisting of cert and key
|
|
|
|
// and sets relevant fields in sslCert object
|
2023-08-31 07:36:48 +00:00
|
|
|
func ConfigureCACertWithCertAndKey(_ string, ca []byte, sslCert *ingress.SSLCert) error {
|
2019-08-13 21:14:55 +00:00
|
|
|
var buffer bytes.Buffer
|
2018-06-04 21:48:30 +00:00
|
|
|
|
2023-06-01 14:29:47 +00:00
|
|
|
_, err := buffer.WriteString(sslCert.PemCertKey)
|
2018-06-04 21:48:30 +00:00
|
|
|
if err != nil {
|
2019-09-13 12:22:24 +00:00
|
|
|
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.CAFileName, err)
|
2018-06-04 21:48:30 +00:00
|
|
|
}
|
|
|
|
|
2023-06-01 14:29:47 +00:00
|
|
|
_, err = buffer.WriteString("\n")
|
2019-03-11 03:25:31 +00:00
|
|
|
if err != nil {
|
2019-09-13 12:22:24 +00:00
|
|
|
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.CAFileName, err)
|
2018-06-04 21:48:30 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 21:14:55 +00:00
|
|
|
_, err = buffer.Write(ca)
|
2019-03-11 03:25:31 +00:00
|
|
|
if err != nil {
|
2019-09-13 12:22:24 +00:00
|
|
|
return fmt.Errorf("could not write ca data to cert file %v: %v", sslCert.CAFileName, err)
|
2018-06-04 21:48:30 +00:00
|
|
|
}
|
|
|
|
|
2023-08-31 07:36:48 +00:00
|
|
|
//nolint:gosec // Not change permission to avoid possible issues
|
|
|
|
return os.WriteFile(sslCert.CAFileName, buffer.Bytes(), 0o644)
|
2019-03-11 03:25:31 +00:00
|
|
|
}
|
2018-06-04 21:48:30 +00:00
|
|
|
|
2019-09-03 20:47:28 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2023-07-03 12:50:52 +00:00
|
|
|
_, err := x509.ParseRevocationList(pemCRLBlock.Bytes)
|
2019-09-03 20:47:28 +00:00
|
|
|
if err != nil {
|
2023-08-31 07:36:48 +00:00
|
|
|
return err
|
2019-09-03 20:47:28 +00:00
|
|
|
}
|
|
|
|
|
2023-08-31 07:36:48 +00:00
|
|
|
//nolint:gosec // Not change permission to avoid possible issues
|
|
|
|
err = os.WriteFile(crlFileName, crl, 0o644)
|
2019-09-03 20:47:28 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-03-11 03:25:31 +00:00
|
|
|
// 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
|
2019-08-13 21:14:55 +00:00
|
|
|
func ConfigureCACert(name string, ca []byte, sslCert *ingress.SSLCert) error {
|
2019-03-11 03:25:31 +00:00
|
|
|
caName := fmt.Sprintf("ca-%v.pem", name)
|
|
|
|
fileName := fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, caName)
|
2018-06-04 21:48:30 +00:00
|
|
|
|
2023-08-31 07:36:48 +00:00
|
|
|
//nolint:gosec // Not change permission to avoid possible issues
|
|
|
|
err := os.WriteFile(fileName, ca, 0o644)
|
2019-03-11 03:25:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not write CA file %v: %v", fileName, err)
|
2018-06-04 21:48:30 +00:00
|
|
|
}
|
|
|
|
|
2019-03-11 03:25:31 +00:00
|
|
|
sslCert.CAFileName = fileName
|
|
|
|
|
2020-09-27 20:32:40 +00:00
|
|
|
klog.V(3).InfoS("Created CA Certificate for Authentication", "path", fileName)
|
2019-03-11 03:25:31 +00:00
|
|
|
|
|
|
|
return nil
|
2018-06-04 21:48:30 +00:00
|
|
|
}
|
|
|
|
|
2017-06-20 23:47:06 +00:00
|
|
|
func getExtension(c *x509.Certificate, id asn1.ObjectIdentifier) []pkix.Extension {
|
|
|
|
var exts []pkix.Extension
|
|
|
|
for _, ext := range c.Extensions {
|
|
|
|
if ext.Id.Equal(id) {
|
|
|
|
exts = append(exts, ext)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return exts
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddresses []net.IP, err error) {
|
|
|
|
// RFC 5280, 4.2.1.6
|
|
|
|
|
|
|
|
// SubjectAltName ::= GeneralNames
|
|
|
|
//
|
|
|
|
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
|
|
|
//
|
|
|
|
// GeneralName ::= CHOICE {
|
|
|
|
// otherName [0] OtherName,
|
|
|
|
// rfc822Name [1] IA5String,
|
|
|
|
// dNSName [2] IA5String,
|
|
|
|
// x400Address [3] ORAddress,
|
|
|
|
// directoryName [4] Name,
|
|
|
|
// ediPartyName [5] EDIPartyName,
|
|
|
|
// uniformResourceIdentifier [6] IA5String,
|
|
|
|
// iPAddress [7] OCTET STRING,
|
|
|
|
// registeredID [8] OBJECT IDENTIFIER }
|
|
|
|
var seq asn1.RawValue
|
|
|
|
var rest []byte
|
|
|
|
if rest, err = asn1.Unmarshal(value, &seq); err != nil {
|
2023-08-31 07:36:48 +00:00
|
|
|
return dnsNames, emailAddresses, ipAddresses, err
|
2017-06-20 23:47:06 +00:00
|
|
|
} else if len(rest) != 0 {
|
|
|
|
err = errors.New("x509: trailing data after X.509 extension")
|
2023-08-31 07:36:48 +00:00
|
|
|
return dnsNames, emailAddresses, ipAddresses, err
|
2017-06-20 23:47:06 +00:00
|
|
|
}
|
|
|
|
if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 {
|
|
|
|
err = asn1.StructuralError{Msg: "bad SAN sequence"}
|
2023-08-31 07:36:48 +00:00
|
|
|
return dnsNames, emailAddresses, ipAddresses, err
|
2017-06-20 23:47:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rest = seq.Bytes
|
|
|
|
for len(rest) > 0 {
|
|
|
|
var v asn1.RawValue
|
|
|
|
rest, err = asn1.Unmarshal(rest, &v)
|
|
|
|
if err != nil {
|
2023-08-31 07:36:48 +00:00
|
|
|
return dnsNames, emailAddresses, ipAddresses, err
|
2017-06-20 23:47:06 +00:00
|
|
|
}
|
|
|
|
switch v.Tag {
|
|
|
|
case 1:
|
|
|
|
emailAddresses = append(emailAddresses, string(v.Bytes))
|
|
|
|
case 2:
|
|
|
|
dnsNames = append(dnsNames, string(v.Bytes))
|
|
|
|
case 7:
|
|
|
|
switch len(v.Bytes) {
|
|
|
|
case net.IPv4len, net.IPv6len:
|
|
|
|
ipAddresses = append(ipAddresses, v.Bytes)
|
|
|
|
default:
|
|
|
|
err = errors.New("x509: certificate contained IP address of length " + strconv.Itoa(len(v.Bytes)))
|
2023-08-31 07:36:48 +00:00
|
|
|
return dnsNames, emailAddresses, ipAddresses, err
|
2017-06-20 23:47:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-31 07:36:48 +00:00
|
|
|
return dnsNames, emailAddresses, ipAddresses, err
|
2017-06-20 23:47:06 +00:00
|
|
|
}
|
|
|
|
|
2017-03-08 13:41:55 +00:00
|
|
|
// AddOrUpdateDHParam creates a dh parameters file with the specified name
|
2019-08-13 21:14:55 +00:00
|
|
|
func AddOrUpdateDHParam(name string, dh []byte) (string, error) {
|
2019-03-08 22:01:08 +00:00
|
|
|
pemFileName, pemName := getPemFileName(name)
|
2016-11-10 22:56:29 +00:00
|
|
|
|
2021-08-06 14:18:17 +00:00
|
|
|
tempPemFile, err := os.CreateTemp(file.DefaultSSLDirectory, pemName)
|
2017-03-08 13:41:55 +00:00
|
|
|
|
2020-09-27 20:32:40 +00:00
|
|
|
klog.V(3).InfoS("Creating temporal file for DH", "path", tempPemFile.Name(), "name", pemName)
|
2017-03-08 13:41:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("could not create temp pem file %v: %v", pemFileName, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = tempPemFile.Write(dh)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("could not write to pem file %v: %v", tempPemFile.Name(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tempPemFile.Close()
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("could not close temp pem file %v: %v", tempPemFile.Name(), err)
|
|
|
|
}
|
|
|
|
|
2019-08-13 21:14:55 +00:00
|
|
|
defer os.Remove(tempPemFile.Name())
|
2018-01-18 19:14:42 +00:00
|
|
|
|
2021-08-06 14:18:17 +00:00
|
|
|
pemCerts, err := os.ReadFile(tempPemFile.Name())
|
2017-03-08 13:41:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
pemBlock, _ := pem.Decode(pemCerts)
|
|
|
|
if pemBlock == nil {
|
2017-01-06 08:12:25 +00:00
|
|
|
return "", fmt.Errorf("no valid PEM formatted block found")
|
2017-03-08 13:41:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the file does not start with 'BEGIN DH PARAMETERS' it's invalid and must not be used.
|
|
|
|
if pemBlock.Type != "DH PARAMETERS" {
|
2017-01-06 08:12:25 +00:00
|
|
|
return "", fmt.Errorf("certificate %v contains invalid data", name)
|
2017-03-08 13:41:55 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 21:14:55 +00:00
|
|
|
err = os.Rename(tempPemFile.Name(), pemFileName)
|
2017-03-08 13:41:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempPemFile.Name(), pemFileName, err)
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
2017-03-08 13:41:55 +00:00
|
|
|
return pemFileName, nil
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
2017-03-01 00:11:16 +00:00
|
|
|
// GetFakeSSLCert creates a Self Signed Certificate
|
|
|
|
// Based in the code https://golang.org/src/crypto/tls/generate_cert.go
|
2019-08-13 21:14:55 +00:00
|
|
|
func GetFakeSSLCert() *ingress.SSLCert {
|
2019-02-21 19:45:21 +00:00
|
|
|
cert, key := getFakeHostSSLCert("ingress.local")
|
|
|
|
|
2019-08-26 14:58:44 +00:00
|
|
|
sslCert, err := CreateSSLCert(cert, key, FakeSSLCertificateUID)
|
2019-02-21 19:45:21 +00:00
|
|
|
if err != nil {
|
|
|
|
klog.Fatalf("unexpected error creating fake SSL Cert: %v", err)
|
|
|
|
}
|
|
|
|
|
2019-08-13 21:14:55 +00:00
|
|
|
path, err := StoreSSLCertOnDisk(fakeCertificateName, sslCert)
|
2019-02-21 19:45:21 +00:00
|
|
|
if err != nil {
|
|
|
|
klog.Fatalf("unexpected error storing fake SSL Cert: %v", err)
|
|
|
|
}
|
|
|
|
|
2019-08-13 21:14:55 +00:00
|
|
|
sslCert.PemFileName = path
|
|
|
|
sslCert.PemSHA = file.SHA1(path)
|
|
|
|
|
2019-02-21 19:45:21 +00:00
|
|
|
return sslCert
|
|
|
|
}
|
|
|
|
|
2023-08-31 07:36:48 +00:00
|
|
|
func getFakeHostSSLCert(host string) (cert, key []byte) {
|
2017-03-01 00:11:16 +00:00
|
|
|
var priv interface{}
|
|
|
|
var err error
|
|
|
|
|
|
|
|
priv, err = rsa.GenerateKey(rand.Reader, 2048)
|
2016-11-10 22:56:29 +00:00
|
|
|
if err != nil {
|
2018-12-05 05:17:17 +00:00
|
|
|
klog.Fatalf("failed to generate fake private key: %v", err)
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
2017-03-01 00:11:16 +00:00
|
|
|
notBefore := time.Now()
|
|
|
|
// This certificate is valid for 365 days
|
|
|
|
notAfter := notBefore.Add(365 * 24 * time.Hour)
|
|
|
|
|
|
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
|
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
2016-11-10 22:56:29 +00:00
|
|
|
if err != nil {
|
2018-12-05 05:17:17 +00:00
|
|
|
klog.Fatalf("failed to generate fake serial number: %v", err)
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
2017-03-01 00:11:16 +00:00
|
|
|
template := x509.Certificate{
|
|
|
|
SerialNumber: serialNumber,
|
|
|
|
Subject: pkix.Name{
|
|
|
|
Organization: []string{"Acme Co"},
|
|
|
|
CommonName: "Kubernetes Ingress Controller Fake Certificate",
|
|
|
|
},
|
|
|
|
NotBefore: notBefore,
|
|
|
|
NotAfter: notAfter,
|
|
|
|
|
|
|
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
|
|
BasicConstraintsValid: true,
|
2019-02-21 19:45:21 +00:00
|
|
|
DNSNames: []string{host},
|
2017-03-01 00:11:16 +00:00
|
|
|
}
|
|
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.(*rsa.PrivateKey).PublicKey, priv)
|
|
|
|
if err != nil {
|
2018-12-05 05:17:17 +00:00
|
|
|
klog.Fatalf("Failed to create fake certificate: %v", err)
|
2017-03-01 00:11:16 +00:00
|
|
|
}
|
|
|
|
|
2023-08-31 07:36:48 +00:00
|
|
|
cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
2017-03-01 00:11:16 +00:00
|
|
|
|
2023-08-31 07:36:48 +00:00
|
|
|
key = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv.(*rsa.PrivateKey))})
|
2017-03-01 00:11:16 +00:00
|
|
|
|
2019-02-21 19:45:21 +00:00
|
|
|
return cert, key
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
2017-10-04 20:11:03 +00:00
|
|
|
|
2019-07-04 22:06:55 +00:00
|
|
|
// fullChainCert checks if a certificate file contains issues in the intermediate CA chain
|
2017-11-13 01:43:28 +00:00
|
|
|
// Returns a new certificate with the intermediate certificates.
|
|
|
|
// If the certificate does not contains issues with the chain it return an empty byte array
|
2019-07-04 22:06:55 +00:00
|
|
|
func fullChainCert(in []byte) ([]byte, error) {
|
|
|
|
cert, err := certUtil.DecodeCertificate(in)
|
2017-10-04 20:11:03 +00:00
|
|
|
if err != nil {
|
2017-11-13 01:43:28 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
certPool := x509.NewCertPool()
|
|
|
|
certPool.AddCert(cert)
|
|
|
|
|
|
|
|
_, err = cert.Verify(x509.VerifyOptions{
|
|
|
|
Intermediates: certPool,
|
|
|
|
})
|
|
|
|
if err == nil {
|
|
|
|
return nil, nil
|
2017-10-04 20:11:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
certs, err := certUtil.FetchCertificateChain(cert)
|
|
|
|
if err != nil {
|
2017-11-13 01:43:28 +00:00
|
|
|
return nil, err
|
2017-10-04 20:11:03 +00:00
|
|
|
}
|
|
|
|
|
2017-11-13 01:43:28 +00:00
|
|
|
return certUtil.EncodeCertificates(certs), nil
|
2017-10-04 20:11:03 +00:00
|
|
|
}
|
2019-01-09 03:33:16 +00:00
|
|
|
|
|
|
|
// IsValidHostname checks if a hostname is valid in a list of common names
|
|
|
|
func IsValidHostname(hostname string, commonNames []string) bool {
|
|
|
|
for _, cn := range commonNames {
|
|
|
|
if strings.EqualFold(hostname, cn) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
labels := strings.Split(hostname, ".")
|
|
|
|
labels[0] = "*"
|
|
|
|
candidate := strings.Join(labels, ".")
|
|
|
|
if strings.EqualFold(candidate, cn) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
2019-02-21 19:45:21 +00:00
|
|
|
|
|
|
|
// TLSListener implements a dynamic certificate loader
|
|
|
|
type TLSListener struct {
|
|
|
|
certificatePath string
|
|
|
|
keyPath string
|
|
|
|
certificate *tls.Certificate
|
|
|
|
err error
|
|
|
|
lock sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewTLSListener watches changes to th certificate and key paths
|
|
|
|
// and reloads it whenever it changes
|
|
|
|
func NewTLSListener(certificate, key string) *TLSListener {
|
|
|
|
l := TLSListener{
|
|
|
|
certificatePath: certificate,
|
|
|
|
keyPath: key,
|
|
|
|
lock: sync.Mutex{},
|
|
|
|
}
|
2020-12-04 12:40:42 +00:00
|
|
|
|
2019-02-21 19:45:21 +00:00
|
|
|
l.load()
|
2020-12-04 12:40:42 +00:00
|
|
|
|
2023-08-31 07:36:48 +00:00
|
|
|
_, err := file.NewFileWatcher(certificate, l.load)
|
|
|
|
if err != nil {
|
|
|
|
klog.Errorf("unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
_, err = file.NewFileWatcher(key, l.load)
|
|
|
|
if err != nil {
|
|
|
|
klog.Errorf("unexpected error: %v", err)
|
|
|
|
}
|
2019-02-21 19:45:21 +00:00
|
|
|
return &l
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetCertificate implements the tls.Config.GetCertificate interface
|
|
|
|
func (tl *TLSListener) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
|
|
tl.lock.Lock()
|
|
|
|
defer tl.lock.Unlock()
|
|
|
|
return tl.certificate, tl.err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TLSConfig instanciates a TLS configuration, always providing an up to date certificate
|
|
|
|
func (tl *TLSListener) TLSConfig() *tls.Config {
|
|
|
|
return &tls.Config{
|
|
|
|
GetCertificate: tl.GetCertificate,
|
2020-12-04 12:40:42 +00:00
|
|
|
MinVersion: tls.VersionTLS12,
|
2019-02-21 19:45:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tl *TLSListener) load() {
|
2020-09-27 20:32:40 +00:00
|
|
|
klog.InfoS("loading tls certificate", "path", tl.certificatePath, "key", tl.keyPath)
|
2021-08-06 14:18:17 +00:00
|
|
|
certBytes, err := os.ReadFile(tl.certificatePath)
|
2019-02-21 19:45:21 +00:00
|
|
|
if err != nil {
|
|
|
|
tl.certificate = nil
|
|
|
|
tl.err = err
|
|
|
|
}
|
2021-08-06 14:18:17 +00:00
|
|
|
keyBytes, err := os.ReadFile(tl.keyPath)
|
2019-02-21 19:45:21 +00:00
|
|
|
if err != nil {
|
|
|
|
tl.certificate = nil
|
|
|
|
tl.err = err
|
|
|
|
}
|
|
|
|
cert, err := tls.X509KeyPair(certBytes, keyBytes)
|
|
|
|
tl.lock.Lock()
|
|
|
|
defer tl.lock.Unlock()
|
|
|
|
tl.certificate, tl.err = &cert, err
|
|
|
|
}
|