Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to convert K8s Obj to PSP #29

Merged
merged 1 commit into from
Oct 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# Kube PodSecurityPolicy Advisor

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).

It has 2 subcommands, `kube-psp-advisor inspect` and `kube-psp-advisor convert`. `inspect` connects to a K8s API server, downloads all Pod-related objects in a given namespace, and generates a PSP based on the properties of those objects. `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.

## Build and Run locally
1. ```make build```
2. ```./kube-psp-advisor``` to generate Pod Security Policy based on running cluster configuration
3. ```./kube-psp-advisor --report``` to print the details reports (why this PSP is recommended for the cluster)
2. ```./kube-psp-advisor inspect``` to generate Pod Security Policy based on running cluster configuration
3. ```./kube-psp-advisor inspect --report``` to print the details reports (why this PSP is recommended for the cluster)
4. ```./kube-psp-advisor convert --podFile <path> --pspFile <path>``` to generate a PSP from a single .yaml file.

## Build and Run as Container
1. ```docker build -t <Image Name> -f container/Dockerfile .```
Expand Down Expand Up @@ -37,9 +42,9 @@ Some attributes(capabilities, host ports etc.) required gathering runtime inform
- [x] Basic functionalities;
- [ ] Create PSP's for common charts
- [ ] Kubectl plugin

`
## Sample Pod Security Policy
Command: `./kube-psp-advisor --namespace=psp-test`
Command: `./kube-psp-advisor inspect --namespace=psp-test`
```
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
Expand Down Expand Up @@ -73,8 +78,8 @@ spec:
- secret
```

## Sample Report
Command: `./kube-psp-advisor --namespace=psp-test --report | jq .podSecuritySpecs`
## Sample Report
Command: `./kube-psp-advisor inspect --namespace=psp-test --report | jq .podSecuritySpecs`
```
{
"hostIPC": [
Expand Down
190 changes: 9 additions & 181 deletions advisor/processor/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ package processor

import (
"fmt"
"time"

"github.com/sysdiglabs/kube-psp-advisor/generator"
"github.com/sysdiglabs/kube-psp-advisor/advisor/report"
"github.com/sysdiglabs/kube-psp-advisor/advisor/types"
"github.com/sysdiglabs/kube-psp-advisor/utils"

v1 "k8s.io/api/core/v1"
"k8s.io/api/policy/v1beta1"
Expand All @@ -20,10 +19,16 @@ type Processor struct {
namespace string
serviceAccountMap map[string]v1.ServiceAccount
serverGitVersion string
gen *generator.Generator
}

// NewProcessor returns a new processor
func NewProcessor(kubeconfig string) (*Processor, error) {

gen, err := generator.NewGenerator(); if err != nil {
return nil, fmt.Errorf("Could not create generator: %v", err)
}

config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, err
Expand All @@ -43,6 +48,7 @@ func NewProcessor(kubeconfig string) (*Processor, error) {
k8sClient: clientset,
resourceNamePrefix: map[string]bool{},
serverGitVersion: info.GitVersion,
gen: gen,
}, nil
}

Expand All @@ -52,186 +58,8 @@ func (p *Processor) SetNamespace(ns string) {

// GeneratePSP generate Pod Security Policy
func (p *Processor) GeneratePSP(cssList []types.ContainerSecuritySpec, pssList []types.PodSecuritySpec) *v1beta1.PodSecurityPolicy {
var ns string
// no PSP will be generated if no security spec is provided
if len(cssList) == 0 && len(pssList) == 0 {
return nil
}

psp := &v1beta1.PodSecurityPolicy{}

psp.APIVersion = "policy/v1beta1"
psp.Kind = "PodSecurityPolicy"

addedCap := map[string]int{}
droppedCap := map[string]int{}

effectiveCap := map[string]bool{}

runAsUser := map[int64]bool{}

volumeTypes := map[string]bool{}

hostPaths := map[string]bool{}

runAsUserCount := 0

runAsNonRootCount := 0

notAllowPrivilegeEscationCount := 0

ns = p.namespace

if ns == "" {
ns = "all"
}

psp.Name = fmt.Sprintf("%s-%s-%s", "pod-security-policy", ns, time.Now().Format("20060102150405"))

for _, sc := range pssList {
psp.Spec.HostPID = psp.Spec.HostPID || sc.HostPID
psp.Spec.HostIPC = psp.Spec.HostIPC || sc.HostIPC
psp.Spec.HostNetwork = psp.Spec.HostNetwork || sc.HostNetwork

for _, t := range sc.VolumeTypes {
volumeTypes[t] = true
}

for path, readOnly := range sc.MountHostPaths {
if _, exists := hostPaths[path]; !exists {
hostPaths[path] = readOnly
} else {
hostPaths[path] = readOnly && hostPaths[path]
}
}
}

for _, sc := range cssList {
for _, cap := range sc.Capabilities {
effectiveCap[cap] = true
}

for _, cap := range sc.AddedCap {
addedCap[cap]++
}

for _, cap := range sc.DroppedCap {
droppedCap[cap]++
}

psp.Spec.Privileged = psp.Spec.Privileged || sc.Privileged

psp.Spec.ReadOnlyRootFilesystem = psp.Spec.ReadOnlyRootFilesystem || sc.ReadOnlyRootFS

if sc.RunAsNonRoot != nil && *sc.RunAsNonRoot {
runAsNonRootCount++
}

if sc.RunAsUser != nil {
runAsUser[*sc.RunAsUser] = true
runAsUserCount++
}

if sc.AllowPrivilegeEscalation != nil && !*sc.AllowPrivilegeEscalation {
notAllowPrivilegeEscationCount++
}

// set host ports
// TODO: need to integrate with listening port during the runtime, might cause false positive.
//for _, port := range sc.HostPorts {
// psp.Spec.HostPorts = append(psp.Spec.HostPorts, v1beta1.HostPortRange{Min: port, Max: port})
//}
}

// set allowedPrivilegeEscalation
if notAllowPrivilegeEscationCount == len(cssList) {
notAllowed := false
psp.Spec.AllowPrivilegeEscalation = &notAllowed
}

// set runAsUser strategy
if runAsNonRootCount == len(cssList) {
psp.Spec.RunAsUser.Rule = v1beta1.RunAsUserStrategyMustRunAsNonRoot
}

if runAsUserCount == len(cssList) {
psp.Spec.RunAsUser.Rule = v1beta1.RunAsUserStrategyMustRunAs
for uid := range runAsUser {
if psp.Spec.RunAsUser.Rule == v1beta1.RunAsUserStrategyMustRunAsNonRoot && uid != 0 {
psp.Spec.RunAsUser.Ranges = append(psp.Spec.RunAsUser.Ranges, v1beta1.IDRange{
Min: uid,
Max: uid,
})
}
}
}

// set allowed host path
enforceReadOnly, _ := utils.CompareVersion(p.serverGitVersion, types.Version1_11)

for path, readOnly := range hostPaths {
psp.Spec.AllowedHostPaths = append(psp.Spec.AllowedHostPaths, v1beta1.AllowedHostPath{
PathPrefix: path,
ReadOnly: readOnly || enforceReadOnly,
})
}

// set limit volumes
volumeTypeList := utils.MapToArray(volumeTypes)

for _, v := range volumeTypeList {
psp.Spec.Volumes = append(psp.Spec.Volumes, v1beta1.FSType(v))
}

// set allowedCapabilities
defaultCap := utils.ArrayToMap(types.DefaultCaps)
for cap := range defaultCap {
if _, exists := effectiveCap[cap]; exists {
delete(effectiveCap, cap)
}
}

// set allowedAddCapabilities
for cap := range effectiveCap {
psp.Spec.AllowedCapabilities = append(psp.Spec.AllowedCapabilities, v1.Capability(cap))
}

// set defaultAddCapabilities
for k, v := range addedCap {
if v == len(cssList) {
psp.Spec.DefaultAddCapabilities = append(psp.Spec.DefaultAddCapabilities, v1.Capability(k))
}
}

// set requiredDroppedCapabilities
for k, v := range droppedCap {
if v == len(cssList) {
psp.Spec.RequiredDropCapabilities = append(psp.Spec.RequiredDropCapabilities, v1.Capability(k))
}
}

// set to default values
if string(psp.Spec.RunAsUser.Rule) == "" {
psp.Spec.RunAsUser.Rule = v1beta1.RunAsUserStrategyRunAsAny
}

if psp.Spec.RunAsGroup != nil && string(psp.Spec.RunAsGroup.Rule) == "" {
psp.Spec.RunAsGroup.Rule = v1beta1.RunAsGroupStrategyRunAsAny
}

if string(psp.Spec.FSGroup.Rule) == "" {
psp.Spec.FSGroup.Rule = v1beta1.FSGroupStrategyRunAsAny
}

if string(psp.Spec.SELinux.Rule) == "" {
psp.Spec.SELinux.Rule = v1beta1.SELinuxStrategyRunAsAny
}

if string(psp.Spec.SupplementalGroups.Rule) == "" {
psp.Spec.SupplementalGroups.Rule = v1beta1.SupplementalGroupsStrategyRunAsAny
}

return psp
return p.gen.GeneratePSP(cssList, pssList, p.namespace, p.serverGitVersion)
}

// GenerateReport generate a JSON report
Expand Down
Loading