2019-06-18 11:47:45 -07:00
|
|
|
package webhooks
|
|
|
|
|
|
|
|
import (
|
2019-07-15 16:07:56 -07:00
|
|
|
"fmt"
|
2019-06-18 11:47:45 -07:00
|
|
|
"strings"
|
2019-06-19 14:05:23 -07:00
|
|
|
|
2020-03-17 11:05:20 -07:00
|
|
|
"github.com/go-logr/logr"
|
2022-03-25 16:40:25 +08:00
|
|
|
wildcard "github.com/kyverno/go-wildcard"
|
2021-10-29 18:13:20 +02:00
|
|
|
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
2022-03-28 16:01:27 +02:00
|
|
|
"github.com/kyverno/kyverno/pkg/autogen"
|
2020-10-07 11:12:31 -07:00
|
|
|
"github.com/kyverno/kyverno/pkg/engine/response"
|
|
|
|
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
2020-04-22 20:45:15 +05:30
|
|
|
yamlv2 "gopkg.in/yaml.v2"
|
2019-11-13 13:13:07 -08:00
|
|
|
"k8s.io/api/admission/v1beta1"
|
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
2019-06-18 11:47:45 -07:00
|
|
|
)
|
|
|
|
|
2020-12-01 22:50:40 -08:00
|
|
|
// isResponseSuccessful return true if all responses are successful
|
2020-12-23 15:10:07 -08:00
|
|
|
func isResponseSuccessful(engineReponses []*response.EngineResponse) bool {
|
2019-08-23 18:34:23 -07:00
|
|
|
for _, er := range engineReponses {
|
2020-06-30 11:53:27 -07:00
|
|
|
if !er.IsSuccessful() {
|
2019-08-23 18:34:23 -07:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-01-21 18:06:44 +05:30
|
|
|
func checkEngineResponse(er *response.EngineResponse) bool {
|
2022-03-23 09:59:41 +01:00
|
|
|
var nsAction kyverno.ValidationFailureAction
|
2022-01-21 18:06:44 +05:30
|
|
|
actionOverride := false
|
|
|
|
|
|
|
|
for _, v := range er.PolicyResponse.ValidationFailureActionOverrides {
|
|
|
|
action := v.Action
|
2022-03-23 09:59:41 +01:00
|
|
|
if action != kyverno.Enforce && action != kyverno.Audit {
|
2022-01-21 18:06:44 +05:30
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ns := range v.Namespaces {
|
|
|
|
if wildcard.Match(ns, er.PatchedResource.GetNamespace()) {
|
|
|
|
nsAction = action
|
|
|
|
actionOverride = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if actionOverride {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-23 09:59:41 +01:00
|
|
|
return !er.IsSuccessful() && ((actionOverride && nsAction == kyverno.Enforce) || (!actionOverride && er.PolicyResponse.ValidationFailureAction == kyverno.Enforce))
|
2022-01-21 18:06:44 +05:30
|
|
|
}
|
|
|
|
|
2019-09-06 10:18:45 -07:00
|
|
|
// returns true -> if there is even one policy that blocks resource request
|
2019-08-23 18:34:23 -07:00
|
|
|
// returns false -> if all the policies are meant to report only, we dont block resource request
|
2020-12-23 15:10:07 -08:00
|
|
|
func toBlockResource(engineReponses []*response.EngineResponse, log logr.Logger) bool {
|
2019-08-23 18:34:23 -07:00
|
|
|
for _, er := range engineReponses {
|
2022-01-21 18:06:44 +05:30
|
|
|
if checkEngineResponse(er) {
|
2021-06-30 00:43:11 +03:00
|
|
|
log.Info("spec.ValidationFailureAction set to enforce blocking resource request", "policy", er.PolicyResponse.Policy.Name)
|
2019-08-23 18:34:23 -07:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2021-07-09 18:01:46 -07:00
|
|
|
|
2020-11-25 00:21:51 -08:00
|
|
|
log.V(4).Info("spec.ValidationFailureAction set to audit for all applicable policies, won't block resource operation")
|
2019-08-23 18:34:23 -07:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-01-16 11:57:28 -08:00
|
|
|
// getEnforceFailureErrorMsg gets the error messages for failed enforce policy
|
2020-12-23 15:10:07 -08:00
|
|
|
func getEnforceFailureErrorMsg(engineResponses []*response.EngineResponse) string {
|
2020-03-06 17:11:33 +05:30
|
|
|
policyToRule := make(map[string]interface{})
|
|
|
|
var resourceName string
|
2020-03-16 14:08:13 +05:30
|
|
|
for _, er := range engineResponses {
|
2022-01-21 18:06:44 +05:30
|
|
|
if checkEngineResponse(er) {
|
2020-03-06 17:11:33 +05:30
|
|
|
ruleToReason := make(map[string]string)
|
2020-01-16 11:57:28 -08:00
|
|
|
for _, rule := range er.PolicyResponse.Rules {
|
2021-09-26 02:12:31 -07:00
|
|
|
if rule.Status != response.RuleStatusPass {
|
2020-03-06 17:11:33 +05:30
|
|
|
ruleToReason[rule.Name] = rule.Message
|
2020-01-16 11:57:28 -08:00
|
|
|
}
|
|
|
|
}
|
2020-03-06 03:47:49 +05:30
|
|
|
|
2021-07-09 18:01:46 -07:00
|
|
|
resourceName = fmt.Sprintf("%s/%s/%s", er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name)
|
2021-06-30 00:43:11 +03:00
|
|
|
policyToRule[er.PolicyResponse.Policy.Name] = ruleToReason
|
2020-01-16 11:57:28 -08:00
|
|
|
}
|
|
|
|
}
|
2020-03-06 03:47:49 +05:30
|
|
|
|
2020-03-06 17:11:33 +05:30
|
|
|
result, _ := yamlv2.Marshal(policyToRule)
|
|
|
|
return "\n\nresource " + resourceName + " was blocked due to the following policies\n\n" + string(result)
|
2020-01-16 11:57:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// getErrorMsg gets all failed engine response message
|
2020-12-23 15:10:07 -08:00
|
|
|
func getErrorMsg(engineReponses []*response.EngineResponse) string {
|
2019-08-23 18:34:23 -07:00
|
|
|
var str []string
|
2019-11-06 17:14:32 -08:00
|
|
|
var resourceInfo string
|
|
|
|
|
2019-08-23 18:34:23 -07:00
|
|
|
for _, er := range engineReponses {
|
2020-06-30 11:53:27 -07:00
|
|
|
if !er.IsSuccessful() {
|
2019-11-06 17:14:32 -08:00
|
|
|
// resource in engineReponses is identical as this was called per admission request
|
|
|
|
resourceInfo = fmt.Sprintf("%s/%s/%s", er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name)
|
2021-06-30 00:43:11 +03:00
|
|
|
str = append(str, fmt.Sprintf("failed policy %s:", er.PolicyResponse.Policy.Name))
|
2019-08-23 18:34:23 -07:00
|
|
|
for _, rule := range er.PolicyResponse.Rules {
|
2021-09-26 02:12:31 -07:00
|
|
|
if rule.Status != response.RuleStatusPass {
|
2019-08-23 18:34:23 -07:00
|
|
|
str = append(str, rule.ToString())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-06 17:07:11 -08:00
|
|
|
return fmt.Sprintf("Resource %s %s", resourceInfo, strings.Join(str, ";"))
|
2019-08-23 18:34:23 -07:00
|
|
|
}
|
|
|
|
|
2019-07-23 00:55:45 -04:00
|
|
|
//ArrayFlags to store filterkinds
|
2019-06-18 11:47:45 -07:00
|
|
|
type ArrayFlags []string
|
|
|
|
|
|
|
|
func (i *ArrayFlags) String() string {
|
|
|
|
var sb strings.Builder
|
|
|
|
for _, str := range *i {
|
|
|
|
sb.WriteString(str)
|
|
|
|
}
|
|
|
|
return sb.String()
|
|
|
|
}
|
|
|
|
|
2019-07-23 00:55:45 -04:00
|
|
|
//Set setter for array flags
|
2019-06-18 11:47:45 -07:00
|
|
|
func (i *ArrayFlags) Set(value string) error {
|
|
|
|
*i = append(*i, value)
|
|
|
|
return nil
|
|
|
|
}
|
2019-06-19 14:05:23 -07:00
|
|
|
|
2020-03-17 11:05:20 -07:00
|
|
|
func processResourceWithPatches(patch []byte, resource []byte, log logr.Logger) []byte {
|
2019-08-23 18:34:23 -07:00
|
|
|
if patch == nil {
|
2020-01-13 10:15:52 -08:00
|
|
|
return resource
|
2019-08-23 18:34:23 -07:00
|
|
|
}
|
2019-10-07 18:31:14 -07:00
|
|
|
|
2020-01-07 17:06:17 -08:00
|
|
|
resource, err := engineutils.ApplyPatchNew(resource, patch)
|
2019-08-23 18:34:23 -07:00
|
|
|
if err != nil {
|
2021-01-19 11:08:06 -08:00
|
|
|
log.Error(err, "failed to patch resource:", "patch", string(patch), "resource", string(resource))
|
2019-08-23 18:34:23 -07:00
|
|
|
return nil
|
|
|
|
}
|
2021-07-09 18:01:46 -07:00
|
|
|
|
|
|
|
log.V(6).Info("", "patchedResource", string(resource))
|
2019-08-23 18:34:23 -07:00
|
|
|
return resource
|
|
|
|
}
|
2019-11-11 14:52:09 -08:00
|
|
|
|
2021-07-09 18:01:46 -07:00
|
|
|
func containsRBACInfo(policies ...[]*kyverno.ClusterPolicy) bool {
|
2020-07-02 12:49:10 -07:00
|
|
|
for _, policySlice := range policies {
|
|
|
|
for _, policy := range policySlice {
|
2022-03-28 16:01:27 +02:00
|
|
|
for _, rule := range autogen.ComputeRules(policy) {
|
2022-01-20 13:59:16 +05:30
|
|
|
if checkForRBACInfo(rule) {
|
|
|
|
return true
|
|
|
|
}
|
2022-01-05 22:38:24 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkForRBACInfo(rule kyverno.Rule) bool {
|
|
|
|
if len(rule.MatchResources.Roles) > 0 || len(rule.MatchResources.ClusterRoles) > 0 || len(rule.ExcludeResources.Roles) > 0 || len(rule.ExcludeResources.ClusterRoles) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if len(rule.MatchResources.All) > 0 {
|
|
|
|
for _, rf := range rule.MatchResources.All {
|
|
|
|
if len(rf.UserInfo.Roles) > 0 || len(rf.UserInfo.ClusterRoles) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(rule.MatchResources.Any) > 0 {
|
|
|
|
for _, rf := range rule.MatchResources.Any {
|
|
|
|
if len(rf.UserInfo.Roles) > 0 || len(rf.UserInfo.ClusterRoles) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(rule.ExcludeResources.All) > 0 {
|
|
|
|
for _, rf := range rule.ExcludeResources.All {
|
|
|
|
if len(rf.UserInfo.Roles) > 0 || len(rf.UserInfo.ClusterRoles) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(rule.ExcludeResources.Any) > 0 {
|
|
|
|
for _, rf := range rule.ExcludeResources.Any {
|
|
|
|
if len(rf.UserInfo.Roles) > 0 || len(rf.UserInfo.ClusterRoles) > 0 {
|
|
|
|
return true
|
2019-11-11 14:52:09 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2019-11-13 13:13:07 -08:00
|
|
|
|
|
|
|
// extracts the new and old resource as unstructured
|
2020-01-07 11:32:52 -08:00
|
|
|
func extractResources(newRaw []byte, request *v1beta1.AdmissionRequest) (unstructured.Unstructured, unstructured.Unstructured, error) {
|
2019-11-13 13:13:07 -08:00
|
|
|
var emptyResource unstructured.Unstructured
|
2020-01-07 11:32:52 -08:00
|
|
|
|
2019-11-13 13:13:07 -08:00
|
|
|
// New Resource
|
2020-01-11 18:33:11 +05:30
|
|
|
if newRaw == nil {
|
|
|
|
newRaw = request.Object.Raw
|
|
|
|
}
|
2019-11-13 13:13:07 -08:00
|
|
|
if newRaw == nil {
|
|
|
|
return emptyResource, emptyResource, fmt.Errorf("new resource is not defined")
|
|
|
|
}
|
2020-01-07 11:32:52 -08:00
|
|
|
|
|
|
|
new, err := convertResource(newRaw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
|
2019-11-13 13:13:07 -08:00
|
|
|
if err != nil {
|
|
|
|
return emptyResource, emptyResource, fmt.Errorf("failed to convert new raw to unstructured: %v", err)
|
|
|
|
}
|
2020-01-07 11:32:52 -08:00
|
|
|
|
2019-11-13 13:13:07 -08:00
|
|
|
// Old Resource - Optional
|
|
|
|
oldRaw := request.OldObject.Raw
|
|
|
|
if oldRaw == nil {
|
2020-01-07 11:32:52 -08:00
|
|
|
return new, emptyResource, nil
|
2019-11-13 13:13:07 -08:00
|
|
|
}
|
2020-01-07 11:32:52 -08:00
|
|
|
|
|
|
|
old, err := convertResource(oldRaw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
|
2019-11-13 13:13:07 -08:00
|
|
|
if err != nil {
|
|
|
|
return emptyResource, emptyResource, fmt.Errorf("failed to convert old raw to unstructured: %v", err)
|
|
|
|
}
|
2020-01-07 11:32:52 -08:00
|
|
|
return new, old, err
|
2019-11-13 13:13:07 -08:00
|
|
|
}
|
|
|
|
|
2020-01-07 11:32:52 -08:00
|
|
|
// convertResource converts raw bytes to an unstructured object
|
|
|
|
func convertResource(raw []byte, group, version, kind, namespace string) (unstructured.Unstructured, error) {
|
2020-01-07 17:06:17 -08:00
|
|
|
obj, err := engineutils.ConvertToUnstructured(raw)
|
2019-11-13 13:13:07 -08:00
|
|
|
if err != nil {
|
2020-01-07 11:32:52 -08:00
|
|
|
return unstructured.Unstructured{}, fmt.Errorf("failed to convert raw to unstructured: %v", err)
|
2019-11-13 13:13:07 -08:00
|
|
|
}
|
2020-01-07 11:32:52 -08:00
|
|
|
|
|
|
|
obj.SetGroupVersionKind(schema.GroupVersionKind{Group: group, Version: version, Kind: kind})
|
|
|
|
obj.SetNamespace(namespace)
|
|
|
|
return *obj, nil
|
2019-11-13 13:13:07 -08:00
|
|
|
}
|
2020-05-18 20:01:20 -07:00
|
|
|
|
|
|
|
func excludeKyvernoResources(kind string) bool {
|
|
|
|
switch kind {
|
2020-12-01 22:50:40 -08:00
|
|
|
case "ClusterPolicyReport":
|
|
|
|
return true
|
|
|
|
case "PolicyReport":
|
|
|
|
return true
|
|
|
|
case "ReportChangeRequest":
|
|
|
|
return true
|
|
|
|
case "GenerateRequest":
|
|
|
|
return true
|
|
|
|
case "ClusterReportChangeRequest":
|
2020-05-18 20:01:20 -07:00
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|