2019-08-13 11:32:12 -07:00
package policy
import (
2019-12-26 11:50:41 -08:00
"encoding/json"
2019-08-26 13:34:42 -07:00
"fmt"
"reflect"
2019-12-26 11:50:41 -08:00
"strings"
2019-08-13 11:32:12 -07:00
"time"
jsonpatch "github.com/evanphx/json-patch"
2020-03-17 11:05:20 -07:00
"github.com/go-logr/logr"
2019-11-13 13:41:08 -08:00
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
2019-08-13 11:32:12 -07:00
"github.com/nirmata/kyverno/pkg/engine"
2019-12-30 17:08:50 -08:00
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/response"
2019-08-13 11:32:12 -07:00
"github.com/nirmata/kyverno/pkg/utils"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// applyPolicy applies policy on a resource
//TODO: generation rules
2020-03-17 11:05:20 -07:00
func applyPolicy ( policy kyverno . ClusterPolicy , resource unstructured . Unstructured , policyStatus PolicyStatusInterface , log logr . Logger ) ( responses [ ] response . EngineResponse ) {
logger := log . WithValues ( "kind" , resource . GetKind ( ) , "namespace" , resource . GetNamespace ( ) , "name" , resource . GetName ( ) )
2019-08-13 11:32:12 -07:00
startTime := time . Now ( )
2019-08-26 13:34:42 -07:00
var policyStats [ ] PolicyStat
2020-03-17 11:05:20 -07:00
logger . Info ( "start applying policy" , "startTime" , startTime )
2019-08-13 11:32:12 -07:00
defer func ( ) {
2020-03-17 11:05:20 -07:00
logger . Info ( "finisnhed applying policy" , "processingTime" , time . Since ( startTime ) )
2019-08-13 11:32:12 -07:00
} ( )
2019-08-26 13:34:42 -07:00
// gather stats from the engine response
2019-12-30 17:08:50 -08:00
gatherStat := func ( policyName string , policyResponse response . PolicyResponse ) {
2019-08-26 13:34:42 -07:00
ps := PolicyStat { }
ps . PolicyName = policyName
ps . Stats . MutationExecutionTime = policyResponse . ProcessingTime
ps . Stats . RulesAppliedCount = policyResponse . RulesAppliedCount
2019-09-03 17:43:36 -07:00
// capture rule level stats
for _ , rule := range policyResponse . Rules {
rs := RuleStatinfo { }
rs . RuleName = rule . Name
rs . ExecutionTime = rule . RuleStats . ProcessingTime
if rule . Success {
rs . RuleAppliedCount ++
} else {
rs . RulesFailedCount ++
}
2019-09-03 18:31:57 -07:00
if rule . Patches != nil {
rs . MutationCount ++
}
2019-09-03 17:43:36 -07:00
ps . Stats . Rules = append ( ps . Stats . Rules , rs )
}
2019-08-26 13:34:42 -07:00
policyStats = append ( policyStats , ps )
}
// send stats for aggregation
sendStat := func ( blocked bool ) {
for _ , stat := range policyStats {
stat . Stats . ResourceBlocked = utils . Btoi ( blocked )
//SEND
policyStatus . SendStat ( stat )
}
}
2019-12-30 17:08:50 -08:00
var engineResponses [ ] response . EngineResponse
var engineResponse response . EngineResponse
2019-08-26 13:34:42 -07:00
var err error
2019-12-30 17:08:50 -08:00
// build context
ctx := context . NewContext ( )
ctx . AddResource ( transformResource ( resource ) )
2019-08-13 17:24:05 -07:00
2019-08-13 11:32:12 -07:00
//MUTATION
2020-03-17 11:05:20 -07:00
engineResponse , err = mutation ( policy , resource , policyStatus , ctx , logger )
2019-08-26 13:34:42 -07:00
engineResponses = append ( engineResponses , engineResponse )
2019-08-13 11:32:12 -07:00
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to process mutation rule" )
2019-08-13 11:32:12 -07:00
}
2019-08-26 13:34:42 -07:00
gatherStat ( policy . Name , engineResponse . PolicyResponse )
//send stats
sendStat ( false )
2019-08-13 11:32:12 -07:00
//VALIDATION
2019-12-30 17:08:50 -08:00
engineResponse = engine . Validate ( engine . PolicyContext { Policy : policy , Context : ctx , NewResource : resource } )
2019-08-26 13:34:42 -07:00
engineResponses = append ( engineResponses , engineResponse )
2019-08-20 16:57:19 -07:00
// gather stats
2019-08-26 13:34:42 -07:00
gatherStat ( policy . Name , engineResponse . PolicyResponse )
2019-08-20 16:57:19 -07:00
//send stats
sendStat ( false )
2019-08-13 11:32:12 -07:00
//TODO: GENERATION
2019-08-26 13:34:42 -07:00
return engineResponses
2019-08-13 11:32:12 -07:00
}
2020-03-17 11:05:20 -07:00
func mutation ( policy kyverno . ClusterPolicy , resource unstructured . Unstructured , policyStatus PolicyStatusInterface , ctx context . EvalInterface , log logr . Logger ) ( response . EngineResponse , error ) {
2019-12-30 17:08:50 -08:00
engineResponse := engine . Mutate ( engine . PolicyContext { Policy : policy , NewResource : resource , Context : ctx } )
2019-08-26 13:34:42 -07:00
if ! engineResponse . IsSuccesful ( ) {
2020-03-17 11:05:20 -07:00
log . V ( 4 ) . Info ( "failed to apply mutation rules; reporting them" )
2019-08-26 13:34:42 -07:00
return engineResponse , nil
2019-08-20 16:57:19 -07:00
}
2019-08-26 13:34:42 -07:00
// Verify if the JSON pathes returned by the Mutate are already applied to the resource
if reflect . DeepEqual ( resource , engineResponse . PatchedResource ) {
// resources matches
2020-03-17 11:05:20 -07:00
log . V ( 4 ) . Info ( "resource already satisfys the policy" )
2019-08-26 13:34:42 -07:00
return engineResponse , nil
2019-08-20 16:57:19 -07:00
}
2020-03-17 11:05:20 -07:00
return getFailedOverallRuleInfo ( resource , engineResponse , log )
2019-08-26 13:34:42 -07:00
}
2019-08-20 16:57:19 -07:00
2019-08-26 13:34:42 -07:00
// getFailedOverallRuleInfo gets detailed info for over-all mutation failure
2020-03-17 11:05:20 -07:00
func getFailedOverallRuleInfo ( resource unstructured . Unstructured , engineResponse response . EngineResponse , log logr . Logger ) ( response . EngineResponse , error ) {
2019-08-26 13:34:42 -07:00
rawResource , err := resource . MarshalJSON ( )
if err != nil {
2020-03-17 11:05:20 -07:00
log . Error ( err , "faield to marshall resource" )
2019-12-30 17:08:50 -08:00
return response . EngineResponse { } , err
2019-08-13 11:32:12 -07:00
}
2019-08-26 13:34:42 -07:00
// resource does not match so there was a mutation rule violated
for index , rule := range engineResponse . PolicyResponse . Rules {
2020-03-17 11:05:20 -07:00
log . V ( 4 ) . Info ( "veriying if policy rule was applied before" , "rule" , rule . Name )
2019-08-26 13:34:42 -07:00
if len ( rule . Patches ) == 0 {
continue
2019-08-13 11:32:12 -07:00
}
2019-08-26 13:34:42 -07:00
patch , err := jsonpatch . DecodePatch ( utils . JoinPatches ( rule . Patches ) )
if err != nil {
2020-03-17 11:05:20 -07:00
log . Error ( err , "failed to decode JSON patch" , "patches" , rule . Patches )
2019-12-30 17:08:50 -08:00
return response . EngineResponse { } , err
2019-08-26 13:34:42 -07:00
}
2019-08-13 11:32:12 -07:00
2019-08-26 13:34:42 -07:00
// apply the patches returned by mutate to the original resource
patchedResource , err := patch . Apply ( rawResource )
if err != nil {
2020-03-17 11:05:20 -07:00
log . Error ( err , "failed to apply JSON patch" , "patches" , rule . Patches )
2019-12-30 17:08:50 -08:00
return response . EngineResponse { } , err
2019-08-26 13:34:42 -07:00
}
if ! jsonpatch . Equal ( patchedResource , rawResource ) {
2020-03-17 11:05:20 -07:00
log . V ( 4 ) . Info ( "policy rule conditions not satisfied by resource" , "rule" , rule . Name )
2019-08-26 13:34:42 -07:00
engineResponse . PolicyResponse . Rules [ index ] . Success = false
2020-03-17 11:05:20 -07:00
engineResponse . PolicyResponse . Rules [ index ] . Message = fmt . Sprintf ( "mutation json patches not found at resource path %s" , extractPatchPath ( rule . Patches , log ) )
2019-08-26 13:34:42 -07:00
}
2019-08-13 11:32:12 -07:00
}
2019-08-26 13:34:42 -07:00
return engineResponse , nil
2019-08-13 11:32:12 -07:00
}
2019-12-26 11:50:41 -08:00
type jsonPatch struct {
Op string ` json:"op" `
Path string ` json:"path" `
Value interface { } ` json:"value" `
}
2020-03-17 11:05:20 -07:00
func extractPatchPath ( patches [ ] [ ] byte , log logr . Logger ) string {
2019-12-26 11:50:41 -08:00
var resultPath [ ] string
// extract the patch path and value
for _ , patch := range patches {
2020-03-17 11:05:20 -07:00
log . V ( 4 ) . Info ( "expected json patch not found in resource" , "patch" , string ( patch ) )
2019-12-26 11:50:41 -08:00
var data jsonPatch
if err := json . Unmarshal ( patch , & data ) ; err != nil {
2020-03-17 11:05:20 -07:00
log . Error ( err , "failed to decode the generate patch" , "patch" , string ( patch ) )
2019-12-26 11:50:41 -08:00
continue
}
resultPath = append ( resultPath , data . Path )
}
return strings . Join ( resultPath , ";" )
}