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.