Skip to content

Commit c6dd9ce

Browse files
deads2kstlaz
authored andcommitted
enable pod security admission for techpreview
1 parent f95ad9e commit c6dd9ce

File tree

6 files changed

+265
-17
lines changed

6 files changed

+265
-17
lines changed

bindata/assets/config/defaultconfig.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ admission:
1414
kind: PodSecurityConfiguration
1515
apiVersion: pod-security.admission.config.k8s.io/v1beta1
1616
defaults:
17-
enforce: "privileged"
18-
enforce-version: "latest"
19-
audit: "restricted"
20-
audit-version: "latest"
21-
warn: "restricted"
22-
warn-version: "latest"
17+
enforce: "invalid-to-force-substitution"
18+
enforce-version: "invalid-to-force-substitution"
19+
audit: "invalid-to-force-substitution"
20+
audit-version: "invalid-to-force-substitution"
21+
warn: "invalid-to-force-substitution"
22+
warn-version: "invalid-to-force-substitution"
2323
exemptions:
2424
usernames:
2525
# The build controller creates pods that are likely to be privileged

pkg/cmd/render/render.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@ import (
1515
"path/filepath"
1616

1717
"github.com/ghodss/yaml"
18+
configv1 "github.com/openshift/api/config/v1"
19+
kubecontrolplanev1 "github.com/openshift/api/kubecontrolplane/v1"
20+
"github.com/openshift/cluster-kube-apiserver-operator/bindata"
21+
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation/auth"
22+
libgoaudit "github.com/openshift/library-go/pkg/operator/apiserver/audit"
23+
genericrender "github.com/openshift/library-go/pkg/operator/render"
24+
genericrenderoptions "github.com/openshift/library-go/pkg/operator/render/options"
1825
"github.com/spf13/cobra"
1926
"github.com/spf13/pflag"
20-
2127
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2228
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/util/sets"
2330
kyaml "k8s.io/apimachinery/pkg/util/yaml"
2431
auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
2532
"k8s.io/klog/v2"
26-
27-
configv1 "github.com/openshift/api/config/v1"
28-
kubecontrolplanev1 "github.com/openshift/api/kubecontrolplane/v1"
29-
"github.com/openshift/cluster-kube-apiserver-operator/bindata"
30-
libgoaudit "github.com/openshift/library-go/pkg/operator/apiserver/audit"
31-
genericrender "github.com/openshift/library-go/pkg/operator/render"
32-
genericrenderoptions "github.com/openshift/library-go/pkg/operator/render/options"
3333
)
3434

3535
// renderOpts holds values to drive the render command.
@@ -260,7 +260,7 @@ func (r *renderOpts) Run() error {
260260
return err
261261
}
262262

263-
defaultConfig, err := bootstrapDefaultConfig()
263+
defaultConfig, err := bootstrapDefaultConfig(configv1.FeatureSet(r.generic.FeatureSet))
264264
if err != nil {
265265
return fmt.Errorf("failed to get default config with audit policy - %s", err)
266266
}
@@ -278,7 +278,7 @@ func (r *renderOpts) Run() error {
278278
return genericrender.WriteFiles(&r.generic, &renderConfig.FileConfig, renderConfig)
279279
}
280280

281-
func bootstrapDefaultConfig() ([]byte, error) {
281+
func bootstrapDefaultConfig(featureSet configv1.FeatureSet) ([]byte, error) {
282282
asset := filepath.Join("assets", "config", "defaultconfig.yaml")
283283
raw, err := bindata.Asset(asset)
284284
if err != nil {
@@ -303,6 +303,17 @@ func bootstrapDefaultConfig() ([]byte, error) {
303303
return nil, fmt.Errorf("failed to add audit policy into default config - %s", err)
304304
}
305305

306+
// modify config for TechPreviewNoUpgrade here.
307+
if sets.NewString(configv1.FeatureSets[featureSet].Disabled...).Has("OpenShiftPodSecurityAdmission") {
308+
if err := auth.SetPodSecurityAdmissionToEnforcePrivileged(defaultConfig); err != nil {
309+
return nil, err
310+
}
311+
} else {
312+
if err := auth.SetPodSecurityAdmissionToEnforceRestricted(defaultConfig); err != nil {
313+
return nil, err
314+
}
315+
}
316+
306317
defaultConfigRaw, err := json.Marshal(defaultConfig)
307318
if err != nil {
308319
return nil, fmt.Errorf("failed to marshal default config - %s", err)

pkg/cmd/render/render_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ spec:
624624
}
625625

626626
func TestGetDefaultConfigWithAuditPolicy(t *testing.T) {
627-
raw, err := bootstrapDefaultConfig()
627+
raw, err := bootstrapDefaultConfig(configv1.Default)
628628
require.NoError(t, err)
629629
require.True(t, len(raw) > 0)
630630

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package auth
2+
3+
import (
4+
"fmt"
5+
6+
configv1 "github.com/openshift/api/config/v1"
7+
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
8+
"github.com/openshift/library-go/pkg/operator/configobserver"
9+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
10+
"github.com/openshift/library-go/pkg/operator/events"
11+
apierrors "k8s.io/apimachinery/pkg/api/errors"
12+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
13+
"k8s.io/apimachinery/pkg/util/sets"
14+
)
15+
16+
type FeatureGateLister interface {
17+
FeatureGateLister() configlistersv1.FeatureGateLister
18+
}
19+
20+
var configPath = []string{"admission", "pluginConfig", "PodSecurity", "configuration", "defaults"}
21+
22+
// We want this:
23+
/*
24+
admission:
25+
pluginConfig:
26+
PodSecurity:
27+
configuration:
28+
kind: PodSecurityConfiguration
29+
apiVersion: pod-security.admission.config.k8s.io/v1beta1
30+
defaults:
31+
enforce: "restricted"
32+
enforce-version: "latest"
33+
*/
34+
func SetPodSecurityAdmissionToEnforceRestricted(config map[string]interface{}) error {
35+
psaEnforceRestricted := map[string]interface{}{
36+
"enforce": "restricted",
37+
"enforce-version": "latest",
38+
"audit": "restricted",
39+
"audit-version": "latest",
40+
"warn": "restricted",
41+
"warn-version": "latest",
42+
}
43+
44+
unstructured.RemoveNestedField(config, configPath...)
45+
if err := unstructured.SetNestedMap(config, psaEnforceRestricted, configPath...); err != nil {
46+
return fmt.Errorf("failed to set PodSecurity to enforce restricted: %w", err)
47+
}
48+
49+
return nil
50+
}
51+
52+
func SetPodSecurityAdmissionToEnforcePrivileged(config map[string]interface{}) error {
53+
psaEnforceRestricted := map[string]interface{}{
54+
"enforce": "privileged",
55+
"enforce-version": "latest",
56+
"audit": "restricted",
57+
"audit-version": "latest",
58+
"warn": "restricted",
59+
"warn-version": "latest",
60+
}
61+
62+
unstructured.RemoveNestedField(config, configPath...)
63+
if err := unstructured.SetNestedMap(config, psaEnforceRestricted, configPath...); err != nil {
64+
return fmt.Errorf("failed to set PodSecurity to enforce restricted: %w", err)
65+
}
66+
67+
return nil
68+
}
69+
70+
// ObserveFeatureFlags fills in --feature-flags for the kube-apiserver
71+
func ObservePodSecurityAdmissionEnforcement(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}) (ret map[string]interface{}, _ []error) {
72+
listers := genericListers.(FeatureGateLister)
73+
errs := []error{}
74+
75+
featureGate, err := listers.FeatureGateLister().Get("cluster")
76+
// if we have no featuregate, then the installer and MCO probably still have way to reconcile certain custom resources
77+
// we will assume that this means the same as default and hope for the best
78+
if apierrors.IsNotFound(err) {
79+
featureGate = &configv1.FeatureGate{
80+
Spec: configv1.FeatureGateSpec{
81+
FeatureGateSelection: configv1.FeatureGateSelection{
82+
FeatureSet: configv1.Default,
83+
},
84+
},
85+
}
86+
} else if err != nil {
87+
return existingConfig, append(errs, err)
88+
}
89+
90+
return observePodSecurityAdmissionEnforcement(featureGate, recorder, existingConfig)
91+
}
92+
93+
func observePodSecurityAdmissionEnforcement(featureGate *configv1.FeatureGate, recorder events.Recorder, existingConfig map[string]interface{}) (ret map[string]interface{}, _ []error) {
94+
defer func() {
95+
ret = configobserver.Pruned(ret, configPath)
96+
}()
97+
98+
errs := []error{}
99+
100+
_, disabled, err := featuregates.FeaturesGatesFromFeatureSets(featureGate)
101+
if err != nil {
102+
return existingConfig, append(errs, err)
103+
}
104+
105+
observedConfig := map[string]interface{}{}
106+
switch {
107+
case sets.NewString(disabled...).Has("OpenShiftPodSecurityAdmission"):
108+
if err := SetPodSecurityAdmissionToEnforcePrivileged(observedConfig); err != nil {
109+
return existingConfig, append(errs, err)
110+
}
111+
default:
112+
if err := SetPodSecurityAdmissionToEnforceRestricted(observedConfig); err != nil {
113+
return existingConfig, append(errs, err)
114+
}
115+
}
116+
117+
return observedConfig, errs
118+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package auth
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
"testing"
7+
8+
utilerrors "k8s.io/apimachinery/pkg/util/errors"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/stretchr/testify/require"
12+
13+
configv1 "github.com/openshift/api/config/v1"
14+
"github.com/openshift/library-go/pkg/operator/events"
15+
)
16+
17+
func TestObservePodSecurityAdmissionEnforcement(t *testing.T) {
18+
privilegedMap := map[string]interface{}{}
19+
require.NoError(t, SetPodSecurityAdmissionToEnforcePrivileged(privilegedMap))
20+
privilegedJSON, err := json.Marshal(privilegedMap)
21+
require.NoError(t, err)
22+
23+
restrictedMap := map[string]interface{}{}
24+
require.NoError(t, SetPodSecurityAdmissionToEnforceRestricted(restrictedMap))
25+
restrictedJSON, err := json.Marshal(restrictedMap)
26+
require.NoError(t, err)
27+
28+
defaultFeatureSet := &configv1.FeatureGate{
29+
Spec: configv1.FeatureGateSpec{
30+
FeatureGateSelection: configv1.FeatureGateSelection{
31+
FeatureSet: "",
32+
CustomNoUpgrade: nil,
33+
},
34+
},
35+
}
36+
37+
corruptFeatureSet := &configv1.FeatureGate{
38+
Spec: configv1.FeatureGateSpec{
39+
FeatureGateSelection: configv1.FeatureGateSelection{
40+
FeatureSet: "Bad",
41+
CustomNoUpgrade: nil,
42+
},
43+
},
44+
}
45+
46+
disabledFeatureSet := &configv1.FeatureGate{
47+
Spec: configv1.FeatureGateSpec{
48+
FeatureGateSelection: configv1.FeatureGateSelection{
49+
FeatureSet: "CustomNoUpgrade",
50+
CustomNoUpgrade: &configv1.CustomFeatureGates{
51+
Enabled: nil,
52+
Disabled: []string{"OpenShiftPodSecurityAdmission"},
53+
},
54+
},
55+
},
56+
}
57+
58+
for _, tc := range []struct {
59+
name string
60+
existingJSON string
61+
featureGate *configv1.FeatureGate
62+
expectedErr string
63+
expectedJSON string
64+
}{
65+
{
66+
name: "enforce",
67+
existingJSON: string(privilegedJSON),
68+
featureGate: defaultFeatureSet,
69+
expectedErr: "",
70+
expectedJSON: string(restrictedJSON),
71+
},
72+
{
73+
name: "corrupt-1",
74+
existingJSON: string(privilegedJSON),
75+
featureGate: corruptFeatureSet,
76+
expectedErr: "not found",
77+
expectedJSON: string(privilegedJSON),
78+
},
79+
{
80+
name: "corrupt-2",
81+
existingJSON: string(restrictedJSON),
82+
featureGate: corruptFeatureSet,
83+
expectedErr: "not found",
84+
expectedJSON: string(restrictedJSON),
85+
},
86+
{
87+
name: "disabled",
88+
existingJSON: string(restrictedJSON),
89+
featureGate: disabledFeatureSet,
90+
expectedErr: "",
91+
expectedJSON: string(privilegedJSON),
92+
},
93+
} {
94+
t.Run(tc.name, func(t *testing.T) {
95+
testRecorder := events.NewInMemoryRecorder("SAIssuerTest")
96+
existingConfig := map[string]interface{}{}
97+
require.NoError(t, json.Unmarshal([]byte(tc.existingJSON), &existingConfig))
98+
99+
actual, errs := observePodSecurityAdmissionEnforcement(tc.featureGate, testRecorder, existingConfig)
100+
101+
switch {
102+
case len(errs) == 0 && len(tc.expectedErr) == 0:
103+
case len(errs) == 0 && len(tc.expectedErr) > 0:
104+
t.Fatalf("missing err: %v", tc.expectedErr)
105+
106+
case len(errs) > 0 && len(tc.expectedErr) == 0:
107+
t.Fatal(errs)
108+
case len(errs) > 0 && len(tc.expectedErr) > 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), tc.expectedErr):
109+
t.Fatalf("missing err: %v in \n%v", tc.expectedErr, errs)
110+
}
111+
112+
actualJSON, err := json.Marshal(actual)
113+
require.NoError(t, err)
114+
115+
require.Equal(t, tc.expectedJSON, string(actualJSON), cmp.Diff(tc.expectedJSON, string(actualJSON)))
116+
})
117+
}
118+
}

pkg/operator/configobservation/configobservercontroller/observe_config_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ func NewConfigObserver(
129129
auth.ObserveAuthMetadata,
130130
auth.ObserveServiceAccountIssuer,
131131
auth.ObserveWebhookTokenAuthenticator,
132+
auth.ObservePodSecurityAdmissionEnforcement,
132133
encryption.NewEncryptionConfigObserver(
133134
operatorclient.TargetNamespace,
134135
// static path at which we expect to find the encryption config secret

0 commit comments

Comments
 (0)