From 38dcb2e94f2bfb03142c79ee1bba008ab35534bb Mon Sep 17 00:00:00 2001 From: shivkumar dudhani <shivkumar@nirmata.com> Date: Mon, 6 Jan 2020 16:12:53 -0800 Subject: [PATCH 1/4] flag to use FQDN as CommonName in CSR --- cmd/kyverno/main.go | 8 +++++--- pkg/dclient/certificates.go | 16 ++++++++-------- pkg/tls/tls.go | 38 ++++++++++++++++++++----------------- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index aee3c434a8..8858429c4a 100644 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -27,12 +27,12 @@ import ( var ( kubeconfig string serverIP string - cpu bool - memory bool webhookTimeout int //TODO: this has been added to backward support command line arguments // will be removed in future and the configuration will be set only via configmaps filterK8Resources string + // User FQDN as CSR CN + FQDNCN bool ) func main() { @@ -168,7 +168,7 @@ func main() { policyMetaStore) // CONFIGURE CERTIFICATES - tlsPair, err := client.InitTLSPemPair(clientConfig) + tlsPair, err := client.InitTLSPemPair(clientConfig, FQDNCN) if err != nil { glog.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err) } @@ -246,6 +246,8 @@ func init() { flag.IntVar(&webhookTimeout, "webhooktimeout", 3, "timeout for webhook configurations") flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.") flag.StringVar(&serverIP, "serverIP", "", "IP address where Kyverno controller runs. Only required if out-of-cluster.") + // Generate CSR with CN as FQDN due to https://github.com/nirmata/kyverno/issues/542 + flag.BoolVar(&FQDNCN, "FQDNAsCN", false, "use FQDN as Common Name in CSR") config.LogDefaultFlags() flag.Parse() } diff --git a/pkg/dclient/certificates.go b/pkg/dclient/certificates.go index 090302c940..a12dec71cb 100644 --- a/pkg/dclient/certificates.go +++ b/pkg/dclient/certificates.go @@ -18,15 +18,15 @@ import ( // 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. -func (c *Client) InitTLSPemPair(configuration *rest.Config) (*tls.TlsPemPair, error) { +func (c *Client) InitTLSPemPair(configuration *rest.Config, FQDNCN bool) (*tls.TlsPemPair, error) { certProps, err := c.GetTLSCertProps(configuration) if err != nil { return nil, err } tlsPair := c.ReadTlsPair(certProps) - if tls.IsTlsPairShouldBeUpdated(tlsPair) { + if tls.IsTLSPairShouldBeUpdated(tlsPair) { glog.Info("Generating new key/certificate pair for TLS") - tlsPair, err = c.GenerateTlsPemPair(certProps) + tlsPair, err = c.generateTLSPemPair(certProps, FQDNCN) if err != nil { return nil, err } @@ -40,15 +40,15 @@ func (c *Client) InitTLSPemPair(configuration *rest.Config) (*tls.TlsPemPair, er return tlsPair, nil } -//GenerateTlsPemPair Issues TLS certificate for webhook server using given PEM private key +//generateTlsPemPair Issues TLS certificate for webhook server using given PEM private key // Returns signed and approved TLS certificate in PEM format -func (c *Client) GenerateTlsPemPair(props tls.TlsCertificateProps) (*tls.TlsPemPair, error) { - privateKey, err := tls.TlsGeneratePrivateKey() +func (c *Client) generateTLSPemPair(props tls.TlsCertificateProps, FQDNCN bool) (*tls.TlsPemPair, error) { + privateKey, err := tls.TLSGeneratePrivateKey() if err != nil { return nil, err } - certRequest, err := tls.TlsCertificateGenerateRequest(privateKey, props) + certRequest, err := tls.CertificateGenerateRequest(privateKey, props, FQDNCN) if err != nil { return nil, fmt.Errorf("Unable to create certificate request: %v", err) } @@ -65,7 +65,7 @@ func (c *Client) GenerateTlsPemPair(props tls.TlsCertificateProps) (*tls.TlsPemP return &tls.TlsPemPair{ Certificate: tlsCert, - PrivateKey: tls.TlsPrivateKeyToPem(privateKey), + PrivateKey: tls.TLSPrivateKeyToPem(privateKey), }, nil } diff --git a/pkg/tls/tls.go b/pkg/tls/tls.go index 93055f7c74..bb640ad560 100644 --- a/pkg/tls/tls.go +++ b/pkg/tls/tls.go @@ -27,13 +27,13 @@ type TlsPemPair struct { PrivateKey []byte } -//TlsGeneratePrivateKey Generates RSA private key -func TlsGeneratePrivateKey() (*rsa.PrivateKey, error) { +//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 { +//TLSPrivateKeyToPem Creates PEM block from private key object +func TLSPrivateKeyToPem(rsaKey *rsa.PrivateKey) []byte { privateKey := &pem.Block{ Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaKey), @@ -43,7 +43,7 @@ func TlsPrivateKeyToPem(rsaKey *rsa.PrivateKey) []byte { } //TlsCertificateRequestToPem Creates PEM block from raw certificate request -func TlsCertificateRequestToPem(csrRaw []byte) []byte { +func certificateRequestToPem(csrRaw []byte) []byte { csrBlock := &pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: csrRaw, @@ -52,26 +52,30 @@ func TlsCertificateRequestToPem(csrRaw []byte) []byte { return pem.EncodeToMemory(csrBlock) } -//TlsCertificateGenerateRequest Generates raw certificate signing request -func TlsCertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificateProps) (*certificates.CertificateSigningRequest, error) { +//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) + 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: props.Service, //commonName, + CommonName: csCommonName, }, SignatureAlgorithm: x509.SHA256WithRSA, DNSNames: dnsNames, @@ -92,7 +96,7 @@ func TlsCertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertific Name: props.Service + "." + props.Namespace + ".cert-request", }, Spec: certificates.CertificateSigningRequestSpec{ - Request: TlsCertificateRequestToPem(csrBytes), + Request: certificateRequestToPem(csrBytes), Groups: []string{"system:masters", "system:authenticated"}, Usages: []certificates.KeyUsage{ certificates.UsageDigitalSignature, @@ -110,7 +114,7 @@ func GenerateInClusterServiceName(props TlsCertificateProps) string { } //TlsCertificateGetExpirationDate Gets NotAfter property from raw certificate -func TlsCertificateGetExpirationDate(certData []byte) (*time.Time, error) { +func tlsCertificateGetExpirationDate(certData []byte) (*time.Time, error) { block, _ := pem.Decode(certData) if block == nil { return nil, errors.New("Failed to decode PEM") @@ -127,13 +131,13 @@ func TlsCertificateGetExpirationDate(certData []byte) (*time.Time, error) { // 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 { +//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) + expirationDate, err := tlsCertificateGetExpirationDate(tlsPair.Certificate) if err != nil { return true } From 1e5f87166566529d874f29d1cfa8d06648626e08 Mon Sep 17 00:00:00 2001 From: shivkumar dudhani <shivkumar@nirmata.com> Date: Wed, 8 Jan 2020 16:40:19 -0800 Subject: [PATCH 2/4] lowercase the cmdline arg --- cmd/kyverno/main.go | 6 +++--- pkg/dclient/certificates.go | 8 ++++---- pkg/tls/tls.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index 8858429c4a..475dcaa790 100644 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -32,7 +32,7 @@ var ( // will be removed in future and the configuration will be set only via configmaps filterK8Resources string // User FQDN as CSR CN - FQDNCN bool + fqdncn bool ) func main() { @@ -168,7 +168,7 @@ func main() { policyMetaStore) // CONFIGURE CERTIFICATES - tlsPair, err := client.InitTLSPemPair(clientConfig, FQDNCN) + tlsPair, err := client.InitTLSPemPair(clientConfig, fqdncn) if err != nil { glog.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err) } @@ -247,7 +247,7 @@ func init() { flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.") flag.StringVar(&serverIP, "serverIP", "", "IP address where Kyverno controller runs. Only required if out-of-cluster.") // Generate CSR with CN as FQDN due to https://github.com/nirmata/kyverno/issues/542 - flag.BoolVar(&FQDNCN, "FQDNAsCN", false, "use FQDN as Common Name in CSR") + flag.BoolVar(&fqdncn, "fqdn-as-cn", false, "use FQDN as Common Name in CSR") config.LogDefaultFlags() flag.Parse() } diff --git a/pkg/dclient/certificates.go b/pkg/dclient/certificates.go index a12dec71cb..1f499f829c 100644 --- a/pkg/dclient/certificates.go +++ b/pkg/dclient/certificates.go @@ -18,7 +18,7 @@ import ( // 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. -func (c *Client) InitTLSPemPair(configuration *rest.Config, FQDNCN bool) (*tls.TlsPemPair, error) { +func (c *Client) InitTLSPemPair(configuration *rest.Config, fqdncn bool) (*tls.TlsPemPair, error) { certProps, err := c.GetTLSCertProps(configuration) if err != nil { return nil, err @@ -26,7 +26,7 @@ func (c *Client) InitTLSPemPair(configuration *rest.Config, FQDNCN bool) (*tls.T tlsPair := c.ReadTlsPair(certProps) if tls.IsTLSPairShouldBeUpdated(tlsPair) { glog.Info("Generating new key/certificate pair for TLS") - tlsPair, err = c.generateTLSPemPair(certProps, FQDNCN) + tlsPair, err = c.generateTLSPemPair(certProps, fqdncn) if err != nil { return nil, err } @@ -42,13 +42,13 @@ func (c *Client) InitTLSPemPair(configuration *rest.Config, FQDNCN bool) (*tls.T //generateTlsPemPair Issues TLS certificate for webhook server using given PEM private key // Returns signed and approved TLS certificate in PEM format -func (c *Client) generateTLSPemPair(props tls.TlsCertificateProps, FQDNCN bool) (*tls.TlsPemPair, error) { +func (c *Client) generateTLSPemPair(props tls.TlsCertificateProps, fqdncn bool) (*tls.TlsPemPair, error) { privateKey, err := tls.TLSGeneratePrivateKey() if err != nil { return nil, err } - certRequest, err := tls.CertificateGenerateRequest(privateKey, props, FQDNCN) + certRequest, err := tls.CertificateGenerateRequest(privateKey, props, fqdncn) if err != nil { return nil, fmt.Errorf("Unable to create certificate request: %v", err) } diff --git a/pkg/tls/tls.go b/pkg/tls/tls.go index bb640ad560..c91c9b922c 100644 --- a/pkg/tls/tls.go +++ b/pkg/tls/tls.go @@ -53,7 +53,7 @@ func certificateRequestToPem(csrRaw []byte) []byte { } //CertificateGenerateRequest Generates raw certificate signing request -func CertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificateProps, FQDNCN bool) (*certificates.CertificateSigningRequest, error) { +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 @@ -61,7 +61,7 @@ func CertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificate commonName := GenerateInClusterServiceName(props) dnsNames[2] = commonName csCommonName := props.Service - if FQDNCN { + if fqdncn { // use FQDN as CommonName as a workaournd for https://github.com/nirmata/kyverno/issues/542 csCommonName = commonName } From 7b3867650594b44ab05325461449006504e1f859 Mon Sep 17 00:00:00 2001 From: shivkumar dudhani <shivkumar@nirmata.com> Date: Thu, 9 Jan 2020 09:52:09 -0800 Subject: [PATCH 3/4] remove nonsupported flags from comments --- definitions/install.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/definitions/install.yaml b/definitions/install.yaml index 7c7d1fe95e..8e030a44cf 100644 --- a/definitions/install.yaml +++ b/definitions/install.yaml @@ -468,8 +468,6 @@ spec: - "--filterK8Resources=[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*]" # customize webhook timout # - "--webhooktimeout=4" - # open one of the profiling flag here - # - "--cpu=true" ports: - containerPort: 443 env: From 291c111b8c1a1021e64f6753f3ed796c4e40a1ad Mon Sep 17 00:00:00 2001 From: shivkumar dudhani <shivkumar@nirmata.com> Date: Thu, 9 Jan 2020 12:20:08 -0800 Subject: [PATCH 4/4] update documentation for fqdncn --- documentation/installation.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/documentation/installation.md b/documentation/installation.md index 9c470afca4..5c609f283f 100644 --- a/documentation/installation.md +++ b/documentation/installation.md @@ -10,6 +10,8 @@ There are 2 ways to configure the secure communications link between Kyverno and Kyverno can request a CA signed certificate-key pair from `kube-controller-manager`. This method requires that the kube-controller-manager is configured to act as a certificate signer. To verify that this option is enabled for your cluster, check the command-line args for the kube-controller-manager. If `--cluster-signing-cert-file` and `--cluster-signing-key-file` are passed to the controller manager with paths to your CA's key-pair, then you can proceed to install Kyverno using this method. +**Deploying on EKS requires enabling a command-line argument `--fqdncn` in the 'kyverno' container in the deployment, due to a current limitation with the certificates returned by EKS for CSR(bug: https://github.com/awslabs/amazon-eks-ami/issues/341)** + To install Kyverno in a cluster that supports certificate signing, run the following command on a host with kubectl `cluster-admin` access: ````sh @@ -130,11 +132,6 @@ To run controller in this mode you should prepare TLS key/certificate pair for d 2. Start the controller using the following command: `sudo kyverno --kubeconfig=~/.kube/config --serverIP=<server_IP>` -# Try Kyverno without a Kubernetes cluster - -The [Kyverno CLI](documentation/testing-policies.md#test-using-the-kyverno-cli) allows you to write and test policies without installing Kyverno in a Kubernetes cluster. Some features are not supported without a Kubernetes cluster. - - # Filter kuberenetes resources that admission webhook should not process The admission webhook checks if a policy is applicable on all admission requests. The kubernetes kinds that are not be processed can be filtered by adding the configmap named `init-config` in namespace `kyverno` and specifying the resources to be filtered under `data.resourceFilters` @@ -152,7 +149,8 @@ data: ``` By default we have specified Nodes, Events, APIService & SubjectAccessReview as the kinds to be skipped in the default configmap -[install.yaml](https://github.com/nirmata/kyverno/raw/master/definitions/init_configMap.yaml). +[install.yaml](https://github.com/nirmata/kyverno/raw/master/definitions/install.yaml). + --- <small>*Read Next >> [Writing Policies](/documentation/writing-policies.md)*</small>