Skip to content

Commit

Permalink
support grant flag
Browse files Browse the repository at this point in the history
Signed-off-by: kaizhe <derek0405@gmail.com>
  • Loading branch information
Kaizhe committed Jan 9, 2020
1 parent c32b6fb commit 7c12817
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 9 deletions.
45 changes: 45 additions & 0 deletions advisor/advisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"os"

"github.com/sysdiglabs/kube-psp-advisor/advisor/types"

"github.com/sysdiglabs/kube-psp-advisor/advisor/processor"
"github.com/sysdiglabs/kube-psp-advisor/advisor/report"

Expand All @@ -19,6 +21,8 @@ type Advisor struct {
k8sClient *kubernetes.Clientset
processor *processor.Processor
report *report.Report
grants []types.PSPGrant
grantWarnings string
}

// Create an podSecurityPolicy advisor instance
Expand All @@ -33,6 +37,7 @@ func NewAdvisor(kubeconfig string) (*Advisor, error) {
podSecurityPolicy: nil,
processor: p,
report: nil,
grants: []types.PSPGrant{},
}, nil
}

Expand All @@ -49,6 +54,8 @@ func (advisor *Advisor) Process(namespace string) error {

advisor.report = advisor.processor.GenerateReport(cssList, pssList)

advisor.grants, advisor.grantWarnings = advisor.processor.GeneratePSPGrant(cssList, pssList)

return nil
}

Expand All @@ -72,3 +79,41 @@ func (advisor *Advisor) PrintPodSecurityPolicy() error {
func (advisor *Advisor) GetPodSecurityPolicy() *v1beta1.PodSecurityPolicy {
return advisor.podSecurityPolicy
}

func (advisor *Advisor) PrintPodSecurityPolicyWithGrants() error {
var err error
e := k8sJSON.NewYAMLSerializer(k8sJSON.DefaultMetaFactory, nil, nil)

if advisor.grantWarnings != "" {
fmt.Println(advisor.grantWarnings)
printYamlSeparator()
}

for _, pspGrant := range advisor.grants {
fmt.Println(pspGrant.Comment)

if err = e.Encode(pspGrant.PodSecurityPolicy, os.Stdout); err != nil {
return err
}

printYamlSeparator()

if err = e.Encode(pspGrant.Role, os.Stdout); err != nil {
return err
}

printYamlSeparator()

if err = e.Encode(pspGrant.RoleBinding, os.Stdout); err != nil {
return err
}

printYamlSeparator()
}

return nil
}

func printYamlSeparator() {
fmt.Println("---")
}
53 changes: 51 additions & 2 deletions advisor/processor/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package processor

import (
"fmt"
"sort"

"github.com/sysdiglabs/kube-psp-advisor/advisor/report"
"github.com/sysdiglabs/kube-psp-advisor/advisor/types"
Expand Down Expand Up @@ -55,12 +56,60 @@ func (p *Processor) SetNamespace(ns string) {
p.namespace = ns
}

// GeneratePSP generate Pod Security Policy
// GeneratePSP generates Pod Security Policy
func (p *Processor) GeneratePSP(cssList []types.ContainerSecuritySpec, pssList []types.PodSecuritySpec) *v1beta1.PodSecurityPolicy {

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

// GeneratePSPGrant generates Pod Security Policies, Roles, RoleBindings for service accounts to use PSP
func (p *Processor) GeneratePSPGrant(cssList []types.ContainerSecuritySpec, pssList []types.PodSecuritySpec) ([]types.PSPGrant, string) {
saSecuritySpecMap := map[string]*types.SASecuritySpec{}
pspGrantList := []types.PSPGrant{}
grantWarnings := ""

for _, css := range cssList {
key := fmt.Sprintf("%s:%s", css.Namespace, css.ServiceAccount)
if _, exists := saSecuritySpecMap[key]; !exists {
saSecuritySpecMap[key] = types.NewSASecuritySpec(css.Namespace, css.ServiceAccount)
}
saSecuritySpecMap[key].AddContainerSecuritySpec(css)
}

for _, pss := range pssList {
key := fmt.Sprintf("%s:%s", pss.Namespace, pss.ServiceAccount)
if _, exists := saSecuritySpecMap[key]; !exists {
saSecuritySpecMap[key] = types.NewSASecuritySpec(pss.Namespace, pss.ServiceAccount)
}
saSecuritySpecMap[key].AddPodSecuritySpec(pss)
}

saSecuritySpecList := types.SASecuritySpecList{}

// convert saSecuritySpecMap into list and then sort
for _, saSecuritySpec := range saSecuritySpecMap {
saSecuritySpecList = append(saSecuritySpecList, saSecuritySpec)
}

sort.Sort(saSecuritySpecList)

for _, s := range saSecuritySpecList {
if !s.IsDefaultServiceAccount() {
pspGrant := types.PSPGrant{
Comment: s.GenerateComment(),
Role: s.GenerateRole(),
RoleBinding: s.GenerateRoleBinding(),
PodSecurityPolicy: p.gen.GeneratePSPWithName(s.ContainerSecuritySpecList, s.PodSecuritySpecList, s.Namespace, p.serverGitVersion, s.GeneratePSPName()),
}

pspGrantList = append(pspGrantList, pspGrant)
} else {
grantWarnings += s.GenerateComment()
}
}

return pspGrantList, grantWarnings
}

// GenerateReport generate a JSON report
func (p *Processor) GenerateReport(cssList []types.ContainerSecuritySpec, pssList []types.PodSecuritySpec) *report.Report {
r := report.NewReport()
Expand Down
146 changes: 146 additions & 0 deletions advisor/types/pspgrant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package types

import (
"fmt"

"k8s.io/apimachinery/pkg/apis/meta/v1"

"k8s.io/api/policy/v1beta1"
v1rbac "k8s.io/api/rbac/v1"
)

const (
rbacV1APIVersion = "rbac.authorization.k8s.io/v1"
rbacAPIGroup = "rbac.authorization.k8s.io"
Role = "Role"
RoleBinding = "RoleBinding"
ServiceAccount = "ServiceAccount"
)

type SASecuritySpecList []*SASecuritySpec

func (sl SASecuritySpecList) Less(i, j int) bool {
keyI := fmt.Sprintf("%s:%s", sl[i].Namespace, sl[i].ServiceAccount)
keyJ := fmt.Sprintf("%s:%s", sl[j].Namespace, sl[j].ServiceAccount)

return keyI < keyJ
}

func (sl SASecuritySpecList) Len() int { return len(sl) }

func (sl SASecuritySpecList) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }

type SASecuritySpec struct {
PSPName string // psp name

ServiceAccount string // serviceAccount

Namespace string // namespace

ContainerSecuritySpecList []ContainerSecuritySpec

PodSecuritySpecList []PodSecuritySpec
}

func NewSASecuritySpec(ns, sa string) *SASecuritySpec {
return &SASecuritySpec{
ServiceAccount: sa,
Namespace: ns,
ContainerSecuritySpecList: []ContainerSecuritySpec{},
PodSecuritySpecList: []PodSecuritySpec{},
}
}

func (s *SASecuritySpec) IsDefaultServiceAccount() bool {
return s.ServiceAccount == "default"
}

func (s *SASecuritySpec) AddContainerSecuritySpec(css ContainerSecuritySpec) {
s.ContainerSecuritySpecList = append(s.ContainerSecuritySpecList, css)
}

func (s *SASecuritySpec) AddPodSecuritySpec(pss PodSecuritySpec) {
s.PodSecuritySpecList = append(s.PodSecuritySpecList, pss)
}

func (s *SASecuritySpec) GeneratePSPName() string {
if s.PSPName == "" {
s.PSPName = fmt.Sprintf("psp-for-%s-%s", s.Namespace, s.ServiceAccount)
}

return s.PSPName
}

func (s *SASecuritySpec) GenerateComment() string {
decision := "will be"

if s.IsDefaultServiceAccount() {
decision = "will not be"
}

return fmt.Sprintf("# Pod security policies %s created for service account: %s in namespace %s for images: %s", decision, s.ServiceAccount, s.Namespace, s.GetImages())
}

func (s *SASecuritySpec) GetImages() []string {
imageList := []string{}

for _, css := range s.ContainerSecuritySpecList {
imageList = append(imageList, css.ImageName)
}

return imageList
}

func (s *SASecuritySpec) GenerateRole() *v1rbac.Role {
roleName := fmt.Sprintf("use-psp-by-%s:%s", s.Namespace, s.ServiceAccount)

rule := v1rbac.PolicyRule{
Verbs: []string{"use"},
APIGroups: []string{"policy"},
Resources: []string{"podsecuritypolicies"},
ResourceNames: []string{s.GeneratePSPName()},
}

return &v1rbac.Role{
TypeMeta: v1.TypeMeta{
Kind: Role,
APIVersion: rbacV1APIVersion,
},
ObjectMeta: v1.ObjectMeta{
Namespace: s.Namespace,
Name: roleName,
},
Rules: []v1rbac.PolicyRule{rule},
}
}

func (s *SASecuritySpec) GenerateRoleBinding() *v1rbac.RoleBinding {
roleBindingName := fmt.Sprintf("use-psp-by-%s:%s-binding", s.Namespace, s.ServiceAccount)
roleName := fmt.Sprintf("use-psp-by-%s:%s", s.Namespace, s.ServiceAccount)

return &v1rbac.RoleBinding{
TypeMeta: v1.TypeMeta{
Kind: RoleBinding,
APIVersion: rbacV1APIVersion,
},
ObjectMeta: v1.ObjectMeta{
Namespace: s.Namespace,
Name: roleBindingName,
},
Subjects: []v1rbac.Subject{
{Kind: ServiceAccount, Name: s.ServiceAccount, Namespace: s.Namespace},
},
RoleRef: v1rbac.RoleRef{
APIGroup: rbacAPIGroup,
Kind: Role,
Name: roleName,
},
}
}

type PSPGrant struct {
Comment string
PodSecurityPolicy *v1beta1.PodSecurityPolicy
Role *v1rbac.Role
RoleBinding *v1rbac.RoleBinding
}
19 changes: 14 additions & 5 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,18 @@ func (pg *Generator) GetSecuritySpecFromPodSpec(metadata types.Metadata, namespa
return cssList, podSecuritySpec
}

func (pg *Generator) GeneratePSP(cssList []types.ContainerSecuritySpec,
pssList []types.PodSecuritySpec,
namespace, serverGitVersion string) *v1beta1.PodSecurityPolicy {

return pg.GeneratePSPWithName(cssList, pssList, namespace, serverGitVersion, "")
}

// GeneratePSP generate Pod Security Policy
func (pg *Generator) GeneratePSP(
func (pg *Generator) GeneratePSPWithName(
cssList []types.ContainerSecuritySpec,
pssList []types.PodSecuritySpec,
namespace string,
serverGitVersion string) *v1beta1.PodSecurityPolicy {

namespace, serverGitVersion, pspName string) *v1beta1.PodSecurityPolicy {
var ns string
// no PSP will be generated if no security spec is provided
if len(cssList) == 0 && len(pssList) == 0 {
Expand Down Expand Up @@ -351,7 +356,11 @@ func (pg *Generator) GeneratePSP(
ns = "all"
}

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

for _, sc := range pssList {
psp.Spec.HostPID = psp.Spec.HostPID || sc.HostPID
Expand Down
9 changes: 7 additions & 2 deletions kube-psp-advisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth"
)

func inspectPsp(kubeconfig string, withReport bool, namespace string) error {
func inspectPsp(kubeconfig string, namespace string, withReport, withGrant bool) error {
advisor, err := advisor.NewAdvisor(kubeconfig)

if err != nil {
Expand All @@ -38,6 +38,9 @@ func inspectPsp(kubeconfig string, withReport bool, namespace string) error {
return nil
}

if withGrant {
return advisor.PrintPodSecurityPolicyWithGrants()
}
err = advisor.PrintPodSecurityPolicy()

if err != nil {
Expand Down Expand Up @@ -85,6 +88,7 @@ func main() {

var kubeconfig string
var withReport bool
var withGrant bool
var namespace string
var podObjFilename string
var pspFilename string
Expand Down Expand Up @@ -115,7 +119,7 @@ func main() {
Short: "Inspect a live K8s Environment to generate a PodSecurityPolicy",
Long: "Fetch all objects in the provided namespace to generate a Pod Security Policy",
Run: func(cmd *cobra.Command, args []string) {
err := inspectPsp(kubeconfig, withReport, namespace)
err := inspectPsp(kubeconfig, namespace, withReport, withGrant)
if err != nil {
log.Fatalf("Could not run inspect command: %v", err)
}
Expand Down Expand Up @@ -150,6 +154,7 @@ func main() {
inspectCmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file")
}
inspectCmd.Flags().BoolVar(&withReport, "report", false, "(optional) return with detail report")
inspectCmd.Flags().BoolVar(&withGrant, "grant", false, "(optional) return with pod security policies, roles and rolebindings")
inspectCmd.Flags().StringVar(&namespace, "namespace", "", "(optional) namespace")

convertCmd.Flags().StringVar(&podObjFilename, "podFile", "", "Path to a yaml file containing an object with a pod Spec")
Expand Down

0 comments on commit 7c12817

Please sign in to comment.