package tls import ( "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "errors" "net" "time" certificates "k8s.io/api/certificates/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) //TlsCertificateProps Properties of TLS certificate which should be issued for webhook server type TlsCertificateProps struct { Service string Namespace string ApiServerHost string } //TlsPemPair The pair of TLS certificate corresponding private key, both in PEM format type TlsPemPair struct { Certificate []byte PrivateKey []byte } //TLSGeneratePrivateKey Generates RSA private key func TLSGeneratePrivateKey() (*rsa.PrivateKey, error) { return rsa.GenerateKey(rand.Reader, 2048) } //TLSPrivateKeyToPem Creates PEM block from private key object func TLSPrivateKeyToPem(rsaKey *rsa.PrivateKey) []byte { privateKey := &pem.Block{ Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaKey), } return pem.EncodeToMemory(privateKey) } //TlsCertificateRequestToPem Creates PEM block from raw certificate request func certificateRequestToPem(csrRaw []byte) []byte { csrBlock := &pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: csrRaw, } return pem.EncodeToMemory(csrBlock) } //CertificateGenerateRequest Generates raw certificate signing request func CertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificateProps, fqdncn bool) (*certificates.CertificateSigningRequest, error) { dnsNames := make([]string, 3) dnsNames[0] = props.Service dnsNames[1] = props.Service + "." + props.Namespace // The full service name is the CommonName for the certificate commonName := GenerateInClusterServiceName(props) dnsNames[2] = commonName csCommonName := props.Service if fqdncn { // use FQDN as CommonName as a workaournd for https://github.com/nirmata/kyverno/issues/542 csCommonName = commonName } var ips []net.IP apiServerIP := net.ParseIP(props.ApiServerHost) if apiServerIP != nil { ips = append(ips, apiServerIP) } else { dnsNames = append(dnsNames, props.ApiServerHost) } csrTemplate := x509.CertificateRequest{ Subject: pkix.Name{ CommonName: csCommonName, }, SignatureAlgorithm: x509.SHA256WithRSA, DNSNames: dnsNames, IPAddresses: ips, } csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, privateKey) if err != nil { return nil, err } return &certificates.CertificateSigningRequest{ TypeMeta: metav1.TypeMeta{ APIVersion: "certificates.k8s.io/v1beta1", Kind: "CertificateSigningRequest", }, ObjectMeta: metav1.ObjectMeta{ Name: props.Service + "." + props.Namespace + ".cert-request", }, Spec: certificates.CertificateSigningRequestSpec{ Request: certificateRequestToPem(csrBytes), Groups: []string{"system:masters", "system:authenticated"}, Usages: []certificates.KeyUsage{ certificates.UsageDigitalSignature, certificates.UsageKeyEncipherment, certificates.UsageServerAuth, certificates.UsageClientAuth, }, }, }, nil } //GenerateInClusterServiceName The generated service name should be the common name for TLS certificate func GenerateInClusterServiceName(props TlsCertificateProps) string { return props.Service + "." + props.Namespace + ".svc" } //TlsCertificateGetExpirationDate Gets NotAfter property from raw certificate func tlsCertificateGetExpirationDate(certData []byte) (*time.Time, error) { 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 //IsTLSPairShouldBeUpdated checks if TLS pair has expited and needs to be updated func IsTLSPairShouldBeUpdated(tlsPair *TlsPemPair) bool { if tlsPair == nil { return true } expirationDate, err := tlsCertificateGetExpirationDate(tlsPair.Certificate) if err != nil { return true } // TODO : should use time.Until instead of t.Sub(time.Now()) (gosimple) return expirationDate.Sub(time.Now()) < timeReserveBeforeCertificateExpiration }