1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-29 02:45:06 +00:00

NK-23: Implemented generating of secrets and configmaps after namespace is created.

Functions for parsing metadata moved to utils.
Changed login of mutation webhook according to last changes.
This commit is contained in:
belyshevdenis 2019-03-06 13:01:17 +02:00
parent b320b4b433
commit 9d5f77a941
5 changed files with 213 additions and 91 deletions

View file

@ -3,10 +3,12 @@ package kubeclient
import (
"log"
"os"
"time"
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
@ -32,18 +34,113 @@ func NewKubeClient(config *rest.Config, logger *log.Logger) (*KubeClient, error)
}, nil
}
func (kc *KubeClient) CopySecret(from *types.PolicyCopyFrom, namespaceTo string) error {
// This is the test code, which works
var secret v1.Secret
secret.Namespace = namespaceTo
secret.ObjectMeta.SetName("test-secret")
secret.StringData = make(map[string]string)
secret.StringData["test-data"] = "test-value"
newSecret, err := kc.client.CoreV1().Secrets(namespaceTo).Create(&secret)
if err != nil {
kc.logger.Printf("Unable to create secret: %s", err)
} else {
kc.logger.Printf("Secret created: %s", newSecret.Name)
func (kc *KubeClient) GenerateConfigMap(generator types.PolicyConfigGenerator, namespace string) error {
kc.logger.Printf("Preparing to create configmap %s/%s", namespace, generator.Name)
configMap := &v1.ConfigMap{}
var err error
if generator.CopyFrom != nil {
kc.logger.Printf("Copying data from configmap %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name)
configMap, err = kc.client.CoreV1().ConfigMaps(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, defaultGetOptions())
if err != nil {
return err
}
}
configMap.ObjectMeta = metav1.ObjectMeta{
Name: generator.Name,
Namespace: namespace,
}
// Copy data from generator to the new configmap
if generator.Data != nil {
if configMap.Data == nil {
configMap.Data = make(map[string]string)
}
for k, v := range generator.Data {
configMap.Data[k] = v
}
}
go kc.createConfigMapAfterNamespaceIsCreated(*configMap, namespace)
return nil
}
func (kc *KubeClient) GenerateSecret(generator types.PolicyConfigGenerator, namespace string) error {
kc.logger.Printf("Preparing to create secret %s/%s", namespace, generator.Name)
secret := &v1.Secret{}
var err error
if generator.CopyFrom != nil {
kc.logger.Printf("Copying data from secret %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name)
secret, err = kc.client.CoreV1().Secrets(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, defaultGetOptions())
if err != nil {
return err
}
}
secret.ObjectMeta = metav1.ObjectMeta{
Name: generator.Name,
Namespace: namespace,
}
// Copy data from generator to the new secret
if generator.Data != nil {
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
for k, v := range generator.Data {
secret.Data[k] = []byte(v)
}
}
go kc.createSecretAfterNamespaceIsCreated(*secret, namespace)
return nil
}
func defaultGetOptions() metav1.GetOptions {
return metav1.GetOptions{
ResourceVersion: "1",
IncludeUninitialized: true,
}
}
const namespaceCreationMaxWaitTime time.Duration = 30 * time.Second
const namespaceCreationWaitInterval time.Duration = 100 * time.Millisecond
// Waits until namespace is created with maximum duration maxWaitTimeForNamespaceCreation
func (kc *KubeClient) waitUntilNamespaceIsCreated(name string) error {
timeStart := time.Now()
var lastError error = nil
for time.Now().Sub(timeStart) < namespaceCreationMaxWaitTime {
_, lastError = kc.client.CoreV1().Namespaces().Get(name, defaultGetOptions())
if lastError == nil {
break
}
time.Sleep(namespaceCreationWaitInterval)
}
return lastError
}
func (kc *KubeClient) createConfigMapAfterNamespaceIsCreated(configMap v1.ConfigMap, namespace string) {
err := kc.waitUntilNamespaceIsCreated(namespace)
if err == nil {
_, err = kc.client.CoreV1().ConfigMaps(namespace).Create(&configMap)
}
if err != nil {
kc.logger.Printf("Can't create a configmap: %s", err)
}
}
func (kc *KubeClient) createSecretAfterNamespaceIsCreated(secret v1.Secret, namespace string) {
err := kc.waitUntilNamespaceIsCreated(namespace)
if err == nil {
_, err = kc.client.CoreV1().Secrets(namespace).Create(&secret)
}
if err != nil {
kc.logger.Printf("Can't create a secret: %s", err)
}
return err
}

View file

@ -23,7 +23,6 @@ import (
type WebhookServer struct {
server http.Server
policyController *controller.PolicyController
kubeclient *kubeclient.KubeClient
mutationWebhook *webhooks.MutationWebhook
logger *log.Logger
}
@ -55,7 +54,7 @@ func NewWebhookServer(config WebhookServerConfig, logger *log.Logger) (*WebhookS
}
tlsConfig.Certificates = []tls.Certificate{pair}
mw, err := webhooks.NewMutationWebhook(logger)
mw, err := webhooks.NewMutationWebhook(logger, config.Kubeclient)
if err != nil {
return nil, err
}
@ -63,7 +62,6 @@ func NewWebhookServer(config WebhookServerConfig, logger *log.Logger) (*WebhookS
ws := &WebhookServer{
logger: logger,
policyController: config.Controller,
kubeclient: config.Kubeclient,
mutationWebhook: mw,
}

View file

@ -1,12 +1,9 @@
package webhooks
import (
"encoding/json"
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
"k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
var supportedKinds = [...]string{
@ -76,29 +73,3 @@ func IsRuleApplicableToRequest(policyResource types.PolicyResource, request *v1b
return true
}
func parseMetadataFromObject(bytes []byte) map[string]interface{} {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
return objectJSON["metadata"].(map[string]interface{})
}
func parseLabelsFromMetadata(meta map[string]interface{}) labels.Set {
if interfaceMap, ok := meta["labels"].(map[string]interface{}); ok {
labelMap := make(labels.Set, len(interfaceMap))
for key, value := range interfaceMap {
labelMap[key] = value.(string)
}
return labelMap
}
return nil
}
func parseNameFromMetadata(meta map[string]interface{}) string {
if name, ok := meta["name"].(string); ok {
return name
}
return ""
}

View file

@ -6,6 +6,7 @@ import (
"fmt"
"log"
kubeclient "github.com/nirmata/kube-policy/kubeclient"
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -14,16 +15,17 @@ import (
// MutationWebhook is a data type that represents
// buisness logic for resource mutation
type MutationWebhook struct {
logger *log.Logger
logger *log.Logger
kubeclient *kubeclient.KubeClient
}
// NewMutationWebhook is a method that returns new instance
// of MutationWebhook struct
func NewMutationWebhook(logger *log.Logger) (*MutationWebhook, error) {
func NewMutationWebhook(logger *log.Logger, kubeclient *kubeclient.KubeClient) (*MutationWebhook, error) {
if logger == nil {
return nil, errors.New("Logger must be set for the mutation webhook")
return nil, errors.New("Logger and/or KubeClient is not set")
}
return &MutationWebhook{logger: logger}, nil
return &MutationWebhook{logger: logger, kubeclient: kubeclient}, nil
}
// Mutate applies admission to request
@ -50,20 +52,15 @@ func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest, policies []
}
if IsRuleApplicableToRequest(rule.Resource, request) {
mw.logger.Printf("Applying policy %s, rule index = %s", policy.ObjectMeta.Name, ruleIdx)
rulePatches, err := mw.applyPolicyRule(request, rule)
/*
* If at least one error is detected in the rule, the entire rule will not be applied.
* This may be changed in the future by varying the policy.Spec.FailurePolicy values.
*/
if err != nil {
mw.logger.Printf("Error occurred while applying the policy: %v", err)
if stopOnError {
mw.logger.Printf("/!\\ Denying the request according to FailurePolicy spec /!\\")
return errorToResponse(err, false)
}
} else {
mw.logger.Printf("Prepared %v patches", len(rulePatches))
mw.logger.Printf("Applying policy %s, rule index = %d", policy.ObjectMeta.Name, ruleIdx)
rulePatches, err := mw.applyRule(request, rule, stopOnError)
// If at least one error is detected in the rule, the entire rule will not be applied:
// it can be changed in the future by varying the policy.Spec.FailurePolicy values.
if err != nil && stopOnError {
mw.logger.Printf("/!\\ Denying the request according to FailurePolicy spec /!\\")
return errorToResponse(err, false)
}
if rulePatches != nil {
allPatches = append(allPatches, rulePatches...)
}
}
@ -86,42 +83,68 @@ func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest, policies []
}
}
// Applies all possible patches in a rule
func (mw *MutationWebhook) applyPolicyRule(request *v1beta1.AdmissionRequest, rule types.PolicyRule) ([]types.PolicyPatch, error) {
var allPatches []types.PolicyPatch
allPatches = append(allPatches, rule.Patches...)
// configMapGenerator and secretGenerator can be applied only to namespaces
if request.Kind.Kind == "Namespace" {
if rule.ConfigMapGenerator != nil {
err := mw.applyConfigGenerator(request, *rule.ConfigMapGenerator, "ConfigMap")
if err != nil {
mw.logger.Printf("Unable to apply configMapGenerator: %s", err)
}
}
if rule.SecretGenerator != nil {
err := mw.applyConfigGenerator(request, *rule.SecretGenerator, "Secret")
if err != nil {
mw.logger.Printf("Unable to apply secretGenerator: %s", err)
}
}
// Applies all rule to the created object and returns list of JSON patches.
// May return nil patches if it is not necessary to create patches for requested object.
func (mw *MutationWebhook) applyRule(request *v1beta1.AdmissionRequest, rule types.PolicyRule, stopOnError bool) ([]types.PolicyPatch, error) {
rulePatches, err := mw.applyRulePatches(request, rule)
if err != nil {
mw.logger.Printf("Error occurred while applying patches according to the policy: %v", err)
} else {
mw.logger.Printf("Prepared %v patches", len(rulePatches))
}
return allPatches, nil
if err == nil || !stopOnError {
err = mw.applyRuleGenerators(request, rule)
}
return rulePatches, err
}
func (mw *MutationWebhook) applyRulePatches(request *v1beta1.AdmissionRequest, rule types.PolicyRule) ([]types.PolicyPatch, error) {
var patches []types.PolicyPatch
patches = append(patches, rule.Patches...)
return patches, nil
}
func (mw *MutationWebhook) applyRuleGenerators(request *v1beta1.AdmissionRequest, rule types.PolicyRule) error {
// configMapGenerator and secretGenerator can be applied only to namespaces
if request.Kind.Kind == "Namespace" {
meta := parseMetadataFromObject(request.Object.Raw)
namespaceName := parseNameFromMetadata(meta)
err := mw.applyConfigGenerator(rule.ConfigMapGenerator, namespaceName, "ConfigMap")
if err == nil {
err = mw.applyConfigGenerator(rule.SecretGenerator, namespaceName, "Secret")
}
return err
}
return nil
}
// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request
func (mw *MutationWebhook) applyConfigGenerator(request *v1beta1.AdmissionRequest, generator types.PolicyConfigGenerator, resourceKind string) error {
err := generator.Validate()
if err != nil {
return errors.New(fmt.Sprintf("Generator for %s is invalid: %s", resourceKind, err))
func (mw *MutationWebhook) applyConfigGenerator(generator *types.PolicyConfigGenerator, namespace string, configKind string) error {
if generator == nil {
return nil
}
if generator.CopyFrom != nil {
// TODO: Implement copying of the object to another namespace
err := generator.Validate()
if err != nil {
return errors.New(fmt.Sprintf("Generator for '%s' is invalid: %s", configKind, err))
}
// TODO: Implement filling of existing object with data from generator
switch configKind {
case "ConfigMap":
err = mw.kubeclient.GenerateConfigMap(*generator, namespace)
case "Secret":
err = mw.kubeclient.GenerateSecret(*generator, namespace)
default:
err = errors.New(fmt.Sprintf("Unsupported config Kind '%s'", configKind))
}
if err != nil {
return errors.New(fmt.Sprintf("Unable to apply generator for %s '%s/%s' : %s", configKind, namespace, generator.Name, err))
}
return nil
}

33
webhooks/utils.go Normal file
View file

@ -0,0 +1,33 @@
package webhooks
import (
"encoding/json"
"k8s.io/apimachinery/pkg/labels"
)
func parseMetadataFromObject(bytes []byte) map[string]interface{} {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
return objectJSON["metadata"].(map[string]interface{})
}
func parseLabelsFromMetadata(meta map[string]interface{}) labels.Set {
if interfaceMap, ok := meta["labels"].(map[string]interface{}); ok {
labelMap := make(labels.Set, len(interfaceMap))
for key, value := range interfaceMap {
labelMap[key] = value.(string)
}
return labelMap
}
return nil
}
func parseNameFromMetadata(meta map[string]interface{}) string {
if name, ok := meta["name"].(string); ok {
return name
}
return ""
}