Skip to content

Commit f86756d

Browse files
NET-12097 Support PodSecurityAdmission at the restricted level (#4478)
* Set API gateway security context to comply with best practices * update deployment security context * Set SeccompProfile on injected dataplane sidecar * Drop all capabilities in the injected sidecar * Set required securityContext properties on connect-inject-init container * Add changelog entry * May it please the linter * May it please the linter * Add helpful logs to failure message assertion * Set default value for API gateway's mapPrivilegedContainerPorts * Update invalidated unit tests * May it please the linter * Allow privilege escalation when expected for backwards compatibility * add init sec comp to mesh gateway deployment * Update invalidated unit tests --------- Co-authored-by: Sarah Alsmiller <[email protected]>
1 parent 79b60f7 commit f86756d

File tree

11 files changed

+114
-28
lines changed

11 files changed

+114
-28
lines changed

.changelog/4478.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
security: Support running Consul under Pod Security Admissions (PSA) restricted mode.
3+
```

acceptance/framework/k8s/deploy.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import (
1111
"time"
1212

1313
"github.com/gruntwork-io/terratest/modules/k8s"
14-
"github.com/hashicorp/consul-k8s/acceptance/framework/helpers"
15-
"github.com/hashicorp/consul-k8s/acceptance/framework/logger"
1614
"github.com/hashicorp/consul/sdk/testutil/retry"
1715
"github.com/stretchr/testify/require"
1816
v1 "k8s.io/api/apps/v1"
1917
batchv1 "k8s.io/api/batch/v1"
2018
"k8s.io/apimachinery/pkg/util/yaml"
19+
20+
"github.com/hashicorp/consul-k8s/acceptance/framework/helpers"
21+
"github.com/hashicorp/consul-k8s/acceptance/framework/logger"
2122
)
2223

2324
// Deploy creates a Kubernetes deployment by applying configuration stored at filepath,
@@ -141,15 +142,15 @@ func CheckStaticServerConnectionMultipleFailureMessages(t *testing.T, options *k
141142
require.Contains(r, output, expectedOutput)
142143
} else {
143144
require.Error(r, err)
144-
require.Condition(r, func() bool {
145+
require.Conditionf(r, func() bool {
145146
exists := false
146147
for _, msg := range failureMessages {
147148
if strings.Contains(output, msg) {
148149
exists = true
149150
}
150151
}
151152
return exists
152-
})
153+
}, "expected failure messages %q but got %q", failureMessages, output)
153154
}
154155
})
155156
}

acceptance/tests/api-gateway/api_gateway_external_servers_test.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ import (
88
"fmt"
99
"testing"
1010

11-
"github.com/hashicorp/consul-k8s/acceptance/framework/consul"
12-
"github.com/hashicorp/consul-k8s/acceptance/framework/helpers"
13-
"github.com/hashicorp/consul-k8s/acceptance/framework/k8s"
14-
"github.com/hashicorp/consul-k8s/acceptance/framework/logger"
1511
"github.com/hashicorp/consul/api"
1612
"github.com/hashicorp/consul/sdk/testutil/retry"
1713
"github.com/stretchr/testify/require"
1814
"k8s.io/apimachinery/pkg/types"
1915
gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
16+
17+
"github.com/hashicorp/consul-k8s/acceptance/framework/consul"
18+
"github.com/hashicorp/consul-k8s/acceptance/framework/helpers"
19+
"github.com/hashicorp/consul-k8s/acceptance/framework/k8s"
20+
"github.com/hashicorp/consul-k8s/acceptance/framework/logger"
2021
)
2122

2223
// TestAPIGateway_ExternalServers tests that connect works when using external servers.

charts/consul/templates/mesh-gateway-deployment.yaml

+14
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ spec:
126126
{{- end }}
127127
initContainers:
128128
- name: mesh-gateway-init
129+
securityContext:
130+
allowPrivilegeEscalation: false
131+
readOnlyRootFilesystem: true
132+
runAsNonRoot: true
133+
seccompProfile:
134+
type: RuntimeDefault
135+
capabilities:
136+
drop:
137+
- ALL
129138
image: {{ .Values.global.imageK8S }}
130139
{{ template "consul.imagePullPolicy" . }}
131140
env:
@@ -188,6 +197,11 @@ spec:
188197
image: {{ .Values.global.imageConsulDataplane | quote }}
189198
{{ template "consul.imagePullPolicy" . }}
190199
securityContext:
200+
allowPrivilegeEscalation: false
201+
readOnlyRootFilesystem: true
202+
runAsNonRoot: true
203+
seccompProfile:
204+
type: RuntimeDefault
191205
capabilities:
192206
{{ if not .Values.meshGateway.hostNetwork}}
193207
drop:

control-plane/api-gateway/gatekeeper/dataplane.go

+24-9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
corev1 "k8s.io/api/core/v1"
1111
"k8s.io/apimachinery/pkg/util/intstr"
1212
"k8s.io/utils/ptr"
13+
gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
1314

1415
"github.com/hashicorp/consul-k8s/control-plane/api-gateway/common"
1516
"github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1"
@@ -27,7 +28,7 @@ const (
2728
volumeNameForTLSCerts = "consul-gateway-tls-certificates"
2829
)
2930

30-
func consulDataplaneContainer(metrics common.MetricsConfig, config common.HelmConfig, gcc v1alpha1.GatewayClassConfig, name, namespace string, mounts []corev1.VolumeMount) (corev1.Container, error) {
31+
func consulDataplaneContainer(metrics common.MetricsConfig, config common.HelmConfig, gcc v1alpha1.GatewayClassConfig, gateway gwv1beta1.Gateway, mounts []corev1.VolumeMount) (corev1.Container, error) {
3132
// Extract the service account token's volume mount.
3233
var (
3334
err error
@@ -38,7 +39,7 @@ func consulDataplaneContainer(metrics common.MetricsConfig, config common.HelmCo
3839
bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
3940
}
4041

41-
args, err := getDataplaneArgs(metrics, namespace, config, bearerTokenFile, name)
42+
args, err := getDataplaneArgs(metrics, gateway.Namespace, config, bearerTokenFile, gateway.Name)
4243
if err != nil {
4344
return corev1.Container{}, err
4445
}
@@ -54,7 +55,7 @@ func consulDataplaneContainer(metrics common.MetricsConfig, config common.HelmCo
5455
}
5556

5657
container := corev1.Container{
57-
Name: name,
58+
Name: gateway.Name,
5859
Image: config.ImageDataplane,
5960
ImagePullPolicy: corev1.PullPolicy(config.GlobalImagePullPolicy),
6061

@@ -110,19 +111,33 @@ func consulDataplaneContainer(metrics common.MetricsConfig, config common.HelmCo
110111
container.Resources = *gcc.Spec.DeploymentSpec.Resources
111112
}
112113

113-
// If running in vanilla K8s, run as root to allow binding to privileged ports;
114-
// otherwise, allow the user to be assigned by OpenShift.
114+
// For backwards-compatibility, we allow privilege escalation if port mapping
115+
// is disabled and the Gateway utilizes a privileged port (< 1024).
116+
usingPrivilegedPorts := false
117+
if gcc.Spec.MapPrivilegedContainerPorts == 0 {
118+
for _, listener := range gateway.Spec.Listeners {
119+
if listener.Port < 1024 {
120+
usingPrivilegedPorts = true
121+
break
122+
}
123+
}
124+
}
125+
115126
container.SecurityContext = &corev1.SecurityContext{
116-
ReadOnlyRootFilesystem: ptr.To(true),
127+
AllowPrivilegeEscalation: ptr.To(usingPrivilegedPorts),
128+
ReadOnlyRootFilesystem: ptr.To(true),
129+
RunAsNonRoot: ptr.To(true),
130+
SeccompProfile: &corev1.SeccompProfile{
131+
Type: corev1.SeccompProfileTypeRuntimeDefault,
132+
},
117133
// Drop any Linux capabilities you'd get as root other than NET_BIND_SERVICE.
134+
// NET_BIND_SERVICE is a requirement for consul-dataplane, even though we don't
135+
// bind to privileged ports.
118136
Capabilities: &corev1.Capabilities{
119137
Add: []corev1.Capability{netBindCapability},
120138
Drop: []corev1.Capability{allCapabilities},
121139
},
122140
}
123-
if !config.EnableOpenShift {
124-
container.SecurityContext.RunAsUser = ptr.To(int64(0))
125-
}
126141

127142
return container, nil
128143
}

control-plane/api-gateway/gatekeeper/deployment.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func (g *Gatekeeper) deployment(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayC
109109

110110
volumes, mounts := volumesAndMounts(gateway)
111111

112-
container, err := consulDataplaneContainer(metrics, config, gcc, gateway.Name, gateway.Namespace, mounts)
112+
container, err := consulDataplaneContainer(metrics, config, gcc, gateway, mounts)
113113
if err != nil {
114114
return nil, err
115115
}

control-plane/api-gateway/gatekeeper/init.go

+3
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ func (g *Gatekeeper) initContainer(config common.HelmConfig, name, namespace str
213213
},
214214
AllowPrivilegeEscalation: ptr.To(false),
215215
ReadOnlyRootFilesystem: ptr.To(true),
216+
SeccompProfile: &corev1.SeccompProfile{
217+
Type: corev1.SeccompProfileTypeRuntimeDefault,
218+
},
216219
}
217220

218221
return container, nil

control-plane/connect-inject/webhook/consul_dataplane_sidecar.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import (
1010
"strings"
1111

1212
"github.com/google/shlex"
13-
"github.com/hashicorp/consul-k8s/control-plane/connect-inject/common"
14-
"github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants"
1513
corev1 "k8s.io/api/core/v1"
1614
"k8s.io/apimachinery/pkg/api/resource"
1715
"k8s.io/apimachinery/pkg/util/intstr"
1816
"k8s.io/utils/ptr"
17+
18+
"github.com/hashicorp/consul-k8s/control-plane/connect-inject/common"
19+
"github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants"
1920
)
2021

2122
const (
@@ -264,10 +265,14 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor
264265
RunAsGroup: ptr.To(group),
265266
RunAsNonRoot: ptr.To(true),
266267
AllowPrivilegeEscalation: ptr.To(false),
268+
SeccompProfile: &corev1.SeccompProfile{
269+
Type: corev1.SeccompProfileTypeRuntimeDefault,
270+
},
267271
// consul-dataplane requires the NET_BIND_SERVICE capability regardless of binding port #.
268272
// See https://developer.hashicorp.com/consul/docs/connect/dataplane#technical-constraints
269273
Capabilities: &corev1.Capabilities{
270-
Add: []corev1.Capability{"NET_BIND_SERVICE"},
274+
Add: []corev1.Capability{"NET_BIND_SERVICE"},
275+
Drop: []corev1.Capability{"ALL"},
271276
},
272277
ReadOnlyRootFilesystem: ptr.To(true),
273278
}

control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go

+20-4
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,11 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) {
808808
ReadOnlyRootFilesystem: ptr.To(true),
809809
AllowPrivilegeEscalation: ptr.To(false),
810810
Capabilities: &corev1.Capabilities{
811-
Add: []corev1.Capability{"NET_BIND_SERVICE"},
811+
Add: []corev1.Capability{"NET_BIND_SERVICE"},
812+
Drop: []corev1.Capability{"ALL"},
813+
},
814+
SeccompProfile: &corev1.SeccompProfile{
815+
Type: corev1.SeccompProfileTypeRuntimeDefault,
812816
},
813817
},
814818
},
@@ -822,7 +826,11 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) {
822826
ReadOnlyRootFilesystem: ptr.To(true),
823827
AllowPrivilegeEscalation: ptr.To(false),
824828
Capabilities: &corev1.Capabilities{
825-
Add: []corev1.Capability{"NET_BIND_SERVICE"},
829+
Add: []corev1.Capability{"NET_BIND_SERVICE"},
830+
Drop: []corev1.Capability{"ALL"},
831+
},
832+
SeccompProfile: &corev1.SeccompProfile{
833+
Type: corev1.SeccompProfileTypeRuntimeDefault,
826834
},
827835
},
828836
},
@@ -836,7 +844,11 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) {
836844
ReadOnlyRootFilesystem: ptr.To(true),
837845
AllowPrivilegeEscalation: ptr.To(false),
838846
Capabilities: &corev1.Capabilities{
839-
Add: []corev1.Capability{"NET_BIND_SERVICE"},
847+
Add: []corev1.Capability{"NET_BIND_SERVICE"},
848+
Drop: []corev1.Capability{"ALL"},
849+
},
850+
SeccompProfile: &corev1.SeccompProfile{
851+
Type: corev1.SeccompProfileTypeRuntimeDefault,
840852
},
841853
},
842854
},
@@ -850,7 +862,11 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) {
850862
ReadOnlyRootFilesystem: ptr.To(true),
851863
AllowPrivilegeEscalation: ptr.To(false),
852864
Capabilities: &corev1.Capabilities{
853-
Add: []corev1.Capability{"NET_BIND_SERVICE"},
865+
Add: []corev1.Capability{"NET_BIND_SERVICE"},
866+
Drop: []corev1.Capability{"ALL"},
867+
},
868+
SeccompProfile: &corev1.SeccompProfile{
869+
Type: corev1.SeccompProfileTypeRuntimeDefault,
854870
},
855871
},
856872
},

control-plane/connect-inject/webhook/container_init.go

+13
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,19 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod,
289289
},
290290
}
291291
}
292+
} else {
293+
container.SecurityContext = &corev1.SecurityContext{
294+
AllowPrivilegeEscalation: ptr.To(false),
295+
Capabilities: &corev1.Capabilities{
296+
Add: []corev1.Capability{},
297+
Drop: []corev1.Capability{"ALL"},
298+
},
299+
ReadOnlyRootFilesystem: ptr.To(true),
300+
RunAsNonRoot: ptr.To(true),
301+
SeccompProfile: &corev1.SeccompProfile{
302+
Type: corev1.SeccompProfileTypeRuntimeDefault,
303+
},
304+
}
292305
}
293306

294307
return container, nil

control-plane/connect-inject/webhook/container_init_test.go

+18-3
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import (
99
"testing"
1010
"time"
1111

12-
"github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants"
13-
"github.com/hashicorp/consul-k8s/control-plane/consul"
14-
"github.com/hashicorp/consul-k8s/control-plane/namespaces"
1512
"github.com/stretchr/testify/require"
1613
corev1 "k8s.io/api/core/v1"
1714
"k8s.io/apimachinery/pkg/api/resource"
1815
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1916
"k8s.io/utils/ptr"
17+
18+
"github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants"
19+
"github.com/hashicorp/consul-k8s/control-plane/consul"
20+
"github.com/hashicorp/consul-k8s/control-plane/namespaces"
2021
)
2122

2223
const k8sNamespace = "k8snamespace"
@@ -328,6 +329,20 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) {
328329
ReadOnlyRootFilesystem: ptr.To(true),
329330
AllowPrivilegeEscalation: ptr.To(false),
330331
}
332+
} else {
333+
// When tproxy disabled
334+
expectedSecurityContext = &corev1.SecurityContext{
335+
AllowPrivilegeEscalation: ptr.To(false),
336+
Capabilities: &corev1.Capabilities{
337+
Add: []corev1.Capability{},
338+
Drop: []corev1.Capability{"ALL"},
339+
},
340+
ReadOnlyRootFilesystem: ptr.To(true),
341+
RunAsNonRoot: ptr.To(true),
342+
SeccompProfile: &corev1.SeccompProfile{
343+
Type: corev1.SeccompProfileTypeRuntimeDefault,
344+
},
345+
}
331346
}
332347
ns := corev1.Namespace{
333348
ObjectMeta: metav1.ObjectMeta{

0 commit comments

Comments
 (0)