Skip to content

Commit e391158

Browse files
Merge pull request #105 from iamP1/SPIRE-439
SPIRE-439: SCC Hardening for Spire Agent
2 parents d85cef1 + 71b8f46 commit e391158

5 files changed

Lines changed: 92 additions & 19 deletions

File tree

pkg/controller/spire-agent/controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (r *SpireAgentReconciler) Reconcile(ctx context.Context, req ctrl.Request)
9999
if kerrors.IsNotFound(err) {
100100
r.log.Error(err, "failed to get ZeroTrustWorkloadIdentityManager")
101101
statusMgr.AddCondition(v1alpha1.Ready, v1alpha1.ReasonFailed,
102-
fmt.Sprintf("Failed to retrieve ZeroTrustWorkloadIdentityManager from cluster"),
102+
"Failed to retrieve ZeroTrustWorkloadIdentityManager from cluster",
103103
metav1.ConditionFalse)
104104
return ctrl.Result{}, nil
105105
}

pkg/controller/spire-agent/daemonset.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ func generateSpireAgentDaemonSet(config v1alpha1.SpireAgentSpec, ztwim *v1alpha1
175175
},
176176
Spec: corev1.PodSpec{
177177
HostPID: true,
178-
HostNetwork: true,
179-
DNSPolicy: corev1.DNSClusterFirstWithHostNet,
178+
HostNetwork: false,
179+
DNSPolicy: corev1.DNSClusterFirst,
180180
ServiceAccountName: "spire-agent",
181181
Containers: []corev1.Container{
182182
{
@@ -219,7 +219,14 @@ func generateSpireAgentDaemonSet(config v1alpha1.SpireAgentSpec, ztwim *v1alpha1
219219
VolumeMounts: volumeMounts,
220220
Resources: utils.DerefResourceRequirements(config.Resources),
221221
SecurityContext: &corev1.SecurityContext{
222-
Privileged: ptr.To(true),
222+
AllowPrivilegeEscalation: ptr.To(false),
223+
Privileged: ptr.To(false),
224+
Capabilities: &corev1.Capabilities{
225+
Drop: []corev1.Capability{
226+
"ALL",
227+
},
228+
},
229+
ReadOnlyRootFilesystem: ptr.To(true),
223230
},
224231
},
225232
},

pkg/controller/spire-agent/daemonset_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"github.com/openshift/zero-trust-workload-identity-manager/api/v1alpha1"
77
"github.com/openshift/zero-trust-workload-identity-manager/pkg/controller/utils"
88
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
corev1 "k8s.io/api/core/v1"
911
)
1012

1113
func TestGetHostCertMountPath(t *testing.T) {
@@ -156,3 +158,67 @@ func TestHostPathTypePtr(t *testing.T) {
156158
assert.NotNil(t, result)
157159
assert.Equal(t, "DirectoryOrCreate", string(*result))
158160
}
161+
162+
// assertSpireAgentContainerHardening checks container securityContext matches
163+
// generateSpireAgentSCC: non-privileged, no escalation, cap drop ALL, R/O rootfs (see scc.go).
164+
func assertSpireAgentContainerHardening(t *testing.T, c *corev1.Container) {
165+
t.Helper()
166+
require.NotNil(t, c.SecurityContext, "spire-agent container must set securityContext for SCC hardening")
167+
sc := c.SecurityContext
168+
require.NotNil(t, sc.AllowPrivilegeEscalation, "allowPrivilegeEscalation must be explicit (false)")
169+
assert.False(t, *sc.AllowPrivilegeEscalation, "AllowPrivilegeEscalation must be false to match spire-agent SCC")
170+
require.NotNil(t, sc.Privileged, "privileged must be explicit (false)")
171+
assert.False(t, *sc.Privileged, "container must not run privileged; matches AllowPrivilegedContainer: false in SCC")
172+
require.NotNil(t, sc.ReadOnlyRootFilesystem, "readOnlyRootFilesystem must be explicit (true)")
173+
assert.True(t, *sc.ReadOnlyRootFilesystem, "ReadOnlyRootFilesystem must be true; matches spire-agent SCC")
174+
require.NotNil(t, sc.Capabilities, "capabilities must be set")
175+
require.NotNil(t, sc.Capabilities.Drop, "must drop capabilities")
176+
assert.Equal(t, []corev1.Capability{corev1.Capability("ALL")}, sc.Capabilities.Drop, "requiredDropCapabilities [ALL] in SCC")
177+
}
178+
179+
func TestGenerateSpireAgentDaemonSet_SCCSecurityHardening(t *testing.T) {
180+
ztwim := &v1alpha1.ZeroTrustWorkloadIdentityManager{
181+
Spec: v1alpha1.ZeroTrustWorkloadIdentityManagerSpec{
182+
TrustDomain: "example.org",
183+
BundleConfigMap: "spire-bundle",
184+
},
185+
}
186+
187+
t.Run("pod boundary matches spire-agent SCC (host PID, no host network/IPC/ports in SCC)", func(t *testing.T) {
188+
spec := v1alpha1.SpireAgentSpec{
189+
SocketPath: "/tmp/spire-agent/public",
190+
}
191+
ds := generateSpireAgentDaemonSet(spec, ztwim, "test-config-hash")
192+
require.NotNil(t, ds)
193+
pod := &ds.Spec.Template.Spec
194+
assert.Equal(t, "spire-agent", pod.ServiceAccountName)
195+
assert.True(t, pod.HostPID, "HostPID required for k8s workload attestation; SCC AllowHostPID: true")
196+
assert.False(t, pod.HostNetwork, "HostNetwork disabled; SCC allowHostNetwork: false")
197+
assert.Equal(t, corev1.DNSClusterFirst, pod.DNSPolicy)
198+
require.Len(t, pod.Containers, 1)
199+
assertSpireAgentContainerHardening(t, &pod.Containers[0])
200+
})
201+
202+
t.Run("security context unchanged when kubelet CA hostPath is present", func(t *testing.T) {
203+
spec := v1alpha1.SpireAgentSpec{
204+
SocketPath: "/tmp/spire-agent/public",
205+
WorkloadAttestors: &v1alpha1.WorkloadAttestors{
206+
K8sEnabled: "true",
207+
WorkloadAttestorsVerification: &v1alpha1.WorkloadAttestorsVerification{
208+
Type: utils.WorkloadAttestorVerificationTypeAuto,
209+
},
210+
},
211+
}
212+
ds := generateSpireAgentDaemonSet(spec, ztwim, "hash")
213+
var sawKubeletCA bool
214+
for _, v := range ds.Spec.Template.Spec.Volumes {
215+
if v.Name == "kubelet-ca" {
216+
sawKubeletCA = true
217+
break
218+
}
219+
}
220+
require.True(t, sawKubeletCA, "expected kubelet-ca volume for auto verification / host cert")
221+
require.Len(t, ds.Spec.Template.Spec.Containers, 1)
222+
assertSpireAgentContainerHardening(t, &ds.Spec.Template.Spec.Containers[0])
223+
})
224+
}

pkg/controller/spire-agent/scc.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func generateSpireAgentSCC(config *v1alpha1.SpireAgent) *securityv1.SecurityCont
2626
},
2727
ReadOnlyRootFilesystem: true,
2828
RunAsUser: securityv1.RunAsUserStrategyOptions{
29-
Type: securityv1.RunAsUserStrategyMustRunAsRange,
29+
Type: securityv1.RunAsUserStrategyRunAsAny,
3030
},
3131
SELinuxContext: securityv1.SELinuxContextStrategyOptions{
3232
Type: securityv1.SELinuxStrategyMustRunAs,
@@ -49,11 +49,11 @@ func generateSpireAgentSCC(config *v1alpha1.SpireAgent) *securityv1.SecurityCont
4949
},
5050
AllowHostDirVolumePlugin: true,
5151
AllowHostIPC: false,
52-
AllowHostNetwork: true,
52+
AllowHostNetwork: false,
5353
AllowHostPID: true,
54-
AllowHostPorts: true,
55-
AllowPrivilegeEscalation: ptr.To(true),
56-
AllowPrivilegedContainer: true,
54+
AllowHostPorts: false,
55+
AllowPrivilegeEscalation: ptr.To(false),
56+
AllowPrivilegedContainer: false,
5757
AllowedCapabilities: []corev1.Capability{},
5858
DefaultAddCapabilities: []corev1.Capability{},
5959
RequiredDropCapabilities: []corev1.Capability{

pkg/controller/spire-agent/scc_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ func TestGenerateSpireAgentSCC(t *testing.T) {
4848
t.Errorf("expected ReadOnlyRootFilesystem to be true")
4949
}
5050

51-
if scc.RunAsUser.Type != securityv1.RunAsUserStrategyMustRunAsRange {
52-
t.Errorf("expected RunAsUser.Type to be MustRunAsRange")
51+
if scc.RunAsUser.Type != securityv1.RunAsUserStrategyRunAsAny {
52+
t.Errorf("expected RunAsUser.Type to be RunAsAny")
5353
}
5454

5555
if scc.SELinuxContext.Type != securityv1.SELinuxStrategyMustRunAs {
@@ -86,20 +86,20 @@ func TestGenerateSpireAgentSCC(t *testing.T) {
8686
if scc.AllowHostIPC {
8787
t.Errorf("expected AllowHostIPC to be false")
8888
}
89-
if !scc.AllowHostNetwork {
90-
t.Errorf("expected AllowHostNetwork to be true")
89+
if scc.AllowHostNetwork {
90+
t.Errorf("expected AllowHostNetwork to be false")
9191
}
9292
if !scc.AllowHostPID {
9393
t.Errorf("expected AllowHostPID to be true")
9494
}
95-
if !scc.AllowHostPorts {
96-
t.Errorf("expected AllowHostPorts to be true")
95+
if scc.AllowHostPorts {
96+
t.Errorf("expected AllowHostPorts to be false")
9797
}
98-
if scc.AllowPrivilegeEscalation == nil || !*scc.AllowPrivilegeEscalation {
99-
t.Errorf("expected AllowPrivilegeEscalation to be true")
98+
if scc.AllowPrivilegeEscalation == nil || *scc.AllowPrivilegeEscalation {
99+
t.Errorf("expected AllowPrivilegeEscalation to be false")
100100
}
101-
if !scc.AllowPrivilegedContainer {
102-
t.Errorf("expected AllowPrivilegedContainer to be true")
101+
if scc.AllowPrivilegedContainer {
102+
t.Errorf("expected AllowPrivilegedContainer to be false")
103103
}
104104

105105
if len(scc.AllowedCapabilities) != 0 {

0 commit comments

Comments
 (0)