Skip to content

Commit c436567

Browse files
sdminonnesoltysh
authored andcommitted
PSP reviews: client
1 parent 03e7153 commit c436567

17 files changed

+862
-0
lines changed

Diff for: pkg/client/client.go

+12
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,18 @@ func (c *Client) RoleBindingRestrictions(namespace string) RoleBindingRestrictio
295295
return newRoleBindingRestrictions(c, namespace)
296296
}
297297

298+
func (c *Client) PodSecurityPolicyReviews(namespace string) PodSecurityPolicyReviewInterface {
299+
return newPodSecurityPolicyReviews(c, namespace)
300+
}
301+
302+
func (c *Client) PodSecurityPolicySelfSubjectReviews(namespace string) PodSecurityPolicySelfSubjectReviewInterface {
303+
return newPodSecurityPolicySelfSubjectReviews(c, namespace)
304+
}
305+
306+
func (c *Client) PodSecurityPolicySubjectReviews(namespace string) PodSecurityPolicySubjectReviewInterface {
307+
return newPodSecurityPolicySubjectReviews(c, namespace)
308+
}
309+
298310
// Client is an OpenShift client object
299311
type Client struct {
300312
*restclient.RESTClient

Diff for: pkg/client/podsecuritypolicyreview.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package client
2+
3+
import securityapi "github.com/openshift/origin/pkg/security/api"
4+
5+
// PodSecurityPolicyReviewsNamespacer has methods to work with PodSecurityPolicyReview resources in the cluster scope
6+
type PodSecurityPolicyReviewsNamespacer interface {
7+
PodSecurityPolicyReviews(namespace string) PodSecurityPolicyReviewInterface
8+
}
9+
10+
// PodSecurityPolicyReviewInterface exposes methods on PodSecurityPolicyReview resources.
11+
type PodSecurityPolicyReviewInterface interface {
12+
Create(policy *securityapi.PodSecurityPolicyReview) (*securityapi.PodSecurityPolicyReview, error)
13+
}
14+
15+
// podSecurityPolicyReviews implements PodSecurityPolicyReviewsNamespacer interface
16+
type podSecurityPolicyReviews struct {
17+
c *Client
18+
ns string
19+
}
20+
21+
// newPodSecurityPolicyReviews returns a podSecurityPolicyReviews
22+
func newPodSecurityPolicyReviews(c *Client, namespace string) *podSecurityPolicyReviews {
23+
return &podSecurityPolicyReviews{
24+
c: c,
25+
ns: namespace,
26+
}
27+
}
28+
29+
// Create creates a PodSecurityPolicyReview
30+
func (p *podSecurityPolicyReviews) Create(pspr *securityapi.PodSecurityPolicyReview) (result *securityapi.PodSecurityPolicyReview, err error) {
31+
result = &securityapi.PodSecurityPolicyReview{}
32+
err = p.c.Post().Namespace(p.ns).Resource("podSecurityPolicyReviews").Body(pspr).Do().Into(result)
33+
return
34+
}

Diff for: pkg/client/podsecuritypolicysubjectreview.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package client
2+
3+
import securityapi "github.com/openshift/origin/pkg/security/api"
4+
5+
// PodSecurityPolicySubjectReviewsNamespacer has methods to work with PodSecurityPolicySubjectReview resources in the cluster scope
6+
type PodSecurityPolicySubjectReviewsNamespacer interface {
7+
PodSecurityPolicySubjectReviews(namespace string) PodSecurityPolicySubjectReviewInterface
8+
}
9+
10+
// PodSecurityPolicySubjectReviewInterface exposes methods on PodSecurityPolicySubjectReview resources.
11+
type PodSecurityPolicySubjectReviewInterface interface {
12+
Create(policy *securityapi.PodSecurityPolicySubjectReview) (*securityapi.PodSecurityPolicySubjectReview, error)
13+
}
14+
15+
// PodSecurityPolicySubjectReviews implements PodSecurityPolicySubjectReviews interface
16+
type podSecurityPolicySubjectReviews struct {
17+
c *Client
18+
ns string
19+
}
20+
21+
// newPodSecurityPolicySubjectReviews returns a PodSecurityPolicySubjectReviews
22+
func newPodSecurityPolicySubjectReviews(c *Client, namespace string) *podSecurityPolicySubjectReviews {
23+
return &podSecurityPolicySubjectReviews{
24+
c: c,
25+
ns: namespace,
26+
}
27+
}
28+
29+
func (p *podSecurityPolicySubjectReviews) Create(pspsr *securityapi.PodSecurityPolicySubjectReview) (result *securityapi.PodSecurityPolicySubjectReview, err error) {
30+
result = &securityapi.PodSecurityPolicySubjectReview{}
31+
err = p.c.Post().Namespace(p.ns).Resource("podSecurityPolicySubjectReviews").Body(pspsr).Do().Into(result)
32+
return
33+
}
34+
35+
// PodSecurityPolicySelfSubjectReviewsNamespacer has methods to work with PodSecurityPolicySelfSubjectReview resources in the cluster scope
36+
type PodSecurityPolicySelfSubjectReviewsNamespacer interface {
37+
PodSecurityPolicySelfSubjectReviews(namespace string) PodSecurityPolicySelfSubjectReviewInterface
38+
}
39+
40+
// PodSecurityPolicySelfSubjectReviewInterface exposes methods on PodSecurityPolicySelfSubjectReview resources.
41+
type PodSecurityPolicySelfSubjectReviewInterface interface {
42+
Create(policy *securityapi.PodSecurityPolicySelfSubjectReview) (*securityapi.PodSecurityPolicySelfSubjectReview, error)
43+
}
44+
45+
type podSecurityPolicySelfSubjectReviews struct {
46+
c *Client
47+
ns string
48+
}
49+
50+
func newPodSecurityPolicySelfSubjectReviews(c *Client, namespace string) *podSecurityPolicySelfSubjectReviews {
51+
return &podSecurityPolicySelfSubjectReviews{
52+
c: c,
53+
ns: namespace,
54+
}
55+
}
56+
57+
func (p *podSecurityPolicySelfSubjectReviews) Create(pspssr *securityapi.PodSecurityPolicySelfSubjectReview) (result *securityapi.PodSecurityPolicySelfSubjectReview, err error) {
58+
result = &securityapi.PodSecurityPolicySelfSubjectReview{}
59+
err = p.c.Post().Namespace(p.ns).Resource("podSecurityPolicySelfSubjectReviews").Body(pspssr).Do().Into(result)
60+
return
61+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package testclient
2+
3+
import (
4+
securityapi "github.com/openshift/origin/pkg/security/api"
5+
"k8s.io/kubernetes/pkg/api/unversioned"
6+
"k8s.io/kubernetes/pkg/client/testing/core"
7+
)
8+
9+
// FakePodSecurityPolicyReviews implements the PodSecurityPolicyReviews interface.
10+
// Meant to be embedded into a struct to get a default implementation.
11+
// This makes faking out just the methods you want to test easier.
12+
type FakePodSecurityPolicyReviews struct {
13+
Fake *Fake
14+
Namespace string
15+
}
16+
17+
var podSecurityPolicyReviewsResource = unversioned.GroupVersionResource{Group: "", Version: "", Resource: "podsecuritypolicyreviews"}
18+
19+
func (c *FakePodSecurityPolicyReviews) Create(inObj *securityapi.PodSecurityPolicyReview) (*securityapi.PodSecurityPolicyReview, error) {
20+
obj, err := c.Fake.Invokes(core.NewCreateAction(podSecurityPolicyReviewsResource, c.Namespace, inObj), &securityapi.PodSecurityPolicyReview{})
21+
if cast, ok := obj.(*securityapi.PodSecurityPolicyReview); ok {
22+
return cast, err
23+
}
24+
return nil, err
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package testclient
2+
3+
import (
4+
securityapi "github.com/openshift/origin/pkg/security/api"
5+
"k8s.io/kubernetes/pkg/api/unversioned"
6+
"k8s.io/kubernetes/pkg/client/testing/core"
7+
)
8+
9+
// FakePodSecurityPolicySubjectReviews implements the PodSecurityPolicySubjectReviews interface.
10+
// Meant to be embedded into a struct to get a default implementation.
11+
// This makes faking out just the methods you want to test easier.
12+
type FakePodSecurityPolicySubjectReviews struct {
13+
Fake *Fake
14+
Namespace string
15+
}
16+
17+
var podSecurityPolicySubjectReviewsResource = unversioned.GroupVersionResource{Group: "", Version: "", Resource: "podsecuritypolicysubjectreviews"}
18+
19+
func (c *FakePodSecurityPolicySubjectReviews) Create(inObj *securityapi.PodSecurityPolicySubjectReview) (*securityapi.PodSecurityPolicySubjectReview, error) {
20+
obj, err := c.Fake.Invokes(core.NewCreateAction(podSecurityPolicySubjectReviewsResource, c.Namespace, inObj), &securityapi.PodSecurityPolicySubjectReview{})
21+
if cast, ok := obj.(*securityapi.PodSecurityPolicySubjectReview); ok {
22+
return cast, err
23+
}
24+
return nil, err
25+
}
26+
27+
// FakePodSecurityPolicySelfSubjectReviews implements the PodSecurityPolicySelfSubjectReviews interface.
28+
// Meant to be embedded into a struct to get a default implementation.
29+
// This makes faking out just the methods you want to test easier.
30+
type FakePodSecurityPolicySelfSubjectReviews struct {
31+
Fake *Fake
32+
Namespace string
33+
}
34+
35+
var podSecurityPolicySelfSubjectReviewsResource = unversioned.GroupVersionResource{Group: "", Version: "", Resource: "podsecuritypolicyselfsubjectreviews"}
36+
37+
func (c *FakePodSecurityPolicySelfSubjectReviews) Create(inObj *securityapi.PodSecurityPolicySelfSubjectReview) (*securityapi.PodSecurityPolicySelfSubjectReview, error) {
38+
obj, err := c.Fake.Invokes(core.NewCreateAction(podSecurityPolicySelfSubjectReviewsResource, c.Namespace, inObj), &securityapi.PodSecurityPolicySelfSubjectReview{})
39+
if cast, ok := obj.(*securityapi.PodSecurityPolicySelfSubjectReview); ok {
40+
return cast, err
41+
}
42+
return nil, err
43+
}

Diff for: pkg/cmd/admin/policy/policy.go

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ func NewCmdPolicy(name, fullName string, f *clientcmd.Factory, out, errout io.Wr
4545
Message: "Discover:",
4646
Commands: []*cobra.Command{
4747
NewCmdWhoCan(WhoCanRecommendedName, fullName+" "+WhoCanRecommendedName, f, out),
48+
NewCmdSccSubjectReview(SubjectReviewRecommendedName, fullName+" "+SubjectReviewRecommendedName, f, out),
49+
NewCmdSccReview(ReviewRecommendedName, fullName+" "+ReviewRecommendedName, f, out),
4850
},
4951
},
5052
{

Diff for: pkg/cmd/admin/policy/review.go

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package policy
2+
3+
import (
4+
"fmt"
5+
"io"
6+
7+
"github.com/spf13/cobra"
8+
9+
kapi "k8s.io/kubernetes/pkg/api"
10+
"k8s.io/kubernetes/pkg/api/meta"
11+
"k8s.io/kubernetes/pkg/apis/apps"
12+
"k8s.io/kubernetes/pkg/kubectl"
13+
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
14+
"k8s.io/kubernetes/pkg/kubectl/resource"
15+
"k8s.io/kubernetes/pkg/runtime"
16+
utilerrors "k8s.io/kubernetes/pkg/util/errors"
17+
18+
ometa "github.com/openshift/origin/pkg/api/meta"
19+
"github.com/openshift/origin/pkg/client"
20+
"github.com/openshift/origin/pkg/cmd/cli/describe"
21+
"github.com/openshift/origin/pkg/cmd/templates"
22+
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
23+
securityapi "github.com/openshift/origin/pkg/security/api"
24+
)
25+
26+
var (
27+
reviewLong = templates.LongDesc(`Checks which Service Account can create a Pod.
28+
The Pod is inferred from the PodTemplateSpec in the provided resource.
29+
If no Service Account is provided the one specified in podTemplateSpec.spec.serviceAccountName is used,
30+
unless it is empty, in which case "default" is used.
31+
If Service Accounts are provided the podTemplateSpec.spec.serviceAccountName is ignored.
32+
`)
33+
reviewExamples = templates.Examples(`# Check whether Service Accounts sa1 and sa2 can admit a Pod with TemplatePodSpec specified in my_resource.yaml
34+
# Service Account specified in myresource.yaml file is ignored
35+
$ %[1]s -s sa1,sa2 -f my_resource.yaml
36+
37+
# Check whether Service Account specified in my_resource_with_sa.yaml can admit the Pod
38+
$ %[1]s -f my_resource_with_sa.yaml
39+
40+
# Check whether default Service Account can admit the Pod, default is taken since no Service Account is defined in myresource_with_no_sa.yaml
41+
$ %[1]s -f myresource_with_no_sa.yaml
42+
`)
43+
)
44+
45+
const ReviewRecommendedName = "scc-review"
46+
47+
type sccReviewOptions struct {
48+
client client.PodSecurityPolicyReviewsNamespacer
49+
namespace string
50+
enforceNamespace bool
51+
out io.Writer
52+
mapper meta.RESTMapper
53+
typer runtime.ObjectTyper
54+
RESTClientFactory func(mapping *meta.RESTMapping) (resource.RESTClient, error)
55+
printer kubectl.ResourcePrinter
56+
FilenameOptions resource.FilenameOptions
57+
ServiceAccountNames []string
58+
}
59+
60+
func NewCmdSccReview(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
61+
o := &sccReviewOptions{}
62+
cmd := &cobra.Command{
63+
Use: name,
64+
Short: "Checks which ServiceAccount can create a Pod",
65+
Long: reviewLong,
66+
Example: fmt.Sprintf(reviewExamples, fullName),
67+
Run: func(cmd *cobra.Command, args []string) {
68+
kcmdutil.CheckErr(o.Complete(f, args, cmd, out))
69+
kcmdutil.CheckErr(o.Run(args))
70+
},
71+
}
72+
73+
cmd.Flags().StringSliceVarP(&o.ServiceAccountNames, "serviceaccounts", "s", o.ServiceAccountNames, "List of ServiceAccount names, comma separated")
74+
kcmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "Filename, directory, or URL to a file identifying the resource to get from a server.")
75+
76+
kcmdutil.AddPrinterFlags(cmd)
77+
78+
return cmd
79+
}
80+
81+
func (o *sccReviewOptions) Complete(f *clientcmd.Factory, args []string, cmd *cobra.Command, out io.Writer) error {
82+
if len(args) == 0 && len(o.FilenameOptions.Filenames) == 0 {
83+
return kcmdutil.UsageError(cmd, cmd.Use)
84+
}
85+
var err error
86+
o.namespace, o.enforceNamespace, err = f.DefaultNamespace()
87+
if err != nil {
88+
return err
89+
}
90+
o.client, _, err = f.Clients()
91+
if err != nil {
92+
return fmt.Errorf("unable to obtain client: %v", err)
93+
}
94+
o.mapper, o.typer = f.Object()
95+
o.RESTClientFactory = f.ClientForMapping
96+
97+
if len(kcmdutil.GetFlagString(cmd, "output")) != 0 {
98+
clientConfig, err := f.ClientConfig()
99+
if err != nil {
100+
return err
101+
}
102+
version, err := kcmdutil.OutputVersion(cmd, clientConfig.GroupVersion)
103+
if err != nil {
104+
return err
105+
}
106+
p, _, err := kcmdutil.PrinterForCommand(cmd)
107+
if err != nil {
108+
return err
109+
}
110+
o.printer = kubectl.NewVersionedPrinter(p, kapi.Scheme, version)
111+
} else {
112+
o.printer = describe.NewHumanReadablePrinter(kubectl.PrintOptions{NoHeaders: kcmdutil.GetFlagBool(cmd, "no-headers")})
113+
}
114+
115+
o.out = out
116+
117+
return nil
118+
}
119+
120+
func (o *sccReviewOptions) Run(args []string) error {
121+
122+
r := resource.NewBuilder(o.mapper, o.typer, resource.ClientMapperFunc(o.RESTClientFactory), kapi.Codecs.UniversalDecoder()).
123+
NamespaceParam(o.namespace).
124+
FilenameParam(o.enforceNamespace, &o.FilenameOptions).
125+
ResourceTypeOrNameArgs(true, args...).
126+
ContinueOnError().
127+
Flatten().
128+
Do()
129+
err := r.Err()
130+
if err != nil {
131+
return err
132+
}
133+
134+
allErrs := []error{}
135+
reviews := []*securityapi.PodSecurityPolicyReview{}
136+
err = r.Visit(func(info *resource.Info, err error) error {
137+
if err != nil {
138+
return err
139+
}
140+
objectName := info.Name
141+
podTemplateSpec, err := GetPodTemplateForObject(info.Object)
142+
if err != nil {
143+
return fmt.Errorf(" %q cannot create pod: %v", objectName, err)
144+
}
145+
err = CheckStatefulSetWithWolumeClaimTemplates(info.Object)
146+
if err != nil {
147+
return err
148+
}
149+
review := &securityapi.PodSecurityPolicyReview{
150+
Spec: securityapi.PodSecurityPolicyReviewSpec{
151+
Template: *podTemplateSpec,
152+
ServiceAccountNames: o.ServiceAccountNames,
153+
},
154+
}
155+
response, err := o.client.PodSecurityPolicyReviews(o.namespace).Create(review)
156+
if err != nil {
157+
return fmt.Errorf("unable to compute Pod Security Policy Review for %q: %v", objectName, err)
158+
}
159+
reviews = append(reviews, response)
160+
return nil
161+
})
162+
allErrs = append(allErrs, err)
163+
for i := range reviews {
164+
if err := o.printer.PrintObj(reviews[i], o.out); err != nil {
165+
allErrs = append(allErrs, err)
166+
}
167+
}
168+
return utilerrors.NewAggregate(allErrs)
169+
}
170+
171+
// CheckStatefulSetWithWolumeClaimTemplates checks whether a supplied object is a statefulSet with volumeClaimTemplates
172+
// Currently scc-review and scc-subject-review commands cannot handle correctly this case since validation is not based
173+
// only on podTemplateSpec.
174+
func CheckStatefulSetWithWolumeClaimTemplates(obj runtime.Object) error {
175+
// TODO remove this as soon upstream statefulSet validation for podSpec is fixed.
176+
// Currently podTemplateSpec for a statefulSet is not fully validated
177+
// spec.volumeClaimTemplates info should be propagated down to
178+
// spec.template.spec validateContainers to validate volumeMounts
179+
//https://github.com/openshift/origin/blob/master/vendor/k8s.io/kubernetes/pkg/apis/apps/validation/validation.go#L57
180+
switch r := obj.(type) {
181+
case *apps.StatefulSet:
182+
if len(r.Spec.VolumeClaimTemplates) > 0 {
183+
return fmt.Errorf("StatefulSet %q with spec.volumeClaimTemplates currently not supported.", r.GetName())
184+
}
185+
}
186+
return nil
187+
}
188+
189+
func GetPodTemplateForObject(obj runtime.Object) (*kapi.PodTemplateSpec, error) {
190+
podSpec, _, err := ometa.GetPodSpec(obj)
191+
if err != nil {
192+
return nil, err
193+
}
194+
return &kapi.PodTemplateSpec{Spec: *podSpec}, nil
195+
}

0 commit comments

Comments
 (0)