Cluster Setup (10%)
CIS Benchmark
What is a security benchmark?
- A security benchmark is a set of standard benchmarks that define a state of optimized security for a given system (servers, network devices, etc.)
- CIS (Center for Internet Security) provides standardized benchmarks (in the form of downloadable files) that one can use to implement security on their system.
- CIS provides benchmarks for public clouds (Azure, AWS, GCP, etc.), operating systems (Linux, Windows, MacOS), network devices (Cisco, Juniper, HP, etc.), mobile devices (Android and Apple), desktop and server software (such as Kubernetes)
- View more info here
- You must register at the CIS website to download benchmarks
- Each benchmark provides a description of a vulnerability, as well as a path to resolution.
- CIS-CAT is a tool you can run on a system to generate recommendations for a given system. There are two versions available for download, CIS-CAT Lite and CIS-CAT Pro. The Lite version only includes benchmarks for Windows 10, MacOS, Ubuntu, and desktop software (Google Chrome, etc.). The Pro version includes all benchmarks.
- CIS Benchmarks for Kubernetes
- Register at the CIS website and download the CIS Benchmarks for kubernetes
- Includes security benchmarks for master and worker nodes
KubeBench
- KubeBench is an alternative to CIS-CAT Pro to run benchmarks against a Kubernetes cluster.
- KubeBench is open source and maintained by Aqua Security
- KubeBench can be deployed as a Docker container or a pod. It can also be invoked directly from the binaries or compiled from source.
- Once run, kube-bench will scan the cluster to identify if best-practices have been implemented. If will output a report specifying which benchmarks have passed/failed. It will tell you how to fix any failed benchmarks.
- You can view the report by tailing the pod logs of the kube-bench pod.
Cluster Upgrades
- The controller-manager and kube-scheduler can be one minor revision behind the API server.
- For example, if the API server is at version 1.10, controller-manager and kube-scheduler can be at 1.9 or 1.10
- The kubelet and kube-proxy can be up to 2 minor revisions behind the API server
- kubectl can be x+1 or x-1 minor revisions from the kube API server
- You can upgrade the cluster one minor version at a time
Upgrade Process
- Drain and cordon the node before upgrading it
kubectl drain <node name> --ignore-daemonsets
- Upgrade the master node first.
- Upgrade worker nodes after the master node.
Upgrading with Kubeadm
- If the cluster was created with kubeadm, you can use kubeadm to upgrade it.
- The upgrade process with kubeadm:
# Increase the minor version in the apt repository file for kubernetes: sudo vi /etc/apt/sources.list.d/kubernetes.list # Determine which version to upgrade to sudo apt update sudo apt-cache madison kubeadm # Upgrade kubeadm first sudo apt-mark unhold kubeadm && \ sudo apt-get update && sudo apt-get install -y kubeadm='1.31.x-*' && \ sudo apt-mark hold kubeadm # Verify the version of kubeadm kubeadm version # Check the kubeadm upgrade plan sudo kubeadm upgrade plan # Apply the upgrade plan sudo kubeadm upgrade apply v1.31.x # Upgrade the nodes sudo kubeadm upgrade node # Upgrade kubelet and kubectl sudo apt-mark unhold kubelet kubectl && \ sudo apt-get update && sudo apt-get install -y kubelet='1.31.x-*' kubectl='1.31.x-*' && \ sudo apt-mark hold kubelet kubectl # Restart the kubelet sudo systemctl daemon-reload sudo systemctl restart kubelet
Network Policies
Overview
-
Kubernetes Network Policies allow you to control the flow of traffic to and from pods. They define rules that specify:
- What traffic is allowed to reach a set of pods.
- What traffic a set of pods can send out.
-
Pods can communicate with each other by default. Network Policies allow you to restrict this communication.
-
Network Policies operate at Layer 3 and Layer 4 (IP and TCP/UDP). They do not cover Layer 7 (application layer).
-
Network Policies are additive. Meaning, to grant more permissions for network communication, simply create another network policy with more fine-grained rules.
-
Network Policies are implemented by the network plugin. The network plugin must support NetworkPolicy for the policies to take effect.
-
Network Policies are namespace-scoped. They apply to pods in the same namespace.
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all namespace: secure-namespace spec: podSelector: {} policyTypes - Ingress -
Say we now want to grant the ‘frontend’ pods with label ‘teir: frontend’ in the ‘app’ namespace access to the ‘backend’ pods in ‘secure-namespace’. We can do that by creating another Network Policy like this:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-app-pods namespace: secure-namespace spec: podSelector: matchLabels: tier: backend policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: name: app podSelector: matchLabels: teir: frontend ports: - protocol: TCP port: 3000
Key Concepts
- Namespace Scope: Network policies are applied at the namespace level.
- Selector-Based Rules:
- Pod Selector: Select pods the policy applies to.
- Namespace Selector: Select pods based on their namespace.
- Traffic Direction:
- Ingress: Traffic coming into the pod.
- Egress: Traffic leaving the pod.
- Default Behavior:
- Pods are non-isolated by default (accept all traffic).
- A pod becomes isolated when a network policy matches it.
Common Fields in a Network Policy
podSelector: Specifies the pods the policy applies to.ingress/egress: Lists rules for ingress or egress traffic.from/to: Specifies allowed sources/destinations (can use IP blocks, pod selectors, or namespace selectors).ports: Specifies allowed ports and protocols.
Example Network Policies
Allow All Ingress Traffic
````
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-ingress
namespace: default
spec:
podSelector: {}
ingress:
- {}
```
Deny All Ingress and Egress Traffic
````
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: defaulT
spec:
podSelector: {}
ingress: []
egress: []
```
Allow Specific Ingress from a Namespace
```
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-namespace-ingress
namespace: default
spec:
podSelector:
matchLabels:
app: my-app
ingress:
- from:
- namespaceSelector:
matchLabels:
team: frontend
```
Allow Egress to a Specific IP
```
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-specific-ip
namespace: default
spec:
podSelector:
matchLabels:
app: my-app
egress:
- to:
- ipBlock:
cidr: 192.168.1.0/24
ports:
- protocol: TCP
port: 8080
```
Cilium Network Policy
- Cilium Network Policies provide more granularity, flexibility, and features than traditional Kubernetes Network Policies
- Cilium Network Policies operate up to layer 7 of the OSI model. Traditional Network Policies only operate up to layer 4.
- Cilium Network Policies perform well due to the fact that they use eBPF
- Hubble allows you to watch traffic going to and from pods
- You can add Cilium to the cluster by:
- Deploying with helm
- Running
cilium installafter you install the cilium CLI tool
Cilium Network Policy Structure
- Cilium Network Policies are defined in YAML files
- The structure is similar to Kubernetes Network Policies
Layer 3 Rules
- Endpoints Based - Apply the policy to pods based on Kubernetes label selectors
- Services Based - Apply the policy based on kubernetes services, controlling traffic based on service names rather than individual pods
- Entities Based - Cilium has pre-defined entities like cluster, host, and world. This type of policy uses these entities to determine what traffic the policy is applied to.
- Cluster - Represents all kubernetes endpoints
- Example:
apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: allow-egress-to-cluster-resources spec: endpointSelector: {} egress: - toEntities: - cluster
- Example:
- World - Represents any external traffic, but not cluster traffic
apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: allow-egress-to-external-resources spec: endpointSelector: {} egress: - toEntities: - world - Host - Represents the local kubernetes node
- Remote-node - Represents traffic from a remote node
- All - Represents all endpoints both internal and external to the cluster
apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: allow-egress-to-external-resources spec: endpointSelector: {} egress: - toEntities: - all
- Cluster - Represents all kubernetes endpoints
- Node Based - Apply the policy based on nodes in the cluster
- IP/CIDR Based - Apply the policy based on IP addresses or CIDR blocks
Layer 4 Rules
- If no layer 4 rules are defined, all traffic is allowed for layer 4
- Example:
apiVersion: "cilium.io/v2" kind: CiliumNetworkPolicy metadata: name: allow-external-80 spec: endpointSelector: matchLabels: run: curl egress: - toPorts: - ports: - port: "80" protocol: TCP
Layer 7 Rules
Deny Policies
- You can create deny policies to explicitly block traffic
- Deny policies take higher precedence over allow policies
- ingressDeny Example:
apiVersion: "cilium.io/v2" kind: CiliumNetworkPolicy metadata: name: deny-ingress-80-for-backend spec: endpointSelector: matchLabels: app: backend ingressDeny: - fromEntities: - all - toPorts: - ports: - port: "80" protocol: TCP - egressDeny Example:
apiVersion: "cilium.io/v2" kind: CiliumNetworkPolicy metadata: name: "deny-egress" spec: endpointSelector: matchLabels: app: random-pod egress: - toEntities: - all egressDeny: - toEndpoints: - matchLabels: app: server
Examples
Default Deny All
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: default-deny-all
spec:
endpointSelector: {}
ingress:
- fromEntities:
- world
Kubernetes Ingress
What is Ingress?
- Ingress is an API object that manages external access to services in a Kubernetes cluster, typically HTTP and HTTPS.
- Provides:
- Load balancing
- SSL termination
- Name-based virtual hosting
Why Use Ingress?
- To consolidate multiple service endpoints behind a single, externally accessible URL.
- Reduce the need for creating individual LoadBalancers or NodePort services.
Key Components of Ingress
-
Ingress Controller
- Software that watches for Ingress resources and implements the rules.
- Popular Ingress controllers:
ingress-nginxTraefikHAProxyIstio Gateway
- Must be installed separately in the cluster.
-
Ingress Resource
- The Kubernetes object that defines how requests should be routed to services.
Ingress Resource Configuration
- As of Kubernetes 1.20, you can create an ingress using kubectl:
kubectl create ingress --rule="host/path=service:port"
Basic Structure
```
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
```
Ingress with TLS
-
Kubernetes automatically creates a self-signed certificate for HTTPS. To view it, first determine the HTTPS port of the ingress controller service:
kubeadmin@kube-controlplane:~$ k get svc -n ingress-nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller NodePort 10.103.169.156 <none> 80:31818/TCP,443:30506/TCP 38m ingress-nginx-controller-admission ClusterIP 10.103.26.228 <none> 443/TCP 38m kubeadmin@kube-controlplane:~$ -
The HTTPS port is 30506 in this case. To view the self-signed certificate, we can use curl:
λ notes $ curl https://13.68.211.113:30506/service1 -k -v * (304) (OUT), TLS handshake, Finished (20): } [52 bytes data] * SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF * ALPN: server accepted h2 * Server certificate: * subject: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate <<<<<<<<<<<<<<<< * start date: Dec 20 14:23:08 2024 GMT * expire date: Dec 20 14:23:08 2025 GMT * issuer: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate * SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://13.68.211.113:30506/service1 * [HTTP/2] [1] [:method: GET] * [HTTP/2] [1] [:scheme: https] * [HTTP/2] [1] [:authority: 13.68.211.113:30506] * [HTTP/2] [1] [:path: /service1] * [HTTP/2] [1] [user-agent: Mozilla/5.0 Gecko] * [HTTP/2] [1] [accept: */*] > GET /service1 HTTP/2 > Host: 13.68.211.113:30506 > User-Agent: Mozilla/5.0 Gecko > Accept: */* -
To configure a ingress resource to use TLS (HTTPS), we first need to create a certificate:
# create a new 2048-bit RSA private key and associated cert openssl req -nodes -new -x509 -keyout my.key -out my.crt -subj "/CN=mysite.com" -
Next, create a secret for the tls cert:
kubectl create secret tls mycert --cert=my.crt --key=my.key -n my-namespace -
Create the ingress:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: secure-ingress annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" spec: tls: - hosts: - example.com secretName: mycert rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: secure-service port: number: 80
Annotations
- Extend the functionality of Ingress controllers.
- Common examples (specific to nginx):
- nginx.ingress.kubernetes.io/rewrite-target: Rewrite request paths.
- nginx.ingress.kubernetes.io/ssl-redirect: Force SSL.
- nginx.ingress.kubernetes.io/proxy-body-size: Limit request size.
Protecting Node Metadata and Endpoints
Protecting Endpoints
- Kubernetes clusters expose information on various ports:
| Port Range | Purpose |
| ---------- | ------- |
| 6443 | kube-api |
| 2379 - 2380 | etcd |
| 10250 | kubelet api |
| 10259 | kube-scheduler |
| 10257 | kube-controller-manager |
- Many of these ports are configurable. For example, to change the port that kube-api listens on, just modify `--secure-port` in the kube-api manifest.
- Setup firewall rules to minimize the attack surface
Securing Node Metadata
-
A lot of information can be obtained from node metadata
- Node name
- Node state
- annotations
- System Info
- etc.
-
Why secure node metadata?
- If node metadata is tampered with, pods may be assigned to the wrong nodes, which has security implications to considers
- You can determine the version of kubelet and other kubernetes components from node metadata
- If an attacker can modify node metadata, they could taint all the nodes, making all nodes unscheduleable
-
Protection Strategies
- Use RBAC to control who has access to modify node metadata
- Node isolation using labels and node selectors
- Audit logs to determine who is accessing the cluster and respond accordingly
- Update node operating systems regularly
- Update cluster components regularly
-
Cloud providers such as Amazon and Azure often expose node information via metadata endpoints on the node. These endpoints are important to protect.
-
This endpoint can be accessed at 169.254.169.254 on nodes in both Azure and AWS. An example for Azure:
curl -s -H Metadata:true --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | jq -
Node metadata endpoints can be prevented from being accessed by pods by creating network policies.
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-ingress-metadata-server namespace: a12 spec: policyTypes: - Egress podSelector: {} egress: - to: - ipBlock: cidr: 0.0.0.0/0 except: - 169.254.169.254/32
Verify Kubernetes Binaries
- The SHA sum of a file changes if the content within the file is changed
- You can download the binaries from github using wget.
Example:
wget -O /opt/kubernetes.tar.gz https://dl.k8s.io/v1.31.1/kubernetes.tar.gz - To validate that a binary downloaded from the internet has not been modified, check the hash code:
echo $(cat kubectl.sha256) kubectl | sha256sum --check
Securing etcd
- etcd is a distributed key-value store that Kubernetes uses to store configuration data
- etcd by default listens on port 2379/tcp
Play with etcd
Step 1: Create the Base Binaries Directory
```sh
mkdir /root/binaries
cd /root/binaries
```
Step 2: Download and Copy the ETCD Binaries to Path
```sh
wget https://github.com/etcd-io/etcd/releases/download/v3.5.18/etcd-v3.5.18-linux-amd64.tar.gz
tar -xzvf etcd-v3.5.18-linux-amd64.tar.gz
cd /root/binaries/etcd-v3.5.18-linux-amd64/
cp etcd etcdctl /usr/local/bin/
```
Step 3: Start etcd
```sh
cd /tmp
etcd
```
Step 4: Verification - Store and Fetch Data from etcd
```sh
etcdctl put key1 "value1"
```
```sh
etcdctl get key1
```
Encrypting data in transit in etcd
- etcd supports TLS encryption for data in transit
- By default, etcd packaged with kubeadm is configured to use TLS encryption
- One can capture packets from etcd using tcpdump:
root@controlplane00:/var/lib/etcd/member# tcpdump -i lo -X port 2379 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes 16:10:01.691453 IP localhost.2379 > localhost.42040: Flags [P.], seq 235868994:235869033, ack 3277609642, win 640, options [nop,nop,TS val 1280288044 ecr 1280288042], length 39 0x0000: 4500 005b 35e4 4000 4006 06b7 7f00 0001 E..[5.@.@....... 0x0010: 7f00 0001 094b a438 0e0f 1342 c35c 5aaa .....K.8...B.\Z. 0x0020: 8018 0280 fe4f 0000 0101 080a 4c4f a52c .....O......LO., 0x0030: 4c4f a52a 1703 0300 2289 00d8 5dcc 7b88 LO.*...."...].{. 0x0040: 6f7a 290f 536b 0fd0 f7d9 1fb4 f83f 4aab oz).Sk.......?J. 0x0050: a6e7 0af8 0835 e597 a93d 4d .....5...=M 16:10:01.691479 IP localhost.42040 > localhost.2379: Flags [.], ack 39, win 14819, options [nop,nop,TS val 1280288044 ecr 1280288044], length 0 0x0000: 4500 0034 7174 4000 4006 cb4d 7f00 0001 E..4qt@.@..M.... 0x0010: 7f00 0001 a438 094b c35c 5aaa 0e0f 1369 .....8.K.\Z....i 0x0020: 8010 39e3 fe28 0000 0101 080a 4c4f a52c ..9..(......LO., 0x0030: 4c4f a52c LO., 16:10:01.691611 IP localhost.2379 > localhost.42040: Flags [P.], seq 39:1222, ack 1, win 640, options [nop,nop,TS val 1280288044 ecr 1280288044], length 1183 0x0000: 4500 04d3 35e5 4000 4006 023e 7f00 0001 E...5.@.@..>.... 0x0010: 7f00 0001 094b a438 0e0f 1369 c35c 5aaa .....K.8...i.\Z. 0x0020: 8018 0280 02c8 0000 0101 080a 4c4f a52c ............LO., 0x0030: 4c4f a52c 1703 0304 9ac0 c579 d4ed 808c LO.,.......y.... ..... redacted - The traffic captured in the output above is encrypted.
Encrypting data at rest in etcd
-
By default, the API server stores plain-text representations of resources into etcd, with no at-rest encryption.
-
etcd stores data in the
/var/lib/etcd/memberdirectory. When the database is not encrypted, one can easily grep the contents of this directory, looking for secrets:root@controlplane00:/var/lib/etcd/member# ls -lisa total 16 639000 4 drwx------ 4 root root 4096 Mar 21 10:53 . 385187 4 drwx------ 3 root root 4096 Mar 21 10:52 .. 639002 4 drwx------ 2 root root 4096 Mar 21 14:43 snap 638820 4 drwx------ 2 root root 4096 Mar 21 11:59 wal root@controlplane00:/var/lib/etcd/member# grep -R test-secret . grep: ./wal/00000000000000ac-0000000000a9340b.wal: binary file matches grep: ./wal/00000000000000a8-0000000000a721c1.wal: binary file matches grep: ./wal/00000000000000aa-0000000000a83f1e.wal: binary file matches grep: ./wal/00000000000000a9-0000000000a7b97e.wal: binary file matches grep: ./wal/00000000000000ab-0000000000a8d8a7.wal: binary file matches grep: ./snap/db: binary file matches -
The kube-apiserver process accepts an argument –encryption-provider-config that specifies a path to a configuration file. The contents of that file, if you specify one, control how Kubernetes API data is encrypted in etcd.
-
If you are running the kube-apiserver without the –encryption-provider-config command line argument, you do not have encryption at rest enabled. If you are running the kube-apiserver with the –encryption-provider-config command line argument, and the file that it references specifies the
identityprovider as the first encryption provider in the list, then you do not have at-rest encryption enabled (the default identity provider does not provide any confidentiality protection.) -
If you are running the kube-apiserver with the –encryption-provider-config command line argument, and the file that it references specifies a provider other than
identityas the first encryption provider in the list, then you already have at-rest encryption enabled. However, that check does not tell you whether a previous migration to encrypted storage has succeeded. -
Example EncryptionConfiguration:
apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets - configmaps - pandas.awesome.bears.example # a custom resource API providers: # This configuration does not provide data confidentiality. The first # configured provider is specifying the "identity" mechanism, which # stores resources as plain text. # - identity: {} # plain text, in other words NO encryption - aesgcm: keys: - name: key1 secret: c2VjcmV0IGlzIHNlY3VyZQ== - name: key2 secret: dGhpcyBpcyBwYXNzd29yZA== - aescbc: keys: - name: key1 secret: c2VjcmV0IGlzIHNlY3VyZQ== - name: key2 secret: dGhpcyBpcyBwYXNzd29yZA== - secretbox: keys: - name: key1 secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY= - resources: - events providers: - identity: {} # do not encrypt Events even though *.* is specified below - resources: - '*.apps' # wildcard match requires Kubernetes 1.27 or later providers: - aescbc: keys: - name: key2 secret: c2VjcmV0IGlzIHNlY3VyZSwgb3IgaXMgaXQ/Cg== - resources: - '*.*' # wildcard match requires Kubernetes 1.27 or later providers: - aescbc: keys: - name: key3 secret: c2VjcmV0IGlzIHNlY3VyZSwgSSB0aGluaw== -
Each resources array item is a separate config and contains a complete configuration. The resources.resources field is an array of Kubernetes resource names (resource or resource.group) that should be encrypted like Secrets, ConfigMaps, or other resources.
-
https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/
-
After enabling encryption in etcd, any resources that you created prior to enabling encryption will not be encrypted. For example, you can encrypt secrets by running:
kubectl get secrets -A -o yaml | kubectl replace -f -
- Example of getting a secret in etcd:
root@controlplane00:/etc/kubernetes/pki# ETCDCTL_API=3 etcdctl --cacert=./etcd/ca.crt --cert=./apiserver-etcd-client.crt --key=./apiserver-etcd-client.key get /registry/secrets/default/mysecret
/registry/secrets/default/mysecret
k8s:enc:aescbc:v1:key1:ܨt>;8ܑ%TUIodEs*lsHGwjeF8S!Aqaj\Pq;9Ⱥ7dJe{B2=|p4#'BuCxUY,*IuFM
wxx@
2Q0e5UzH^^)rX_H%GUɈ-XqC.˽pC `kBW>K12 n
The path to the resource in the etcd database is ‘/registry/
Securing kube-apiserver
- Kube-apiserver acts as the gateway for all resources in kubernetes. Kube-apiserver is the only component in kubernetes that communicates with etcd
- kube-apiserver authenticates to etcd using TLS client certificates.
- Kube-apiserver should encrypt data before it is stored in etcd
- kube-apiserver should only listen on an HTTPS endpoint. There was an option to host kube-apiserver on an HTTP endpoint, but this option has been deprecated as of 1.10 and removed in 1.22
- kube-apiserver should have auditing enabled
Authentication
- One can authentication to the KubeAPI server using certificates or a kubeconfig file
Access Controls
- After a request is authenticated, it is authorized. Authorization is the process of determining what actions a user can perform.
- Multiple authorization modules are supported:
- AlwaysAllow - Allows all requests
- AlwaysDeny - Blocks all requests
- RBAC - Role-based access control for requests. This is the default authorization module in kubernetes
- Node - Authorizes kubelets to access the kube-api server