The module lifecycle stage: General Availability
How do I configure alternative security policy management solutions?
For DKP to work correctly, extended privileges are required to run and operate system component payloads. If you are using some alternative security policy management solution (e. g., Kyverno) instead of the admission-policy-engine module, you have to configure exceptions for the following namespaces:
kube-system;- all namespaces with the
d8-*prefix (e.g.,d8-system).
How do I extend Pod Security Standards policies?
Pod Security Standards respond to the
security.deckhouse.io/pod-policy: restrictedorsecurity.deckhouse.io/pod-policy: baselinelabel.
To extend the Pod Security Standards policy by adding your checks to existing checks, you need to:
- Create a constraint template for the check (
ConstraintTemplate). - Bind it to the
restrictedorbaselinepolicy.
Example of the ConstraintTemplate for checking a repository URL of a container image:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package d8.pod_security_standards.extended
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
satisfied := [good | repo = input.parameters.repos[_] ; good = startswith(container.image, repo)]
not any(satisfied)
msg := sprintf("container <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
satisfied := [good | repo = input.parameters.repos[_] ; good = startswith(container.image, repo)]
not any(satisfied)
msg := sprintf("container <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
}
Example of binding a check to the restricted policy:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: prod-repo
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaceSelector:
matchLabels:
security.deckhouse.io/pod-policy: restricted
parameters:
repos:
- "mycompany.registry.com"
The example demonstrates the configuration of checking the repository address in the image field for all Pods created in the namespace having the security.deckhouse.io/pod-policy : restricted label. A Pod will not be created if the address in the image field of the Pod does not start with mycompany.registry.com.
The Gatekeeper documentation may find more info about templates and policy language.
Find more examples of checks for policy extension in the Gatekeeper Library.
How to allow some Pod Security Standards policies without disabling whole list?
To apply only the required security policies without turning off the entire built-in set:
- Add the
security.deckhouse.io/pod-policy: privilegedlabel to your namespace in order to disable built-in policies. - Create a SecurityPolicy that matches the baseline or restricted policy while also editing the list of
policieselements as you see fit. - Add a label to your namespace that matches the
namespaceSelectorin the SecurityPolicy. In the examples below, the label issecurity-policy.deckhouse.io/baseline-enabled: "true"orsecurity-policy.deckhouse.io/restricted-enabled: "true".
SecurityPolicy that matches baseline standard:
apiVersion: deckhouse.io/v1alpha1
kind: SecurityPolicy
metadata:
name: baseline
spec:
enforcementAction: Deny
policies:
allowHostIPC: false
allowHostNetwork: false
allowHostPID: false
allowPrivilegeEscalation: true
allowPrivileged: false
allowedAppArmor:
- runtime/default
- localhost/*
allowedCapabilities:
- AUDIT_WRITE
- CHOWN
- DAC_OVERRIDE
- FOWNER
- FSETID
- KILL
- MKNOD
- NET_BIND_SERVICE
- SETFCAP
- SETGID
- SETPCAP
- SETUID
- SYS_CHROOT
allowedHostPaths: []
allowedHostPorts:
- max: 0
min: 0
allowedProcMount: Default
allowedUnsafeSysctls:
- kernel.shm_rmid_forced
- net.ipv4.ip_local_port_range
- net.ipv4.ip_unprivileged_port_start
- net.ipv4.tcp_syncookies
- net.ipv4.ping_group_range
- net.ipv4.ip_local_reserved_ports
- net.ipv4.tcp_keepalive_time
- net.ipv4.tcp_fin_timeout
- net.ipv4.tcp_keepalive_intvl
- net.ipv4.tcp_keepalive_probes
seLinux:
- type: ""
- type: container_t
- type: container_init_t
- type: container_kvm_t
- type: container_engine_t
seccompProfiles:
allowedProfiles:
- RuntimeDefault
- Localhost
- undefined
- ''
allowedLocalhostFiles:
- '*'
match:
namespaceSelector:
labelSelector:
matchLabels:
security-policy.deckhouse.io/baseline-enabled: "true"
SecurityPolicy that matches restricted standard:
apiVersion: deckhouse.io/v1alpha1
kind: SecurityPolicy
metadata:
name: restricted
spec:
enforcementAction: Deny
policies:
allowHostIPC: false
allowHostNetwork: false
allowHostPID: false
allowPrivilegeEscalation: false
allowPrivileged: false
allowedAppArmor:
- runtime/default
- localhost/*
allowedCapabilities:
- NET_BIND_SERVICE
allowedHostPaths: []
allowedHostPorts:
- max: 0
min: 0
allowedProcMount: Default
allowedUnsafeSysctls:
- kernel.shm_rmid_forced
- net.ipv4.ip_local_port_range
- net.ipv4.ip_unprivileged_port_start
- net.ipv4.tcp_syncookies
- net.ipv4.ping_group_range
- net.ipv4.ip_local_reserved_ports
- net.ipv4.tcp_keepalive_time
- net.ipv4.tcp_fin_timeout
- net.ipv4.tcp_keepalive_intvl
- net.ipv4.tcp_keepalive_probes
allowedVolumes:
- configMap
- csi
- downwardAPI
- emptyDir
- ephemeral
- persistentVolumeClaim
- projected
- secret
requiredDropCapabilities:
- ALL
runAsUser:
rule: MustRunAsNonRoot
seLinux:
- type: ""
- type: container_t
- type: container_init_t
- type: container_kvm_t
- type: container_engine_t
seccompProfiles:
allowedProfiles:
- RuntimeDefault
- Localhost
allowedLocalhostFiles:
- '*'
match:
namespaceSelector:
labelSelector:
matchLabels:
security-policy.deckhouse.io/restricted-enabled: "true"
What if there are multiple policies (operational or security) that are applied to the same object?
In that case the object’s specification have to fulfil all the requirements imposed by the policies.
For example, consider the following two security policies:
apiVersion: deckhouse.io/v1alpha1
kind: SecurityPolicy
metadata:
name: foo
spec:
enforcementAction: Deny
match:
namespaceSelector:
labelSelector:
matchLabels:
name: test
policies:
readOnlyRootFilesystem: true
requiredDropCapabilities:
- MKNOD
---
apiVersion: deckhouse.io/v1alpha1
kind: SecurityPolicy
metadata:
name: bar
spec:
enforcementAction: Deny
match:
namespaceSelector:
labelSelector:
matchLabels:
name: test
policies:
requiredDropCapabilities:
- NET_BIND_SERVICE
Then, in order to fulfill the requirements of the above security policies, the following settings must be set in a container specification:
securityContext:
capabilities:
drop:
- MKNOD
- NET_BIND_SERVICE
readOnlyRootFilesystem: true
Verification of image signatures
Available in the following DKP editions: SE+, EE.
Cosign versions up to v2 are supported. Versions v3 and above are not supported.
The module implements a function for verifying signatures of container images signed using Cosign. For more details on signing and verifying container images, see the DKP documentation.
How to block deleting a node without a label
DELETE operations are handled by Gatekeeper by default.
You can create your own Gatekeeper policy to block Node deletion unless a special label is present. The example below uses oldObject to check labels on the Node being deleted:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: d8customnodedeleteguard
spec:
crd:
spec:
names:
kind: D8CustomNodeDeleteGuard
validation:
openAPIV3Schema:
type: object
properties:
requiredLabelKey:
type: string
requiredLabelValue:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package d8.custom
is_delete { input.review.operation == "DELETE" }
is_node { input.review.kind.kind == "Node" }
has_required_label {
key := input.parameters.requiredLabelKey
val := input.parameters.requiredLabelValue
obj := input.review.oldObject
obj.metadata.labels[key] == val
}
violation[{"msg": msg}] {
is_delete
is_node
not has_required_label
msg := sprintf("Node deletion is blocked. Add label %q=%q to proceed.", [input.parameters.requiredLabelKey, input.parameters.requiredLabelValue])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: D8CustomNodeDeleteGuard
metadata:
name: require-node-delete-label
spec:
enforcementAction: warn
match:
kinds:
- apiGroups: [""]
kinds: ["Node"]
parameters:
requiredLabelKey: "admission.deckhouse.io/allow-delete"
requiredLabelValue: "true"
How to deny kubectl exec and kubectl attach to specific Pods?
The
admission-policy-enginemodule’s webhook routesCONNECTrequests forpods/execandpods/attachthrough Gatekeeper. This allows custom policies to allow or denykubectl execandkubectl attachoperations.
Built-in policy for heritage: deckhouse Pods
To protect system components managed by Deckhouse, the admission-policy-engine module includes a built-in policy D8DenyExecHeritage that forbids running kubectl exec and kubectl attach operations to all Pods with the heritage: deckhouse label.
This policy doesn’t apply to the following users who are allowed to run kubectl exec and kubectl attach operations to Pods labeled with heritage: deckhouse:
system:sudouser;- service accounts from
d8-*namespaces (system:serviceaccount:d8-*); - service accounts from
kube-*namespaces (system:serviceaccount:kube-*).
Custom policy example
You can create your own Gatekeeper policy to deny kubectl exec and kubectl attach operations in specific namespaces. In the following example, input.review.operation and input.review.resource.resource are used to check for CONNECT operations:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: d8customdenyexec
spec:
crd:
spec:
names:
kind: D8CustomDenyExec
validation:
openAPIV3Schema:
type: object
properties:
forbiddenNamespaces:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package d8.custom
is_connect {
input.review.operation == "CONNECT"
}
# requestSubResource is preferred, but fall back to subResource for older APIs
subresource_is(sub) {
sr := object.get(input.review, "requestSubResource", input.review.subResource)
sr == sub
}
is_exec_or_attach {
input.review.resource.resource == "pods"
subresource_is("exec")
}
is_exec_or_attach {
input.review.resource.resource == "pods"
subresource_is("attach")
}
is_forbidden_namespace {
ns := input.review.namespace
ns == input.parameters.forbiddenNamespaces[_]
}
violation[{"msg": msg}] {
is_connect
is_exec_or_attach
is_forbidden_namespace
msg := sprintf("Exec/attach is forbidden in namespace %q", [input.review.namespace])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: D8CustomDenyExec
metadata:
name: deny-exec-in-namespaces
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ["*"]
kinds: ["*"]
scope: Namespaced
parameters:
forbiddenNamespaces:
- production
- staging
Key data and checks for CONNECT validation:
- Use
input.review.operation == "CONNECT"to check forCONNECToperations. - User information is available in
input.review.userInfo.usernameandinput.review.userInfo.groups. - The namespace is available in
input.review.namespace.