2019-05-15 07:30:22 -07:00
package client
2019-03-15 19:03:55 +02:00
import (
"errors"
"fmt"
2019-05-29 12:36:03 -07:00
"net/url"
2019-03-15 19:03:55 +02:00
"time"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/config"
tls "github.com/kyverno/kyverno/pkg/tls"
2019-03-15 19:03:55 +02:00
certificates "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-05-29 12:36:03 -07:00
"k8s.io/client-go/rest"
2019-03-15 19:03:55 +02:00
)
2019-11-18 11:41:37 -08:00
// InitTLSPemPair Loads or creates PEM private key and TLS certificate for webhook server.
// Created pair is stored in cluster's secret.
// Returns struct with key/certificate pair.
2020-01-08 16:40:19 -08:00
func ( c * Client ) InitTLSPemPair ( configuration * rest . Config , fqdncn bool ) ( * tls . TlsPemPair , error ) {
2020-03-17 11:05:20 -07:00
logger := c . log
2019-11-18 11:41:37 -08:00
certProps , err := c . GetTLSCertProps ( configuration )
if err != nil {
return nil , err
}
tlsPair := c . ReadTlsPair ( certProps )
2020-01-06 16:12:53 -08:00
if tls . IsTLSPairShouldBeUpdated ( tlsPair ) {
2020-03-17 11:05:20 -07:00
logger . Info ( "Generating new key/certificate pair for TLS" )
2020-01-08 16:40:19 -08:00
tlsPair , err = c . generateTLSPemPair ( certProps , fqdncn )
2019-11-18 11:41:37 -08:00
if err != nil {
return nil , err
}
if err = c . WriteTlsPair ( certProps , tlsPair ) ; err != nil {
return nil , fmt . Errorf ( "Unable to save TLS pair to the cluster: %v" , err )
}
return tlsPair , nil
}
2020-03-17 11:05:20 -07:00
logger . Info ( "Using existing TLS key/certificate pair" )
2019-11-18 11:41:37 -08:00
return tlsPair , nil
}
2020-01-06 16:12:53 -08:00
//generateTlsPemPair Issues TLS certificate for webhook server using given PEM private key
2019-03-15 19:03:55 +02:00
// Returns signed and approved TLS certificate in PEM format
2020-01-08 16:40:19 -08:00
func ( c * Client ) generateTLSPemPair ( props tls . TlsCertificateProps , fqdncn bool ) ( * tls . TlsPemPair , error ) {
2020-01-06 16:12:53 -08:00
privateKey , err := tls . TLSGeneratePrivateKey ( )
2019-03-15 19:03:55 +02:00
if err != nil {
return nil , err
}
2020-01-08 16:40:19 -08:00
certRequest , err := tls . CertificateGenerateRequest ( privateKey , props , fqdncn )
2019-03-15 19:03:55 +02:00
if err != nil {
2019-06-05 17:43:59 -07:00
return nil , fmt . Errorf ( "Unable to create certificate request: %v" , err )
2019-03-15 19:03:55 +02:00
}
2019-05-15 07:30:22 -07:00
certRequest , err = c . submitAndApproveCertificateRequest ( certRequest )
2019-03-15 19:03:55 +02:00
if err != nil {
2019-06-05 17:43:59 -07:00
return nil , fmt . Errorf ( "Unable to submit and approve certificate request: %v" , err )
2019-03-15 19:03:55 +02:00
}
2019-05-15 07:30:22 -07:00
tlsCert , err := c . fetchCertificateFromRequest ( certRequest , 10 )
2019-03-15 19:03:55 +02:00
if err != nil {
2020-10-07 11:12:31 -07:00
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 )
2019-03-15 19:03:55 +02:00
}
2019-05-14 18:10:25 +03:00
return & tls . TlsPemPair {
2019-03-15 19:03:55 +02:00
Certificate : tlsCert ,
2020-01-06 16:12:53 -08:00
PrivateKey : tls . TLSPrivateKeyToPem ( privateKey ) ,
2019-03-15 19:03:55 +02:00
} , nil
}
// Submits and approves certificate request, returns request which need to be fetched
2019-05-15 07:30:22 -07:00
func ( c * Client ) submitAndApproveCertificateRequest ( req * certificates . CertificateSigningRequest ) ( * certificates . CertificateSigningRequest , error ) {
2020-03-17 11:05:20 -07:00
logger := c . log . WithName ( "submitAndApproveCertificateRequest" )
2019-05-15 07:30:22 -07:00
certClient , err := c . GetCSRInterface ( )
if err != nil {
return nil , err
}
2020-08-07 09:47:33 +05:30
csrList , err := c . ListResource ( "" , CSRs , "" , nil )
2019-03-15 19:03:55 +02:00
if err != nil {
2019-06-05 17:43:59 -07:00
return nil , fmt . Errorf ( "Unable to list existing certificate requests: %v" , err )
2019-03-15 19:03:55 +02:00
}
for _ , csr := range csrList . Items {
2019-05-15 07:30:22 -07:00
if csr . GetName ( ) == req . ObjectMeta . Name {
2020-08-07 09:47:33 +05:30
err := c . DeleteResource ( "" , CSRs , "" , csr . GetName ( ) , false )
2019-03-15 19:03:55 +02:00
if err != nil {
2019-06-05 17:43:59 -07:00
return nil , fmt . Errorf ( "Unable to delete existing certificate request: %v" , err )
2019-03-15 19:03:55 +02:00
}
2020-03-17 11:05:20 -07:00
logger . Info ( "Old certificate request is deleted" )
2019-03-15 19:03:55 +02:00
break
}
}
2020-08-07 09:47:33 +05:30
unstrRes , err := c . CreateResource ( "" , CSRs , "" , req , false )
2019-03-15 19:03:55 +02:00
if err != nil {
return nil , err
}
2020-03-17 11:05:20 -07:00
logger . Info ( "Certificate request created" , "name" , unstrRes . GetName ( ) )
2019-03-15 19:03:55 +02:00
2019-05-15 07:30:22 -07:00
res , err := convertToCSR ( unstrRes )
if err != nil {
return nil , err
}
2019-03-15 19:03:55 +02:00
res . Status . Conditions = append ( res . Status . Conditions , certificates . CertificateSigningRequestCondition {
Type : certificates . CertificateApproved ,
Reason : "NKP-Approve" ,
2019-05-28 18:16:22 -07:00
Message : "This CSR was approved by Nirmata kyverno controller" ,
2019-03-15 19:03:55 +02:00
} )
res , err = certClient . UpdateApproval ( res )
if err != nil {
2019-06-05 17:43:59 -07:00
return nil , fmt . Errorf ( "Unable to approve certificate request: %v" , err )
2019-03-15 19:03:55 +02:00
}
2020-03-17 11:05:20 -07:00
logger . Info ( "Certificate request is approved" , "name" , res . ObjectMeta . Name )
2019-03-15 19:03:55 +02:00
return res , nil
}
// Fetches certificate from given request. Tries to obtain certificate for maxWaitSeconds
2019-05-15 07:30:22 -07:00
func ( c * Client ) fetchCertificateFromRequest ( req * certificates . CertificateSigningRequest , maxWaitSeconds uint8 ) ( [ ] byte , error ) {
2019-03-15 19:03:55 +02:00
// TODO: react of SIGINT and SIGTERM
timeStart := time . Now ( )
2020-01-24 16:27:51 -08:00
for time . Since ( timeStart ) < time . Duration ( maxWaitSeconds ) * time . Second {
2020-08-07 09:47:33 +05:30
unstrR , err := c . GetResource ( "" , CSRs , "" , req . ObjectMeta . Name )
2019-05-15 07:30:22 -07:00
if err != nil {
return nil , err
}
r , err := convertToCSR ( unstrR )
2019-03-15 19:03:55 +02:00
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 ( ) )
}
}
}
2019-06-05 17:43:59 -07:00
return nil , fmt . Errorf ( "Cerificate fetch timeout is reached: %d seconds" , maxWaitSeconds )
2019-03-15 19:03:55 +02:00
}
2019-06-05 17:43:59 -07:00
//ReadRootCASecret returns the RootCA from the pre-defined secret
2019-05-29 12:36:03 -07:00
func ( c * Client ) ReadRootCASecret ( ) ( result [ ] byte ) {
2020-03-17 11:05:20 -07:00
logger := c . log . WithName ( "ReadRootCASecret" )
2019-05-29 12:36:03 -07:00
certProps , err := c . GetTLSCertProps ( c . clientConfig )
2019-05-28 18:16:22 -07:00
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to get TLS Cert Properties" )
2019-05-29 12:36:03 -07:00
return result
2019-05-28 18:16:22 -07:00
}
2019-05-29 12:36:03 -07:00
sname := generateRootCASecretName ( certProps )
2020-08-07 09:47:33 +05:30
stlsca , err := c . GetResource ( "" , Secrets , certProps . Namespace , sname )
2019-05-28 18:16:22 -07:00
if err != nil {
return result
}
tlsca , err := convertToSecret ( stlsca )
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to convert secret" , "name" , sname , "namespace" , certProps . Namespace )
2019-05-28 18:16:22 -07:00
return result
}
2019-05-29 12:36:03 -07:00
result = tlsca . Data [ rootCAKey ]
2019-05-28 18:16:22 -07:00
if len ( result ) == 0 {
2020-03-17 11:05:20 -07:00
logger . Info ( "root CA certificate not found in secret" , "name" , tlsca . Name , "namespace" , certProps . Namespace )
2019-05-28 18:16:22 -07:00
return result
}
2020-03-17 11:05:20 -07:00
logger . V ( 4 ) . Info ( "using CA bundle defined in secret to validate the webhook's server certificate" , "name" , tlsca . Name , "namespace" , certProps . Namespace )
2019-05-28 18:16:22 -07:00
return result
}
2019-05-29 12:36:03 -07:00
const selfSignedAnnotation string = "self-signed-cert"
const rootCAKey string = "rootCA.crt"
2019-03-15 19:03:55 +02:00
2019-06-05 17:43:59 -07:00
//ReadTlsPair Reads the pair of TLS certificate and key from the specified secret.
2019-05-15 11:24:27 -07:00
func ( c * Client ) ReadTlsPair ( props tls . TlsCertificateProps ) * tls . TlsPemPair {
2020-03-17 11:05:20 -07:00
logger := c . log . WithName ( "ReadTlsPair" )
2019-05-29 12:36:03 -07:00
sname := generateTLSPairSecretName ( props )
2020-08-07 09:47:33 +05:30
unstrSecret , err := c . GetResource ( "" , Secrets , props . Namespace , sname )
2019-05-15 07:30:22 -07:00
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "Failed to get secret" , "name" , sname , "namespace" , props . Namespace )
2019-05-15 07:30:22 -07:00
return nil
}
2019-05-29 12:36:03 -07:00
// If secret contains annotation 'self-signed-cert', then it's created using helper scripts to setup self-signed certificates.
// As the root CA used to sign the certificate is required for webhook cnofiguration, check if the corresponding secret is created
annotations := unstrSecret . GetAnnotations ( )
if _ , ok := annotations [ selfSignedAnnotation ] ; ok {
sname := generateRootCASecretName ( props )
2020-08-07 09:47:33 +05:30
_ , err := c . GetResource ( "" , Secrets , props . Namespace , sname )
2019-05-29 12:36:03 -07:00
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "Root CA secret is required while using self-signed certificates TLS pair, defaulting to generating new TLS pair" , "name" , sname , "namespace" , props . Namespace )
2019-05-29 12:36:03 -07:00
return nil
}
}
2019-05-15 07:30:22 -07:00
secret , err := convertToSecret ( unstrSecret )
2019-03-15 19:03:55 +02:00
if err != nil {
return nil
}
2019-05-14 18:10:25 +03:00
pemPair := tls . TlsPemPair {
2019-05-29 12:36:03 -07:00
Certificate : secret . Data [ v1 . TLSCertKey ] ,
PrivateKey : secret . Data [ v1 . TLSPrivateKeyKey ] ,
2019-03-15 19:03:55 +02:00
}
if len ( pemPair . Certificate ) == 0 {
2020-03-17 11:05:20 -07:00
logger . Info ( "TLS Certificate not found in secret" , "name" , sname , "namespace" , props . Namespace )
2019-03-15 19:03:55 +02:00
return nil
}
if len ( pemPair . PrivateKey ) == 0 {
2020-03-17 11:05:20 -07:00
logger . Info ( "TLS PrivateKey not found in secret" , "name" , sname , "namespace" , props . Namespace )
2019-03-15 19:03:55 +02:00
return nil
}
return & pemPair
}
2019-06-05 17:43:59 -07:00
//WriteTlsPair Writes the pair of TLS certificate and key to the specified secret.
2019-03-15 19:03:55 +02:00
// Updates existing secret or creates new one.
2019-05-15 11:24:27 -07:00
func ( c * Client ) WriteTlsPair ( props tls . TlsCertificateProps , pemPair * tls . TlsPemPair ) error {
2020-03-17 11:05:20 -07:00
logger := c . log . WithName ( "WriteTlsPair" )
2019-05-29 12:36:03 -07:00
name := generateTLSPairSecretName ( props )
2020-08-07 09:47:33 +05:30
_ , err := c . GetResource ( "" , Secrets , props . Namespace , name )
2019-05-23 11:59:30 -07:00
if err != nil {
2019-05-15 07:30:22 -07:00
secret := & v1 . Secret {
2019-03-15 19:03:55 +02:00
TypeMeta : metav1 . TypeMeta {
Kind : "Secret" ,
APIVersion : "v1" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
Name : name ,
Namespace : props . Namespace ,
} ,
Data : map [ string ] [ ] byte {
2019-05-29 12:36:03 -07:00
v1 . TLSCertKey : pemPair . Certificate ,
v1 . TLSPrivateKeyKey : pemPair . PrivateKey ,
2019-03-15 19:03:55 +02:00
} ,
2019-05-29 12:36:03 -07:00
Type : v1 . SecretTypeTLS ,
2019-03-15 19:03:55 +02:00
}
2020-08-07 09:47:33 +05:30
_ , err := c . CreateResource ( "" , Secrets , props . Namespace , secret , false )
2019-03-15 19:03:55 +02:00
if err == nil {
2020-03-17 11:05:20 -07:00
logger . Info ( "secret created" , "name" , name , "namespace" , props . Namespace )
2019-03-15 19:03:55 +02:00
}
2019-05-23 11:59:30 -07:00
return err
}
secret := v1 . Secret { }
if secret . Data == nil {
secret . Data = make ( map [ string ] [ ] byte )
}
2019-05-29 12:36:03 -07:00
secret . Data [ v1 . TLSCertKey ] = pemPair . Certificate
secret . Data [ v1 . TLSPrivateKeyKey ] = pemPair . PrivateKey
2019-05-23 11:59:30 -07:00
2020-08-07 09:47:33 +05:30
_ , err = c . UpdateResource ( "" , Secrets , props . Namespace , secret , false )
2019-05-23 11:59:30 -07:00
if err != nil {
return err
2019-03-15 19:03:55 +02:00
}
2020-03-17 11:05:20 -07:00
logger . Info ( "secret updated" , "name" , name , "namespace" , props . Namespace )
2019-05-23 11:59:30 -07:00
return nil
2019-03-15 19:03:55 +02:00
}
2019-05-29 12:36:03 -07:00
func generateTLSPairSecretName ( props tls . TlsCertificateProps ) string {
2019-05-28 18:16:22 -07:00
return tls . GenerateInClusterServiceName ( props ) + ".kyverno-tls-pair"
2019-03-15 19:03:55 +02:00
}
2019-05-29 12:36:03 -07:00
func generateRootCASecretName ( props tls . TlsCertificateProps ) string {
return tls . GenerateInClusterServiceName ( props ) + ".kyverno-tls-ca"
}
//GetTLSCertProps provides the TLS Certificate Properties
func ( c * Client ) GetTLSCertProps ( configuration * rest . Config ) ( certProps tls . TlsCertificateProps , err error ) {
apiServerURL , err := url . Parse ( configuration . Host )
if err != nil {
return certProps , err
}
certProps = tls . TlsCertificateProps {
Service : config . WebhookServiceName ,
Namespace : config . KubePolicyNamespace ,
ApiServerHost : apiServerURL . Hostname ( ) ,
}
return certProps , nil
}