Skip to content

Commit c1c9273

Browse files
darryk10Kaizhe
authored andcommitted
introduced opa policy generation
1 parent a3a4395 commit c1c9273

File tree

4 files changed

+38
-26
lines changed

4 files changed

+38
-26
lines changed

README.MD

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Kube PodSecurityPolicy Advisor
22

3-
kube-psp-advisor is a tool that makes it easier to create K8s Pod Security Policies (PSPs) from either a live K8s environment or from a single .yaml file containing a pod specification (Deployment, DaemonSet, Pod, etc).
3+
kube-psp-advisor is a tool that makes it easier to create K8s Pod Security Policies (PSPs) or OPA Policy from either a live K8s environment or from a single .yaml file containing a pod specification (Deployment, DaemonSet, Pod, etc).
44

5-
It has 2 subcommands, `kube-psp-advisor inspect` and `kube-psp-advisor convert`. `inspect` connects to a K8s API server, scans the security context of workloads in a given namespace or the entire cluster, and generates a PSP based on the security context. `convert` works without connecting to an API Server, reading a single .yaml file containing a object with a pod spec and generating a PSP based on the file.
5+
It has 2 subcommands, `kube-psp-advisor inspect` and `kube-psp-advisor convert`. `inspect` connects to a K8s API server, scans the security context of workloads in a given namespace or the entire cluster, and generates a PSP or an OPA Policy based on the security context. `convert` works without connecting to an API Server, reading a single .yaml file containing a object with a pod spec and generating a PSP or OPA Policy based on the file.
66

77
## Installation as a Krew Plugin
88

@@ -20,15 +20,20 @@ The plugin will be available as `kubectl advise-psp`.
2020
- 2.1 ```./kube-psp-advisor inspect --report``` to print the details reports (why this PSP is recommended for the cluster)
2121
- 2.2 ```./kube-psp-advisor inspect --grant``` to print PSPs, roles and rolebindings for service accounts (refer to [psp-grant.yaml](./test-yaml/psp-grant.yaml))
2222
- 2.3 ```./kube-psp-advisor inspect --namespace=<ns>``` to print report or PSP(s) within a given namespace (default to all)
23+
- 2.4 ```./kube-psp-advisor inspect --opa``` to generate OPA Policy based on running cluster configuration
24+
- 2.5 ```./kube-psp-advisor inspect --opa --deny-by-default``` to generate an OPA Policy, where OPA Default Rule is Deny ALL
2325
4. ```./kube-psp-advisor convert --podFile <path> --pspFile <path>``` to generate a PSP from a single .yaml file.
24-
26+
- 4.1 ```./kube-psp-advisor convert --podFile <path> --pspFile <path> --opa``` to generate an OPA Policy from a single .yaml file.
27+
- 4.2 ```./kube-psp-advisor convert --podFile <path> --pspFile <path> --opa --deny-by-default``` to generate an OPA Policy from a single .yaml file, where OPA Default Rule is Deny ALL.
28+
2529
## Build and Run as Container
2630
1. ```docker build -t <Image Name> -f container/Dockerfile .```
2731
2. ```docker run -v ~/.kube:/root/.kube -v ~/.aws:/root/.aws <Image Name>``` (the `.aws` folder mount is optional and totally depends on your clould provider)
2832

2933
## Use Cases
3034
1. Help verify the deployment, daemonset settings in cluster and plan to reduce unnecessary privileges/resources
3135
2. Apply Pod Security Policy to the target cluster
36+
3. Apply OPA Policy to the target cluster
3237
3. flag `--namespace=<namespace>` is introduced to debug and narrow down the security context per namespace
3338

3439
## Attributes Aggregated for Pod Security Policy

generator/generator.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -380,14 +380,14 @@ func (pg *Generator) GenerateOPA(cssList []types.ContainerSecuritySpec,
380380
pssList []types.PodSecuritySpec,
381381
namespace, serverGitVersion string, OPAdefaultRule bool) *ast.Module {
382382

383-
return pg.GenerateOPAPodWithName(cssList, pssList, namespace, serverGitVersion, "", OPAdefaultRule)
383+
return pg.GenerateOPAWithName(cssList, pssList, namespace, serverGitVersion, "", OPAdefaultRule)
384384
}
385385

386386
func (pg *Generator) GenerateOPAPod(cssList []types.ContainerSecuritySpec,
387387
pssList []types.PodSecuritySpec,
388388
namespace, serverGitVersion string, OPAdefaultRule bool) *ast.Module {
389389

390-
return pg.GenerateOPAPodWithName(cssList, pssList, namespace, serverGitVersion, "", OPAdefaultRule)
390+
return pg.GenerateOPAWithName(cssList, pssList, namespace, serverGitVersion, "", OPAdefaultRule)
391391
}
392392

393393
// GeneratePSP generate Pod Security Policy
@@ -626,7 +626,8 @@ func (pg *Generator) GeneratePSPWithName(
626626
return psp
627627
}
628628

629-
func (pg *Generator) GenerateOPAPodWithName(
629+
// GenerateOPA generate OPA Policy
630+
func (pg *Generator) GenerateOPAWithName(
630631
cssList []types.ContainerSecuritySpec,
631632
pssList []types.PodSecuritySpec,
632633
namespace, serverGitVersion, pspName string, OPAdefaultRule bool) *ast.Module {
@@ -685,7 +686,7 @@ func (pg *Generator) GenerateOPAPodWithName(
685686
ns = "all"
686687
}
687688
rule.Body.Append(ast.MustParseExpr("workload := input.request.object"))
688-
rule.Body.Append(ast.NewExpr(ast.VarTerm("valueWorkLoadSecContext(workload)")))
689+
rule.Body.Append(ast.NewExpr(ast.VarTerm(checkOPADefault(OPAdefaultRule) + "valueWorkLoadSecContext(workload)")))
689690
valueWorkLoadSecContext := addOPARule("valueWorkLoadSecContext", "workload")
690691

691692
for _, wsc := range pssList {
@@ -709,10 +710,12 @@ func (pg *Generator) GenerateOPAPodWithName(
709710
}
710711
}
711712

713+
// Sysctls is set
712714
for _, s := range wsc.Sysctls {
713715
sysctls = append(sysctls, "\""+s+"\"")
714716
}
715717

718+
// Check if workload or pod
716719
if wsc.Metadata.Kind == "Deployment" || wsc.Metadata.Kind == "Job" || wsc.Metadata.Kind == "ReplicaSet" || wsc.Metadata.Kind == "DaemonSet" || wsc.Metadata.Kind == "ReplicationController" {
717720
basepath = "input.request.object.spec.template.spec"
718721
} else {
@@ -723,6 +726,7 @@ func (pg *Generator) GenerateOPAPodWithName(
723726

724727
valueWorkLoadSecContext.Body.Append(ast.MustParseExpr("container := " + basepath + ".containers[_]"))
725728

729+
// Add rule hostPaths
726730
if len(hostPaths) > 0 {
727731
valueWorkLoadSecContext.Body.Append(ast.MustParseExpr("volumeHostPaths(workload)"))
728732
valueHostPathRule := addOPARule("volumeHostPaths", "workload")
@@ -744,6 +748,7 @@ func (pg *Generator) GenerateOPAPodWithName(
744748
mod.Rules = append(mod.Rules, valueHostPathRule)
745749
}
746750

751+
// Add rule sysctls
747752
if len(sysctls) > 0 {
748753
valueWorkLoadSecContext.Body.Append(ast.MustParseExpr("valueSysctls(workload)"))
749754
valueSysctlsRule := addOPARule("valueSysctls", "sysctls")
@@ -755,12 +760,17 @@ func (pg *Generator) GenerateOPAPodWithName(
755760
mod.Rules = append(mod.Rules, valueSysctlsRule)
756761
}
757762

763+
// Add rule hostPid
758764
if hostPid {
759765
valueWorkLoadSecContext.Body.Append(ast.MustParseExpr(basepath + ".hostPID"))
760766
}
767+
768+
// Add rule HostIPC
761769
if HostIPC {
762770
valueWorkLoadSecContext.Body.Append(ast.MustParseExpr(basepath + ".hostIPC"))
763771
}
772+
773+
// Add rule HostNet
764774
if HostNet {
765775
valueWorkLoadSecContext.Body.Append(ast.MustParseExpr(basepath + ".hostNetwork"))
766776
}
@@ -795,6 +805,7 @@ func (pg *Generator) GenerateOPAPodWithName(
795805
runAsGroupCount++
796806
}
797807

808+
// port is set
798809
for _, port := range sc.HostPorts {
799810
hostPorts = append(hostPorts, fmt.Sprint(port))
800811
}
@@ -814,6 +825,7 @@ func (pg *Generator) GenerateOPAPodWithName(
814825

815826
valueSecContextRule := addOPARule("valueSecContext", "container")
816827

828+
// Add rule addedCap
817829
if len(addedCap) > 0 {
818830
valueSecContextRule.Body.Append(ast.NewExpr(ast.VarTerm("valueAddedCap(container)")))
819831
valueAddedCapRule := addOPARule("valueAddedCap", "addedCap")
@@ -823,6 +835,7 @@ func (pg *Generator) GenerateOPAPodWithName(
823835
mod.Rules = append(mod.Rules, valueAddedCapRule)
824836
}
825837

838+
// Add rule droppedCap
826839
if len(droppedCap) > 0 {
827840
valueSecContextRule.Body.Append(ast.NewExpr(ast.VarTerm("valueDroppedCap(container)")))
828841
valueDroppedCapRule := addOPARule("valueDroppedCap", "droppedCap")
@@ -832,6 +845,7 @@ func (pg *Generator) GenerateOPAPodWithName(
832845
mod.Rules = append(mod.Rules, valueDroppedCapRule)
833846
}
834847

848+
// Add rule runAsUser
835849
if len(runAsUser) > 0 {
836850
valueSecContextRule.Body.Append(ast.NewExpr(ast.VarTerm("valueRunAsUserID(container)")))
837851
valueHostRunAsUserRule := addOPARule("valueRunAsUserID", "uid")
@@ -843,7 +857,7 @@ func (pg *Generator) GenerateOPAPodWithName(
843857
mod.Rules = append(mod.Rules, valueHostRunAsUserRule)
844858
}
845859

846-
// runAsGroup is set
860+
// Add rule runAsGroup
847861
if len(runAsGroup) > 0 {
848862
valueSecContextRule.Body.Append(ast.NewExpr(ast.VarTerm("valueRunAsGroupID(container)")))
849863
valueHostRunAsGroupRule := addOPARule("valueRunAsGroupID", "gid")
@@ -855,6 +869,7 @@ func (pg *Generator) GenerateOPAPodWithName(
855869
mod.Rules = append(mod.Rules, valueHostRunAsGroupRule)
856870
}
857871

872+
// Add rule hostPorts
858873
if len(hostPorts) > 0 {
859874
valueSecContextRule.Body.Append(ast.NewExpr(ast.VarTerm("valueHostPort(container)")))
860875
valueHostPortRule := addOPARule("valueHostPort", "container")
@@ -866,33 +881,37 @@ func (pg *Generator) GenerateOPAPodWithName(
866881
mod.Rules = append(mod.Rules, valueHostPortRule)
867882
}
868883

869-
// set allowed host path
870-
884+
// Add rule Privileged
871885
if Privileged {
872886
valueSecContextRule.Body.Append(ast.MustParseExpr("container.securityContext.privileged"))
873887
}
874888

889+
// Add rule ReadOnlyRootFS
875890
if ReadOnlyRootFS == len(cssList) {
876891
valueSecContextRule.Body.Append(ast.MustParseExpr("container.securityContext.readOnlyRootFilesystem"))
877892
}
878893

894+
// Add rule RunAsNonRoot
879895
if RunAsNonRoot == len(cssList) {
880896
valueSecContextRule.Body.Append(ast.MustParseExpr("container.securityContext.runAsNonRoot"))
881897
}
882898

899+
// Add rule AllowPrivilegeEscalation
883900
if AllowPrivilegeEscalation == len(cssList) {
884901
valueSecContextRule.Body.Append(ast.MustParseExpr("container.securityContext.allowPrivilegeEscalation == false"))
885902
}
903+
886904
mod.Rules = append(mod.Rules, valueSecContextRule)
887905

888-
rule.Body.Append(ast.MustParseExpr("message := sprintf(\"Container runs in privileged mode.\", [workload.metadata.name])"))
906+
rule.Body.Append(ast.MustParseExpr("message := sprintf(\"Workflow or pod compliant with the policy.\", [workload.metadata.name])"))
889907
mod.Package = pack
890908
mod.Rules = append(mod.Rules, &rule)
891909

892910
return &mod
893911

894912
}
895913

914+
// deny-by-default option check
896915
func checkOPADefault(OPAdefaultRule bool) string {
897916
if !OPAdefaultRule {
898917
return "not "

kube-psp-advisor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ func main() {
240240
convertCmd.Flags().StringVar(&podObjFilename, "podFile", "", "Path to a yaml file containing an object with a pod Spec")
241241
convertCmd.Flags().StringVar(&pspFilename, "pspFile", "", "Write the resulting output to this file")
242242
convertCmd.Flags().BoolVarP(&OPAformat, "opa", "", false, "(optional) OPA option for output in OPA format")
243-
convertCmd.Flags().BoolVarP(&OPAdefaultRule, "OPADefaultRule", "", false, "(optional) OPA Default Rule: use this option iF OPA Default Rule is Deny ALL")
243+
convertCmd.Flags().BoolVarP(&OPAdefaultRule, "deny-by-default", "", false, "(optional) OPA Default Rule: use this option if OPA Default Rule is Deny ALL")
244244

245245
compareCmd.Flags().StringVar(&srcYamlDir, "sourceDir", "", "Source YAML directory to load YAMLs")
246246
compareCmd.Flags().StringVar(&targetYamlDir, "targetDir", "", "Target YAML directory to load YAMLs")

kube-psp-advisor_test.go

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ var (
1212
expectedYamls = []string{
1313
"test-yaml/base-busybox.yaml",
1414
"test-yaml/psp-grant.yaml",
15-
"test-yaml/test-opa.yaml",
1615
"test-yaml/srcYamls/busy-box.yaml",
1716
"test-yaml/srcYamls/nginx.yaml",
1817
"test-yaml/targetYamls/busy-box.yaml",
1918
"test-yaml/targetYamls/nginx.yaml",
19+
"test-yaml/targetYamls/web-deployment.yaml",
20+
"test-yaml/test-opa.yaml",
2021
}
2122
)
2223

@@ -33,16 +34,3 @@ func TestReadYamls(t *testing.T) {
3334
t.Fatalf("expected: %s\nactual: %s\n", expectedYamls, yamls)
3435
}
3536
}
36-
37-
func TestReadOPAYAmls(t *testing.T) {
38-
testYaml := "test-yaml/test-opa.yaml"
39-
yamls, err := getWorkLoadYamls(testYaml)
40-
41-
if err != nil {
42-
t.Fatal(err)
43-
}
44-
45-
if len(yamls) != 1 && yamls[0] != "test-yaml/base-busybox.yaml" {
46-
t.Fatalf("expected: %s\nactual: %s\n", testYaml, yamls[0])
47-
}
48-
}

0 commit comments

Comments
 (0)