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:
parent
b320b4b433
commit
9d5f77a941
5 changed files with 213 additions and 91 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
}
|
||||
|
|
|
@ -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
33
webhooks/utils.go
Normal 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 ""
|
||||
}
|
Loading…
Add table
Reference in a new issue