2019-05-13 18:17:28 -07:00
package engine
2019-05-13 21:27:47 +03:00
import (
2019-08-19 16:40:10 -07:00
"time"
2019-05-13 21:27:47 +03:00
2019-08-08 15:15:47 -07:00
"fmt"
2019-05-13 21:27:47 +03:00
2019-05-31 17:59:36 -07:00
"github.com/golang/glog"
2019-08-09 16:55:43 -07:00
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
2019-05-29 14:12:09 -07:00
client "github.com/nirmata/kyverno/pkg/dclient"
2019-08-08 15:15:47 -07:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-07-26 08:11:20 -04:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
2019-05-13 21:27:47 +03:00
)
2019-07-26 08:11:20 -04:00
//Generate apply generation rules on a resource
2019-10-08 10:57:24 -07:00
func Generate ( client * client . Client , policy kyverno . ClusterPolicy , ns unstructured . Unstructured ) ( response EngineResponse ) {
2019-08-19 16:40:10 -07:00
startTime := time . Now ( )
2019-08-26 13:34:42 -07:00
// policy information
func ( ) {
// set policy information
response . PolicyResponse . Policy = policy . Name
// resource details
response . PolicyResponse . Resource . Name = ns . GetName ( )
response . PolicyResponse . Resource . Kind = ns . GetKind ( )
response . PolicyResponse . Resource . APIVersion = ns . GetAPIVersion ( )
} ( )
2019-08-19 16:40:10 -07:00
glog . V ( 4 ) . Infof ( "started applying generation rules of policy %q (%v)" , policy . Name , startTime )
defer func ( ) {
2019-08-26 13:34:42 -07:00
response . PolicyResponse . ProcessingTime = time . Since ( startTime )
glog . V ( 4 ) . Infof ( "finished applying generation rules policy %v (%v)" , policy . Name , response . PolicyResponse . ProcessingTime )
glog . V ( 4 ) . Infof ( "Generation Rules appplied succesfully count %v for policy %q" , response . PolicyResponse . RulesAppliedCount , policy . Name )
2019-08-19 16:40:10 -07:00
} ( )
2019-08-20 12:51:25 -07:00
incrementAppliedRuleCount := func ( ) {
2019-08-19 16:40:10 -07:00
// rules applied succesfully count
2019-08-26 13:34:42 -07:00
response . PolicyResponse . RulesAppliedCount ++
2019-08-19 16:40:10 -07:00
}
2019-05-20 14:48:38 +03:00
for _ , rule := range policy . Spec . Rules {
2019-10-21 14:22:31 -07:00
if ! rule . HasGenerate ( ) {
2019-06-25 18:16:02 -07:00
continue
}
2019-08-14 14:56:53 -07:00
glog . V ( 4 ) . Infof ( "applying policy %s generate rule %s on resource %s/%s/%s" , policy . Name , rule . Name , ns . GetKind ( ) , ns . GetNamespace ( ) , ns . GetName ( ) )
2019-08-26 13:34:42 -07:00
ruleResponse := applyRuleGenerator ( client , ns , rule , policy . GetCreationTimestamp ( ) )
response . PolicyResponse . Rules = append ( response . PolicyResponse . Rules , ruleResponse )
2019-08-20 12:51:25 -07:00
incrementAppliedRuleCount ( )
2019-05-13 21:27:47 +03:00
}
2019-08-19 18:57:19 -07:00
return response
2019-05-13 21:27:47 +03:00
}
2019-08-26 13:34:42 -07:00
func applyRuleGenerator ( client * client . Client , ns unstructured . Unstructured , rule kyverno . Rule , policyCreationTime metav1 . Time ) ( response RuleResponse ) {
startTime := time . Now ( )
glog . V ( 4 ) . Infof ( "started applying generation rule %q (%v)" , rule . Name , startTime )
response . Name = rule . Name
response . Type = Generation . String ( )
defer func ( ) {
response . RuleStats . ProcessingTime = time . Since ( startTime )
glog . V ( 4 ) . Infof ( "finished applying generation rule %q (%v)" , response . Name , response . RuleStats . ProcessingTime )
} ( )
2019-05-20 17:25:57 +03:00
var err error
2019-07-26 08:11:20 -04:00
resource := & unstructured . Unstructured { }
var rdata map [ string ] interface { }
2019-08-08 15:15:47 -07:00
// To manage existing resource , we compare the creation time for the default resource to be generate and policy creation time
processExisting := func ( ) bool {
nsCreationTime := ns . GetCreationTimestamp ( )
return nsCreationTime . Before ( & policyCreationTime )
} ( )
2019-08-26 13:34:42 -07:00
if rule . Generation . Data != nil {
2019-08-14 14:56:53 -07:00
glog . V ( 4 ) . Info ( "generate rule: creates new resource" )
2019-07-26 08:11:20 -04:00
// 1> Check if resource exists
2019-08-26 13:34:42 -07:00
obj , err := client . GetResource ( rule . Generation . Kind , ns . GetName ( ) , rule . Generation . Name )
2019-07-26 08:11:20 -04:00
if err == nil {
2019-08-26 13:34:42 -07:00
glog . V ( 4 ) . Infof ( "generate rule: resource %s/%s/%s already present. checking if it contains the required configuration" , rule . Generation . Kind , ns . GetName ( ) , rule . Generation . Name )
2019-07-26 08:11:20 -04:00
// 2> If already exsists, then verify the content is contained
// found the resource
// check if the rule is create, if yes, then verify if the specified configuration is present in the resource
2019-08-26 13:34:42 -07:00
ok , err := checkResource ( rule . Generation . Data , obj )
2019-07-26 08:11:20 -04:00
if err != nil {
2019-09-12 15:04:35 -07:00
glog . V ( 4 ) . Infof ( "generate rule: unable to check if configuration %v, is present in resource '%s/%s' in namespace '%s'" , rule . Generation . Data , rule . Generation . Kind , rule . Generation . Name , ns . GetName ( ) )
2019-08-26 13:34:42 -07:00
response . Success = false
2019-09-12 15:04:35 -07:00
response . Message = fmt . Sprintf ( "unable to check if configuration %v, is present in resource '%s/%s' in namespace '%s'" , rule . Generation . Data , rule . Generation . Kind , rule . Generation . Name , ns . GetName ( ) )
2019-08-26 13:34:42 -07:00
return response
2019-07-26 08:11:20 -04:00
}
if ! ok {
2019-09-12 15:04:35 -07:00
glog . V ( 4 ) . Infof ( "generate rule: configuration %v not present in resource '%s/%s' in namespace '%s'" , rule . Generation . Data , rule . Generation . Kind , rule . Generation . Name , ns . GetName ( ) )
2019-08-26 13:34:42 -07:00
response . Success = false
2019-09-12 15:04:35 -07:00
response . Message = fmt . Sprintf ( "configuration %v not present in resource '%s/%s' in namespace '%s'" , rule . Generation . Data , rule . Generation . Kind , rule . Generation . Name , ns . GetName ( ) )
2019-08-26 13:34:42 -07:00
return response
2019-07-26 08:11:20 -04:00
}
2019-08-26 13:34:42 -07:00
response . Success = true
2019-09-12 15:04:35 -07:00
response . Message = fmt . Sprintf ( "required configuration %v is present in resource '%s/%s' in namespace '%s'" , rule . Generation . Data , rule . Generation . Kind , rule . Generation . Name , ns . GetName ( ) )
2019-08-26 13:34:42 -07:00
return response
2019-07-26 08:11:20 -04:00
}
2019-08-26 13:34:42 -07:00
rdata , err = runtime . DefaultUnstructuredConverter . ToUnstructured ( & rule . Generation . Data )
2019-07-26 08:11:20 -04:00
if err != nil {
glog . Error ( err )
2019-08-26 13:34:42 -07:00
response . Success = false
response . Message = fmt . Sprintf ( "failed to parse the specified resource spec %v: %v" , rule . Generation . Data , err )
return response
2019-07-26 08:11:20 -04:00
}
}
2019-08-26 13:34:42 -07:00
if rule . Generation . Clone != ( kyverno . CloneFrom { } ) {
2019-08-14 14:56:53 -07:00
glog . V ( 4 ) . Info ( "generate rule: clone resource" )
2019-07-26 08:11:20 -04:00
// 1> Check if resource exists
2019-08-26 13:34:42 -07:00
_ , err := client . GetResource ( rule . Generation . Kind , ns . GetName ( ) , rule . Generation . Name )
2019-07-26 08:11:20 -04:00
if err == nil {
2019-09-12 15:04:35 -07:00
glog . V ( 4 ) . Infof ( "generate rule: resource '%s/%s' already present in namespace '%s'" , rule . Generation . Kind , rule . Generation . Name , ns . GetName ( ) )
2019-08-26 13:34:42 -07:00
response . Success = true
2019-09-12 15:04:35 -07:00
response . Message = fmt . Sprintf ( "resource '%s/%s' already present in namespace '%s'" , rule . Generation . Kind , rule . Generation . Name , ns . GetName ( ) )
2019-08-26 13:34:42 -07:00
return response
2019-07-26 08:11:20 -04:00
}
2019-08-14 14:56:53 -07:00
// 2> If clone already exists return
2019-08-26 13:34:42 -07:00
resource , err = client . GetResource ( rule . Generation . Kind , rule . Generation . Clone . Namespace , rule . Generation . Clone . Name )
2019-07-26 08:11:20 -04:00
if err != nil {
2019-09-12 15:04:35 -07:00
glog . V ( 4 ) . Infof ( "generate rule: clone reference resource '%s/%s' not present in namespace '%s': %v" , rule . Generation . Kind , rule . Generation . Clone . Name , rule . Generation . Clone . Namespace , err )
2019-08-26 13:34:42 -07:00
response . Success = false
2019-09-12 15:04:35 -07:00
response . Message = fmt . Sprintf ( "clone reference resource '%s/%s' not present in namespace '%s': %v" , rule . Generation . Kind , rule . Generation . Clone . Name , rule . Generation . Clone . Namespace , err )
2019-08-26 13:34:42 -07:00
return response
2019-07-26 08:11:20 -04:00
}
2019-09-12 15:04:35 -07:00
glog . V ( 4 ) . Infof ( "generate rule: clone reference resource '%s/%s' present in namespace '%s'" , rule . Generation . Kind , rule . Generation . Clone . Name , rule . Generation . Clone . Namespace )
2019-07-26 08:11:20 -04:00
rdata = resource . UnstructuredContent ( )
}
2019-08-08 15:15:47 -07:00
if processExisting {
2019-09-12 15:04:35 -07:00
glog . V ( 4 ) . Infof ( "resource '%s/%s' not found in existing namespace '%s'" , rule . Generation . Kind , rule . Generation . Name , ns . GetName ( ) )
2019-08-26 13:34:42 -07:00
response . Success = false
2019-09-12 15:04:35 -07:00
response . Message = fmt . Sprintf ( "resource '%s/%s' not found in existing namespace '%s'" , rule . Generation . Kind , rule . Generation . Name , ns . GetName ( ) )
2019-08-08 15:15:47 -07:00
// for existing resources we generate an error which indirectly generates a policy violation
2019-08-26 13:34:42 -07:00
return response
2019-08-08 15:15:47 -07:00
}
2019-07-26 08:11:20 -04:00
resource . SetUnstructuredContent ( rdata )
2019-08-26 13:34:42 -07:00
resource . SetName ( rule . Generation . Name )
2019-07-26 08:11:20 -04:00
resource . SetNamespace ( ns . GetName ( ) )
// Reset resource version
resource . SetResourceVersion ( "" )
2019-08-26 13:34:42 -07:00
_ , err = client . CreateResource ( rule . Generation . Kind , ns . GetName ( ) , resource , false )
2019-05-16 14:09:02 -07:00
if err != nil {
2019-08-26 13:34:42 -07:00
glog . V ( 4 ) . Infof ( "generate rule: unable to create resource %s/%s/%s: %v" , rule . Generation . Kind , resource . GetNamespace ( ) , resource . GetName ( ) , err )
response . Success = false
response . Message = fmt . Sprintf ( "unable to create resource %s/%s/%s: %v" , rule . Generation . Kind , resource . GetNamespace ( ) , resource . GetName ( ) , err )
return response
2019-05-13 21:27:47 +03:00
}
2019-08-26 13:34:42 -07:00
glog . V ( 4 ) . Infof ( "generate rule: created resource %s/%s/%s" , rule . Generation . Kind , resource . GetNamespace ( ) , resource . GetName ( ) )
response . Success = true
response . Message = fmt . Sprintf ( "created resource %s/%s/%s" , rule . Generation . Kind , resource . GetNamespace ( ) , resource . GetName ( ) )
return response
2019-05-13 21:27:47 +03:00
}
2019-07-26 08:11:20 -04:00
2019-08-14 14:56:53 -07:00
//checkResource checks if the config is present in th eresource
2019-07-26 08:11:20 -04:00
func checkResource ( config interface { } , resource * unstructured . Unstructured ) ( bool , error ) {
2019-10-31 13:29:03 -07:00
// we are checking if config is a subset of resource with default pattern
path , err := validateResourceWithPattern ( resource . Object , config )
2019-07-26 08:11:20 -04:00
if err != nil {
2019-10-31 13:29:03 -07:00
glog . V ( 4 ) . Infof ( "config not a subset of resource. failed at path %s: %v" , path , err )
2019-07-26 08:11:20 -04:00
return false , err
}
2019-10-31 13:29:03 -07:00
return true , nil
2019-07-26 08:11:20 -04:00
}