mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
Merge branch 'master' of https://github.com/nirmata/kube-policy
This commit is contained in:
commit
a9843b2f55
14 changed files with 756 additions and 87 deletions
17
config/config.go
Normal file
17
config/config.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
const (
|
||||||
|
// These constants MUST be equal to the corresponding names in service definition in definitions/install.yaml
|
||||||
|
WebhookServiceNamespace = "kube-system"
|
||||||
|
WebhookServiceName = "kube-policy-svc"
|
||||||
|
|
||||||
|
WebhookConfigName = "nirmata-kube-policy-webhook-cfg"
|
||||||
|
MutationWebhookName = "webhook.nirmata.kube-policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
WebhookServicePath = "/mutate"
|
||||||
|
WebhookConfigLabels = map[string]string {
|
||||||
|
"app": "kube-policy",
|
||||||
|
}
|
||||||
|
)
|
17
constants/constants.go
Normal file
17
constants/constants.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
// These constants MUST be equal to the corresponding names in service definition in definitions/install.yaml
|
||||||
|
WebhookServiceNamespace = "kube-system"
|
||||||
|
WebhookServiceName = "kube-policy-svc"
|
||||||
|
|
||||||
|
WebhookConfigName = "nirmata-kube-policy-webhook-cfg"
|
||||||
|
MutationWebhookName = "webhook.nirmata.kube-policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
WebhookServicePath = "/mutate"
|
||||||
|
WebhookConfigLabels = map[string]string {
|
||||||
|
"app": "kube-policy",
|
||||||
|
}
|
||||||
|
)
|
|
@ -145,3 +145,60 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
namespace: kube-system
|
||||||
|
name: kube-policy-svc
|
||||||
|
labels:
|
||||||
|
app: kube-policy
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
targetPort: 443
|
||||||
|
selector:
|
||||||
|
app: kube-policy
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: kube-policy-service-account
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: kube-policy-admin
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: cluster-admin
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: kube-policy-service-account
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
namespace: kube-system
|
||||||
|
name: kube-policy-deployment
|
||||||
|
labels:
|
||||||
|
app: kube-policy
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: kube-policy
|
||||||
|
spec:
|
||||||
|
serviceAccountName: kube-policy-service-account
|
||||||
|
containers:
|
||||||
|
- name: kube-policy
|
||||||
|
image: nirmata/kube-policy:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- containerPort: 443
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
|
76
init.go
Normal file
76
init.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/nirmata/kube-policy/config"
|
||||||
|
"github.com/nirmata/kube-policy/kubeclient"
|
||||||
|
"github.com/nirmata/kube-policy/utils"
|
||||||
|
|
||||||
|
rest "k8s.io/client-go/rest"
|
||||||
|
clientcmd "k8s.io/client-go/tools/clientcmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createClientConfig(kubeconfig string) (*rest.Config, error) {
|
||||||
|
if kubeconfig == "" {
|
||||||
|
log.Printf("Using in-cluster configuration")
|
||||||
|
return rest.InClusterConfig()
|
||||||
|
} else {
|
||||||
|
log.Printf("Using configuration from '%s'", kubeconfig)
|
||||||
|
return clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readTlsPairFromFiles(certFile, keyFile string) *utils.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 &utils.TlsPemPair{
|
||||||
|
Certificate: certContent,
|
||||||
|
PrivateKey: keyContent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loads or creates PEM private key and TLS certificate for webhook server
|
||||||
|
// Returns struct with key/certificate pair
|
||||||
|
func initTlsPemsPair(configuration *rest.Config, client *kubeclient.KubeClient) (*utils.TlsPemPair, error) {
|
||||||
|
apiServerUrl, err := url.Parse(configuration.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certProps := utils.TlsCertificateProps{
|
||||||
|
Service: config.WebhookServiceName,
|
||||||
|
Namespace: config.WebhookServiceNamespace,
|
||||||
|
ApiServerHost: apiServerUrl.Hostname(),
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsPair := client.ReadTlsPair(certProps)
|
||||||
|
if utils.IsTlsPairShouldBeUpdated(tlsPair) {
|
||||||
|
log.Printf("Generating new key/certificate pair for TLS")
|
||||||
|
tlsPair, err = client.GenerateTlsPemPair(certProps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = client.WriteTlsPair(certProps, tlsPair)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to save TLS pair to the cluster: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tlsPair, nil
|
||||||
|
}
|
181
kubeclient/certificates.go
Normal file
181
kubeclient/certificates.go
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
package kubeclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nirmata/kube-policy/utils"
|
||||||
|
|
||||||
|
certificates "k8s.io/api/certificates/v1beta1"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Issues TLS certificate for webhook server using given PEM private key
|
||||||
|
// Returns signed and approved TLS certificate in PEM format
|
||||||
|
func (kc *KubeClient) GenerateTlsPemPair(props utils.TlsCertificateProps) (*utils.TlsPemPair, error) {
|
||||||
|
privateKey, err := utils.TlsGeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
certRequest, err := utils.TlsCertificateGenerateRequest(privateKey, props)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Unable to create certificate request: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
certRequest, err = kc.submitAndApproveCertificateRequest(certRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Unable to submit and approve certificate request: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsCert, err := kc.fetchCertificateFromRequest(certRequest, 10)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Unable to fetch certificate from request: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &utils.TlsPemPair{
|
||||||
|
Certificate: tlsCert,
|
||||||
|
PrivateKey: utils.TlsPrivateKeyToPem(privateKey),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submits and approves certificate request, returns request which need to be fetched
|
||||||
|
func (kc *KubeClient) submitAndApproveCertificateRequest(req *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, error) {
|
||||||
|
certClient := kc.client.CertificatesV1beta1().CertificateSigningRequests()
|
||||||
|
|
||||||
|
csrList, err := certClient.List(metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Unable to list existing certificate requests: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, csr := range csrList.Items {
|
||||||
|
if csr.ObjectMeta.Name == req.ObjectMeta.Name {
|
||||||
|
err := certClient.Delete(csr.ObjectMeta.Name, defaultDeleteOptions())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Unable to delete existing certificate request: %v", err))
|
||||||
|
}
|
||||||
|
kc.logger.Printf("Old certificate request is deleted")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := certClient.Create(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kc.logger.Printf("Certificate request %s is created", req.ObjectMeta.Name)
|
||||||
|
|
||||||
|
res.Status.Conditions = append(res.Status.Conditions, certificates.CertificateSigningRequestCondition{
|
||||||
|
Type: certificates.CertificateApproved,
|
||||||
|
Reason: "NKP-Approve",
|
||||||
|
Message: "This CSR was approved by Nirmata kube-policy controller",
|
||||||
|
})
|
||||||
|
res, err = certClient.UpdateApproval(res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Unable to approve certificate request: %v", err))
|
||||||
|
}
|
||||||
|
kc.logger.Printf("Certificate request %s is approved", res.ObjectMeta.Name)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const certificateFetchWaitInterval time.Duration = 200 * time.Millisecond
|
||||||
|
|
||||||
|
// Fetches certificate from given request. Tries to obtain certificate for maxWaitSeconds
|
||||||
|
func (kc *KubeClient) fetchCertificateFromRequest(req *certificates.CertificateSigningRequest, maxWaitSeconds uint8) ([]byte, error) {
|
||||||
|
// TODO: react of SIGINT and SIGTERM
|
||||||
|
timeStart := time.Now()
|
||||||
|
certClient := kc.client.CertificatesV1beta1().CertificateSigningRequests()
|
||||||
|
for time.Now().Sub(timeStart) < time.Duration(maxWaitSeconds)*time.Second {
|
||||||
|
r, err := certClient.Get(req.ObjectMeta.Name, defaultGetOptions())
|
||||||
|
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, errors.New(fmt.Sprintf("Cerificate fetch timeout is reached: %d seconds", maxWaitSeconds))
|
||||||
|
}
|
||||||
|
|
||||||
|
const privateKeyField string = "privateKey"
|
||||||
|
const certificateField string = "certificate"
|
||||||
|
|
||||||
|
// Reads the pair of TLS certificate and key from the specified secret.
|
||||||
|
func (kc *KubeClient) ReadTlsPair(props utils.TlsCertificateProps) *utils.TlsPemPair {
|
||||||
|
name := generateSecretName(props)
|
||||||
|
secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, defaultGetOptions())
|
||||||
|
if err != nil {
|
||||||
|
kc.logger.Printf("Unable to get secret %s/%s: %s", props.Namespace, name, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pemPair := utils.TlsPemPair{
|
||||||
|
Certificate: secret.Data[certificateField],
|
||||||
|
PrivateKey: secret.Data[privateKeyField],
|
||||||
|
}
|
||||||
|
if len(pemPair.Certificate) == 0 {
|
||||||
|
kc.logger.Printf("TLS Certificate not found in secret %s/%s", props.Namespace, name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(pemPair.PrivateKey) == 0 {
|
||||||
|
kc.logger.Printf("TLS PrivateKey not found in secret %s/%s", props.Namespace, name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &pemPair
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes the pair of TLS certificate and key to the specified secret.
|
||||||
|
// Updates existing secret or creates new one.
|
||||||
|
func (kc *KubeClient) WriteTlsPair(props utils.TlsCertificateProps, pemPair *utils.TlsPemPair) error {
|
||||||
|
name := generateSecretName(props)
|
||||||
|
secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, defaultGetOptions())
|
||||||
|
|
||||||
|
if err == nil { // Update existing secret
|
||||||
|
if secret.Data == nil {
|
||||||
|
secret.Data = make(map[string][]byte)
|
||||||
|
}
|
||||||
|
secret.Data[certificateField] = pemPair.Certificate
|
||||||
|
secret.Data[privateKeyField] = pemPair.PrivateKey
|
||||||
|
|
||||||
|
secret, err = kc.client.CoreV1().Secrets(props.Namespace).Update(secret)
|
||||||
|
if err == nil {
|
||||||
|
kc.logger.Printf("Secret %s is updated", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // Create new secret
|
||||||
|
secret = &v1.Secret{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "Secret",
|
||||||
|
APIVersion: "v1",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: props.Namespace,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
certificateField: pemPair.Certificate,
|
||||||
|
privateKeyField: pemPair.PrivateKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err = kc.client.CoreV1().Secrets(props.Namespace).Create(secret)
|
||||||
|
if err == nil {
|
||||||
|
kc.logger.Printf("Secret %s is created", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateSecretName(props utils.TlsCertificateProps) string {
|
||||||
|
return utils.GenerateInClusterServiceName(props) + ".kube-policy-tls-pair"
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ type KubeClient struct {
|
||||||
// Checks parameters and creates new instance of KubeClient
|
// Checks parameters and creates new instance of KubeClient
|
||||||
func NewKubeClient(config *rest.Config, logger *log.Logger) (*KubeClient, error) {
|
func NewKubeClient(config *rest.Config, logger *log.Logger) (*KubeClient, error) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
logger = log.New(os.Stdout, "Policy Controller: ", log.LstdFlags|log.Lshortfile)
|
logger = log.New(os.Stdout, "Kubernetes client: ", log.LstdFlags|log.Lshortfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := kubernetes.NewForConfig(config)
|
client, err := kubernetes.NewForConfig(config)
|
||||||
|
@ -113,6 +113,13 @@ func defaultGetOptions() metav1.GetOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func defaultDeleteOptions() *metav1.DeleteOptions {
|
||||||
|
var deletePeriod int64 = 0
|
||||||
|
return &metav1.DeleteOptions{
|
||||||
|
GracePeriodSeconds: &deletePeriod,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const namespaceCreationMaxWaitTime time.Duration = 30 * time.Second
|
const namespaceCreationMaxWaitTime time.Duration = 30 * time.Second
|
||||||
const namespaceCreationWaitInterval time.Duration = 100 * time.Millisecond
|
const namespaceCreationWaitInterval time.Duration = 100 * time.Millisecond
|
||||||
|
|
||||||
|
|
54
main.go
54
main.go
|
@ -2,51 +2,36 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/nirmata/kube-policy/controller"
|
"github.com/nirmata/kube-policy/controller"
|
||||||
"github.com/nirmata/kube-policy/kubeclient"
|
"github.com/nirmata/kube-policy/kubeclient"
|
||||||
|
"github.com/nirmata/kube-policy/webhooks"
|
||||||
"github.com/nirmata/kube-policy/server"
|
"github.com/nirmata/kube-policy/server"
|
||||||
|
|
||||||
rest "k8s.io/client-go/rest"
|
|
||||||
clientcmd "k8s.io/client-go/tools/clientcmd"
|
|
||||||
signals "k8s.io/sample-controller/pkg/signals"
|
signals "k8s.io/sample-controller/pkg/signals"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
masterURL string
|
|
||||||
kubeconfig string
|
kubeconfig string
|
||||||
cert string
|
cert string
|
||||||
key string
|
key string
|
||||||
)
|
)
|
||||||
|
|
||||||
func createClientConfig(masterURL, kubeconfig string) (*rest.Config, error) {
|
|
||||||
// TODO: make possible to create config within a cluster with proper rights
|
|
||||||
config, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
clientConfig, err := createClientConfig(kubeconfig)
|
||||||
|
|
||||||
if cert == "" || key == "" {
|
|
||||||
log.Fatal("TLS certificate or/and key is not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
clientConfig, err := createClientConfig(masterURL, kubeconfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error building kubeconfig: %v\n", err)
|
log.Fatalf("Error building kubeconfig: %v\n", err)
|
||||||
return
|
}
|
||||||
|
|
||||||
|
err = webhooks.RegisterMutationWebhook(clientConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error registering mutation webhook server: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
controller, err := controller.NewPolicyController(clientConfig, nil)
|
controller, err := controller.NewPolicyController(clientConfig, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error creating PolicyController! Error: %s\n", err)
|
log.Fatalf("Error creating PolicyController: %s\n", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeclient, err := kubeclient.NewKubeClient(clientConfig, nil)
|
kubeclient, err := kubeclient.NewKubeClient(clientConfig, nil)
|
||||||
|
@ -54,13 +39,21 @@ func main() {
|
||||||
log.Fatalf("Error creating kubeclient: %v\n", err)
|
log.Fatalf("Error creating kubeclient: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tlsPair := readTlsPairFromFiles(cert, key)
|
||||||
|
if tlsPair != nil {
|
||||||
|
log.Print("Using given TLS key/certificate pair")
|
||||||
|
} else {
|
||||||
|
tlsPair, err = initTlsPemsPair(clientConfig, kubeclient)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serverConfig := server.WebhookServerConfig{
|
serverConfig := server.WebhookServerConfig{
|
||||||
CertFile: cert,
|
TlsPemPair: tlsPair,
|
||||||
KeyFile: key,
|
|
||||||
Controller: controller,
|
Controller: controller,
|
||||||
Kubeclient: kubeclient,
|
Kubeclient: kubeclient,
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := server.NewWebhookServer(serverConfig, nil)
|
server, err := server.NewWebhookServer(serverConfig, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Unable to create webhook server: %v\n", err)
|
log.Fatalf("Unable to create webhook server: %v\n", err)
|
||||||
|
@ -71,19 +64,18 @@ func main() {
|
||||||
controller.Run(stopCh)
|
controller.Run(stopCh)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error running PolicyController! Error: %s\n", err)
|
log.Fatalf("Error running PolicyController: %s\n", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Policy Controller has started")
|
log.Println("Policy Controller has started")
|
||||||
<-stopCh
|
<-stopCh
|
||||||
server.Stop()
|
server.Stop()
|
||||||
fmt.Println("Policy Controller has stopped")
|
log.Println("Policy Controller has stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
|
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
|
||||||
flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
|
|
||||||
flag.StringVar(&cert, "cert", "", "TLS certificate used in connection with cluster.")
|
flag.StringVar(&cert, "cert", "", "TLS certificate used in connection with cluster.")
|
||||||
flag.StringVar(&key, "key", "", "Key, used in TLS connection.")
|
flag.StringVar(&key, "key", "", "Key, used in TLS connection.")
|
||||||
|
flag.Parse()
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,12 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
controller "github.com/nirmata/kube-policy/controller"
|
"github.com/nirmata/kube-policy/config"
|
||||||
kubeclient "github.com/nirmata/kube-policy/kubeclient"
|
"github.com/nirmata/kube-policy/controller"
|
||||||
webhooks "github.com/nirmata/kube-policy/webhooks"
|
"github.com/nirmata/kube-policy/kubeclient"
|
||||||
|
"github.com/nirmata/kube-policy/utils"
|
||||||
|
"github.com/nirmata/kube-policy/webhooks"
|
||||||
|
|
||||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,31 +33,30 @@ type WebhookServer struct {
|
||||||
// Configuration struct for WebhookServer used in NewWebhookServer
|
// Configuration struct for WebhookServer used in NewWebhookServer
|
||||||
// Controller and Kubeclient should be initialized and valid
|
// Controller and Kubeclient should be initialized and valid
|
||||||
type WebhookServerConfig struct {
|
type WebhookServerConfig struct {
|
||||||
CertFile string
|
TlsPemPair *utils.TlsPemPair
|
||||||
KeyFile string
|
|
||||||
Controller *controller.PolicyController
|
Controller *controller.PolicyController
|
||||||
Kubeclient *kubeclient.KubeClient
|
Kubeclient *kubeclient.KubeClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
|
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
|
||||||
// Policy Controller and Kubernetes Client should be initialized in configuration
|
// Policy Controller and Kubernetes Client should be initialized in configuration
|
||||||
func NewWebhookServer(config WebhookServerConfig, logger *log.Logger) (*WebhookServer, error) {
|
func NewWebhookServer(configuration WebhookServerConfig, logger *log.Logger) (*WebhookServer, error) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
logger = log.New(os.Stdout, "HTTPS Server: ", log.LstdFlags|log.Lshortfile)
|
logger = log.New(os.Stdout, "HTTPS Server: ", log.LstdFlags|log.Lshortfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Controller == nil || config.Kubeclient == nil {
|
if configuration.TlsPemPair == nil || configuration.Controller == nil || configuration.Kubeclient == nil {
|
||||||
return nil, errors.New("WebHook server requires initialized Policy Controller and Kubernetes Client")
|
return nil, errors.New("WebhookServerConfig is not initialized properly")
|
||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
pair, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
|
pair, err := tls.X509KeyPair(configuration.TlsPemPair.Certificate, configuration.TlsPemPair.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = []tls.Certificate{pair}
|
tlsConfig.Certificates = []tls.Certificate{pair}
|
||||||
|
|
||||||
mw, err := webhooks.NewMutationWebhook(config.Kubeclient, config.Controller, logger)
|
mw, err := webhooks.NewMutationWebhook(configuration.Kubeclient, configuration.Controller, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -65,7 +67,7 @@ func NewWebhookServer(config WebhookServerConfig, logger *log.Logger) (*WebhookS
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/mutate", ws.serve)
|
mux.HandleFunc(config.WebhookServicePath, ws.serve)
|
||||||
|
|
||||||
ws.server = http.Server{
|
ws.server = http.Server{
|
||||||
Addr: ":443", // Listen on port for HTTPS requests
|
Addr: ":443", // Listen on port for HTTPS requests
|
||||||
|
@ -81,7 +83,7 @@ func NewWebhookServer(config WebhookServerConfig, logger *log.Logger) (*WebhookS
|
||||||
|
|
||||||
// Main server endpoint for all requests
|
// Main server endpoint for all requests
|
||||||
func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
|
func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/mutate" {
|
if r.URL.Path == config.WebhookServicePath {
|
||||||
admissionReview := ws.parseAdmissionReview(r, w)
|
admissionReview := ws.parseAdmissionReview(r, w)
|
||||||
if admissionReview == nil {
|
if admissionReview == nil {
|
||||||
return
|
return
|
||||||
|
|
141
utils/certificates_utils.go
Normal file
141
utils/certificates_utils.go
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
certificates "k8s.io/api/certificates/v1beta1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Properties of TLS certificate which should be issued for webhook server
|
||||||
|
type TlsCertificateProps struct {
|
||||||
|
Service string
|
||||||
|
Namespace string
|
||||||
|
ApiServerHost string
|
||||||
|
}
|
||||||
|
|
||||||
|
// The pair of TLS certificate corresponding private key, both in PEM format
|
||||||
|
type TlsPemPair struct {
|
||||||
|
Certificate []byte
|
||||||
|
PrivateKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates RSA private key
|
||||||
|
func TlsGeneratePrivateKey() (*rsa.PrivateKey, error) {
|
||||||
|
return rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates PEM block from private key object
|
||||||
|
func TlsPrivateKeyToPem(rsaKey *rsa.PrivateKey) []byte {
|
||||||
|
privateKey := &pem.Block{
|
||||||
|
Type: "PRIVATE KEY",
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
return pem.EncodeToMemory(privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates PEM block from raw certificate request
|
||||||
|
func TlsCertificateRequestToPem(csrRaw []byte) []byte {
|
||||||
|
csrBlock := &pem.Block{
|
||||||
|
Type: "CERTIFICATE REQUEST",
|
||||||
|
Bytes: csrRaw,
|
||||||
|
}
|
||||||
|
|
||||||
|
return pem.EncodeToMemory(csrBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates raw certificate signing request
|
||||||
|
func TlsCertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificateProps) (*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
|
||||||
|
|
||||||
|
var ips []net.IP
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||||
|
DNSNames: dnsNames,
|
||||||
|
IPAddresses: ips,
|
||||||
|
}
|
||||||
|
|
||||||
|
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 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: TlsCertificateRequestToPem(csrBytes),
|
||||||
|
Groups: []string{"system:masters", "system:authenticated"},
|
||||||
|
Usages: []certificates.KeyUsage{
|
||||||
|
certificates.UsageDigitalSignature,
|
||||||
|
certificates.UsageKeyEncipherment,
|
||||||
|
certificates.UsageServerAuth,
|
||||||
|
certificates.UsageClientAuth,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The generated service name should be the common name for TLS certificate
|
||||||
|
func GenerateInClusterServiceName(props TlsCertificateProps) string {
|
||||||
|
return props.Service + "." + props.Namespace + ".svc"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Gets NotAfter property from raw certificate
|
||||||
|
func TlsCertificateGetExpirationDate(certData []byte) (*time.Time, error) {
|
||||||
|
block, _ := pem.Decode(certData)
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("Failed to decode PEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Failed to parse certificate: %v" + err.Error())
|
||||||
|
}
|
||||||
|
return &cert.NotAfter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The certificate is valid for a year, but we update it earlier to avoid using
|
||||||
|
// 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
|
||||||
|
|
||||||
|
func IsTlsPairShouldBeUpdated(tlsPair *TlsPemPair) bool {
|
||||||
|
if tlsPair == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
expirationDate, err := TlsCertificateGetExpirationDate(tlsPair.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return expirationDate.Sub(time.Now()) < timeReserveBeforeCertificateExpiration
|
||||||
|
}
|
|
@ -41,42 +41,32 @@ func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest) *v1beta1.Ad
|
||||||
|
|
||||||
var allPatches []PatchBytes
|
var allPatches []PatchBytes
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
patchingSets := getPolicyPatchingSets(policy)
|
mw.logger.Printf("Applying policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules))
|
||||||
|
|
||||||
for ruleIdx, rule := range policy.Spec.Rules {
|
policyPatches, err := mw.applyPolicyRules(request, policy)
|
||||||
err := rule.Validate()
|
if err != nil {
|
||||||
if err != nil {
|
mw.controller.LogPolicyError(policy.Name, err.Error())
|
||||||
mw.logger.Printf("Invalid rule detected: #%d in policy %s", ruleIdx, policy.ObjectMeta.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mw.logger.Printf("Applying policy %s, rule #%d", policy.ObjectMeta.Name, ruleIdx)
|
errStr := fmt.Sprintf("Unable to apply policy %s: %v", policy.Name, err)
|
||||||
rulePatches, err := mw.applyRule(request, rule, patchingSets)
|
mw.logger.Printf("Denying the request because of error: %s", errStr)
|
||||||
if err != nil {
|
return mw.denyResourceCreation(errStr)
|
||||||
return mw.denyResourceCreation(policy.Name, fmt.Sprintf("Unable to apply rule #%d: %s", ruleIdx, err))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
rulePatchesProcessed, err := ProcessPatches(rulePatches, request.Object.Raw, patchingSets)
|
if len(policyPatches) > 0 {
|
||||||
if err != nil {
|
meta := parseMetadataFromObject(request.Object.Raw)
|
||||||
return mw.denyResourceCreation(policy.Name, fmt.Sprintf("Unable to apply rule #%d: %s", ruleIdx, err))
|
namespace := parseNamespaceFromMetadata(meta)
|
||||||
}
|
name := parseNameFromMetadata(meta)
|
||||||
|
mw.controller.LogPolicyInfo(policy.Name, fmt.Sprintf("Applied to %s %s/%s", request.Kind.Kind, namespace, name))
|
||||||
|
|
||||||
if rulePatches != nil {
|
allPatches = append(allPatches, policyPatches...)
|
||||||
allPatches = append(allPatches, rulePatchesProcessed...)
|
|
||||||
mw.logger.Printf("Prepared %d patches", len(rulePatchesProcessed))
|
|
||||||
} else {
|
|
||||||
mw.logger.Print("No patches prepared")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patchType := v1beta1.PatchTypeJSONPatch
|
||||||
return &v1beta1.AdmissionResponse{
|
return &v1beta1.AdmissionResponse{
|
||||||
Allowed: true,
|
Allowed: true,
|
||||||
Patch: JoinPatches(allPatches),
|
Patch: JoinPatches(allPatches),
|
||||||
PatchType: func() *v1beta1.PatchType {
|
PatchType: &patchType,
|
||||||
pt := v1beta1.PatchTypeJSONPatch
|
|
||||||
return &pt
|
|
||||||
}(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,20 +79,43 @@ func getPolicyPatchingSets(policy types.Policy) PatchingSets {
|
||||||
return PatchingSetsDefault
|
return PatchingSetsDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applies all rule to the created object and returns list of JSON patches.
|
// Applies all policy rules to the created object and returns list of processed JSON patches.
|
||||||
// May return nil patches if it is not necessary to create patches for requested object.
|
// May return nil patches if it is not necessary to create patches for requested object.
|
||||||
// Returns error ONLY in case when creation of resource should be denied.
|
// Returns error ONLY in case when creation of resource should be denied.
|
||||||
func (mw *MutationWebhook) applyRule(request *v1beta1.AdmissionRequest, rule types.PolicyRule, errorBehavior PatchingSets) ([]types.PolicyPatch, error) {
|
func (mw *MutationWebhook) applyPolicyRules(request *v1beta1.AdmissionRequest, policy types.Policy) ([]PatchBytes, error) {
|
||||||
if !IsRuleApplicableToRequest(rule.Resource, request) {
|
patchingSets := getPolicyPatchingSets(policy)
|
||||||
return nil, nil
|
var policyPatches []PatchBytes
|
||||||
|
|
||||||
|
for ruleIdx, rule := range policy.Spec.Rules {
|
||||||
|
err := rule.Validate()
|
||||||
|
if err != nil {
|
||||||
|
mw.logger.Printf("Invalid rule detected: #%d in policy %s", ruleIdx, policy.ObjectMeta.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsRuleApplicableToRequest(rule.Resource, request) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mw.applyRuleGenerators(request, rule)
|
||||||
|
if err != nil && patchingSets == PatchingSetsStopOnError {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Failed to apply generators from rule #%d: %s", ruleIdx, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
rulePatchesProcessed, err := ProcessPatches(rule.Patches, request.Object.Raw, patchingSets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Failed to process patches from rule #%d: %s", ruleIdx, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if rulePatchesProcessed != nil {
|
||||||
|
policyPatches = append(policyPatches, rulePatchesProcessed...)
|
||||||
|
mw.logger.Printf("Rule %d: prepared %d patches", ruleIdx, len(rulePatchesProcessed))
|
||||||
|
} else {
|
||||||
|
mw.logger.Print("Rule %d: no patches prepared")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := mw.applyRuleGenerators(request, rule)
|
return policyPatches, nil
|
||||||
if err != nil && errorBehavior == PatchingSetsStopOnError {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return rule.Patches, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
|
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
|
||||||
|
@ -121,7 +134,7 @@ func (mw *MutationWebhook) applyRuleGenerators(request *v1beta1.AdmissionRequest
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request
|
// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request.
|
||||||
func (mw *MutationWebhook) applyConfigGenerator(generator *types.PolicyConfigGenerator, namespace string, configKind string) error {
|
func (mw *MutationWebhook) applyConfigGenerator(generator *types.PolicyConfigGenerator, namespace string, configKind string) error {
|
||||||
if generator == nil {
|
if generator == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -149,10 +162,7 @@ func (mw *MutationWebhook) applyConfigGenerator(generator *types.PolicyConfigGen
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forms AdmissionResponse with denial of resource creation and error message
|
// Forms AdmissionResponse with denial of resource creation and error message
|
||||||
func (mw *MutationWebhook) denyResourceCreation(policyName, errStr string) *v1beta1.AdmissionResponse {
|
func (mw *MutationWebhook) denyResourceCreation(errStr string) *v1beta1.AdmissionResponse {
|
||||||
mw.logger.Printf("Denying the request because of error: %s", errStr)
|
|
||||||
mw.controller.LogPolicyError(policyName, errStr)
|
|
||||||
|
|
||||||
return &v1beta1.AdmissionResponse{
|
return &v1beta1.AdmissionResponse{
|
||||||
Result: &metav1.Status{
|
Result: &metav1.Status{
|
||||||
Message: errStr,
|
Message: errStr,
|
||||||
|
|
82
webhooks/registration.go
Normal file
82
webhooks/registration.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package webhooks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/nirmata/kube-policy/config"
|
||||||
|
|
||||||
|
adm "k8s.io/api/admissionregistration/v1beta1"
|
||||||
|
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
admreg "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1"
|
||||||
|
rest "k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterMutationWebhook(config *rest.Config) error {
|
||||||
|
registrationClient, err := admreg.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = registrationClient.MutatingWebhookConfigurations().Create(constructWebhookConfig(config))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func constructWebhookConfig(configuration *rest.Config) *adm.MutatingWebhookConfiguration {
|
||||||
|
return &adm.MutatingWebhookConfiguration{
|
||||||
|
ObjectMeta: meta.ObjectMeta{
|
||||||
|
Name: config.WebhookConfigName,
|
||||||
|
Labels: config.WebhookConfigLabels,
|
||||||
|
},
|
||||||
|
Webhooks: []adm.Webhook{
|
||||||
|
adm.Webhook{
|
||||||
|
Name: config.MutationWebhookName,
|
||||||
|
ClientConfig: adm.WebhookClientConfig{
|
||||||
|
Service: &adm.ServiceReference{
|
||||||
|
Namespace: config.WebhookServiceNamespace,
|
||||||
|
Name: config.WebhookServiceName,
|
||||||
|
Path: &config.WebhookServicePath,
|
||||||
|
},
|
||||||
|
CABundle: ExtractCA(configuration),
|
||||||
|
},
|
||||||
|
Rules: []adm.RuleWithOperations{
|
||||||
|
adm.RuleWithOperations{
|
||||||
|
Operations: []adm.OperationType{
|
||||||
|
adm.Create,
|
||||||
|
},
|
||||||
|
Rule: adm.Rule{
|
||||||
|
APIGroups: []string{
|
||||||
|
"*",
|
||||||
|
},
|
||||||
|
APIVersions: []string{
|
||||||
|
"*",
|
||||||
|
},
|
||||||
|
Resources: []string{
|
||||||
|
"*/*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractCA(config *rest.Config) (result []byte) {
|
||||||
|
fileName := config.TLSClientConfig.CAFile
|
||||||
|
|
||||||
|
if fileName != "" {
|
||||||
|
result, err := ioutil.ReadFile(fileName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
return config.TLSClientConfig.CAData
|
||||||
|
}
|
||||||
|
}
|
66
webhooks/registration_test.go
Normal file
66
webhooks/registration_test.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package webhooks_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gotest.tools/assert"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/nirmata/kube-policy/webhooks"
|
||||||
|
|
||||||
|
rest "k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExtractCA_EmptyBundle(t *testing.T) {
|
||||||
|
CAFile := "resources/CAFile"
|
||||||
|
|
||||||
|
config := &rest.Config {
|
||||||
|
TLSClientConfig: rest.TLSClientConfig {
|
||||||
|
CAData: nil,
|
||||||
|
CAFile: CAFile,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected, err := ioutil.ReadFile(CAFile)
|
||||||
|
assert.Assert(t, err == nil)
|
||||||
|
actual := webhooks.ExtractCA(config)
|
||||||
|
assert.Assert(t, bytes.Equal(expected, actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractCA_EmptyCAFile(t *testing.T) {
|
||||||
|
CABundle := []byte(`LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1ETXhPVEUwTURjd05Gb1hEVEk1TURNeE5qRTBNRGN3TkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTStQClVLVmExcm9tQndOZzdqNnBBSGo5TDQ4RVJpdEplRzRXM1pUYmNMNWNKbnVTQmFsc1h1TWpQTGZmbUV1VEZIdVAKenRqUlBEUHcreEg1d3VTWFF2U0tIaXF2VE1pUm9DSlJFa09sQXpIa1dQM0VrdnUzNzRqZDVGV3Q3NEhnRk91cApIZ1ZwdUxPblczK2NDVE5iQ3VkeDFMVldRbGgwQzJKbm1Lam5uS1YrTkxzNFJVaVk1dk91ekpuNHl6QldLRjM2CmJLZ3ZDOVpMWlFSM3dZcnJNZWllYzBnWVY2VlJtaGgxSjRDV3V1UWd0ckM2d2NJanFWZFdEUlJyNHFMdEtDcDIKQVNIZmNieitwcEdHblJ5Z2FzcWNJdnpiNUVwV3NIRGtHRStUUW5WQ0JmTmsxN0NEOTZBQ1pmRWVybzEvWE16MgpRbzZvcUE0dnF5ZkdWWVU5RVZFQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFNWFVpUVJpdUc4cGdzcHMrZTdGZWdCdEJOZEcKZlFUdHVLRWFUZ0U0RjQwamJ3UmdrN25DTHlsSHgvRG04aVRRQmsyWjR4WnNuY0huRys4SkwrckRLdlJBSE5iVQpsYnpReXA1V3FwdjdPcThwZ01wU0o5bTdVY3BGZmRVZkorNW43aXFnTGdMb3lhNmtRVTR2Rk0yTE1rWjI5NVpxCmVId0hnREo5Z3IwWGNyOWM1L2tRdkxFc2Z2WU5QZVhuamNyWXlDb2JNcVduSElxeVd3cHM1VTJOaGgraXhSZEIKbzRRL3RJS04xOU93WGZBaVc5SENhNzZMb3ZXaUhPU2UxVnFzK1h1N1A5ckx4eW1vQm91aFcxVmZ0bUo5Qy9vTAp3cFVuNnlXRCttY0tkZ3J5QTFjTWJ4Q281bUd6YTNLaFk1QTd5eDQ1cThkSEIzTWU4d0FCam1wWEs0ST0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=`)
|
||||||
|
|
||||||
|
config := &rest.Config {
|
||||||
|
TLSClientConfig: rest.TLSClientConfig {
|
||||||
|
CAData: CABundle,
|
||||||
|
CAFile: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := webhooks.ExtractCA(config)
|
||||||
|
assert.Assert(t, bytes.Equal(CABundle, actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractCA_EmptyConfig(t *testing.T) {
|
||||||
|
config := &rest.Config {
|
||||||
|
TLSClientConfig: rest.TLSClientConfig {
|
||||||
|
CAData: nil,
|
||||||
|
CAFile: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := webhooks.ExtractCA(config)
|
||||||
|
assert.Assert(t, actual == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractCA_InvalidFile(t *testing.T) {
|
||||||
|
config := &rest.Config {
|
||||||
|
TLSClientConfig: rest.TLSClientConfig {
|
||||||
|
CAData: nil,
|
||||||
|
CAFile: "somenonexistingfile",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := webhooks.ExtractCA(config)
|
||||||
|
assert.Assert(t, actual == nil)
|
||||||
|
}
|
14
webhooks/resources/CAFile
Normal file
14
webhooks/resources/CAFile
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
V2VsY29tZSB0byBUaGUgUnVzdCBQcm9ncmFtbWluZyBMYW5ndWFnZSwgY
|
||||||
|
W4gaW50cm9kdWN0b3J5IGJvb2sgYWJvdXQgUnVzdC4gVGhlIFJ1c3QgcH
|
||||||
|
JvZ3JhbW1pbmcgbGFuZ3VhZ2UgaGVscHMgeW91IHdyaXRlIGZhc3Rlciw
|
||||||
|
gbW9yZSByZWxpYWJsZSBzb2Z0d2FyZS4gSGlnaC1sZXZlbCBlcmdvbm9t
|
||||||
|
aWNzIGFuZCBsb3ctbGV2ZWwgY29udHJvbCBhcmUgb2Z0ZW4gYXQgb2Rkc
|
||||||
|
yBpbiBwcm9ncmFtbWluZyBsYW5ndWFnZSBkZXNpZ247IFJ1c3QgY2hhbG
|
||||||
|
xlbmdlcyB0aGF0IGNvbmZsaWN0LiBUaHJvdWdoIGJhbGFuY2luZyBwb3d
|
||||||
|
lcmZ1bCB0ZWNobmljYWwgY2FwYWNpdHkgYW5kIGEgZ3JlYXQgZGV2ZWxv
|
||||||
|
cGVyIGV4cGVyaWVuY2UsIFJ1c3QgZ2l2ZXMgeW91IHRoZSBvcHRpb24gd
|
||||||
|
G8gY29udHJvbCBsb3ctbGV2ZWwgZGV0YWlscyAoc3VjaCBhcyBtZW1vcn
|
||||||
|
kgdXNhZ2UpIHdpdGhvdXQgYWxsIHRoZSBoYXNzbGUgdHJhZGl0aW9uYWx
|
||||||
|
seSBhc3NvY2lhdGVkIHdpdGggc3VjaCBjb250cm9sLgyzmqp31l8rqr1==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -31,3 +31,10 @@ func parseNameFromMetadata(meta map[string]interface{}) string {
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseNamespaceFromMetadata(meta map[string]interface{}) string {
|
||||||
|
if namespace, ok := meta["namespace"].(string); ok {
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue