2020-01-07 10:33:28 -08:00
package generate
import (
2020-02-04 12:13:41 -08:00
"encoding/json"
2020-01-09 17:53:27 -08:00
"errors"
2020-01-07 10:33:28 -08:00
"fmt"
2020-01-09 17:53:27 -08:00
"reflect"
2020-01-07 10:33:28 -08:00
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/validate"
"github.com/nirmata/kyverno/pkg/engine/variables"
"github.com/nirmata/kyverno/pkg/policyviolation"
2020-01-09 17:53:27 -08:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2020-01-07 10:33:28 -08:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
func ( c * Controller ) processGR ( gr * kyverno . GenerateRequest ) error {
2020-01-07 15:13:57 -08:00
var err error
var resource * unstructured . Unstructured
var genResources [ ] kyverno . ResourceSpec
2020-01-07 10:33:28 -08:00
// 1 - Check if the resource exists
2020-01-07 15:13:57 -08:00
resource , err = getResource ( c . client , gr . Spec . Resource )
2020-01-07 10:33:28 -08:00
if err != nil {
// Dont update status
glog . V ( 4 ) . Infof ( "resource does not exist or is yet to be created, requeuing: %v" , err )
return err
}
// 2 - Apply the generate policy on the resource
2020-01-07 15:13:57 -08:00
genResources , err = c . applyGenerate ( * resource , * gr )
2020-01-07 10:33:28 -08:00
switch e := err . ( type ) {
case * Violation :
// Generate event
// - resource -> rule failed and created PV
// - policy -> failed to apply of resource and created PV
c . pvGenerator . Add ( generatePV ( * gr , * resource , e ) )
default :
// Generate event
// - resource -> rule failed
// - policy -> failed tp apply on resource
glog . V ( 4 ) . Info ( e )
}
// 3 - Report Events
reportEvents ( err , c . eventGen , * gr , * resource )
// 4 - Update Status
2020-01-07 15:13:57 -08:00
return updateStatus ( c . statusControl , * gr , err , genResources )
2020-01-07 10:33:28 -08:00
}
2020-01-07 15:13:57 -08:00
func ( c * Controller ) applyGenerate ( resource unstructured . Unstructured , gr kyverno . GenerateRequest ) ( [ ] kyverno . ResourceSpec , error ) {
2020-01-07 10:33:28 -08:00
// Get the list of rules to be applied
// get policy
policy , err := c . pLister . Get ( gr . Spec . Policy )
if err != nil {
glog . V ( 4 ) . Infof ( "policy %s not found: %v" , gr . Spec . Policy , err )
2020-01-07 15:13:57 -08:00
return nil , nil
2020-01-07 10:33:28 -08:00
}
// build context
ctx := context . NewContext ( )
resourceRaw , err := resource . MarshalJSON ( )
if err != nil {
glog . V ( 4 ) . Infof ( "failed to marshal resource: %v" , err )
2020-01-07 15:13:57 -08:00
return nil , err
2020-01-07 10:33:28 -08:00
}
2020-01-24 09:37:12 -08:00
err = ctx . AddResource ( resourceRaw )
if err != nil {
glog . Infof ( "Failed to load resource in context: %v" , err )
return nil , err
}
err = ctx . AddUserInfo ( gr . Spec . Context . UserRequestInfo )
if err != nil {
glog . Infof ( "Failed to load userInfo in context: %v" , err )
return nil , err
}
err = ctx . AddSA ( gr . Spec . Context . UserRequestInfo . AdmissionUserInfo . Username )
if err != nil {
glog . Infof ( "Failed to load serviceAccount in context: %v" , err )
return nil , err
}
2020-01-07 10:33:28 -08:00
policyContext := engine . PolicyContext {
NewResource : resource ,
Policy : * policy ,
Context : ctx ,
AdmissionInfo : gr . Spec . Context . UserRequestInfo ,
}
// check if the policy still applies to the resource
2020-01-24 12:05:53 -08:00
engineResponse := engine . Generate ( policyContext )
2020-01-07 10:33:28 -08:00
if len ( engineResponse . PolicyResponse . Rules ) == 0 {
glog . V ( 4 ) . Infof ( "policy %s, dont not apply to resource %v" , gr . Spec . Policy , gr . Spec . Resource )
2020-01-07 15:13:57 -08:00
return nil , fmt . Errorf ( "policy %s, dont not apply to resource %v" , gr . Spec . Policy , gr . Spec . Resource )
2020-01-07 10:33:28 -08:00
}
2020-01-07 15:13:57 -08:00
2020-01-10 11:59:05 -08:00
if pv := buildPathNotPresentPV ( engineResponse ) ; pv != nil {
c . pvGenerator . Add ( pv ... )
// variable substitiution fails in ruleInfo (match,exclude,condition)
// the overall policy should not apply to resource
return nil , fmt . Errorf ( "referenced path not present in generate policy %s" , policy . Name )
}
2020-01-07 10:33:28 -08:00
// Apply the generate rule on resource
2020-02-04 12:13:41 -08:00
return applyGeneratePolicy ( c . client , policyContext )
2020-01-07 10:33:28 -08:00
}
2020-01-07 15:13:57 -08:00
func updateStatus ( statusControl StatusControlInterface , gr kyverno . GenerateRequest , err error , genResources [ ] kyverno . ResourceSpec ) error {
2020-01-07 10:33:28 -08:00
if err != nil {
2020-01-07 15:13:57 -08:00
return statusControl . Failed ( gr , err . Error ( ) , genResources )
2020-01-07 10:33:28 -08:00
}
// Generate request successfully processed
2020-01-07 15:13:57 -08:00
return statusControl . Success ( gr , genResources )
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
func applyGeneratePolicy ( client * dclient . Client , policyContext engine . PolicyContext ) ( [ ] kyverno . ResourceSpec , error ) {
2020-01-07 15:13:57 -08:00
// List of generatedResources
var genResources [ ] kyverno . ResourceSpec
2020-01-07 10:33:28 -08:00
// Get the response as the actions to be performed on the resource
// - - substitute values
policy := policyContext . Policy
resource := policyContext . NewResource
ctx := policyContext . Context
// To manage existing resources, we compare the creation time for the default resiruce to be generated and policy creation time
processExisting := func ( ) bool {
rcreationTime := resource . GetCreationTimestamp ( )
pcreationTime := policy . GetCreationTimestamp ( )
return rcreationTime . Before ( & pcreationTime )
} ( )
for _ , rule := range policy . Spec . Rules {
if ! rule . HasGenerate ( ) {
continue
}
2020-02-04 12:13:41 -08:00
genResource , err := applyRule ( client , rule , resource , ctx , processExisting )
2020-01-07 15:13:57 -08:00
if err != nil {
return nil , err
2020-01-07 10:33:28 -08:00
}
2020-01-07 15:13:57 -08:00
genResources = append ( genResources , genResource )
2020-01-07 10:33:28 -08:00
}
2020-01-07 15:13:57 -08:00
return genResources , nil
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
func applyRule ( client * dclient . Client , rule kyverno . Rule , resource unstructured . Unstructured , ctx context . EvalInterface , processExisting bool ) ( kyverno . ResourceSpec , error ) {
2020-01-07 10:33:28 -08:00
var rdata map [ string ] interface { }
var err error
2020-02-04 12:13:41 -08:00
var mode ResourceMode
2020-01-07 15:13:57 -08:00
var noGenResource kyverno . ResourceSpec
2020-01-07 10:33:28 -08:00
2020-01-10 11:59:05 -08:00
if invalidPaths := variables . ValidateVariables ( ctx , rule . Generation . ResourceSpec ) ; len ( invalidPaths ) != 0 {
return noGenResource , NewViolation ( rule . Name , fmt . Errorf ( "path not present in generate resource spec: %s" , invalidPaths ) )
}
2020-01-07 10:33:28 -08:00
// variable substitution
// - name
// - namespace
// - clone.name
// - clone.namespace
gen := variableSubsitutionForAttributes ( rule . Generation , ctx )
2020-01-07 15:13:57 -08:00
// Resource to be generated
newGenResource := kyverno . ResourceSpec {
Kind : gen . Kind ,
Namespace : gen . Namespace ,
Name : gen . Name ,
}
2020-01-07 10:33:28 -08:00
// DATA
if gen . Data != nil {
2020-02-04 12:13:41 -08:00
if rdata , mode , err = handleData ( rule . Name , gen , client , resource , ctx ) ; err != nil {
2020-01-07 10:33:28 -08:00
glog . V ( 4 ) . Info ( err )
switch e := err . ( type ) {
case * ParseFailed , * NotFound , * ConfigNotFound :
// handled errors
2020-02-04 12:13:41 -08:00
return noGenResource , e
2020-01-07 10:33:28 -08:00
case * Violation :
// create policy violation
2020-01-07 15:13:57 -08:00
return noGenResource , e
2020-01-07 10:33:28 -08:00
default :
// errors that cant be handled
2020-01-07 15:13:57 -08:00
return noGenResource , e
2020-01-07 10:33:28 -08:00
}
}
if rdata == nil {
// existing resource contains the configuration
2020-01-07 15:13:57 -08:00
return newGenResource , nil
2020-01-07 10:33:28 -08:00
}
}
// CLONE
if gen . Clone != ( kyverno . CloneFrom { } ) {
2020-02-04 12:13:41 -08:00
if rdata , mode , err = handleClone ( rule . Name , gen , client , resource , ctx ) ; err != nil {
2020-01-07 15:13:57 -08:00
glog . V ( 4 ) . Info ( err )
2020-01-07 10:33:28 -08:00
switch e := err . ( type ) {
case * NotFound :
// handled errors
2020-01-07 15:13:57 -08:00
return noGenResource , e
2020-01-07 10:33:28 -08:00
default :
// errors that cant be handled
2020-01-07 15:13:57 -08:00
return noGenResource , e
2020-01-07 10:33:28 -08:00
}
}
if rdata == nil {
// resource already exists
2020-01-07 15:13:57 -08:00
return newGenResource , nil
2020-01-07 10:33:28 -08:00
}
}
if processExisting {
// handle existing resources
// policy was generated after the resource
// we do not create new resource
2020-01-07 15:13:57 -08:00
return noGenResource , err
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
// build the resource template
2020-01-07 10:33:28 -08:00
newResource := & unstructured . Unstructured { }
newResource . SetUnstructuredContent ( rdata )
newResource . SetName ( gen . Name )
newResource . SetNamespace ( gen . Namespace )
2020-02-10 12:44:20 -08:00
// manage labels
// - app.kubernetes.io/managed-by: kyverno
// - kyverno.io/generated-by: kind/namespace/name (trigger resource)
manageLabels ( newResource , resource )
2020-02-04 12:13:41 -08:00
if mode == Create {
// Reset resource version
newResource . SetResourceVersion ( "" )
// Create the resource
glog . V ( 4 ) . Infof ( "Creating new resource %s/%s/%s" , gen . Kind , gen . Namespace , gen . Name )
_ , err = client . CreateResource ( gen . Kind , gen . Namespace , newResource , false )
if err != nil {
// Failed to create resource
return noGenResource , err
}
glog . V ( 4 ) . Infof ( "Created new resource %s/%s/%s" , gen . Kind , gen . Namespace , gen . Name )
} else if mode == Update {
glog . V ( 4 ) . Infof ( "Updating existing resource %s/%s/%s" , gen . Kind , gen . Namespace , gen . Name )
// Update the resource
_ , err := client . UpdateResource ( gen . Kind , gen . Namespace , newResource , false )
if err != nil {
// Failed to update resource
return noGenResource , err
}
glog . V ( 4 ) . Infof ( "Updated existing resource %s/%s/%s" , gen . Kind , gen . Namespace , gen . Name )
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
2020-01-07 15:13:57 -08:00
return newGenResource , nil
2020-01-07 10:33:28 -08:00
}
func variableSubsitutionForAttributes ( gen kyverno . Generation , ctx context . EvalInterface ) kyverno . Generation {
// Name
name := gen . Name
namespace := gen . Namespace
newNameVar := variables . SubstituteVariables ( ctx , name )
if newName , ok := newNameVar . ( string ) ; ok {
gen . Name = newName
}
newNamespaceVar := variables . SubstituteVariables ( ctx , namespace )
if newNamespace , ok := newNamespaceVar . ( string ) ; ok {
gen . Namespace = newNamespace
}
2020-01-10 08:01:18 -08:00
if gen . Clone != ( kyverno . CloneFrom { } ) {
// Clone
cloneName := gen . Clone . Name
cloneNamespace := gen . Clone . Namespace
newcloneNameVar := variables . SubstituteVariables ( ctx , cloneName )
if newcloneName , ok := newcloneNameVar . ( string ) ; ok {
gen . Clone . Name = newcloneName
}
newcloneNamespaceVar := variables . SubstituteVariables ( ctx , cloneNamespace )
if newcloneNamespace , ok := newcloneNamespaceVar . ( string ) ; ok {
gen . Clone . Namespace = newcloneNamespace
}
2020-01-07 10:33:28 -08:00
}
return gen
}
2020-02-04 12:13:41 -08:00
// ResourceMode defines the mode for generated resource
type ResourceMode string
const (
//Skip : failed to process rule, will not update the resource
Skip ResourceMode = "SKIP"
//Create : create a new resource
Create = "CREATE"
//Update : update/override the new resource
Update = "UPDATE"
)
func copyInterface ( original interface { } ) ( interface { } , error ) {
tempData , err := json . Marshal ( original )
if err != nil {
return nil , err
}
fmt . Println ( string ( tempData ) )
var temp interface { }
err = json . Unmarshal ( tempData , & temp )
if err != nil {
return nil , err
2020-01-10 11:59:05 -08:00
}
2020-02-04 12:13:41 -08:00
return temp , nil
}
2020-01-10 11:59:05 -08:00
2020-02-04 12:13:41 -08:00
// manage the creation/update of resource to be generated using the spec defined in the policy
func handleData ( ruleName string , generateRule kyverno . Generation , client * dclient . Client , resource unstructured . Unstructured , ctx context . EvalInterface ) ( map [ string ] interface { } , ResourceMode , error ) {
//work on copy of the data
2020-02-10 12:44:20 -08:00
// as the type of data stored in interface is not know,
2020-02-04 12:13:41 -08:00
// we marshall the data and unmarshal it into a new resource to create a copy
dataCopy , err := copyInterface ( generateRule . Data )
if err != nil {
glog . V ( 4 ) . Infof ( "failed to create a copy of the interface %v" , generateRule . Data )
return nil , Skip , err
}
// replace variables with the corresponding values
newData := variables . SubstituteVariables ( ctx , dataCopy )
// if any variable defined in the data is not avaialbe in the context
if invalidPaths := variables . ValidateVariables ( ctx , newData ) ; len ( invalidPaths ) != 0 {
return nil , Skip , NewViolation ( ruleName , fmt . Errorf ( "path not present in generate data: %s" , invalidPaths ) )
}
2020-01-07 10:33:28 -08:00
// check if resource exists
obj , err := client . GetResource ( generateRule . Kind , generateRule . Namespace , generateRule . Name )
2020-01-09 17:53:27 -08:00
if apierrors . IsNotFound ( err ) {
2020-01-07 10:33:28 -08:00
// Resource does not exist
2020-02-04 12:13:41 -08:00
// Processing the request first time
rdata , err := runtime . DefaultUnstructuredConverter . ToUnstructured ( & newData )
if err != nil {
return nil , Skip , NewParseFailed ( newData , err )
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
glog . V ( 4 ) . Infof ( "Resource %s/%s/%s does not exists, will try to create" , generateRule . Kind , generateRule . Namespace , generateRule . Name )
return rdata , Create , nil
2020-01-07 10:33:28 -08:00
}
if err != nil {
//something wrong while fetching resource
2020-02-04 12:13:41 -08:00
return nil , Skip , err
2020-01-07 10:33:28 -08:00
}
// Resource exists; verfiy the content of the resource
ok , err := checkResource ( ctx , newData , obj )
if err != nil {
2020-02-04 12:13:41 -08:00
// error while evaluating if the existing resource contains the required information
return nil , Skip , err
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
2020-01-07 10:33:28 -08:00
if ! ok {
2020-02-04 12:13:41 -08:00
// existing resource does not contain the configuration mentioned in spec, will try to update
rdata , err := runtime . DefaultUnstructuredConverter . ToUnstructured ( & newData )
if err != nil {
return nil , Skip , NewParseFailed ( newData , err )
}
glog . V ( 4 ) . Infof ( "Resource %s/%s/%s exists but missing required configuration, will try to update" , generateRule . Kind , generateRule . Namespace , generateRule . Name )
return rdata , Update , nil
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
// Existing resource does contain the mentioned configuration in spec, skip processing the resource as it is already in expected state
return nil , Skip , nil
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
// manage the creation/update based on the reference clone resource
func handleClone ( ruleName string , generateRule kyverno . Generation , client * dclient . Client , resource unstructured . Unstructured , ctx context . EvalInterface ) ( map [ string ] interface { } , ResourceMode , error ) {
// if any variable defined in the data is not avaialbe in the context
2020-01-10 11:59:05 -08:00
if invalidPaths := variables . ValidateVariables ( ctx , generateRule . Clone ) ; len ( invalidPaths ) != 0 {
2020-02-04 12:13:41 -08:00
return nil , Skip , NewViolation ( ruleName , fmt . Errorf ( "path not present in generate clone: %s" , invalidPaths ) )
2020-01-10 11:59:05 -08:00
}
2020-02-04 12:13:41 -08:00
// check if resource to be generated exists
2020-01-07 10:33:28 -08:00
_ , err := client . GetResource ( generateRule . Kind , generateRule . Namespace , generateRule . Name )
if err == nil {
2020-02-04 12:13:41 -08:00
// resource does exists, not need to process further as it is already in expected state
return nil , Skip , nil
2020-01-07 10:33:28 -08:00
}
2020-01-09 17:53:27 -08:00
if ! apierrors . IsNotFound ( err ) {
2020-01-07 10:33:28 -08:00
//something wrong while fetching resource
2020-02-04 12:13:41 -08:00
return nil , Skip , err
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
// get clone resource reference in the rule
2020-01-07 10:33:28 -08:00
obj , err := client . GetResource ( generateRule . Kind , generateRule . Clone . Namespace , generateRule . Clone . Name )
2020-01-09 17:53:27 -08:00
if apierrors . IsNotFound ( err ) {
2020-02-04 12:13:41 -08:00
// reference resource does not exist, cant generate the resources
return nil , Skip , NewNotFound ( generateRule . Kind , generateRule . Clone . Namespace , generateRule . Clone . Name )
2020-01-07 10:33:28 -08:00
}
if err != nil {
//something wrong while fetching resource
2020-02-04 12:13:41 -08:00
return nil , Skip , err
2020-01-07 10:33:28 -08:00
}
2020-02-04 12:13:41 -08:00
// create the resource based on the reference clone
return obj . UnstructuredContent ( ) , Create , nil
2020-01-07 10:33:28 -08:00
}
func checkResource ( ctx context . EvalInterface , newResourceSpec interface { } , resource * unstructured . Unstructured ) ( bool , error ) {
// check if the resource spec if a subset of the resource
path , err := validate . ValidateResourceWithPattern ( ctx , resource . Object , newResourceSpec )
2020-01-09 17:53:27 -08:00
if ! reflect . DeepEqual ( err , validate . ValidationError { } ) {
2020-01-07 10:33:28 -08:00
glog . V ( 4 ) . Infof ( "config not a subset of resource. failed at path %s: %v" , path , err )
2020-01-09 17:53:27 -08:00
return false , errors . New ( err . ErrorMsg )
2020-01-07 10:33:28 -08:00
}
return true , nil
}
func generatePV ( gr kyverno . GenerateRequest , resource unstructured . Unstructured , err * Violation ) policyviolation . Info {
info := policyviolation . Info {
PolicyName : gr . Spec . Policy ,
Resource : resource ,
2020-02-03 13:38:24 -08:00
Rules : [ ] kyverno . ViolatedRule { {
2020-01-07 10:33:28 -08:00
Name : err . rule ,
Type : "Generation" ,
Message : err . Error ( ) ,
} } ,
}
return info
}