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