2019-05-14 14:57:57 +00:00
|
|
|
package tls
|
2019-03-15 17:03:55 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
|
|
|
"encoding/pem"
|
|
|
|
"errors"
|
2020-10-07 21:17:37 +00:00
|
|
|
"fmt"
|
|
|
|
"math/big"
|
2019-03-15 17:03:55 +00:00
|
|
|
"net"
|
2021-01-20 23:25:27 +00:00
|
|
|
"strings"
|
2019-03-15 17:03:55 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2021-03-16 18:31:04 +00:00
|
|
|
// CertRenewalInterval is the renewal interval for rootCA
|
|
|
|
const CertRenewalInterval time.Duration = 12 * time.Hour
|
|
|
|
|
|
|
|
// CertValidityDuration is the valid duration for a new cert
|
|
|
|
const CertValidityDuration time.Duration = 365 * 24 * time.Hour
|
2020-10-07 21:17:37 +00:00
|
|
|
|
2020-11-17 21:07:30 +00:00
|
|
|
// CertificateProps Properties of TLS certificate which should be issued for webhook server
|
|
|
|
type CertificateProps struct {
|
2019-03-15 17:03:55 +00:00
|
|
|
Service string
|
|
|
|
Namespace string
|
2020-11-17 21:07:30 +00:00
|
|
|
APIServerHost string
|
2021-01-20 23:25:27 +00:00
|
|
|
ServerIP string
|
2019-03-15 17:03:55 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 21:07:30 +00:00
|
|
|
// PemPair The pair of TLS certificate corresponding private key, both in PEM format
|
|
|
|
type PemPair struct {
|
2019-03-15 17:03:55 +00:00
|
|
|
Certificate []byte
|
|
|
|
PrivateKey []byte
|
|
|
|
}
|
|
|
|
|
2020-11-17 21:07:30 +00:00
|
|
|
// KeyPair ...
|
2020-10-07 21:17:37 +00:00
|
|
|
type KeyPair struct {
|
|
|
|
Cert *x509.Certificate
|
|
|
|
Key *rsa.PrivateKey
|
|
|
|
}
|
|
|
|
|
2020-11-17 21:07:30 +00:00
|
|
|
// GeneratePrivateKey Generates RSA private key
|
|
|
|
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
|
2019-03-15 17:03:55 +00:00
|
|
|
return rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
}
|
|
|
|
|
2020-11-17 21:07:30 +00:00
|
|
|
// PrivateKeyToPem Creates PEM block from private key object
|
|
|
|
func PrivateKeyToPem(rsaKey *rsa.PrivateKey) []byte {
|
2019-03-15 17:03:55 +00:00
|
|
|
privateKey := &pem.Block{
|
|
|
|
Type: "PRIVATE KEY",
|
|
|
|
Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
|
|
|
|
}
|
|
|
|
|
|
|
|
return pem.EncodeToMemory(privateKey)
|
|
|
|
}
|
|
|
|
|
2020-11-17 21:07:30 +00:00
|
|
|
// CertificateToPem ...
|
|
|
|
func CertificateToPem(certificateDER []byte) []byte {
|
2020-10-07 21:17:37 +00:00
|
|
|
certificate := &pem.Block{
|
|
|
|
Type: "CERTIFICATE",
|
|
|
|
Bytes: certificateDER,
|
|
|
|
}
|
|
|
|
|
|
|
|
return pem.EncodeToMemory(certificate)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GenerateCACert creates the self-signed CA cert and private key
|
|
|
|
// it will be used to sign the webhook server certificate
|
2021-03-16 18:31:04 +00:00
|
|
|
func GenerateCACert(certValidityDuration time.Duration) (*KeyPair, *PemPair, error) {
|
2020-10-07 21:17:37 +00:00
|
|
|
now := time.Now()
|
|
|
|
begin := now.Add(-1 * time.Hour)
|
|
|
|
end := now.Add(certValidityDuration)
|
2021-01-20 23:25:27 +00:00
|
|
|
|
2020-10-07 21:17:37 +00:00
|
|
|
templ := &x509.Certificate{
|
|
|
|
SerialNumber: big.NewInt(0),
|
|
|
|
Subject: pkix.Name{
|
|
|
|
CommonName: "*.kyverno.svc",
|
|
|
|
},
|
|
|
|
NotBefore: begin,
|
|
|
|
NotAfter: end,
|
|
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
|
|
|
|
BasicConstraintsValid: true,
|
|
|
|
IsCA: true,
|
|
|
|
}
|
2021-01-20 23:25:27 +00:00
|
|
|
|
2020-10-07 21:17:37 +00:00
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("error generating key: %v", err)
|
|
|
|
}
|
2021-01-20 23:25:27 +00:00
|
|
|
|
2020-10-07 21:17:37 +00:00
|
|
|
der, err := x509.CreateCertificate(rand.Reader, templ, templ, key.Public(), key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("error creating certificate: %v", err)
|
|
|
|
}
|
|
|
|
|
2020-11-17 21:07:30 +00:00
|
|
|
pemPair := &PemPair{
|
|
|
|
Certificate: CertificateToPem(der),
|
|
|
|
PrivateKey: PrivateKeyToPem(key),
|
2020-10-07 21:17:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cert, err := x509.ParseCertificate(der)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("error parsing certificate %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
caCert := &KeyPair{
|
|
|
|
Cert: cert,
|
|
|
|
Key: key,
|
|
|
|
}
|
|
|
|
|
|
|
|
return caCert, pemPair, nil
|
2019-03-15 17:03:55 +00:00
|
|
|
}
|
|
|
|
|
2020-10-07 21:44:36 +00:00
|
|
|
// GenerateCertPem takes the results of GenerateCACert and uses it to create the
|
2020-10-07 21:17:37 +00:00
|
|
|
// PEM-encoded public certificate and private key, respectively
|
2021-03-16 18:31:04 +00:00
|
|
|
func GenerateCertPem(caCert *KeyPair, props CertificateProps, serverIP string, certValidityDuration time.Duration) (*PemPair, error) {
|
2020-10-07 21:17:37 +00:00
|
|
|
now := time.Now()
|
|
|
|
begin := now.Add(-1 * time.Hour)
|
|
|
|
end := now.Add(certValidityDuration)
|
|
|
|
|
2019-03-15 17:03:55 +00:00
|
|
|
dnsNames := make([]string, 3)
|
2020-10-07 21:17:37 +00:00
|
|
|
dnsNames[0] = fmt.Sprintf("%s", props.Service)
|
|
|
|
csCommonName := dnsNames[0]
|
|
|
|
|
|
|
|
dnsNames[1] = fmt.Sprintf("%s.%s", props.Service, props.Namespace)
|
2019-03-15 17:03:55 +00:00
|
|
|
// The full service name is the CommonName for the certificate
|
2021-03-16 18:31:04 +00:00
|
|
|
commonName := generateInClusterServiceName(props)
|
2020-10-07 21:17:37 +00:00
|
|
|
dnsNames[2] = fmt.Sprintf("%s", commonName)
|
|
|
|
|
2019-03-15 17:03:55 +00:00
|
|
|
var ips []net.IP
|
2020-11-17 21:07:30 +00:00
|
|
|
apiServerIP := net.ParseIP(props.APIServerHost)
|
2020-01-07 00:12:53 +00:00
|
|
|
if apiServerIP != nil {
|
|
|
|
ips = append(ips, apiServerIP)
|
2019-03-15 17:03:55 +00:00
|
|
|
} else {
|
2020-11-17 21:07:30 +00:00
|
|
|
dnsNames = append(dnsNames, props.APIServerHost)
|
2019-03-15 17:03:55 +00:00
|
|
|
}
|
|
|
|
|
2021-01-20 23:25:27 +00:00
|
|
|
if serverIP != "" {
|
|
|
|
if strings.Contains(serverIP, ":") {
|
|
|
|
host, _, _ := net.SplitHostPort(serverIP)
|
|
|
|
serverIP = host
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := net.ParseIP(serverIP)
|
|
|
|
ips = append(ips, ip)
|
|
|
|
}
|
|
|
|
|
2020-10-07 21:17:37 +00:00
|
|
|
templ := &x509.Certificate{
|
|
|
|
SerialNumber: big.NewInt(1),
|
2019-03-15 17:03:55 +00:00
|
|
|
Subject: pkix.Name{
|
2020-01-07 00:12:53 +00:00
|
|
|
CommonName: csCommonName,
|
2019-03-15 17:03:55 +00:00
|
|
|
},
|
2021-01-20 23:25:27 +00:00
|
|
|
DNSNames: dnsNames,
|
|
|
|
IPAddresses: ips,
|
2020-10-07 21:17:37 +00:00
|
|
|
NotBefore: begin,
|
|
|
|
NotAfter: end,
|
|
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
|
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
|
|
BasicConstraintsValid: true,
|
2019-03-15 17:03:55 +00:00
|
|
|
}
|
|
|
|
|
2020-10-07 21:17:37 +00:00
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error generating key for webhook %v", err)
|
|
|
|
}
|
|
|
|
der, err := x509.CreateCertificate(rand.Reader, templ, caCert.Cert, key.Public(), caCert.Key)
|
2019-03-15 17:03:55 +00:00
|
|
|
if err != nil {
|
2020-10-07 21:17:37 +00:00
|
|
|
return nil, fmt.Errorf("error creating certificate for webhook %v", err)
|
2019-03-15 17:03:55 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 21:07:30 +00:00
|
|
|
pemPair := &PemPair{
|
|
|
|
Certificate: CertificateToPem(der),
|
|
|
|
PrivateKey: PrivateKeyToPem(key),
|
2020-10-07 21:17:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return pemPair, nil
|
|
|
|
}
|
|
|
|
|
2019-06-06 00:43:59 +00:00
|
|
|
//GenerateInClusterServiceName The generated service name should be the common name for TLS certificate
|
2021-03-16 18:31:04 +00:00
|
|
|
func generateInClusterServiceName(props CertificateProps) string {
|
2019-03-15 17:03:55 +00:00
|
|
|
return props.Service + "." + props.Namespace + ".svc"
|
|
|
|
}
|
|
|
|
|
2019-06-06 00:43:59 +00:00
|
|
|
//TlsCertificateGetExpirationDate Gets NotAfter property from raw certificate
|
2020-01-07 00:12:53 +00:00
|
|
|
func tlsCertificateGetExpirationDate(certData []byte) (*time.Time, error) {
|
2019-03-15 17:03:55 +00:00
|
|
|
block, _ := pem.Decode(certData)
|
|
|
|
if block == nil {
|
|
|
|
return nil, errors.New("Failed to decode PEM")
|
|
|
|
}
|
|
|
|
|
|
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("Failed to parse certificate: %v" + err.Error())
|
|
|
|
}
|
|
|
|
return &cert.NotAfter, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// The certificate is valid for a year, but we update it earlier to avoid using
|
|
|
|
// an expired certificate in a controller that has been running for a long time
|
|
|
|
const timeReserveBeforeCertificateExpiration time.Duration = time.Hour * 24 * 30 * 6 // About half a year
|
|
|
|
|
2020-01-07 00:12:53 +00:00
|
|
|
//IsTLSPairShouldBeUpdated checks if TLS pair has expited and needs to be updated
|
2020-11-17 21:07:30 +00:00
|
|
|
func IsTLSPairShouldBeUpdated(tlsPair *PemPair) bool {
|
2019-03-15 17:03:55 +00:00
|
|
|
if tlsPair == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-01-07 00:12:53 +00:00
|
|
|
expirationDate, err := tlsCertificateGetExpirationDate(tlsPair.Certificate)
|
2019-03-15 17:03:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-03-23 19:05:05 +00:00
|
|
|
// TODO : should use time.Until instead of t.Sub(time.Now()) (gosimple)
|
2019-03-15 17:03:55 +00:00
|
|
|
return expirationDate.Sub(time.Now()) < timeReserveBeforeCertificateExpiration
|
|
|
|
}
|