1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-08 10:04:25 +00:00

use single secret for tlsPair, check for secret annotation for self-signed cert & change type of secret from generic to tls

This commit is contained in:
shivdudhani 2019-05-29 12:36:03 -07:00
parent 88f3579b2c
commit 7ec9315e57
5 changed files with 68 additions and 153 deletions

View file

@ -3,13 +3,16 @@ package client
import (
"errors"
"fmt"
"net/url"
"time"
"github.com/nirmata/kyverno/pkg/config"
tls "github.com/nirmata/kyverno/pkg/tls"
certificates "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/rest"
)
// Issues TLS certificate for webhook server using given PEM private key
@ -114,47 +117,14 @@ func (c *Client) fetchCertificateFromRequest(req *certificates.CertificateSignin
return nil, errors.New(fmt.Sprintf("Cerificate fetch timeout is reached: %d seconds", maxWaitSeconds))
}
const (
ns string = "kyverno"
tlskeypair string = "tls.kyverno"
tlskeypaircert string = "tls.crt"
tlskeypairkey string = "tls.key"
tlsca string = "tls-ca"
tlscarootca string = "rootCA.crt"
)
// CheckPrePreqSelfSignedCert checks if the required secrets are defined
// if the user is providing self-signed certificates,key pair and CA
func (c *Client) CheckPrePreqSelfSignedCert() bool {
// Check if secrets are defined if user is specifiying self-signed certificates
tlspairfound := true
tlscafound := true
_, err := c.GetResource(Secrets, ns, tlskeypair)
func (c *Client) ReadRootCASecret() (result []byte) {
certProps, err := c.GetTLSCertProps(c.clientConfig)
if err != nil {
tlspairfound = false
utilruntime.HandleError(err)
return result
}
_, err = c.GetResource(Secrets, ns, tlsca)
if err != nil {
tlscafound = false
}
if tlspairfound == tlscafound {
return true
}
// Fail if only one of them is defined
c.logger.Printf("while using self-signed certificates specify both secrets %s/%s & %s/%s for (cert,key) pair & CA respectively", ns, tlskeypair, ns, tlsca)
if !tlspairfound {
c.logger.Printf("secret %s/%s not defined for (cert,key) pair", ns, tlskeypair)
}
if !tlscafound {
c.logger.Printf("secret %s/%s not defined for CA", ns, tlsca)
}
return false
}
func (c *Client) TlsrootCAfromSecret() (result []byte) {
stlsca, err := c.GetResource(Secrets, ns, tlsca)
sname := generateRootCASecretName(certProps)
stlsca, err := c.GetResource(Secrets, certProps.Namespace, sname)
if err != nil {
return result
}
@ -164,69 +134,52 @@ func (c *Client) TlsrootCAfromSecret() (result []byte) {
return result
}
result = tlsca.Data[tlscarootca]
result = tlsca.Data[rootCAKey]
if len(result) == 0 {
c.logger.Printf("root CA certificate not found in secret %s/%s", ns, tlsca.Name)
c.logger.Printf("root CA certificate not found in secret %s/%s", certProps.Namespace, tlsca.Name)
return result
}
c.logger.Printf("using CA bundle defined in secret %s/%s to validate the webhook's server certificate", ns, tlsca.Name)
c.logger.Printf("using CA bundle defined in secret %s/%s to validate the webhook's server certificate", certProps.Namespace, tlsca.Name)
return result
}
func (c *Client) TlsPairFromSecrets() *tls.TlsPemPair {
// Check if secrets are defined
stlskeypair, err := c.GetResource(Secrets, ns, tlskeypair)
if err != nil {
return nil
}
tlskeypair, err := convertToSecret(stlskeypair)
if err != nil {
utilruntime.HandleError(err)
return nil
}
pemPair := tls.TlsPemPair{
Certificate: tlskeypair.Data[tlskeypaircert],
PrivateKey: tlskeypair.Data[tlskeypairkey],
}
if len(pemPair.Certificate) == 0 {
c.logger.Printf("TLS Certificate not found in secret %s/%s", ns, tlskeypair.Name)
return nil
}
if len(pemPair.PrivateKey) == 0 {
c.logger.Printf("TLS PrivateKey not found in secret %s/%s", ns, tlskeypair.Name)
return nil
}
c.logger.Printf("using TLS pair defined in secret %s/%s for webhook's server tls configuration", ns, tlskeypair.Name)
return &pemPair
}
const privateKeyField string = "privateKey"
const certificateField string = "certificate"
const selfSignedAnnotation string = "self-signed-cert"
const rootCAKey string = "rootCA.crt"
// Reads the pair of TLS certificate and key from the specified secret.
func (c *Client) ReadTlsPair(props tls.TlsCertificateProps) *tls.TlsPemPair {
name := generateSecretName(props)
unstrSecret, err := c.GetResource(Secrets, props.Namespace, name)
sname := generateTLSPairSecretName(props)
unstrSecret, err := c.GetResource(Secrets, props.Namespace, sname)
if err != nil {
c.logger.Printf("Unable to get secret %s/%s: %s", props.Namespace, name, err)
c.logger.Printf("Unable to get secret %s/%s: %s", props.Namespace, sname, err)
return nil
}
// 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 {
utilruntime.HandleError(fmt.Errorf("Root CA secret %s/%s is required while using self-signed certificates TLS pair, defaulting to generating new TLS pair", props.Namespace, sname))
return nil
}
}
secret, err := convertToSecret(unstrSecret)
if err != nil {
return nil
}
pemPair := tls.TlsPemPair{
Certificate: secret.Data[certificateField],
PrivateKey: secret.Data[privateKeyField],
Certificate: secret.Data[v1.TLSCertKey],
PrivateKey: secret.Data[v1.TLSPrivateKeyKey],
}
if len(pemPair.Certificate) == 0 {
c.logger.Printf("TLS Certificate not found in secret %s/%s", props.Namespace, name)
c.logger.Printf("TLS Certificate not found in secret %s/%s", props.Namespace, sname)
return nil
}
if len(pemPair.PrivateKey) == 0 {
c.logger.Printf("TLS PrivateKey not found in secret %s/%s", props.Namespace, name)
c.logger.Printf("TLS PrivateKey not found in secret %s/%s", props.Namespace, sname)
return nil
}
return &pemPair
@ -235,7 +188,7 @@ func (c *Client) ReadTlsPair(props tls.TlsCertificateProps) *tls.TlsPemPair {
// 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 {
name := generateSecretName(props)
name := generateTLSPairSecretName(props)
_, err := c.GetResource(Secrets, props.Namespace, name)
if err != nil {
secret := &v1.Secret{
@ -248,9 +201,10 @@ func (c *Client) WriteTlsPair(props tls.TlsCertificateProps, pemPair *tls.TlsPem
Namespace: props.Namespace,
},
Data: map[string][]byte{
certificateField: pemPair.Certificate,
privateKeyField: pemPair.PrivateKey,
v1.TLSCertKey: pemPair.Certificate,
v1.TLSPrivateKeyKey: pemPair.PrivateKey,
},
Type: v1.SecretTypeTLS,
}
_, err := c.CreateResource(Secrets, props.Namespace, secret)
@ -264,8 +218,8 @@ func (c *Client) WriteTlsPair(props tls.TlsCertificateProps, pemPair *tls.TlsPem
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
secret.Data[certificateField] = pemPair.Certificate
secret.Data[privateKeyField] = pemPair.PrivateKey
secret.Data[v1.TLSCertKey] = pemPair.Certificate
secret.Data[v1.TLSPrivateKeyKey] = pemPair.PrivateKey
_, err = c.UpdateResource(Secrets, props.Namespace, secret)
if err != nil {
@ -275,6 +229,24 @@ func (c *Client) WriteTlsPair(props tls.TlsCertificateProps, pemPair *tls.TlsPem
return nil
}
func generateSecretName(props tls.TlsCertificateProps) string {
func generateTLSPairSecretName(props tls.TlsCertificateProps) string {
return tls.GenerateInClusterServiceName(props) + ".kyverno-tls-pair"
}
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
}

View file

@ -56,13 +56,14 @@ the following files are generated and are used to create kubernetes secrets:
To create the required secrets:
1. `kubectl create ns kyverno`
2. `kubectl -n kyverno create secret tls tls.kyverno --cert=webhook.crt --key=webhook.key `
3. `kubectl -n kyverno create secret generic tls-ca --from-file=rootCA.crt`
2. `kubectl -n kyverno create secret tls kyverno-svc.kyverno.svc.kyverno-tls-pair --cert=webhook.crt --key=webhook.key `
3. `kubectl annotate secret kyverno-svc.kyverno.svc.kyverno-tls-pair -n kyverno self-signed-cert=true`
4. `kubectl -n kyverno create secret generic kyverno-svc.kyverno.svc.kyverno-tls-ca --from-file=rootCA.crt`
Secret | Data | Content
------------ | ------------- | -------------
`tls.ca` | rootCA.crt | root CA used to sign the certificate
`tls.kyverno` | tls.key & tls.crt | key and signed certificate
`kyverno-svc.kyverno.svc.kyverno-tls-pair` | rootCA.crt | root CA used to sign the certificate
`kyverno-svc.kyverno.svc.kyverno-tls-ca` | tls.key & tls.crt | key and signed certificate
Kyverno uses secrets created above to define the TLS configuration for the webserver hook and specify the CA bundle used to validate the webhook's server certificate in the admission webhook configurations.

54
init.go
View file

@ -1,12 +1,9 @@
package main
import (
"io/ioutil"
"log"
"net/url"
client "github.com/nirmata/kyverno/client"
"github.com/nirmata/kyverno/pkg/config"
tls "github.com/nirmata/kyverno/pkg/tls"
rest "k8s.io/client-go/rest"
@ -22,60 +19,14 @@ func createClientConfig(kubeconfig string) (*rest.Config, error) {
return clientcmd.BuildConfigFromFlags("", kubeconfig)
}
func initTlsPemPair(certFile, keyFile string, clientConfig *rest.Config, client *client.Client) (*tls.TlsPemPair, error) {
var tlsPair *tls.TlsPemPair
if certFile != "" || keyFile != "" {
tlsPair = tlsPairFromFiles(certFile, keyFile)
}
// if cert & key defined in secret(tls.kyverno) use it,
// the CA used to sign the cert is expected in secret (tls-ca)
tlsPair = client.TlsPairFromSecrets()
var err error
if tlsPair != nil {
return tlsPair, nil
}
tlsPair, err = tlsPairFromCluster(clientConfig, client)
return tlsPair, err
}
// Loads PEM private key and TLS certificate from given files
func tlsPairFromFiles(certFile, keyFile string) *tls.TlsPemPair {
if certFile == "" || keyFile == "" {
return nil
}
certContent, err := ioutil.ReadFile(certFile)
if err != nil {
log.Printf("Unable to read file with TLS certificate: path - %s, error - %v", certFile, err)
return nil
}
keyContent, err := ioutil.ReadFile(keyFile)
if err != nil {
log.Printf("Unable to read file with TLS private key: path - %s, error - %v", keyFile, err)
return nil
}
return &tls.TlsPemPair{
Certificate: certContent,
PrivateKey: keyContent,
}
}
// 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 tlsPairFromCluster(configuration *rest.Config, client *client.Client) (*tls.TlsPemPair, error) {
apiServerURL, err := url.Parse(configuration.Host)
func initTlsPemPair(configuration *rest.Config, client *client.Client) (*tls.TlsPemPair, error) {
certProps, err := client.GetTLSCertProps(configuration)
if err != nil {
return nil, err
}
certProps := tls.TlsCertificateProps{
Service: config.WebhookServiceName,
Namespace: config.KubePolicyNamespace,
ApiServerHost: apiServerURL.Hostname(),
}
tlsPair := client.ReadTlsPair(certProps)
if tls.IsTlsPairShouldBeUpdated(tlsPair) {
log.Printf("Generating new key/certificate pair for TLS")
@ -88,6 +39,5 @@ func tlsPairFromCluster(configuration *rest.Config, client *client.Client) (*tls
log.Printf("Unable to save TLS pair to the cluster: %v", err)
}
}
return tlsPair, nil
}

10
main.go
View file

@ -15,8 +15,6 @@ import (
var (
kubeconfig string
cert string
key string
)
func main() {
@ -30,10 +28,6 @@ func main() {
log.Fatalf("Error creating client: %v\n", err)
}
if !client.CheckPrePreqSelfSignedCert() {
log.Fatalf("Error loading the pre-requisites\n")
}
policyInformerFactory, err := sharedinformer.NewSharedInformerFactory(clientConfig)
if err != nil {
log.Fatalf("Error creating policy sharedinformer: %v\n", err)
@ -48,7 +42,7 @@ func main() {
eventController,
nil)
tlsPair, err := initTlsPemPair(cert, key, clientConfig, client)
tlsPair, err := initTlsPemPair(clientConfig, client)
if err != nil {
log.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err)
}
@ -84,7 +78,5 @@ func main() {
func init() {
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
flag.StringVar(&cert, "cert", "", "TLS certificate used in connection with cluster.")
flag.StringVar(&key, "key", "", "Key, used in TLS connection.")
flag.Parse()
}

View file

@ -74,7 +74,7 @@ func (wrc *WebhookRegistrationClient) constructMutatingWebhookConfig(configurati
var caData []byte
// Check if ca is defined in the secret tls-ca
// assume the key and signed cert have been defined in secret tls.kyverno
caData = wrc.client.TlsrootCAfromSecret()
caData = wrc.client.ReadRootCASecret()
if len(caData) == 0 {
// load the CA from kubeconfig
caData = extractCA(configuration)
@ -103,7 +103,7 @@ func (wrc *WebhookRegistrationClient) constructMutatingWebhookConfig(configurati
func (wrc *WebhookRegistrationClient) constructValidatingWebhookConfig(configuration *rest.Config) (*admregapi.ValidatingWebhookConfiguration, error) {
// Check if ca is defined in the secret tls-ca
// assume the key and signed cert have been defined in secret tls.kyverno
caData := wrc.client.TlsrootCAfromSecret()
caData := wrc.client.ReadRootCASecret()
if len(caData) == 0 {
// load the CA from kubeconfig
caData = extractCA(configuration)