Skip to content

Commit

Permalink
feat: Add logic for Kyverno policy recommendation
Browse files Browse the repository at this point in the history
Signed-off-by: Vyom-Yadav <jackhammervyom@gmail.com>
  • Loading branch information
Vyom-Yadav committed Mar 31, 2023
1 parent ce80984 commit d0dfd5f
Show file tree
Hide file tree
Showing 31 changed files with 1,259 additions and 210 deletions.
256 changes: 256 additions & 0 deletions src/admissioncontrollerpolicy/admissionControllerPolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package admissioncontrollerpolicy

import (
"context"
"fmt"
"github.com/accuknox/auto-policy-discovery/src/cluster"
cfg "github.com/accuknox/auto-policy-discovery/src/config"
"github.com/accuknox/auto-policy-discovery/src/libs"
logger "github.com/accuknox/auto-policy-discovery/src/logging"
obs "github.com/accuknox/auto-policy-discovery/src/observability"
opb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/observability"
wpb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/worker"
"github.com/accuknox/auto-policy-discovery/src/types"
"github.com/clarketm/json"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/rs/zerolog"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"path/filepath"
"regexp"
"sigs.k8s.io/yaml"
"strings"
)

var log *zerolog.Logger
var CfgDB types.ConfigDB

type containerMountPathServiceAccountToken struct {
podName string
podNamespace string
containerName string
saTokenMountPath string
}

func init() {
log = logger.GetInstance()
}

func InitAdmissionControllerPolicyDiscoveryConfiguration() {
CfgDB = cfg.GetCfgDB()
}

func UpdateOrInsertKyvernoPolicies(kyvernoPolicies []kyvernov1.Policy) {
UpdateOrInsertPolicyYamlToDB(kyvernoPolicies)
}

func DeleteKyvernoPolicies(kyvernoPolicies []string, namespace string, labels map[string]string) {
for _, kyvernoPolicy := range kyvernoPolicies {
err := libs.DeletePolicyBasedOnPolicyName(CfgDB, kyvernoPolicy, namespace, libs.LabelMapToString(labels))
if err != nil {
log.Warn().Msgf("deleting policy from DB failed err=%v", err.Error())
}
}
}

// UpdateOrInsertPolicyYamlToDB upserts admission controller policies to DB
func UpdateOrInsertPolicyYamlToDB(kyvernoPolicies []kyvernov1.Policy) {

var res []types.PolicyYaml
for _, kyvernoPolicy := range kyvernoPolicies {
jsonBytes, err := json.Marshal(kyvernoPolicy)
if err != nil {
log.Error().Msg(err.Error())
continue
}
yamlBytes, err := yaml.JSONToYAML(jsonBytes)
if err != nil {
log.Error().Msg(err.Error())
continue
}

policyYaml := types.PolicyYaml{
Type: types.PolicyTypeAdmissionController,
Kind: kyvernoPolicy.TypeMeta.Kind,
Name: kyvernoPolicy.ObjectMeta.Name,
Namespace: kyvernoPolicy.ObjectMeta.Namespace,
Cluster: cfg.GetCfgClusterName(),
WorkspaceId: cfg.GetCfgWorkspaceId(),
ClusterId: cfg.GetCfgClusterId(),
Labels: kyvernoPolicy.Spec.Rules[0].MatchResources.Any[0].ResourceDescription.Selector.MatchLabels,
Yaml: yamlBytes,
}
res = append(res, policyYaml)
}

if err := libs.UpdateOrInsertPolicyYamls(CfgDB, res); err != nil {
log.Error().Msgf(err.Error())
}
}

func GetAdmissionControllerPolicy(namespace, clusterName, labels string) []kyvernov1.Policy {
var kyvernoPolicies []kyvernov1.Policy
policyYamls, err := libs.GetPolicyYamls(CfgDB, types.PolicyTypeAdmissionController)
if err != nil {
log.Error().Msgf("fetching policy yaml from DB failed err=%v", err.Error())
return nil
}
for _, policyYaml := range policyYamls {
if policyYaml.Namespace == namespace &&
policyYaml.Cluster == clusterName &&
libs.LabelMapToString(policyYaml.Labels) == labels {
var kyvernoPolicy kyvernov1.Policy
err := yaml.Unmarshal(policyYaml.Yaml, &kyvernoPolicy)
if err != nil {
log.Error().Msgf("unmarshalling policy yaml failed err=%v", err.Error())
continue
}
kyvernoPolicies = append(kyvernoPolicies, kyvernoPolicy)
}
}
return kyvernoPolicies
}

func ConvertPoliciesToWorkerResponse(policies []kyvernov1.Policy) *wpb.WorkerResponse {
var response wpb.WorkerResponse

for i := range policies {
kyvernoPolicy := wpb.Policy{}

val, err := json.Marshal(&policies[i])
if err != nil {
log.Error().Msgf("kyvernoPolicy json marshal failed err=%v", err.Error())
}
kyvernoPolicy.Data = val

response.AdmissionControllerPolicy = append(response.AdmissionControllerPolicy, &kyvernoPolicy)
}
response.Res = "OK"

return &response
}

func ShouldSATokenBeAutoMounted(namespace string, labels map[string]string) bool {
client := cluster.ConnectK8sClient()

podList, err := client.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{
LabelSelector: libs.LabelMapToString(labels),
})

if err != nil {
log.Warn().Msg(err.Error())
return true
}

if len(podList.Items) > 0 {
// Only inspect the first pod in the list as deployment pods have same behavior
pod := podList.Items[0]
containersSATokenMountPath, err := getSATokenMountPath(&pod)
if err != nil && strings.Contains(err.Error(), "service account token not mounted") {
log.Warn().Msg(err.Error())
return false
}
var sumResponses []*opb.Response
for _, container := range pod.Spec.Containers {
sumResp, err := obs.GetSummaryData(&opb.Request{
PodName: pod.Name,
NameSpace: pod.Namespace,
ContainerName: container.Name,
Type: "process,file",
})
if err != nil {
log.Warn().Msg(err.Error())
return true
}
log.Info().Msg(sumResp.String())
sumResponses = append(sumResponses, sumResp)
}
return serviceAccountTokenUsed(containersSATokenMountPath, sumResponses)
} else {
log.Warn().Msg("No pods found for the given labels")
}

return true
}

func getSATokenMountPath(pod *corev1.Pod) ([]containerMountPathServiceAccountToken, error) {
volumes := pod.Spec.Volumes
var tokenPath string
var projectedVolumeName string
var result = make([]containerMountPathServiceAccountToken, 0)
for _, volume := range volumes {
if volume.Projected != nil {
for _, projectedSources := range volume.Projected.Sources {
serviceAccountToken := projectedSources.ServiceAccountToken
if serviceAccountToken != nil {
tokenPath = serviceAccountToken.Path
projectedVolumeName = volume.Name
break
}
}
}
}

if tokenPath == "" || projectedVolumeName == "" {
return result,
fmt.Errorf("service account token not mounted for %s in namespace %s", pod.Name, pod.Namespace)
}

containers := pod.Spec.Containers
for _, container := range containers {
volumeMounts := container.VolumeMounts
for _, volumeMount := range volumeMounts {
if volumeMount.Name == projectedVolumeName {
result = append(result, containerMountPathServiceAccountToken{
podName: pod.Name,
podNamespace: pod.Namespace,
containerName: container.Name,
saTokenMountPath: volumeMount.MountPath + string(filepath.Separator) + tokenPath,
})
}
}
}
return result, nil
}

func serviceAccountTokenUsed(containersSATokenMountPath []containerMountPathServiceAccountToken, sumResponses []*opb.Response) bool {
serviceAccountTokenUsed := false
for _, containerSATokenMountPath := range containersSATokenMountPath {
for _, sumResp := range sumResponses {
for _, fileData := range sumResp.FileData {
if sumResp.ContainerName == containerSATokenMountPath.containerName {
// Even if one container uses the service account token, we should allow auto mounting
if matchesSATokenPath(containerSATokenMountPath.saTokenMountPath, fileData.Destination) {
serviceAccountTokenUsed = true
break
}
}
}
}
}
return serviceAccountTokenUsed
}

func matchesSATokenPath(saTokenPath, sumRespPath string) bool {
sumRespPathParts := strings.Split(sumRespPath, string(filepath.Separator))
pattern := "..[0-9]{4}_[0-9]{2}_[0-9]{2}.*"
sumRespPathPartsWithoutDoubleDot := removeMatchingElements(sumRespPathParts, pattern)
sumpRespPathWithoutDoubleDot := strings.Join(sumRespPathPartsWithoutDoubleDot, string(filepath.Separator))
if strings.HasSuffix(saTokenPath, sumpRespPathWithoutDoubleDot) {
return true
}
return false
}

func removeMatchingElements(slice []string, pattern string) []string {
r := regexp.MustCompile(pattern)
result := make([]string, 0)

for _, s := range slice {
if !r.MatchString(s) {
result = append(result, s)
}
}

return result
}
Loading

0 comments on commit d0dfd5f

Please sign in to comment.