1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-07 00:17:13 +00:00

- use self-signed certificate to build TLS webhook server;

- remove CSR related code
This commit is contained in:
Shuting Zhao 2020-10-07 14:17:37 -07:00
parent 47525f9923
commit 52ef3f42f6
2 changed files with 178 additions and 139 deletions

View file

@ -1,16 +1,15 @@
package client package client
import ( import (
"errors" "encoding/base64"
"fmt" "fmt"
"net/url" "net/url"
"time"
"github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/config"
tls "github.com/kyverno/kyverno/pkg/tls" tls "github.com/kyverno/kyverno/pkg/tls"
certificates "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
) )
@ -26,7 +25,8 @@ func (c *Client) InitTLSPemPair(configuration *rest.Config, fqdncn bool) (*tls.T
tlsPair := c.ReadTlsPair(certProps) tlsPair := c.ReadTlsPair(certProps)
if tls.IsTLSPairShouldBeUpdated(tlsPair) { if tls.IsTLSPairShouldBeUpdated(tlsPair) {
logger.Info("Generating new key/certificate pair for TLS") logger.Info("Generating new key/certificate pair for TLS")
tlsPair, err = c.generateTLSPemPair(certProps, fqdncn) // tlsPair, err = c.generateTLSPemPair(certProps, fqdncn)
tlsPair, err = c.buildTLSPemPair(certProps, fqdncn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -39,107 +39,18 @@ func (c *Client) InitTLSPemPair(configuration *rest.Config, fqdncn bool) (*tls.T
return tlsPair, nil return tlsPair, nil
} }
//generateTlsPemPair Issues TLS certificate for webhook server using given PEM private key //buildTLSPemPair Issues TLS certificate for webhook server using self-signed CA cert
// Returns signed and approved TLS certificate in PEM format // Returns signed and approved TLS certificate in PEM format
func (c *Client) generateTLSPemPair(props tls.TlsCertificateProps, fqdncn bool) (*tls.TlsPemPair, error) { func (c *Client) buildTLSPemPair(props tls.TlsCertificateProps, fqdncn bool) (*tls.TlsPemPair, error) {
privateKey, err := tls.TLSGeneratePrivateKey() caCert, caPEM, err := tls.GenerateCACert()
if err != nil { if err != nil {
return nil, err return nil, err
} }
certRequest, err := tls.CertificateGenerateRequest(privateKey, props, fqdncn) if err := c.WriteCACert(caPEM, props); err != nil {
if err != nil {
return nil, fmt.Errorf("Unable to create certificate request: %v", err)
}
certRequest, err = c.submitAndApproveCertificateRequest(certRequest)
if err != nil {
return nil, fmt.Errorf("Unable to submit and approve certificate request: %v", err)
}
tlsCert, err := c.fetchCertificateFromRequest(certRequest, 10)
if err != nil {
return nil, fmt.Errorf("Failed to configure a certificate for the Kyverno controller. A CA certificate is required to allow the Kubernetes API Server to communicate with Kyverno. You can either provide a certificate or configure your cluster to allow certificate signing. Please refer to https://github.com/kyverno/kyverno/installation.md.: %v", err)
}
return &tls.TlsPemPair{
Certificate: tlsCert,
PrivateKey: tls.TLSPrivateKeyToPem(privateKey),
}, nil
}
// Submits and approves certificate request, returns request which need to be fetched
func (c *Client) submitAndApproveCertificateRequest(req *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, error) {
logger := c.log.WithName("submitAndApproveCertificateRequest")
certClient, err := c.GetCSRInterface()
if err != nil {
return nil, err return nil, err
} }
csrList, err := c.ListResource("", CSRs, "", nil) return tls.GenerateCertPEM(caCert, props, fqdncn)
if err != nil {
return nil, fmt.Errorf("Unable to list existing certificate requests: %v", err)
}
for _, csr := range csrList.Items {
if csr.GetName() == req.ObjectMeta.Name {
err := c.DeleteResource("", CSRs, "", csr.GetName(), false)
if err != nil {
return nil, fmt.Errorf("Unable to delete existing certificate request: %v", err)
}
logger.Info("Old certificate request is deleted")
break
}
}
unstrRes, err := c.CreateResource("", CSRs, "", req, false)
if err != nil {
return nil, err
}
logger.Info("Certificate request created", "name", unstrRes.GetName())
res, err := convertToCSR(unstrRes)
if err != nil {
return nil, err
}
res.Status.Conditions = append(res.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateApproved,
Reason: "NKP-Approve",
Message: "This CSR was approved by Nirmata kyverno controller",
})
res, err = certClient.UpdateApproval(res)
if err != nil {
return nil, fmt.Errorf("Unable to approve certificate request: %v", err)
}
logger.Info("Certificate request is approved", "name", res.ObjectMeta.Name)
return res, nil
}
// Fetches certificate from given request. Tries to obtain certificate for maxWaitSeconds
func (c *Client) fetchCertificateFromRequest(req *certificates.CertificateSigningRequest, maxWaitSeconds uint8) ([]byte, error) {
// TODO: react of SIGINT and SIGTERM
timeStart := time.Now()
for time.Since(timeStart) < time.Duration(maxWaitSeconds)*time.Second {
unstrR, err := c.GetResource("", CSRs, "", req.ObjectMeta.Name)
if err != nil {
return nil, err
}
r, err := convertToCSR(unstrR)
if err != nil {
return nil, err
}
if r.Status.Certificate != nil {
return r.Status.Certificate, nil
}
for _, condition := range r.Status.Conditions {
if condition.Type == certificates.CertificateDenied {
return nil, errors.New(condition.String())
}
}
}
return nil, fmt.Errorf("Cerificate fetch timeout is reached: %d seconds", maxWaitSeconds)
} }
//ReadRootCASecret returns the RootCA from the pre-defined secret //ReadRootCASecret returns the RootCA from the pre-defined secret
@ -213,6 +124,56 @@ func (c *Client) ReadTlsPair(props tls.TlsCertificateProps) *tls.TlsPemPair {
return &pemPair return &pemPair
} }
func (c *Client) WriteCACert(caPEM *tls.TlsPemPair, props tls.TlsCertificateProps) error {
logger := c.log.WithName("CAcert")
name := generateRootCASecretName(props)
secretUnstr, err := c.GetResource("", Secrets, props.Namespace, name)
if err != nil {
secret := &v1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: props.Namespace,
Annotations: map[string]string{
selfSignedAnnotation: "true",
},
},
Data: map[string][]byte{
rootCAKey: caPEM.Certificate,
},
Type: v1.SecretTypeOpaque,
}
_, err := c.CreateResource("", Secrets, props.Namespace, secret, false)
if err == nil {
logger.Info("secret created", "name", name, "namespace", props.Namespace)
}
return err
}
// secret := v1.Secret{}
if _, ok := secretUnstr.GetAnnotations()[selfSignedAnnotation]; !ok {
secretUnstr.SetAnnotations(map[string]string{selfSignedAnnotation: "true"})
}
dataMap := map[string]interface{}{
rootCAKey: base64.StdEncoding.EncodeToString(caPEM.Certificate)}
if err := unstructured.SetNestedMap(secretUnstr.Object, dataMap, "data"); err != nil {
return err
}
_, err = c.UpdateResource("", Secrets, props.Namespace, secretUnstr, false)
if err != nil {
return err
}
logger.Info("secret updated", "name", name, "namespace", props.Namespace)
return nil
}
//WriteTlsPair Writes the pair of TLS certificate and key to the specified secret. //WriteTlsPair Writes the pair of TLS certificate and key to the specified secret.
// Updates existing secret or creates new one. // Updates existing secret or creates new one.
func (c *Client) WriteTlsPair(props tls.TlsCertificateProps, pemPair *tls.TlsPemPair) error { func (c *Client) WriteTlsPair(props tls.TlsCertificateProps, pemPair *tls.TlsPemPair) error {

View file

@ -1,19 +1,21 @@
package tls package tls
import ( import (
"bytes"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt"
"math/big"
"net" "net"
"time" "time"
certificates "k8s.io/api/certificates/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
const certValidityDuration = 10 * 365 * 24 * time.Hour
//TlsCertificateProps Properties of TLS certificate which should be issued for webhook server //TlsCertificateProps Properties of TLS certificate which should be issued for webhook server
type TlsCertificateProps struct { type TlsCertificateProps struct {
Service string Service string
@ -27,6 +29,11 @@ type TlsPemPair struct {
PrivateKey []byte PrivateKey []byte
} }
type KeyPair struct {
Cert *x509.Certificate
Key *rsa.PrivateKey
}
//TLSGeneratePrivateKey Generates RSA private key //TLSGeneratePrivateKey Generates RSA private key
func TLSGeneratePrivateKey() (*rsa.PrivateKey, error) { func TLSGeneratePrivateKey() (*rsa.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, 2048) return rsa.GenerateKey(rand.Reader, 2048)
@ -42,29 +49,92 @@ func TLSPrivateKeyToPem(rsaKey *rsa.PrivateKey) []byte {
return pem.EncodeToMemory(privateKey) return pem.EncodeToMemory(privateKey)
} }
//TlsCertificateRequestToPem Creates PEM block from raw certificate request func pemEncode(certificateDER []byte, key *rsa.PrivateKey) ([]byte, []byte, error) {
func certificateRequestToPem(csrRaw []byte) []byte { certBuf := &bytes.Buffer{}
csrBlock := &pem.Block{ if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: certificateDER}); err != nil {
Type: "CERTIFICATE REQUEST", return nil, nil, fmt.Errorf("encoding cert: %v", err)
Bytes: csrRaw, }
keyBuf := &bytes.Buffer{}
if err := pem.Encode(keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}); err != nil {
return nil, nil, fmt.Errorf("encoding key: %v", err)
}
return certBuf.Bytes(), keyBuf.Bytes(), nil
} }
return pem.EncodeToMemory(csrBlock) func TLSCertificateToPem(certificateDER []byte) []byte {
certificate := &pem.Block{
Type: "CERTIFICATE",
Bytes: certificateDER,
} }
//CertificateGenerateRequest Generates raw certificate signing request return pem.EncodeToMemory(certificate)
func CertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificateProps, fqdncn bool) (*certificates.CertificateSigningRequest, error) { }
// GenerateCACert creates the self-signed CA cert and private key
// it will be used to sign the webhook server certificate
func GenerateCACert() (*KeyPair, *TlsPemPair, error) {
now := time.Now()
begin := now.Add(-1 * time.Hour)
end := now.Add(certValidityDuration)
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,
}
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, fmt.Errorf("error generating key: %v", err)
}
der, err := x509.CreateCertificate(rand.Reader, templ, templ, key.Public(), key)
if err != nil {
return nil, nil, fmt.Errorf("error creating certificate: %v", err)
}
pemPair := &TlsPemPair{
Certificate: TLSCertificateToPem(der),
PrivateKey: TLSPrivateKeyToPem(key),
}
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
}
// GenerateCertPEM takes the results of GenerateCACert and uses it to create the
// PEM-encoded public certificate and private key, respectively
func GenerateCertPEM(caCert *KeyPair, props TlsCertificateProps, fqdncn bool) (*TlsPemPair, error) {
now := time.Now()
begin := now.Add(-1 * time.Hour)
end := now.Add(certValidityDuration)
dnsNames := make([]string, 3) dnsNames := make([]string, 3)
dnsNames[0] = props.Service dnsNames[0] = fmt.Sprintf("%s", props.Service)
dnsNames[1] = props.Service + "." + props.Namespace csCommonName := dnsNames[0]
dnsNames[1] = fmt.Sprintf("%s.%s", props.Service, props.Namespace)
// The full service name is the CommonName for the certificate // The full service name is the CommonName for the certificate
commonName := GenerateInClusterServiceName(props) commonName := GenerateInClusterServiceName(props)
dnsNames[2] = commonName dnsNames[2] = fmt.Sprintf("%s", commonName)
csCommonName := props.Service
if fqdncn { if fqdncn {
// use FQDN as CommonName as a workaournd for https://github.com/kyverno/kyverno/issues/542 // use FQDN as CommonName as a workaournd for https://github.com/kyverno/kyverno/issues/542
csCommonName = commonName csCommonName = commonName
} }
var ips []net.IP var ips []net.IP
apiServerIP := net.ParseIP(props.ApiServerHost) apiServerIP := net.ParseIP(props.ApiServerHost)
if apiServerIP != nil { if apiServerIP != nil {
@ -73,39 +143,47 @@ func CertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificate
dnsNames = append(dnsNames, props.ApiServerHost) dnsNames = append(dnsNames, props.ApiServerHost)
} }
csrTemplate := x509.CertificateRequest{ templ := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: csCommonName, CommonName: csCommonName,
}, },
SignatureAlgorithm: x509.SHA256WithRSA,
DNSNames: dnsNames, DNSNames: dnsNames,
IPAddresses: ips, // IPAddresses: ips,
NotBefore: begin,
NotAfter: end,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
} }
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, privateKey) key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("error generating key for webhook %v", err)
}
der, err := x509.CreateCertificate(rand.Reader, templ, caCert.Cert, key.Public(), caCert.Key)
if err != nil {
return nil, fmt.Errorf("error creating certificate for webhook %v", err)
} }
return &certificates.CertificateSigningRequest{ pemPair := &TlsPemPair{
TypeMeta: metav1.TypeMeta{ Certificate: TLSCertificateToPem(der),
APIVersion: "certificates.k8s.io/v1beta1", PrivateKey: TLSPrivateKeyToPem(key),
Kind: "CertificateSigningRequest", }
},
ObjectMeta: metav1.ObjectMeta{ // certPEM := TLSCertificateToPem(der)
Name: props.Service + "." + props.Namespace + ".cert-request", // keyPEM := TLSPrivateKeyToPem(key)
}, return pemPair, nil
Spec: certificates.CertificateSigningRequestSpec{ }
Request: certificateRequestToPem(csrBytes),
Groups: []string{"system:masters", "system:authenticated"}, //TlsCertificateRequestToPem Creates PEM block from raw certificate request
Usages: []certificates.KeyUsage{ func certificateRequestToPem(csrRaw []byte) []byte {
certificates.UsageDigitalSignature, csrBlock := &pem.Block{
certificates.UsageKeyEncipherment, Type: "CERTIFICATE REQUEST",
certificates.UsageServerAuth, Bytes: csrRaw,
certificates.UsageClientAuth, }
},
}, return pem.EncodeToMemory(csrBlock)
}, nil
} }
//GenerateInClusterServiceName The generated service name should be the common name for TLS certificate //GenerateInClusterServiceName The generated service name should be the common name for TLS certificate