mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-29 10:55:05 +00:00
Merge branch 'develop' into 26-PolicyUseCasesForTesting
This commit is contained in:
commit
3416e2fccb
26 changed files with 747 additions and 326 deletions
|
@ -31,3 +31,7 @@ required = ["k8s.io/code-generator/cmd/client-gen"]
|
|||
[[override]]
|
||||
name = "github.com/golang/protobuf"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/yaml.v2"
|
||||
version = "2.2.2"
|
||||
|
|
1
cmd/build
Executable file
1
cmd/build
Executable file
|
@ -0,0 +1 @@
|
|||
go build -o kyverno
|
17
cmd/kyverno.go
Normal file
17
cmd/kyverno.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
kyverno "github.com/nirmata/kube-policy/pkg/kyverno"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd := kyverno.NewDefaultKyvernoCommand()
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -29,14 +29,14 @@ spec:
|
|||
required:
|
||||
- name
|
||||
- resource
|
||||
parameters:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
resource:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
parameters:
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
|
@ -115,36 +115,33 @@ spec:
|
|||
pattern:
|
||||
AnyValue: {}
|
||||
generate:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
- copyFrom
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
copyFrom:
|
||||
type: object
|
||||
required:
|
||||
- namespace
|
||||
- name
|
||||
properties:
|
||||
namespace:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
data:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
copyFrom:
|
||||
type: object
|
||||
required:
|
||||
- namespace
|
||||
- name
|
||||
properties:
|
||||
namespace:
|
||||
type: string
|
||||
labels:
|
||||
type: object
|
||||
additionalProperties:
|
||||
name:
|
||||
type: string
|
||||
data:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
labels:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
|
30
examples/CLI/deployment/policy-deployment.yaml
Normal file
30
examples/CLI/deployment/policy-deployment.yaml
Normal file
|
@ -0,0 +1,30 @@
|
|||
apiVersion : kubepolicy.nirmata.io/v1alpha1
|
||||
kind : Policy
|
||||
metadata :
|
||||
name : policy-deployment
|
||||
spec :
|
||||
rules:
|
||||
- name: deployment-policy
|
||||
resource:
|
||||
kind : Deployment
|
||||
selector :
|
||||
matchLabels :
|
||||
cli: test
|
||||
mutate:
|
||||
patches:
|
||||
- path: /metadata/labels/isMutated
|
||||
op: add
|
||||
value: "true"
|
||||
- path: /metadata/labels/app
|
||||
op: replace
|
||||
value: "nginx_is_mutated"
|
||||
validate:
|
||||
message: "The imagePullPolicy shoud set to Always"
|
||||
pattern:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- (name): "*"
|
||||
imagePullPolicy: Always
|
||||
|
23
examples/CLI/deployment/resource/d1.yaml
Normal file
23
examples/CLI/deployment/resource/d1.yaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
cli: test
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.7.9
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
35
examples/CLI/deployment/resource/d2.yaml
Normal file
35
examples/CLI/deployment/resource/d2.yaml
Normal file
|
@ -0,0 +1,35 @@
|
|||
kind: "Deployment"
|
||||
apiVersion: "extensions/v1beta1"
|
||||
metadata:
|
||||
name: "ghost"
|
||||
labels:
|
||||
nirmata.io/deployment.name: "ghost"
|
||||
nirmata.io/application.name: "ghost"
|
||||
nirmata.io/component: "ghost"
|
||||
cli: test
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 5
|
||||
selector:
|
||||
matchLabels:
|
||||
nirmata.io/application.name: "ghost"
|
||||
nirmata.io/component: "ghost"
|
||||
strategy:
|
||||
type: "RollingUpdate"
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
nirmata.io/deployment.name: "ghost"
|
||||
nirmata.io/application.name: "ghost"
|
||||
nirmata.io/component: "ghost"
|
||||
spec:
|
||||
containers:
|
||||
- name: "ghost"
|
||||
image: "ghost:2.9.1-alpine"
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: "TCP"
|
2
init.go
2
init.go
|
@ -5,8 +5,8 @@ import (
|
|||
"log"
|
||||
"net/url"
|
||||
|
||||
"github.com/nirmata/kube-policy/config"
|
||||
"github.com/nirmata/kube-policy/kubeclient"
|
||||
"github.com/nirmata/kube-policy/pkg/config"
|
||||
"github.com/nirmata/kube-policy/pkg/tls"
|
||||
|
||||
rest "k8s.io/client-go/rest"
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/nirmata/kube-policy/config"
|
||||
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
"github.com/nirmata/kube-policy/pkg/config"
|
||||
apps "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -67,10 +67,12 @@ func (kc *KubeClient) GenerateConfigMap(generator types.Generation, namespace st
|
|||
|
||||
var err error
|
||||
|
||||
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, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
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, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
configMap.ObjectMeta = metav1.ObjectMeta{
|
||||
|
@ -101,10 +103,12 @@ func (kc *KubeClient) GenerateSecret(generator types.Generation, namespace strin
|
|||
|
||||
var err error
|
||||
|
||||
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, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
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, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
secret.ObjectMeta = metav1.ObjectMeta{
|
||||
|
|
2
main.go
2
main.go
|
@ -59,7 +59,7 @@ func main() {
|
|||
log.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err)
|
||||
}
|
||||
|
||||
server, err := webhooks.NewWebhookServer(tlsPair, policyInformer.Lister(), nil)
|
||||
server, err := webhooks.NewWebhookServer(tlsPair, policyInformer.Lister(), kubeclient, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create webhook server: %v\n", err)
|
||||
}
|
||||
|
|
|
@ -60,9 +60,9 @@ type Validation struct {
|
|||
|
||||
// Generation describes which resources will be created when other resource is created
|
||||
type Generation struct {
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
CopyFrom `json:"copyFrom"`
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
CopyFrom *CopyFrom `json:"copyFrom"`
|
||||
Data map[string]string `json:"data"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
||||
|
|
|
@ -64,21 +64,12 @@ func (pp *Patch) Validate() error {
|
|||
return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation)
|
||||
}
|
||||
|
||||
// Validate returns error if Name or namespace is not cpecified
|
||||
func (pcf *CopyFrom) Validate() error {
|
||||
if pcf.Name == "" || pcf.Namespace == "" {
|
||||
return errors.New("Name or/and Namespace is not specified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate returns error if generator is configured incompletely
|
||||
func (pcg *Generation) Validate() error {
|
||||
if pcg.Name == "" || pcg.Kind == "" {
|
||||
return errors.New("Name or/and Kind of generator is not specified")
|
||||
if len(pcg.Data) == 0 && pcg.CopyFrom == nil {
|
||||
return fmt.Errorf("Neither Data nor CopyFrom (source) of %s/%s is specified", pcg.Kind, pcg.Name)
|
||||
}
|
||||
|
||||
return pcg.CopyFrom.Validate()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is declared because k8s:deepcopy-gen is
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
kubeClient "github.com/nirmata/kube-policy/kubeclient"
|
||||
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
@ -14,58 +16,51 @@ type GenerationResponse struct {
|
|||
|
||||
// Generate should be called to process generate rules on the resource
|
||||
// TODO: extend kubeclient(will change to dynamic client) to create resources
|
||||
func Generate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) []GenerationResponse {
|
||||
func Generate(policy kubepolicy.Policy, rawResource []byte, kubeClient *kubeClient.KubeClient, gvk metav1.GroupVersionKind) {
|
||||
// configMapGenerator and secretGenerator can be applied only to namespaces
|
||||
// TODO: support for any resource
|
||||
if gvk.Kind != "Namespace" {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
var generateResps []GenerationResponse
|
||||
|
||||
for i, rule := range policy.Spec.Rules {
|
||||
|
||||
// Checks for preconditions
|
||||
// TODO: Rework PolicyEngine interface that it receives not a policy, but mutation object for
|
||||
// Mutate, validation for Validate and so on. It will allow to bring this checks outside of PolicyEngine
|
||||
// to common part as far as they present for all: mutation, validation, generation
|
||||
|
||||
err := rule.Validate()
|
||||
if err != nil {
|
||||
log.Printf("Rule has invalid structure: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
ok, err := ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk)
|
||||
if err != nil {
|
||||
log.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||
continue
|
||||
}
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
|
||||
|
||||
if !ok {
|
||||
log.Printf("Rule is not applicable to the request: rule name = %s in policy %s \n", rule.Name, policy.ObjectMeta.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
generateResps, err = applyRuleGenerator(rawResource, rule.Generation)
|
||||
err := applyRuleGenerator(rawResource, rule.Generation, kubeClient)
|
||||
if err != nil {
|
||||
log.Printf("Failed to apply rule generator: %v", err)
|
||||
} else {
|
||||
generateResps = append(generateResps, generateResps...)
|
||||
}
|
||||
}
|
||||
|
||||
return generateResps
|
||||
}
|
||||
|
||||
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
|
||||
// TODO: plan to support all kinds of generator
|
||||
func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation) ([]GenerationResponse, error) {
|
||||
var generationResponse []GenerationResponse
|
||||
func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation, kubeClient *kubeClient.KubeClient) error {
|
||||
if generator == nil {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
namespaceName := ParseNameFromObject(rawResource)
|
||||
generationResponse = append(generationResponse, GenerationResponse{generator, namespaceName})
|
||||
return generationResponse, nil
|
||||
var err error
|
||||
|
||||
namespace := ParseNameFromObject(rawResource)
|
||||
switch generator.Kind {
|
||||
case "ConfigMap":
|
||||
err = kubeClient.GenerateConfigMap(*generator, namespace)
|
||||
case "Secret":
|
||||
err = kubeClient.GenerateSecret(*generator, namespace)
|
||||
default:
|
||||
err = fmt.Errorf("Unsupported config Kind '%s'", generator.Kind)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to apply generator for %s '%s/%s' : %v", generator.Kind, namespace, generator.Name, err)
|
||||
}
|
||||
|
||||
log.Printf("Successfully applied generator %s/%s", generator.Kind, generator.Name)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,43 +10,29 @@ import (
|
|||
|
||||
// Mutate performs mutation. Overlay first and then mutation patches
|
||||
// TODO: return events and violations
|
||||
func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) []mutation.PatchBytes {
|
||||
func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]mutation.PatchBytes, []byte) {
|
||||
var policyPatches []mutation.PatchBytes
|
||||
var processedPatches []mutation.PatchBytes
|
||||
var err error
|
||||
patchedDocument := rawResource
|
||||
|
||||
for i, rule := range policy.Spec.Rules {
|
||||
|
||||
// Checks for preconditions
|
||||
// TODO: Rework PolicyEngine interface that it receives not a policy, but mutation object for
|
||||
// Mutate, validation for Validate and so on. It will allow to bring this checks outside of PolicyEngine
|
||||
// to common part as far as they present for all: mutation, validation, generation
|
||||
|
||||
err := rule.Validate()
|
||||
if err != nil {
|
||||
log.Printf("Rule has invalid structure: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
ok, err := ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk)
|
||||
if err != nil {
|
||||
log.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !ok {
|
||||
log.Printf("Rule is not applicable to the request: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if rule.Mutation == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
|
||||
if !ok {
|
||||
log.Printf("Rule \"%s\" is not applicable to resource\n", rule.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Process Overlay
|
||||
|
||||
if rule.Mutation.Overlay != nil {
|
||||
overlayPatches, err := mutation.ProcessOverlay(rule.Mutation.Overlay, rawResource)
|
||||
if err != nil {
|
||||
log.Printf("Overlay application failed: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||
log.Printf("Overlay application has failed for rule %s in policy %s, err: %v\n", rule.Name, policy.ObjectMeta.Name, err)
|
||||
} else {
|
||||
policyPatches = append(policyPatches, overlayPatches...)
|
||||
}
|
||||
|
@ -55,14 +41,14 @@ func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersio
|
|||
// Process Patches
|
||||
|
||||
if rule.Mutation.Patches != nil {
|
||||
processedPatches, err := mutation.ProcessPatches(rule.Mutation.Patches, rawResource)
|
||||
processedPatches, patchedDocument, err = mutation.ProcessPatches(rule.Mutation.Patches, patchedDocument)
|
||||
if err != nil {
|
||||
log.Printf("Patches application failed: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||
log.Printf("Patches application has failed for rule %s in policy %s, err: %v\n", rule.Name, policy.ObjectMeta.Name, err)
|
||||
} else {
|
||||
policyPatches = append(policyPatches, processedPatches...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return policyPatches
|
||||
return policyPatches, patchedDocument
|
||||
}
|
||||
|
|
|
@ -13,28 +13,32 @@ type PatchBytes []byte
|
|||
|
||||
// ProcessPatches Returns array from separate patches that can be applied to the document
|
||||
// Returns error ONLY in case when creation of resource should be denied.
|
||||
func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes, error) {
|
||||
func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes, []byte, error) {
|
||||
if len(resource) == 0 {
|
||||
return nil, errors.New("Source document for patching is empty")
|
||||
return nil, nil, errors.New("Source document for patching is empty")
|
||||
}
|
||||
|
||||
var appliedPatches []PatchBytes
|
||||
patchedDocument := resource
|
||||
for i, patch := range patches {
|
||||
patchRaw, err := json.Marshal(patch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
_, err = applyPatch(resource, patchRaw)
|
||||
patchedDocument, err = applyPatch(patchedDocument, patchRaw)
|
||||
if err != nil {
|
||||
// TODO: continue on error if one of the patches fails, will add the failure event in such case
|
||||
if patch.Operation == "remove" {
|
||||
continue
|
||||
}
|
||||
log.Printf("Patch failed: patch number = %d, patch Operation = %s, err: %v", i, patch.Operation, err)
|
||||
continue
|
||||
}
|
||||
|
||||
appliedPatches = append(appliedPatches, patchRaw)
|
||||
}
|
||||
return appliedPatches, nil
|
||||
return appliedPatches, patchedDocument, nil
|
||||
}
|
||||
|
||||
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array
|
||||
|
@ -65,5 +69,9 @@ func applyPatch(resource []byte, patchRaw []byte) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return patch.Apply(resource)
|
||||
patchedDocument, err := patch.Apply(resource)
|
||||
if err != nil {
|
||||
return resource, err
|
||||
}
|
||||
return patchedDocument, err
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ const endpointsDocument string = `{
|
|||
|
||||
func TestProcessPatches_EmptyPatches(t *testing.T) {
|
||||
var empty []types.Patch
|
||||
patches, err := ProcessPatches(empty, []byte(endpointsDocument))
|
||||
patches, _, err := ProcessPatches(empty, []byte(endpointsDocument))
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(patches) == 0)
|
||||
}
|
||||
|
@ -51,13 +51,13 @@ func makeAddIsMutatedLabelPatch() types.Patch {
|
|||
func TestProcessPatches_EmptyDocument(t *testing.T) {
|
||||
var patches []types.Patch
|
||||
patches = append(patches, makeAddIsMutatedLabelPatch())
|
||||
patchesBytes, err := ProcessPatches(patches, nil)
|
||||
patchesBytes, _, err := ProcessPatches(patches, nil)
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, len(patchesBytes) == 0)
|
||||
}
|
||||
|
||||
func TestProcessPatches_AllEmpty(t *testing.T) {
|
||||
patchesBytes, err := ProcessPatches(nil, nil)
|
||||
patchesBytes, _, err := ProcessPatches(nil, nil)
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, len(patchesBytes) == 0)
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ func TestProcessPatches_AddPathDoesntExist(t *testing.T) {
|
|||
patch := makeAddIsMutatedLabelPatch()
|
||||
patch.Path = "/metadata/additional/is-mutated"
|
||||
patches := []types.Patch{patch}
|
||||
patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(patchesBytes) == 0)
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ func TestProcessPatches_AddPathDoesntExist(t *testing.T) {
|
|||
func TestProcessPatches_RemovePathDoesntExist(t *testing.T) {
|
||||
patch := types.Patch{Path: "/metadata/labels/is-mutated", Operation: "remove"}
|
||||
patches := []types.Patch{patch}
|
||||
patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(patchesBytes) == 0)
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ func TestProcessPatches_AddAndRemovePathsDontExist_EmptyResult(t *testing.T) {
|
|||
patch1 := types.Patch{Path: "/metadata/labels/is-mutated", Operation: "remove"}
|
||||
patch2 := types.Patch{Path: "/spec/labels/label3", Operation: "add", Value: "label3Value"}
|
||||
patches := []types.Patch{patch1, patch2}
|
||||
patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(patchesBytes) == 0)
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_NotEmptyResul
|
|||
patch2 := types.Patch{Path: "/spec/labels/label2", Operation: "remove", Value: "label2Value"}
|
||||
patch3 := types.Patch{Path: "/metadata/labels/label3", Operation: "add", Value: "label3Value"}
|
||||
patches := []types.Patch{patch1, patch2, patch3}
|
||||
patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(patchesBytes) == 1)
|
||||
assertEqStringAndData(t, `{"path":"/metadata/labels/label3","op":"add","value":"label3Value"}`, patchesBytes[0])
|
||||
|
@ -102,7 +102,7 @@ func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_NotEmptyResul
|
|||
func TestProcessPatches_RemovePathDoesntExist_EmptyResult(t *testing.T) {
|
||||
patch := types.Patch{Path: "/metadata/labels/is-mutated", Operation: "remove"}
|
||||
patches := []types.Patch{patch}
|
||||
patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(patchesBytes) == 0)
|
||||
}
|
||||
|
@ -111,8 +111,29 @@ func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) {
|
|||
patch1 := types.Patch{Path: "/metadata/labels/is-mutated", Operation: "remove"}
|
||||
patch2 := types.Patch{Path: "/metadata/labels/label2", Operation: "add", Value: "label2Value"}
|
||||
patches := []types.Patch{patch1, patch2}
|
||||
patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(patchesBytes) == 1)
|
||||
assertEqStringAndData(t, `{"path":"/metadata/labels/label2","op":"add","value":"label2Value"}`, patchesBytes[0])
|
||||
}
|
||||
|
||||
func assertEqDataImpl(t *testing.T, expected, actual []byte, formatModifier string) {
|
||||
if len(expected) != len(actual) {
|
||||
t.Errorf("len(expected) != len(actual): %d != %d\n1:"+formatModifier+"\n2:"+formatModifier, len(expected), len(actual), expected, actual)
|
||||
return
|
||||
}
|
||||
|
||||
for idx, val := range actual {
|
||||
if val != expected[idx] {
|
||||
t.Errorf("Slices not equal at index %d:\n1:"+formatModifier+"\n2:"+formatModifier, idx, expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqData(t *testing.T, expected, actual []byte) {
|
||||
assertEqDataImpl(t, expected, actual, "%x")
|
||||
}
|
||||
|
||||
func assertEqStringAndData(t *testing.T, str string, data []byte) {
|
||||
assertEqDataImpl(t, []byte(str), data, "%s")
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ import (
|
|||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
// ResourceMeetsRules checks requests kind, name and labels to fit the policy
|
||||
func ResourceMeetsRules(resourceRaw []byte, description kubepolicy.ResourceDescription, gvk metav1.GroupVersionKind) (bool, error) {
|
||||
// ResourceMeetsDescription checks requests kind, name and labels to fit the policy rule
|
||||
func ResourceMeetsDescription(resourceRaw []byte, description kubepolicy.ResourceDescription, gvk metav1.GroupVersionKind) bool {
|
||||
if description.Kind != gvk.Kind {
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
|
||||
if resourceRaw != nil {
|
||||
|
@ -24,7 +24,7 @@ func ResourceMeetsRules(resourceRaw []byte, description kubepolicy.ResourceDescr
|
|||
if description.Name != nil {
|
||||
|
||||
if !wildcard.Match(*description.Name, name) {
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,18 +32,18 @@ func ResourceMeetsRules(resourceRaw []byte, description kubepolicy.ResourceDescr
|
|||
selector, err := metav1.LabelSelectorAsSelector(description.Selector)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false
|
||||
}
|
||||
|
||||
labelMap := ParseLabelsFromMetadata(meta)
|
||||
|
||||
if !selector.Matches(labelMap) {
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
return true
|
||||
}
|
||||
|
||||
func ParseMetadataFromObject(bytes []byte) map[string]interface{} {
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// TODO: This operators are already implemented in kubernetes
|
||||
// Operator is string alias that represents selection operators enum
|
||||
type Operator string
|
||||
|
||||
const (
|
||||
|
@ -25,66 +25,49 @@ const (
|
|||
)
|
||||
|
||||
// TODO: Refactor using State pattern
|
||||
// TODO: Return Events and pass all checks to get all validation errors (not )
|
||||
|
||||
// Validate handles validating admission request
|
||||
// Checks the target resourse for rules defined in the policy
|
||||
func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) bool {
|
||||
func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) error {
|
||||
var resource interface{}
|
||||
json.Unmarshal(rawResource, &resource)
|
||||
|
||||
allowed := true
|
||||
for i, rule := range policy.Spec.Rules {
|
||||
|
||||
// Checks for preconditions
|
||||
// TODO: Rework PolicyEngine interface that it receives not a policy, but mutation object for
|
||||
// Mutate, validation for Validate and so on. It will allow to bring this checks outside of PolicyEngine
|
||||
// to common part as far as they present for all: mutation, validation, generation
|
||||
|
||||
err := rule.Validate()
|
||||
if err != nil {
|
||||
log.Printf("Rule has invalid structure: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
ok, err := ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk)
|
||||
if err != nil {
|
||||
log.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !ok {
|
||||
log.Printf("Rule is not applicable to the request: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if rule.Validation == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if !validateMap(resource, rule.Validation.Pattern) {
|
||||
log.Printf("Validation with the rule %s has failed: %s\n", rule.Name, *rule.Validation.Message)
|
||||
allowed = false
|
||||
} else {
|
||||
log.Printf("Validation rule %s is successful\n", rule.Name)
|
||||
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
|
||||
if !ok {
|
||||
log.Printf("Rule \"%s\" is not applicable to resource\n", rule.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := validateMap(resource, rule.Validation.Pattern); err != nil {
|
||||
message := *rule.Validation.Message
|
||||
if len(message) == 0 {
|
||||
message = fmt.Sprintf("%v", err)
|
||||
} else {
|
||||
message = fmt.Sprintf("%s, %s", message, err.Error())
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s: %s", *rule.Validation.Message, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return allowed
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMap(resourcePart, patternPart interface{}) bool {
|
||||
func validateMap(resourcePart, patternPart interface{}) error {
|
||||
pattern, ok := patternPart.(map[string]interface{})
|
||||
|
||||
if !ok {
|
||||
fmt.Printf("Validating error: expected Map, found %T\n", patternPart)
|
||||
return false
|
||||
return fmt.Errorf("expected map, found %T", patternPart)
|
||||
}
|
||||
|
||||
resource, ok := resourcePart.(map[string]interface{})
|
||||
|
||||
if !ok {
|
||||
fmt.Printf("Validating error: expected Map, found %T\n", resourcePart)
|
||||
return false
|
||||
return fmt.Errorf("expected map, found %T", resourcePart)
|
||||
}
|
||||
|
||||
for key, value := range pattern {
|
||||
|
@ -92,98 +75,106 @@ func validateMap(resourcePart, patternPart interface{}) bool {
|
|||
key = key[1 : len(key)-1]
|
||||
}
|
||||
|
||||
if !validateMapElement(resource[key], value) {
|
||||
return false
|
||||
if err := validateMapElement(resource[key], value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateArray(resourcePart, patternPart interface{}) bool {
|
||||
func validateArray(resourcePart, patternPart interface{}) error {
|
||||
patternArray, ok := patternPart.([]interface{})
|
||||
|
||||
if !ok {
|
||||
fmt.Printf("Validating error: expected array, found %T\n", patternPart)
|
||||
return false
|
||||
return fmt.Errorf("expected array, found %T", patternPart)
|
||||
}
|
||||
|
||||
resourceArray, ok := resourcePart.([]interface{})
|
||||
|
||||
if !ok {
|
||||
fmt.Printf("Validating error: expected array, found %T\n", resourcePart)
|
||||
return false
|
||||
return fmt.Errorf("expected array, found %T", resourcePart)
|
||||
}
|
||||
|
||||
switch pattern := patternArray[0].(type) {
|
||||
case map[string]interface{}:
|
||||
anchors, err := getAnchorsFromMap(pattern)
|
||||
if err != nil {
|
||||
fmt.Printf("Validating error: %v\n", err)
|
||||
return false
|
||||
return err
|
||||
}
|
||||
|
||||
for _, value := range resourceArray {
|
||||
resource, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
fmt.Printf("Validating error: expected Map, found %T\n", resourcePart)
|
||||
return false
|
||||
return fmt.Errorf("expected array, found %T", resourcePart)
|
||||
}
|
||||
|
||||
if skipArrayObject(resource, anchors) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !validateMap(resource, pattern) {
|
||||
return false
|
||||
if err := validateMap(resource, pattern); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
default:
|
||||
for _, value := range resourceArray {
|
||||
if !checkSingleValue(value, patternArray[0]) {
|
||||
return false
|
||||
if err := checkSingleValue(value, patternArray[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMapElement(resourcePart, patternPart interface{}) bool {
|
||||
func validateMapElement(resourcePart, patternPart interface{}) error {
|
||||
switch pattern := patternPart.(type) {
|
||||
case map[string]interface{}:
|
||||
dictionary, ok := resourcePart.(map[string]interface{})
|
||||
|
||||
if !ok {
|
||||
fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart)
|
||||
return false
|
||||
return fmt.Errorf("expected %T, found %T", patternPart, resourcePart)
|
||||
}
|
||||
|
||||
return validateMap(dictionary, pattern)
|
||||
case []interface{}:
|
||||
array, ok := resourcePart.([]interface{})
|
||||
|
||||
if !ok {
|
||||
fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart)
|
||||
return false
|
||||
return fmt.Errorf("expected %T, found %T", patternPart, resourcePart)
|
||||
}
|
||||
|
||||
return validateArray(array, pattern)
|
||||
case string:
|
||||
str, ok := resourcePart.(string)
|
||||
|
||||
if !ok {
|
||||
fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart)
|
||||
return false
|
||||
return checkSingleValue(resourcePart, patternPart)
|
||||
case float64:
|
||||
switch num := resourcePart.(type) {
|
||||
case float64:
|
||||
if num != pattern {
|
||||
return fmt.Errorf("%f not equal %f", num, pattern)
|
||||
}
|
||||
case int64:
|
||||
if float64(num) != pattern {
|
||||
return fmt.Errorf("%d not equal %f", num, pattern)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("expected %T, found %T", patternPart, resourcePart)
|
||||
}
|
||||
case int64:
|
||||
switch num := resourcePart.(type) {
|
||||
case float64:
|
||||
if num != float64(pattern) {
|
||||
return fmt.Errorf("%f not equal %d", num, pattern)
|
||||
}
|
||||
case int64:
|
||||
if float64(num) != float64(num) {
|
||||
return fmt.Errorf("%d not equal %d", num, pattern)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("expected %T, found %T", patternPart, resourcePart)
|
||||
}
|
||||
|
||||
return checkSingleValue(str, pattern)
|
||||
default:
|
||||
fmt.Printf("Validating error: unknown type in map: %T\n", patternPart)
|
||||
return false
|
||||
return fmt.Errorf("validating error: unknown type in map: %T", patternPart)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAnchorsFromMap(pattern map[string]interface{}) (map[string]interface{}, error) {
|
||||
|
@ -207,7 +198,7 @@ func skipArrayObject(object, anchors map[string]interface{}) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
if !checkSingleValue(value, pattern) {
|
||||
if err := checkSingleValue(value, pattern); err != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -215,7 +206,7 @@ func skipArrayObject(object, anchors map[string]interface{}) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func checkSingleValue(value, pattern interface{}) bool {
|
||||
func checkSingleValue(value, pattern interface{}) error {
|
||||
switch typedPattern := pattern.(type) {
|
||||
case string:
|
||||
switch typedValue := value.(type) {
|
||||
|
@ -226,46 +217,54 @@ func checkSingleValue(value, pattern interface{}) bool {
|
|||
case int:
|
||||
return checkForOperator(float64(typedValue), typedPattern)
|
||||
default:
|
||||
fmt.Printf("Validating error: expected string or numerical type, found %T, pattern: %s\n", value, typedPattern)
|
||||
return false
|
||||
return fmt.Errorf("expected string or numerical type, found %T, pattern: %s", value, typedPattern)
|
||||
}
|
||||
case float64:
|
||||
num, ok := value.(float64)
|
||||
if !ok {
|
||||
fmt.Printf("Validating error: expected float, found %T\n", value)
|
||||
return false
|
||||
return fmt.Errorf("expected float, found %T", value)
|
||||
}
|
||||
|
||||
return typedPattern == num
|
||||
if typedPattern != num {
|
||||
return fmt.Errorf("value %f is not equal to pattern %f", value, typedPattern)
|
||||
}
|
||||
case int:
|
||||
num, ok := value.(int)
|
||||
if !ok {
|
||||
fmt.Printf("Validating error: expected int, found %T\n", value)
|
||||
return false
|
||||
return fmt.Errorf("expected int, found %T", value)
|
||||
}
|
||||
|
||||
return typedPattern == num
|
||||
if typedPattern != num {
|
||||
return fmt.Errorf("value %d is not equal to pattern %d", num, typedPattern)
|
||||
}
|
||||
default:
|
||||
fmt.Printf("Validating error: expected pattern (string or numerical type), found %T\n", pattern)
|
||||
return false
|
||||
return fmt.Errorf("expected pattern (string or numerical type), found %T", pattern)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkForWildcard(value, pattern string) bool {
|
||||
return wildcard.Match(pattern, value)
|
||||
func checkForWildcard(value, pattern string) error {
|
||||
if !wildcard.Match(pattern, value) {
|
||||
return fmt.Errorf("wildcard check has failed. Pattern: \"%s\". Value: \"%s\"", pattern, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkForOperator(value float64, pattern string) bool {
|
||||
func checkForOperator(value float64, pattern string) error {
|
||||
operators := strings.Split(pattern, "|")
|
||||
|
||||
for _, operator := range operators {
|
||||
operator = strings.Replace(operator, " ", "", -1)
|
||||
|
||||
// At least one success - return nil
|
||||
if checkSingleOperator(value, operator) {
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return fmt.Errorf("operator check has failed. Pattern: \"%s\". Value: \"%f\"", pattern, value)
|
||||
}
|
||||
|
||||
func checkSingleOperator(value float64, pattern string) bool {
|
||||
|
|
|
@ -4,7 +4,9 @@ import (
|
|||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
"gotest.tools/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestWrappedWithParentheses_StringIsWrappedWithParentheses(t *testing.T) {
|
||||
|
@ -62,190 +64,190 @@ func TestCheckForWildcard_LeftAsteriskTest(t *testing.T) {
|
|||
value = "leftmiddle"
|
||||
middle := "middle"
|
||||
|
||||
assert.Assert(t, !checkForWildcard(value, pattern))
|
||||
assert.Assert(t, !checkForWildcard(middle, pattern))
|
||||
assert.Assert(t, checkForWildcard(value, pattern) != nil)
|
||||
assert.Assert(t, checkForWildcard(middle, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckForWildcard_MiddleAsteriskTest(t *testing.T) {
|
||||
pattern := "ab*ba"
|
||||
value := "abbba"
|
||||
assert.Assert(t, checkForWildcard(value, pattern))
|
||||
value := "abbeba"
|
||||
assert.NilError(t, checkForWildcard(value, pattern))
|
||||
|
||||
value = "abbca"
|
||||
assert.Assert(t, !checkForWildcard(value, pattern))
|
||||
assert.Assert(t, checkForWildcard(value, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckForWildcard_QuestionMark(t *testing.T) {
|
||||
pattern := "ab?ba"
|
||||
value := "abbba"
|
||||
assert.Assert(t, checkForWildcard(value, pattern))
|
||||
assert.NilError(t, checkForWildcard(value, pattern))
|
||||
|
||||
value = "abbbba"
|
||||
assert.Assert(t, !checkForWildcard(value, pattern))
|
||||
assert.Assert(t, checkForWildcard(value, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckInt(t *testing.T) {
|
||||
pattern := 89
|
||||
value := 89
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
value = 202
|
||||
assert.Assert(t, !checkSingleValue(value, pattern))
|
||||
assert.Assert(t, checkSingleValue(value, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckFloat(t *testing.T) {
|
||||
pattern := 89.9091
|
||||
value := 89.9091
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
value = 89.9092
|
||||
assert.Assert(t, !checkSingleValue(value, pattern))
|
||||
assert.Assert(t, checkSingleValue(value, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorMoreEqual(t *testing.T) {
|
||||
pattern := " >= 89 "
|
||||
value := 89
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
pattern = ">=10.0001"
|
||||
floatValue := 89.901
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern))
|
||||
assert.NilError(t, checkSingleValue(floatValue, pattern))
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorMoreEqualFail(t *testing.T) {
|
||||
pattern := " >= 90 "
|
||||
value := 89
|
||||
assert.Assert(t, !checkSingleValue(value, pattern))
|
||||
assert.Assert(t, checkSingleValue(value, pattern) != nil)
|
||||
|
||||
pattern = ">=910.0001"
|
||||
floatValue := 89.901
|
||||
assert.Assert(t, !checkSingleValue(floatValue, pattern))
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorLessEqual(t *testing.T) {
|
||||
pattern := " <= 1 "
|
||||
value := 1
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
pattern = "<=10.0001"
|
||||
floatValue := 1.901
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern))
|
||||
assert.NilError(t, checkSingleValue(floatValue, pattern))
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorLessEqualFail(t *testing.T) {
|
||||
pattern := " <= 0.1558 "
|
||||
value := 1
|
||||
assert.Assert(t, !checkSingleValue(value, pattern))
|
||||
assert.Assert(t, checkSingleValue(value, pattern) != nil)
|
||||
|
||||
pattern = "<=10.0001"
|
||||
floatValue := 12.901
|
||||
assert.Assert(t, !checkSingleValue(floatValue, pattern))
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorMore(t *testing.T) {
|
||||
pattern := " > 10 "
|
||||
value := 89
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
pattern = ">10.0001"
|
||||
floatValue := 89.901
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern))
|
||||
assert.NilError(t, checkSingleValue(floatValue, pattern))
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorMoreFail(t *testing.T) {
|
||||
pattern := " > 89 "
|
||||
value := 89
|
||||
assert.Assert(t, !checkSingleValue(value, pattern))
|
||||
assert.Assert(t, checkSingleValue(value, pattern) != nil)
|
||||
|
||||
pattern = ">910.0001"
|
||||
floatValue := 89.901
|
||||
assert.Assert(t, !checkSingleValue(floatValue, pattern))
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorLess(t *testing.T) {
|
||||
pattern := " < 10 "
|
||||
value := 9
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
pattern = "<10.0001"
|
||||
floatValue := 9.901
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern))
|
||||
assert.NilError(t, checkSingleValue(floatValue, pattern))
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorLessFail(t *testing.T) {
|
||||
pattern := " < 10 "
|
||||
value := 10
|
||||
assert.Assert(t, !checkSingleValue(value, pattern))
|
||||
assert.Assert(t, checkSingleValue(value, pattern) != nil)
|
||||
|
||||
pattern = "<10.0001"
|
||||
floatValue := 19.901
|
||||
assert.Assert(t, !checkSingleValue(floatValue, pattern))
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorNotEqual(t *testing.T) {
|
||||
pattern := " != 10 "
|
||||
value := 9.99999
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
pattern = "!=10.0001"
|
||||
floatValue := 10.0000
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern))
|
||||
assert.NilError(t, checkSingleValue(floatValue, pattern))
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorNotEqualFail(t *testing.T) {
|
||||
pattern := " != 9.99999 "
|
||||
value := 9.99999
|
||||
assert.Assert(t, !checkSingleValue(value, pattern))
|
||||
assert.Assert(t, checkSingleValue(value, pattern) != nil)
|
||||
|
||||
pattern = "!=10"
|
||||
floatValue := 10
|
||||
assert.Assert(t, !checkSingleValue(floatValue, pattern))
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorEqual(t *testing.T) {
|
||||
pattern := " 10.000001 "
|
||||
value := 10.000001
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
pattern = "10.000000"
|
||||
floatValue := 10
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern))
|
||||
assert.NilError(t, checkSingleValue(floatValue, pattern))
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckOperatorEqualFail(t *testing.T) {
|
||||
pattern := " 10.000000 "
|
||||
value := 10.000001
|
||||
assert.Assert(t, !checkSingleValue(value, pattern))
|
||||
assert.Assert(t, checkSingleValue(value, pattern) != nil)
|
||||
|
||||
pattern = "10.000001"
|
||||
floatValue := 10
|
||||
assert.Assert(t, !checkSingleValue(floatValue, pattern))
|
||||
assert.Assert(t, checkSingleValue(floatValue, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckSeveralOperators(t *testing.T) {
|
||||
pattern := " <-1 | 10.000001 "
|
||||
value := 10.000001
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
value = -30
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
value = 5
|
||||
assert.Assert(t, !checkSingleValue(value, pattern))
|
||||
assert.Assert(t, checkSingleValue(value, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestCheckSingleValue_CheckWildcard(t *testing.T) {
|
||||
pattern := "nirmata_*"
|
||||
value := "nirmata_awesome"
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
|
||||
pattern = "nirmata_*"
|
||||
value = "spasex_awesome"
|
||||
assert.Assert(t, !checkSingleValue(value, pattern))
|
||||
assert.Assert(t, checkSingleValue(value, pattern) != nil)
|
||||
|
||||
pattern = "g?t"
|
||||
value = "git"
|
||||
assert.Assert(t, checkSingleValue(value, pattern))
|
||||
assert.NilError(t, checkSingleValue(value, pattern))
|
||||
}
|
||||
|
||||
func TestSkipArrayObject_OneAnchor(t *testing.T) {
|
||||
|
@ -330,7 +332,7 @@ func TestValidateMapElement_TwoElementsInArrayOnePass(t *testing.T) {
|
|||
json.Unmarshal(rawPattern, &pattern)
|
||||
json.Unmarshal(rawMap, &resource)
|
||||
|
||||
assert.Assert(t, validateMapElement(resource, pattern))
|
||||
assert.NilError(t, validateMapElement(resource, pattern))
|
||||
}
|
||||
|
||||
func TestValidateMapElement_OneElementInArrayPass(t *testing.T) {
|
||||
|
@ -341,7 +343,7 @@ func TestValidateMapElement_OneElementInArrayPass(t *testing.T) {
|
|||
json.Unmarshal(rawPattern, &pattern)
|
||||
json.Unmarshal(rawMap, &resource)
|
||||
|
||||
assert.Assert(t, validateMapElement(resource, pattern))
|
||||
assert.NilError(t, validateMapElement(resource, pattern))
|
||||
}
|
||||
|
||||
func TestValidateMapElement_OneElementInArrayNotPass(t *testing.T) {
|
||||
|
@ -352,5 +354,33 @@ func TestValidateMapElement_OneElementInArrayNotPass(t *testing.T) {
|
|||
json.Unmarshal(rawPattern, &pattern)
|
||||
json.Unmarshal(rawMap, &resource)
|
||||
|
||||
assert.Assert(t, !validateMapElement(resource, pattern))
|
||||
assert.Assert(t, validateMapElement(resource, pattern) != nil)
|
||||
}
|
||||
|
||||
func TestValidate_ServiceTest(t *testing.T) {
|
||||
rawPolicy := []byte(`{ "apiVersion": "kubepolicy.nirmata.io/v1alpha1", "kind": "Policy", "metadata": { "name": "policy-service" }, "spec": { "rules": [ { "name": "ps1", "resource": { "kind": "Service", "name": "game-service*" }, "mutate": { "patches": [ { "path": "/metadata/labels/isMutated", "op": "add", "value": "true" }, { "path": "/metadata/labels/secretLabel", "op": "replace", "value": "weKnow" }, { "path": "/metadata/labels/originalLabel", "op": "remove" }, { "path": "/spec/selector/app", "op": "replace", "value": "mutedApp" } ] }, "validate": { "message": "This resource is broken", "pattern": { "spec": { "ports": [ { "name": "hs", "protocol": 32 } ] } } } } ] } }`)
|
||||
rawResource := []byte(`{ "kind": "Service", "apiVersion": "v1", "metadata": { "name": "game-service", "labels": { "originalLabel": "isHere", "secretLabel": "thisIsMySecret" } }, "spec": { "selector": { "app": "MyApp" }, "ports": [ { "name": "http", "protocol": "TCP", "port": 80, "targetPort": 9376 } ] } }`)
|
||||
|
||||
var policy kubepolicy.Policy
|
||||
json.Unmarshal(rawPolicy, &policy)
|
||||
|
||||
gvk := metav1.GroupVersionKind{
|
||||
Kind: "Service",
|
||||
}
|
||||
|
||||
assert.Assert(t, Validate(policy, rawResource, gvk) != nil)
|
||||
}
|
||||
|
||||
func TestValidate_MapHasFloats(t *testing.T) {
|
||||
rawPolicy := []byte(`{ "apiVersion": "kubepolicy.nirmata.io/v1alpha1", "kind": "Policy", "metadata": { "name": "policy-deployment-changed" }, "spec": { "rules": [ { "name": "First policy v2", "resource": { "kind": "Deployment", "name": "nginx-*" }, "mutate": { "patches": [ { "path": "/metadata/labels/isMutated", "op": "add", "value": "true" }, { "path": "/metadata/labels/app", "op": "replace", "value": "nginx_is_mutated" } ] }, "validate": { "message": "replicas number is wrong", "pattern": { "metadata": { "labels": { "app": "*" } }, "spec": { "replicas": 3 } } } } ] } }`)
|
||||
rawResource := []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:1.7.9", "ports": [ { "containerPort": 80 } ] } ] } } } }`)
|
||||
|
||||
var policy kubepolicy.Policy
|
||||
json.Unmarshal(rawPolicy, &policy)
|
||||
|
||||
gvk := metav1.GroupVersionKind{
|
||||
Kind: "Deployment",
|
||||
}
|
||||
|
||||
assert.NilError(t, Validate(policy, rawResource, gvk))
|
||||
}
|
||||
|
|
237
pkg/kyverno/apply/apply.go
Normal file
237
pkg/kyverno/apply/apply.go
Normal file
|
@ -0,0 +1,237 @@
|
|||
package apply
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
"github.com/nirmata/kube-policy/pkg/engine"
|
||||
"github.com/spf13/cobra"
|
||||
yamlv2 "gopkg.in/yaml.v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
yaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
const applyExample = ` # Apply a policy to the resource.
|
||||
kyverno apply @policy.yaml @resource.yaml
|
||||
kyverno apply @policy.yaml @resourceDir/`
|
||||
|
||||
// NewCmdApply returns the apply command for kyverno
|
||||
func NewCmdApply(in io.Reader, out, errout io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "apply",
|
||||
Short: "Apply policy on the resource(s)",
|
||||
Example: applyExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var output string
|
||||
policy, resources := complete(args)
|
||||
|
||||
for _, resource := range resources {
|
||||
patchedDocument, err := applyPolicy(policy, resource.rawResource, resource.gvk)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
out, err := prettyPrint(patchedDocument)
|
||||
if err != nil {
|
||||
fmt.Printf("JSON parse error: %v\n", err)
|
||||
fmt.Printf("%v\n", string(patchedDocument))
|
||||
return
|
||||
}
|
||||
|
||||
output = output + fmt.Sprintf("---\n%s", string(out))
|
||||
}
|
||||
fmt.Printf("%v\n", output)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func complete(args []string) (*kubepolicy.Policy, []*resourceInfo) {
|
||||
|
||||
policyDir, resourceDir, err := validateDir(args)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to parse file path, err: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// extract policy
|
||||
policy, err := extractPolicy(policyDir)
|
||||
if err != nil {
|
||||
log.Printf("failed to extract policy: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// extract rawResource
|
||||
resources, err := extractResource(resourceDir)
|
||||
if err != nil {
|
||||
log.Printf("failed to parse resource: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return policy, resources
|
||||
}
|
||||
|
||||
func applyPolicy(policy *kubepolicy.Policy, rawResource []byte, gvk *metav1.GroupVersionKind) ([]byte, error) {
|
||||
_, patchedDocument := engine.Mutate(*policy, rawResource, *gvk)
|
||||
|
||||
if err := engine.Validate(*policy, rawResource, *gvk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return patchedDocument, nil
|
||||
}
|
||||
|
||||
func extractPolicy(fileDir string) (*kubepolicy.Policy, error) {
|
||||
policy := &kubepolicy.Policy{}
|
||||
|
||||
file, err := loadFile(fileDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load file: %v", err)
|
||||
}
|
||||
|
||||
policyBytes, err := yaml.ToJSON(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(policyBytes, policy); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode policy %s, err: %v", policy.Name, err)
|
||||
}
|
||||
|
||||
if policy.TypeMeta.Kind != "Policy" {
|
||||
return nil, fmt.Errorf("failed to parse policy")
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
type resourceInfo struct {
|
||||
rawResource []byte
|
||||
gvk *metav1.GroupVersionKind
|
||||
}
|
||||
|
||||
func extractResource(fileDir string) ([]*resourceInfo, error) {
|
||||
var files []string
|
||||
var resources []*resourceInfo
|
||||
// check if applied on multiple resources
|
||||
isDir, err := isDir(fileDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isDir {
|
||||
files, err = ScanDir(fileDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
files = []string{fileDir}
|
||||
}
|
||||
|
||||
for _, dir := range files {
|
||||
file, err := loadFile(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load file: %v", err)
|
||||
}
|
||||
|
||||
data := make(map[interface{}]interface{})
|
||||
|
||||
if err = yamlv2.Unmarshal([]byte(file), &data); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse resource: %v", err)
|
||||
}
|
||||
|
||||
apiVersion, ok := data["apiVersion"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to parse apiversion: %v", err)
|
||||
}
|
||||
|
||||
kind, ok := data["kind"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to parse kind of resource: %v", err)
|
||||
}
|
||||
|
||||
var gvkInfo *metav1.GroupVersionKind
|
||||
gv := strings.Split(apiVersion, "/")
|
||||
if len(gv) == 2 {
|
||||
gvkInfo = &metav1.GroupVersionKind{Group: gv[0], Version: gv[1], Kind: kind}
|
||||
} else {
|
||||
gvkInfo = &metav1.GroupVersionKind{Version: gv[0], Kind: kind}
|
||||
}
|
||||
|
||||
json, err := yaml.ToJSON(file)
|
||||
|
||||
resources = append(resources, &resourceInfo{rawResource: json, gvk: gvkInfo})
|
||||
}
|
||||
|
||||
return resources, err
|
||||
}
|
||||
|
||||
func loadFile(fileDir string) ([]byte, error) {
|
||||
if _, err := os.Stat(fileDir); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ioutil.ReadFile(fileDir)
|
||||
}
|
||||
|
||||
func validateDir(args []string) (policyDir, resourceDir string, err error) {
|
||||
if len(args) != 2 {
|
||||
return "", "", fmt.Errorf("missing policy and/or resource manifest")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(args[0], "@") {
|
||||
policyDir = args[0][1:]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(args[1], "@") {
|
||||
resourceDir = args[1][1:]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func prettyPrint(data []byte) ([]byte, error) {
|
||||
out := make(map[interface{}]interface{})
|
||||
if err := yamlv2.Unmarshal(data, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return yamlv2.Marshal(&out)
|
||||
}
|
||||
|
||||
func isDir(dir string) (bool, error) {
|
||||
fi, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return fi.IsDir(), nil
|
||||
}
|
||||
|
||||
func ScanDir(dir string) ([]string, error) {
|
||||
var res []string
|
||||
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", dir, err)
|
||||
return err
|
||||
}
|
||||
/* if len(strings.Split(path, "/")) == 4 {
|
||||
fmt.Println(path)
|
||||
} */
|
||||
res = append(res, path)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error walking the path %q: %v", dir, err)
|
||||
}
|
||||
|
||||
return res[1:], nil
|
||||
}
|
25
pkg/kyverno/cmd.go
Normal file
25
pkg/kyverno/cmd.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/nirmata/kube-policy/pkg/kyverno/apply"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDefaultKyvernoCommand ...
|
||||
func NewDefaultKyvernoCommand() *cobra.Command {
|
||||
return NewKyvernoCommand(os.Stdin, os.Stdout, os.Stderr)
|
||||
}
|
||||
|
||||
// NewKyvernoCommand returns the new kynerno command
|
||||
func NewKyvernoCommand(in io.Reader, out, errout io.Writer) *cobra.Command {
|
||||
cmds := &cobra.Command{
|
||||
Use: "kyverno",
|
||||
Short: "kyverno manages native policies of Kubernetes",
|
||||
}
|
||||
|
||||
cmds.AddCommand(apply.NewCmdApply(in, out, errout))
|
||||
return cmds
|
||||
}
|
|
@ -4,8 +4,8 @@ import (
|
|||
"errors"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/nirmata/kube-policy/config"
|
||||
kubeclient "github.com/nirmata/kube-policy/kubeclient"
|
||||
"github.com/nirmata/kube-policy/pkg/config"
|
||||
|
||||
admregapi "k8s.io/api/admissionregistration/v1beta1"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
|
@ -12,14 +12,15 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/nirmata/kube-policy/config"
|
||||
kubeClient "github.com/nirmata/kube-policy/kubeclient"
|
||||
policylister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1"
|
||||
"github.com/nirmata/kube-policy/pkg/config"
|
||||
engine "github.com/nirmata/kube-policy/pkg/engine"
|
||||
"github.com/nirmata/kube-policy/pkg/engine/mutation"
|
||||
tlsutils "github.com/nirmata/kube-policy/pkg/tls"
|
||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// WebhookServer contains configured TLS server with MutationWebhook.
|
||||
|
@ -27,6 +28,7 @@ import (
|
|||
type WebhookServer struct {
|
||||
server http.Server
|
||||
policyLister policylister.PolicyLister
|
||||
kubeClient *kubeClient.KubeClient
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
|
@ -35,6 +37,7 @@ type WebhookServer struct {
|
|||
func NewWebhookServer(
|
||||
tlsPair *tlsutils.TlsPemPair,
|
||||
policyLister policylister.PolicyLister,
|
||||
kubeClient *kubeClient.KubeClient,
|
||||
logger *log.Logger) (*WebhookServer, error) {
|
||||
if logger == nil {
|
||||
logger = log.New(os.Stdout, "Webhook Server: ", log.LstdFlags)
|
||||
|
@ -53,6 +56,7 @@ func NewWebhookServer(
|
|||
|
||||
ws := &WebhookServer{
|
||||
policyLister: policyLister,
|
||||
kubeClient: kubeClient,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
|
@ -135,7 +139,7 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
|
|||
|
||||
policies, err := ws.policyLister.List(labels.NewSelector())
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
ws.logger.Printf("%v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -143,13 +147,13 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
|
|||
for _, policy := range policies {
|
||||
ws.logger.Printf("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
|
||||
|
||||
policyPatches := engine.Mutate(*policy, request.Object.Raw, request.Kind)
|
||||
policyPatches, _ := engine.Mutate(*policy, request.Object.Raw, request.Kind)
|
||||
allPatches = append(allPatches, policyPatches...)
|
||||
|
||||
if len(policyPatches) > 0 {
|
||||
namespace := engine.ParseNamespaceFromObject(request.Object.Raw)
|
||||
name := engine.ParseNameFromObject(request.Object.Raw)
|
||||
ws.logger.Printf("Policy %s applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name)
|
||||
ws.logger.Printf("Mutation from policy %s has applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,26 +172,35 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
|
|||
|
||||
policies, err := ws.policyLister.List(labels.NewSelector())
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
ws.logger.Printf("%v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
allowed := true
|
||||
for _, policy := range policies {
|
||||
// validation
|
||||
ws.logger.Printf("Validating resource with policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules))
|
||||
|
||||
if ok := engine.Validate(*policy, request.Object.Raw, request.Kind); !ok {
|
||||
ws.logger.Printf("Validation has failed: %v\n", err)
|
||||
utilruntime.HandleError(err)
|
||||
allowed = false
|
||||
} else {
|
||||
ws.logger.Println("Validation is successful")
|
||||
if err := engine.Validate(*policy, request.Object.Raw, request.Kind); err != nil {
|
||||
message := fmt.Sprintf("validation has failed: %s", err.Error())
|
||||
ws.logger.Println(message)
|
||||
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Message: message,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// generation
|
||||
engine.Generate(*policy, request.Object.Raw, ws.kubeClient, request.Kind)
|
||||
}
|
||||
|
||||
ws.logger.Println("Validation is successful")
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: allowed,
|
||||
Allowed: true,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// bodyToAdmissionReview creates AdmissionReview object from request body
|
||||
|
|
|
@ -4,15 +4,17 @@ metadata :
|
|||
name: "policy-configmapgenerator-test"
|
||||
spec:
|
||||
rules:
|
||||
- name: "Policy ConfigMap sample rule"
|
||||
resource:
|
||||
- name: "copyCM"
|
||||
resource :
|
||||
kind : Namespace
|
||||
name: "ns2"
|
||||
generate:
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
generate :
|
||||
kind: ConfigMap
|
||||
name: copied-cm
|
||||
copyFrom:
|
||||
namespace: default
|
||||
name: game-config
|
||||
data:
|
||||
name : copied-cm
|
||||
copyFrom :
|
||||
namespace : default
|
||||
name : game-config
|
||||
data :
|
||||
secretData: "data from cmg"
|
|
@ -3,42 +3,47 @@
|
|||
# To apply this policy you need to create secret and configMap in "default" namespace
|
||||
# and then create a namespace
|
||||
|
||||
apiVersion : policy.nirmata.io/v1alpha1
|
||||
apiVersion : kubepolicy.nirmata.io/v1alpha1
|
||||
kind : Policy
|
||||
metadata :
|
||||
name : "policy-ns-patch-cmg-sg"
|
||||
spec :
|
||||
failurePolicy: stopOnError
|
||||
rules:
|
||||
- resource :
|
||||
- name: "patchNamespace2"
|
||||
resource :
|
||||
kind : Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
patch:
|
||||
- path: "/metadata/labels/isMutatedByPolicy"
|
||||
op: add
|
||||
value: "true"
|
||||
mutate:
|
||||
patches:
|
||||
- path: "/metadata/labels/isMutatedByPolicy"
|
||||
op: add
|
||||
value: "true"
|
||||
|
||||
- resource :
|
||||
- name: "copyCM"
|
||||
resource :
|
||||
kind : Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
configMapGenerator :
|
||||
generate :
|
||||
kind: ConfigMap
|
||||
name : copied-cm
|
||||
copyFrom :
|
||||
namespace : default
|
||||
name : game-config
|
||||
data :
|
||||
secretData: "data from cmg"
|
||||
|
||||
- resource :
|
||||
|
||||
- name: "generateCM"
|
||||
resource :
|
||||
kind : Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
configMapGenerator :
|
||||
generate :
|
||||
kind: ConfigMap
|
||||
name : generated-cm
|
||||
data :
|
||||
secretData: "very sensitive data from cmg"
|
||||
|
@ -49,13 +54,12 @@ spec :
|
|||
image.public.key=771
|
||||
rsa.public.key=42
|
||||
|
||||
- resource :
|
||||
- name: "generateSecret"
|
||||
resource :
|
||||
kind : Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
|
||||
secretGenerator :
|
||||
name: ns2
|
||||
generate :
|
||||
kind: Secret
|
||||
name : generated-secrets
|
||||
data :
|
||||
foo : bar
|
||||
|
@ -66,13 +70,12 @@ spec :
|
|||
foo1=bar1
|
||||
foo2=bar2
|
||||
|
||||
- resource :
|
||||
- name: "copySecret"
|
||||
resource :
|
||||
kind : Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
|
||||
secretGenerator :
|
||||
name: ns2
|
||||
generate :
|
||||
kind: Secret
|
||||
name : copied-secrets
|
||||
copyFrom :
|
||||
namespace : default
|
||||
|
|
Loading…
Add table
Reference in a new issue