Skip to content

Commit 7c12817

Browse files
committed
support grant flag
Signed-off-by: kaizhe <[email protected]>
1 parent c32b6fb commit 7c12817

File tree

5 files changed

+263
-9
lines changed

5 files changed

+263
-9
lines changed

advisor/advisor.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"os"
77

8+
"github.com/sysdiglabs/kube-psp-advisor/advisor/types"
9+
810
"github.com/sysdiglabs/kube-psp-advisor/advisor/processor"
911
"github.com/sysdiglabs/kube-psp-advisor/advisor/report"
1012

@@ -19,6 +21,8 @@ type Advisor struct {
1921
k8sClient *kubernetes.Clientset
2022
processor *processor.Processor
2123
report *report.Report
24+
grants []types.PSPGrant
25+
grantWarnings string
2226
}
2327

2428
// Create an podSecurityPolicy advisor instance
@@ -33,6 +37,7 @@ func NewAdvisor(kubeconfig string) (*Advisor, error) {
3337
podSecurityPolicy: nil,
3438
processor: p,
3539
report: nil,
40+
grants: []types.PSPGrant{},
3641
}, nil
3742
}
3843

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

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

57+
advisor.grants, advisor.grantWarnings = advisor.processor.GeneratePSPGrant(cssList, pssList)
58+
5259
return nil
5360
}
5461

@@ -72,3 +79,41 @@ func (advisor *Advisor) PrintPodSecurityPolicy() error {
7279
func (advisor *Advisor) GetPodSecurityPolicy() *v1beta1.PodSecurityPolicy {
7380
return advisor.podSecurityPolicy
7481
}
82+
83+
func (advisor *Advisor) PrintPodSecurityPolicyWithGrants() error {
84+
var err error
85+
e := k8sJSON.NewYAMLSerializer(k8sJSON.DefaultMetaFactory, nil, nil)
86+
87+
if advisor.grantWarnings != "" {
88+
fmt.Println(advisor.grantWarnings)
89+
printYamlSeparator()
90+
}
91+
92+
for _, pspGrant := range advisor.grants {
93+
fmt.Println(pspGrant.Comment)
94+
95+
if err = e.Encode(pspGrant.PodSecurityPolicy, os.Stdout); err != nil {
96+
return err
97+
}
98+
99+
printYamlSeparator()
100+
101+
if err = e.Encode(pspGrant.Role, os.Stdout); err != nil {
102+
return err
103+
}
104+
105+
printYamlSeparator()
106+
107+
if err = e.Encode(pspGrant.RoleBinding, os.Stdout); err != nil {
108+
return err
109+
}
110+
111+
printYamlSeparator()
112+
}
113+
114+
return nil
115+
}
116+
117+
func printYamlSeparator() {
118+
fmt.Println("---")
119+
}

advisor/processor/generate.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package processor
22

33
import (
44
"fmt"
5+
"sort"
56

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

58-
// GeneratePSP generate Pod Security Policy
59+
// GeneratePSP generates Pod Security Policy
5960
func (p *Processor) GeneratePSP(cssList []types.ContainerSecuritySpec, pssList []types.PodSecuritySpec) *v1beta1.PodSecurityPolicy {
60-
6161
return p.gen.GeneratePSP(cssList, pssList, p.namespace, p.serverGitVersion)
6262
}
6363

64+
// GeneratePSPGrant generates Pod Security Policies, Roles, RoleBindings for service accounts to use PSP
65+
func (p *Processor) GeneratePSPGrant(cssList []types.ContainerSecuritySpec, pssList []types.PodSecuritySpec) ([]types.PSPGrant, string) {
66+
saSecuritySpecMap := map[string]*types.SASecuritySpec{}
67+
pspGrantList := []types.PSPGrant{}
68+
grantWarnings := ""
69+
70+
for _, css := range cssList {
71+
key := fmt.Sprintf("%s:%s", css.Namespace, css.ServiceAccount)
72+
if _, exists := saSecuritySpecMap[key]; !exists {
73+
saSecuritySpecMap[key] = types.NewSASecuritySpec(css.Namespace, css.ServiceAccount)
74+
}
75+
saSecuritySpecMap[key].AddContainerSecuritySpec(css)
76+
}
77+
78+
for _, pss := range pssList {
79+
key := fmt.Sprintf("%s:%s", pss.Namespace, pss.ServiceAccount)
80+
if _, exists := saSecuritySpecMap[key]; !exists {
81+
saSecuritySpecMap[key] = types.NewSASecuritySpec(pss.Namespace, pss.ServiceAccount)
82+
}
83+
saSecuritySpecMap[key].AddPodSecuritySpec(pss)
84+
}
85+
86+
saSecuritySpecList := types.SASecuritySpecList{}
87+
88+
// convert saSecuritySpecMap into list and then sort
89+
for _, saSecuritySpec := range saSecuritySpecMap {
90+
saSecuritySpecList = append(saSecuritySpecList, saSecuritySpec)
91+
}
92+
93+
sort.Sort(saSecuritySpecList)
94+
95+
for _, s := range saSecuritySpecList {
96+
if !s.IsDefaultServiceAccount() {
97+
pspGrant := types.PSPGrant{
98+
Comment: s.GenerateComment(),
99+
Role: s.GenerateRole(),
100+
RoleBinding: s.GenerateRoleBinding(),
101+
PodSecurityPolicy: p.gen.GeneratePSPWithName(s.ContainerSecuritySpecList, s.PodSecuritySpecList, s.Namespace, p.serverGitVersion, s.GeneratePSPName()),
102+
}
103+
104+
pspGrantList = append(pspGrantList, pspGrant)
105+
} else {
106+
grantWarnings += s.GenerateComment()
107+
}
108+
}
109+
110+
return pspGrantList, grantWarnings
111+
}
112+
64113
// GenerateReport generate a JSON report
65114
func (p *Processor) GenerateReport(cssList []types.ContainerSecuritySpec, pssList []types.PodSecuritySpec) *report.Report {
66115
r := report.NewReport()

advisor/types/pspgrant.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package types
2+
3+
import (
4+
"fmt"
5+
6+
"k8s.io/apimachinery/pkg/apis/meta/v1"
7+
8+
"k8s.io/api/policy/v1beta1"
9+
v1rbac "k8s.io/api/rbac/v1"
10+
)
11+
12+
const (
13+
rbacV1APIVersion = "rbac.authorization.k8s.io/v1"
14+
rbacAPIGroup = "rbac.authorization.k8s.io"
15+
Role = "Role"
16+
RoleBinding = "RoleBinding"
17+
ServiceAccount = "ServiceAccount"
18+
)
19+
20+
type SASecuritySpecList []*SASecuritySpec
21+
22+
func (sl SASecuritySpecList) Less(i, j int) bool {
23+
keyI := fmt.Sprintf("%s:%s", sl[i].Namespace, sl[i].ServiceAccount)
24+
keyJ := fmt.Sprintf("%s:%s", sl[j].Namespace, sl[j].ServiceAccount)
25+
26+
return keyI < keyJ
27+
}
28+
29+
func (sl SASecuritySpecList) Len() int { return len(sl) }
30+
31+
func (sl SASecuritySpecList) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
32+
33+
type SASecuritySpec struct {
34+
PSPName string // psp name
35+
36+
ServiceAccount string // serviceAccount
37+
38+
Namespace string // namespace
39+
40+
ContainerSecuritySpecList []ContainerSecuritySpec
41+
42+
PodSecuritySpecList []PodSecuritySpec
43+
}
44+
45+
func NewSASecuritySpec(ns, sa string) *SASecuritySpec {
46+
return &SASecuritySpec{
47+
ServiceAccount: sa,
48+
Namespace: ns,
49+
ContainerSecuritySpecList: []ContainerSecuritySpec{},
50+
PodSecuritySpecList: []PodSecuritySpec{},
51+
}
52+
}
53+
54+
func (s *SASecuritySpec) IsDefaultServiceAccount() bool {
55+
return s.ServiceAccount == "default"
56+
}
57+
58+
func (s *SASecuritySpec) AddContainerSecuritySpec(css ContainerSecuritySpec) {
59+
s.ContainerSecuritySpecList = append(s.ContainerSecuritySpecList, css)
60+
}
61+
62+
func (s *SASecuritySpec) AddPodSecuritySpec(pss PodSecuritySpec) {
63+
s.PodSecuritySpecList = append(s.PodSecuritySpecList, pss)
64+
}
65+
66+
func (s *SASecuritySpec) GeneratePSPName() string {
67+
if s.PSPName == "" {
68+
s.PSPName = fmt.Sprintf("psp-for-%s-%s", s.Namespace, s.ServiceAccount)
69+
}
70+
71+
return s.PSPName
72+
}
73+
74+
func (s *SASecuritySpec) GenerateComment() string {
75+
decision := "will be"
76+
77+
if s.IsDefaultServiceAccount() {
78+
decision = "will not be"
79+
}
80+
81+
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())
82+
}
83+
84+
func (s *SASecuritySpec) GetImages() []string {
85+
imageList := []string{}
86+
87+
for _, css := range s.ContainerSecuritySpecList {
88+
imageList = append(imageList, css.ImageName)
89+
}
90+
91+
return imageList
92+
}
93+
94+
func (s *SASecuritySpec) GenerateRole() *v1rbac.Role {
95+
roleName := fmt.Sprintf("use-psp-by-%s:%s", s.Namespace, s.ServiceAccount)
96+
97+
rule := v1rbac.PolicyRule{
98+
Verbs: []string{"use"},
99+
APIGroups: []string{"policy"},
100+
Resources: []string{"podsecuritypolicies"},
101+
ResourceNames: []string{s.GeneratePSPName()},
102+
}
103+
104+
return &v1rbac.Role{
105+
TypeMeta: v1.TypeMeta{
106+
Kind: Role,
107+
APIVersion: rbacV1APIVersion,
108+
},
109+
ObjectMeta: v1.ObjectMeta{
110+
Namespace: s.Namespace,
111+
Name: roleName,
112+
},
113+
Rules: []v1rbac.PolicyRule{rule},
114+
}
115+
}
116+
117+
func (s *SASecuritySpec) GenerateRoleBinding() *v1rbac.RoleBinding {
118+
roleBindingName := fmt.Sprintf("use-psp-by-%s:%s-binding", s.Namespace, s.ServiceAccount)
119+
roleName := fmt.Sprintf("use-psp-by-%s:%s", s.Namespace, s.ServiceAccount)
120+
121+
return &v1rbac.RoleBinding{
122+
TypeMeta: v1.TypeMeta{
123+
Kind: RoleBinding,
124+
APIVersion: rbacV1APIVersion,
125+
},
126+
ObjectMeta: v1.ObjectMeta{
127+
Namespace: s.Namespace,
128+
Name: roleBindingName,
129+
},
130+
Subjects: []v1rbac.Subject{
131+
{Kind: ServiceAccount, Name: s.ServiceAccount, Namespace: s.Namespace},
132+
},
133+
RoleRef: v1rbac.RoleRef{
134+
APIGroup: rbacAPIGroup,
135+
Kind: Role,
136+
Name: roleName,
137+
},
138+
}
139+
}
140+
141+
type PSPGrant struct {
142+
Comment string
143+
PodSecurityPolicy *v1beta1.PodSecurityPolicy
144+
Role *v1rbac.Role
145+
RoleBinding *v1rbac.RoleBinding
146+
}

generator/generator.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -310,13 +310,18 @@ func (pg *Generator) GetSecuritySpecFromPodSpec(metadata types.Metadata, namespa
310310
return cssList, podSecuritySpec
311311
}
312312

313+
func (pg *Generator) GeneratePSP(cssList []types.ContainerSecuritySpec,
314+
pssList []types.PodSecuritySpec,
315+
namespace, serverGitVersion string) *v1beta1.PodSecurityPolicy {
316+
317+
return pg.GeneratePSPWithName(cssList, pssList, namespace, serverGitVersion, "")
318+
}
319+
313320
// GeneratePSP generate Pod Security Policy
314-
func (pg *Generator) GeneratePSP(
321+
func (pg *Generator) GeneratePSPWithName(
315322
cssList []types.ContainerSecuritySpec,
316323
pssList []types.PodSecuritySpec,
317-
namespace string,
318-
serverGitVersion string) *v1beta1.PodSecurityPolicy {
319-
324+
namespace, serverGitVersion, pspName string) *v1beta1.PodSecurityPolicy {
320325
var ns string
321326
// no PSP will be generated if no security spec is provided
322327
if len(cssList) == 0 && len(pssList) == 0 {
@@ -351,7 +356,11 @@ func (pg *Generator) GeneratePSP(
351356
ns = "all"
352357
}
353358

354-
psp.Name = fmt.Sprintf("%s-%s-%s", "pod-security-policy", ns, time.Now().Format("20060102150405"))
359+
if pspName == "" {
360+
psp.Name = fmt.Sprintf("%s-%s-%s", "pod-security-policy", ns, time.Now().Format("20060102150405"))
361+
} else {
362+
psp.Name = pspName
363+
}
355364

356365
for _, sc := range pssList {
357366
psp.Spec.HostPID = psp.Spec.HostPID || sc.HostPID

kube-psp-advisor.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
_ "k8s.io/client-go/plugin/pkg/client/auth"
2121
)
2222

23-
func inspectPsp(kubeconfig string, withReport bool, namespace string) error {
23+
func inspectPsp(kubeconfig string, namespace string, withReport, withGrant bool) error {
2424
advisor, err := advisor.NewAdvisor(kubeconfig)
2525

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

41+
if withGrant {
42+
return advisor.PrintPodSecurityPolicyWithGrants()
43+
}
4144
err = advisor.PrintPodSecurityPolicy()
4245

4346
if err != nil {
@@ -85,6 +88,7 @@ func main() {
8588

8689
var kubeconfig string
8790
var withReport bool
91+
var withGrant bool
8892
var namespace string
8993
var podObjFilename string
9094
var pspFilename string
@@ -115,7 +119,7 @@ func main() {
115119
Short: "Inspect a live K8s Environment to generate a PodSecurityPolicy",
116120
Long: "Fetch all objects in the provided namespace to generate a Pod Security Policy",
117121
Run: func(cmd *cobra.Command, args []string) {
118-
err := inspectPsp(kubeconfig, withReport, namespace)
122+
err := inspectPsp(kubeconfig, namespace, withReport, withGrant)
119123
if err != nil {
120124
log.Fatalf("Could not run inspect command: %v", err)
121125
}
@@ -150,6 +154,7 @@ func main() {
150154
inspectCmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file")
151155
}
152156
inspectCmd.Flags().BoolVar(&withReport, "report", false, "(optional) return with detail report")
157+
inspectCmd.Flags().BoolVar(&withGrant, "grant", false, "(optional) return with pod security policies, roles and rolebindings")
153158
inspectCmd.Flags().StringVar(&namespace, "namespace", "", "(optional) namespace")
154159

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

0 commit comments

Comments
 (0)