mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-06 16:06:56 +00:00
- use self-signed certificate to build TLS webhook server;
- remove CSR related code
This commit is contained in:
parent
47525f9923
commit
52ef3f42f6
2 changed files with 178 additions and 139 deletions
|
@ -1,16 +1,15 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
tls "github.com/kyverno/kyverno/pkg/tls"
|
||||
certificates "k8s.io/api/certificates/v1beta1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
|
@ -26,7 +25,8 @@ func (c *Client) InitTLSPemPair(configuration *rest.Config, fqdncn bool) (*tls.T
|
|||
tlsPair := c.ReadTlsPair(certProps)
|
||||
if tls.IsTLSPairShouldBeUpdated(tlsPair) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -39,107 +39,18 @@ func (c *Client) InitTLSPemPair(configuration *rest.Config, fqdncn bool) (*tls.T
|
|||
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
|
||||
func (c *Client) generateTLSPemPair(props tls.TlsCertificateProps, fqdncn bool) (*tls.TlsPemPair, error) {
|
||||
privateKey, err := tls.TLSGeneratePrivateKey()
|
||||
func (c *Client) buildTLSPemPair(props tls.TlsCertificateProps, fqdncn bool) (*tls.TlsPemPair, error) {
|
||||
caCert, caPEM, err := tls.GenerateCACert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certRequest, err := tls.CertificateGenerateRequest(privateKey, props, fqdncn)
|
||||
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 {
|
||||
if err := c.WriteCACert(caPEM, props); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csrList, err := c.ListResource("", CSRs, "", nil)
|
||||
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)
|
||||
return tls.GenerateCertPEM(caCert, props, fqdncn)
|
||||
}
|
||||
|
||||
//ReadRootCASecret returns the RootCA from the pre-defined secret
|
||||
|
@ -213,6 +124,56 @@ func (c *Client) ReadTlsPair(props tls.TlsCertificateProps) *tls.TlsPemPair {
|
|||
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.
|
||||
// Updates existing secret or creates new one.
|
||||
func (c *Client) WriteTlsPair(props tls.TlsCertificateProps, pemPair *tls.TlsPemPair) error {
|
||||
|
|
160
pkg/tls/tls.go
160
pkg/tls/tls.go
|
@ -1,19 +1,21 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"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
|
||||
type TlsCertificateProps struct {
|
||||
Service string
|
||||
|
@ -27,6 +29,11 @@ type TlsPemPair struct {
|
|||
PrivateKey []byte
|
||||
}
|
||||
|
||||
type KeyPair struct {
|
||||
Cert *x509.Certificate
|
||||
Key *rsa.PrivateKey
|
||||
}
|
||||
|
||||
//TLSGeneratePrivateKey Generates RSA private key
|
||||
func TLSGeneratePrivateKey() (*rsa.PrivateKey, error) {
|
||||
return rsa.GenerateKey(rand.Reader, 2048)
|
||||
|
@ -42,29 +49,92 @@ func TLSPrivateKeyToPem(rsaKey *rsa.PrivateKey) []byte {
|
|||
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,
|
||||
func pemEncode(certificateDER []byte, key *rsa.PrivateKey) ([]byte, []byte, error) {
|
||||
certBuf := &bytes.Buffer{}
|
||||
if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: certificateDER}); err != nil {
|
||||
return nil, nil, fmt.Errorf("encoding cert: %v", err)
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(csrBlock)
|
||||
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
|
||||
}
|
||||
|
||||
//CertificateGenerateRequest Generates raw certificate signing request
|
||||
func CertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificateProps, fqdncn bool) (*certificates.CertificateSigningRequest, error) {
|
||||
func TLSCertificateToPem(certificateDER []byte) []byte {
|
||||
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
|
||||
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[0] = props.Service
|
||||
dnsNames[1] = props.Service + "." + props.Namespace
|
||||
dnsNames[0] = fmt.Sprintf("%s", props.Service)
|
||||
csCommonName := dnsNames[0]
|
||||
|
||||
dnsNames[1] = fmt.Sprintf("%s.%s", props.Service, props.Namespace)
|
||||
// The full service name is the CommonName for the certificate
|
||||
commonName := GenerateInClusterServiceName(props)
|
||||
dnsNames[2] = commonName
|
||||
csCommonName := props.Service
|
||||
dnsNames[2] = fmt.Sprintf("%s", commonName)
|
||||
|
||||
if fqdncn {
|
||||
// use FQDN as CommonName as a workaournd for https://github.com/kyverno/kyverno/issues/542
|
||||
csCommonName = commonName
|
||||
}
|
||||
|
||||
var ips []net.IP
|
||||
apiServerIP := net.ParseIP(props.ApiServerHost)
|
||||
if apiServerIP != nil {
|
||||
|
@ -73,39 +143,47 @@ func CertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificate
|
|||
dnsNames = append(dnsNames, props.ApiServerHost)
|
||||
}
|
||||
|
||||
csrTemplate := x509.CertificateRequest{
|
||||
templ := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
CommonName: csCommonName,
|
||||
},
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ips,
|
||||
DNSNames: dnsNames,
|
||||
// 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 {
|
||||
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{
|
||||
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
|
||||
pemPair := &TlsPemPair{
|
||||
Certificate: TLSCertificateToPem(der),
|
||||
PrivateKey: TLSPrivateKeyToPem(key),
|
||||
}
|
||||
|
||||
// certPEM := TLSCertificateToPem(der)
|
||||
// keyPEM := TLSPrivateKeyToPem(key)
|
||||
return pemPair, nil
|
||||
}
|
||||
|
||||
//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)
|
||||
}
|
||||
|
||||
//GenerateInClusterServiceName The generated service name should be the common name for TLS certificate
|
||||
|
|
Loading…
Add table
Reference in a new issue