github.com/cilium/cilium@v1.16.2/Documentation/security/kafka.rst (about) 1 .. only:: not (epub or latex or html) 2 3 WARNING: You are looking at unreleased Cilium documentation. 4 Please use the official rendered version released here: 5 https://docs.cilium.io 6 7 .. _gs_kafka: 8 9 ************************ 10 Securing a Kafka Cluster 11 ************************ 12 13 This document serves as an introduction to using Cilium to enforce Kafka-aware 14 security policies. It is a detailed walk-through of getting a single-node 15 Cilium environment running on your machine. It is designed to take 15-30 16 minutes. 17 18 .. include:: gsg_requirements.rst 19 20 Deploy the Demo Application 21 =========================== 22 23 Now that we have Cilium deployed and ``kube-dns`` operating correctly we can 24 deploy our demo Kafka application. Since our first demo of Cilium + HTTP-aware security 25 policies was Star Wars-themed we decided to do the same for Kafka. While the 26 `HTTP-aware Cilium Star Wars demo <https://cilium.io/blog/2017/5/4/demo-may-the-force-be-with-you/>`_ 27 showed how the Galactic Empire used HTTP-aware security policies to protect the Death Star from the 28 Rebel Alliance, this Kafka demo shows how the lack of Kafka-aware security policies allowed the 29 Rebels to steal the Death Star plans in the first place. 30 31 Kafka is a powerful platform for passing datastreams between different components of an application. 32 A cluster of "Kafka brokers" connect nodes that "produce" data into a data stream, or "consume" data 33 from a datastream. Kafka refers to each datastream as a "topic". 34 Because scalable and highly-available Kafka clusters are non-trivial to run, the same cluster of 35 Kafka brokers often handles many different topics at once (read this `Introduction to Kafka 36 <https://kafka.apache.org/intro>`_ for more background). 37 38 In our simple example, the Empire uses a Kafka cluster to handle two different topics: 39 40 - *empire-announce* : Used to broadcast announcements to sites spread across the galaxy 41 - *deathstar-plans* : Used by a small group of sites coordinating on building the ultimate battlestation. 42 43 To keep the setup small, we will just launch a small number of pods to represent this setup: 44 45 - *kafka-broker* : A single pod running Kafka and Zookeeper representing the Kafka cluster 46 (label app=kafka). 47 - *empire-hq* : A pod representing the Empire's Headquarters, which is the only pod that should 48 produce messages to *empire-announce* or *deathstar-plans* (label app=empire-hq). 49 - *empire-backup* : A secure backup facility located in `Scarif <https://starwars.fandom.com/wiki/Scarif_vault>`_ , 50 which is allowed to "consume" from the secret *deathstar-plans* topic (label app=empire-backup). 51 - *empire-outpost-8888* : A random outpost in the empire. It needs to "consume" messages from 52 the *empire-announce* topic (label app=empire-outpost). 53 - *empire-outpost-9999* : Another random outpost in the empire that "consumes" messages from 54 the *empire-announce* topic (label app=empire-outpost). 55 56 All pods other than *kafka-broker* are Kafka clients, which need access to the *kafka-broker* 57 container on TCP port 9092 in order to send Kafka protocol messages. 58 59 .. image:: images/cilium_kafka_gsg_topology.png 60 61 The file ``kafka-sw-app.yaml`` contains a Kubernetes Deployment for each of the pods described 62 above, as well as a Kubernetes Service for both Kafka and Zookeeper. 63 64 .. parsed-literal:: 65 66 $ kubectl create -f \ |SCM_WEB|\/examples/kubernetes-kafka/kafka-sw-app.yaml 67 deployment "kafka-broker" created 68 deployment "zookeeper" created 69 service "zook" created 70 service "kafka-service" created 71 deployment "empire-hq" created 72 deployment "empire-outpost-8888" created 73 deployment "empire-outpost-9999" created 74 deployment "empire-backup" created 75 76 Kubernetes will deploy the pods and service in the background. 77 Running ``kubectl get svc,pods`` will inform you about the progress of the operation. 78 Each pod will go through several states until it reaches ``Running`` at which 79 point the setup is ready. 80 81 .. code-block:: shell-session 82 83 $ kubectl get svc,pods 84 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 85 kafka-service ClusterIP None <none> 9092/TCP 2m 86 kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10m 87 zook ClusterIP 10.97.250.131 <none> 2181/TCP 2m 88 89 NAME READY STATUS RESTARTS AGE 90 empire-backup-6f4567d5fd-gcrvg 1/1 Running 0 2m 91 empire-hq-59475b4b64-mrdww 1/1 Running 0 2m 92 empire-outpost-8888-78dffd49fb-tnnhf 1/1 Running 0 2m 93 empire-outpost-9999-7dd9fc5f5b-xp6jw 1/1 Running 0 2m 94 kafka-broker-b874c78fd-jdwqf 1/1 Running 0 2m 95 zookeeper-85f64b8cd4-nprck 1/1 Running 0 2m 96 97 Setup Client Terminals 98 ====================== 99 100 First we will open a set of windows to represent the different Kafka clients discussed above. 101 For consistency, we recommend opening them in the pattern shown in the image below, but this is optional. 102 103 .. image:: images/cilium_kafka_gsg_terminal_layout.png 104 105 In each window, use copy-paste to have each terminal provide a shell inside each pod. 106 107 empire-hq terminal: 108 109 .. code-block:: shell-session 110 111 $ HQ_POD=$(kubectl get pods -l app=empire-hq -o jsonpath='{.items[0].metadata.name}') && kubectl exec -it $HQ_POD -- sh -c "PS1=\"empire-hq $\" /bin/bash" 112 113 empire-backup terminal: 114 115 .. code-block:: shell-session 116 117 $ BACKUP_POD=$(kubectl get pods -l app=empire-backup -o jsonpath='{.items[0].metadata.name}') && kubectl exec -it $BACKUP_POD -- sh -c "PS1=\"empire-backup $\" /bin/bash" 118 119 outpost-8888 terminal: 120 121 .. code-block:: shell-session 122 123 $ OUTPOST_8888_POD=$(kubectl get pods -l outpostid=8888 -o jsonpath='{.items[0].metadata.name}') && kubectl exec -it $OUTPOST_8888_POD -- sh -c "PS1=\"outpost-8888 $\" /bin/bash" 124 125 outpost-9999 terminal: 126 127 .. code-block:: shell-session 128 129 $ OUTPOST_9999_POD=$(kubectl get pods -l outpostid=9999 -o jsonpath='{.items[0].metadata.name}') && kubectl exec -it $OUTPOST_9999_POD -- sh -c "PS1=\"outpost-9999 $\" /bin/bash" 130 131 132 Test Basic Kafka Produce & Consume 133 ================================== 134 135 First, let's start the consumer clients listening to their respective Kafka topics. All of the consumer 136 commands below will hang intentionally, waiting to print data they consume from the Kafka topic: 137 138 In the *empire-backup* window, start listening on the top-secret *deathstar-plans* topic: 139 140 .. code-block:: shell-session 141 142 $ ./kafka-consume.sh --topic deathstar-plans 143 144 In the *outpost-8888* window, start listening to *empire-announcement*: 145 146 .. code-block:: shell-session 147 148 $ ./kafka-consume.sh --topic empire-announce 149 150 Do the same in the *outpost-9999* window: 151 152 .. code-block:: shell-session 153 154 $ ./kafka-consume.sh --topic empire-announce 155 156 Now from the *empire-hq*, first produce a message to the *empire-announce* topic: 157 158 .. code-block:: shell-session 159 160 $ echo "Happy 40th Birthday to General Tagge" | ./kafka-produce.sh --topic empire-announce 161 162 This message will be posted to the *empire-announce* topic, and shows up in both the *outpost-8888* and 163 *outpost-9999* windows who consume that topic. It will not show up in *empire-backup*. 164 165 *empire-hq* can also post a version of the top-secret deathstar plans to the *deathstar-plans* topic: 166 167 .. code-block:: shell-session 168 169 $ echo "deathstar reactor design v3" | ./kafka-produce.sh --topic deathstar-plans 170 171 This message shows up in the *empire-backup* window, but not for the outposts. 172 173 Congratulations, Kafka is working as expected :) 174 175 The Danger of a Compromised Kafka Client 176 ======================================== 177 178 But what if a rebel spy gains access to any of the remote outposts that act as Kafka clients? 179 Since every client has access to the Kafka broker on port 9092, it can do some bad stuff. 180 For starters, the outpost container can actually switch roles from a consumer to a producer, 181 sending "malicious" data to all other consumers on the topic. 182 183 To prove this, kill the existing ``kafka-consume.sh`` command in the outpost-9999 window 184 by typing control-C and instead run: 185 186 .. code-block:: shell-session 187 188 $ echo "Vader Booed at Empire Karaoke Party" | ./kafka-produce.sh --topic empire-announce 189 190 Uh oh! Outpost-8888 and all of the other outposts in the empire have now received this fake announcement. 191 192 But even more nasty from a security perspective is that the outpost container can access any topic 193 on the kafka-broker. 194 195 In the outpost-9999 container, run: 196 197 .. code-block:: shell-session 198 199 $ ./kafka-consume.sh --topic deathstar-plans 200 "deathstar reactor design v3" 201 202 We see that any outpost can actually access the secret deathstar plans. Now we know how the rebels got 203 access to them! 204 205 Securing Access to Kafka with Cilium 206 ==================================== 207 208 Obviously, it would be much more secure to limit each pod's access to the Kafka broker to be 209 least privilege (i.e., only what is needed for the app to operate correctly and nothing more). 210 211 We can do that with the following Cilium security policy. As with Cilium HTTP policies, we can write 212 policies that identify pods by labels, and then limit the traffic in/out of this pod. In 213 this case, we'll create a policy that identifies the exact traffic that should be allowed to reach the 214 Kafka broker, and deny the rest. 215 216 As an example, a policy could limit containers with label *app=empire-outpost* to only be able to consume 217 topic *empire-announce*, but would block any attempt by a compromised container (e.g., empire-outpost-9999) 218 from producing to *empire-announce* or consuming from *deathstar-plans*. 219 220 .. image:: images/cilium_kafka_gsg_attack.png 221 222 Here is the *CiliumNetworkPolicy* rule that limits access of pods with label *app=empire-outpost* to 223 only consume on topic *empire-announce*: 224 225 .. literalinclude:: ../../examples/policies/getting-started/kafka.yaml 226 227 A *CiliumNetworkPolicy* contains a list of rules that define allowed requests, meaning that requests 228 that do not match any rules are denied as invalid. 229 230 The above rule applies to inbound (i.e., "ingress") connections to kafka-broker pods (as 231 indicated by "app: kafka" 232 in the "endpointSelector" section). The rule will apply to connections from pods with label 233 "app: empire-outpost" as indicated by the "fromEndpoints" section. The rule explicitly matches 234 Kafka connections destined to TCP 9092, and allows consume/produce actions on various topics of interest. 235 For example we are allowing *consume* from topic *empire-announce* in this case. 236 237 The full policy adds two additional rules that permit the legitimate "produce" 238 (topic *empire-announce* and topic *deathstar-plans*) from *empire-hq* and the 239 legitimate consume (topic = "deathstar-plans") from *empire-backup*. The full policy 240 can be reviewed by opening the URL in the command below in a browser. 241 242 Apply this Kafka-aware network security policy using ``kubectl`` in the main window: 243 244 .. parsed-literal:: 245 246 $ kubectl create -f \ |SCM_WEB|\/examples/kubernetes-kafka/kafka-sw-security-policy.yaml 247 248 If we then again try to produce a message from outpost-9999 to *empire-annnounce*, it is denied. 249 Type control-c and then run: 250 251 .. code-block:: shell-session 252 253 $ echo "Vader Trips on His Own Cape" | ./kafka-produce.sh --topic empire-announce 254 >>[2018-04-10 23:50:34,638] ERROR Error when sending message to topic empire-announce with key: null, value: 27 bytes with error: (org.apache.kafka.clients.producer.internals.ErrorLoggingCallback) 255 org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [empire-announce] 256 257 This is because the policy does not allow messages with role = "produce" for topic "empire-announce" from 258 containers with label app = empire-outpost. Its worth noting that we don't simply drop the message (which 259 could easily be confused with a network error), but rather we respond with the Kafka access denied error 260 (similar to how HTTP would return an error code of 403 unauthorized). 261 262 Likewise, if the outpost container ever tries to consume from topic *deathstar-plans*, it is denied, as 263 role = consume is only allowed for topic *empire-announce*. 264 265 To test, from the outpost-9999 terminal, run: 266 267 .. code-block:: shell-session 268 269 $./kafka-consume.sh --topic deathstar-plans 270 [2018-04-10 23:51:12,956] WARN Error while fetching metadata with correlation id 2 : {deathstar-plans=TOPIC_AUTHORIZATION_FAILED} (org.apache.kafka.clients.NetworkClient) 271 272 This is blocked as well, thanks to the Cilium network policy. Imagine how different things would have been if the empire had been using 273 Cilium from the beginning! 274 275 Clean Up 276 ======== 277 278 You have now installed Cilium, deployed a demo app, and tested both 279 L7 Kafka-aware network security policies. To clean up, run: 280 281 .. parsed-literal:: 282 283 $ kubectl delete -f \ |SCM_WEB|\/examples/kubernetes-kafka/kafka-sw-app.yaml 284 $ kubectl delete cnp secure-empire-kafka 285 286 287 After this, you can re-run the tutorial from Step 1.