Sécurisation des services avec Open Policy Agent
22 janv. 2020
- Catégories
- Cybersécurité
- Gouvernance des données
- Tags
- Ranger
- REST
- Kafka
- Autorisation
- Cloud
- Kubernetes
- SSL/TLS [plus][moins]
Ne ratez pas nos articles sur l'open source, le big data et les systèmes distribués, fréquence faible d’un email tous les deux mois.
Open Policy Agent est un un moteur de règles multifonction. L’objectif principal du projet est de centraliser l’application de règles de sécurité à travers la stack cloud native. Le projet a été crée par Styra et il est actuellement incubé au sein de la Cloud Native Computing Foundation. OPA est utilisé chez Netflix, SAP, Cloudflare et d’autres sociétés.
Nous allons maintenant étudier le fonctionnement d’Open Policy Agent et voir comment l’utiliser pour implémenter des règles d’accès à Kafka, Kubernetes et dans une application personnalisée.
Architecture
Open Policy Agent (OPA) est conçu pour tourner avec votre application, en tant que daemon. La principal avantage à ce que OPA tourne localement avec votre service est que cela permet à ce dernier de prendre des décisions rapides sans avoir à contacter de service externe.
Le processus de prise de décision avec OPA est le suivant :
- L’application reçoit une requête
- L’application traduit cette requête dans un format JSON
- L’application envoie ce JSON à OPA
- OPA évalue la l’objet de la requête par rapport aux règles de sécurité définies
- OPA envoie la décision (authorisation ou non) dans un objet JSON à l’application
- L’application répond (ou non) à la requête, la police de sécurité a bien été appliquée.
Dans la plupart des cas, OPA sera executé en tant que daemon et requêté via REST. Pour les applications en Go, OPA peut être utilisé en tant que librairie avec le package github.com/open-policy-agent/opa/rego
.
Dissociation des règles de sécurité
Open Policy Agent est conçu autour du concept de policy decoupling, ou dissociation des règles de sécurité. Celui-ci peut être décrit comme le besoin pour un logiciel de pouvoir être capable d’appliquer des règles de sécurités stockées à l’extérieur de l’application et que celles-ci soient facilement modifiables sans avoir à re-builder ou re-déployer toute l’application.
OPA fonctionne de la même manière pour toutes les applications. En effet il peut être utilisé pour à peu près tout tant que la logique est définie dans l’application et que les entrées / sorties de celle-ci peuvent être exprimées à dans le langage Rego. Si vous êtes familier de l’écosystème Big Data, vous pouvez pensez d’OPA qu’il s’agit d’un service comme Apache Ranger implémentable partout.
Il existe des dizaines d’implémentations d’OPA disponibles pour des services variés : Kafka, Docker, SSH, sudo, etc. Elles sont disponibles ici. L’implémentation pour Kubernetes est un peu particulière et fait l’objet d’un projet à part : Gatekeeper.
Le langage Rego
Rego est un langage haut-niveau utilisé par OPA pour définir les polices de sécurité. Il est conçu pour être particulièrement facile à lire et écrire.
Une police OPA se résume à “utilisateur U peut/ne peut pas effectuer l’opération O”
Voici un exemple d’une règle très simple qui bloque tous les utilisateurs à part leo
:
package example
default allow = false
allow {
u := input.user
u == "leo"
}
Rego supporte l’utilisation de variables, des strings, valeurs numériques, bouléens, les comparaisons, etc. Si vous voulez voir rapidement toutes les possibilitées offertes par Rego, la cheat sheet est une bonne solution.
Styra a conçu un outil intéressant : Rego Playground. Celui-ci est très pratique pour tester ses polices.
Voici notre police définie précédemment testée dans l’outil :
Nous rencontrerons d’autres exemples de polices dans la suite de cet article.
Open Policy Agent avec Apache Kafka
Dans cette partie, nous allons voir comment implémenter des polices de sécurité dans Apache Kafka avec OPA. Par soucis de simplicité, la démo s’effectuera avec une instalation de Kafka sur un noeud unique. Le kafka n’étant pas sécurisé, il n’y a pas de notion d’identité.
En premier lieu, téléchargeons et installons Open Policy Agent :
mkdir /opt/opa && cd /opt/opa
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_darwin_amd64
chmod 755 ./opa
Nous allons également créer une policy qui bloque tout par défaut :
mkdir -p data/kafka/authz
cat <<EOF > data/kafka/authz/allow.rego
package kafka.authz
default allow = false
EOF
Démarrons Open Policy Agent en mode serveur :
./opa run --server --watch data/
Téléchargement et installation d’une version récente de Kafka.
curl http://apache.crihan.fr/dist/kafka/2.3.1/kafka_2.12-2.3.1.tgz -o /tmp/kafka_2.12-2.3.1.tgz
tar -xvzf /tmp/kafka_2.12-2.3.1.tgz -C /opt
Il nous faut maintenant récupérer les sources du repository contrib de Open Policy Agent et compiler le plugin Kafka.
git clone https://github.com/open-policy-agent/contrib
cd kafka_authorizer
mvn install
Par défaut, il est compilé pour Kafka 1.0.0, nous allons devoir changer kafka_authorizer/pom.xml
pour le rendre compatible avec notre version de Kafka.
Les JARs obtenus doivent être déployés dans le dossier lib
de Kafka :
cp target/kafka-authorizer-opa-1.0.jar /opt/kafka_2.12-2.3.1/libs/
cp target/kafka-authorizer-opa-1.0-package/share/java/kafka-authorizer-opa/gson-2.8.2.jar /opt/kafka_2.12-2.3.1/libs/
Toutes les configurations du broker Kafka garderont leurs valeurs par défaut. Nous allons uniquement ajouter les propiétés nécéssaire pour faire de OPA l’autorité de sécurité de Kakfa et lier le service à la police que nous venons de créer.
cd /opt/kafka_2.12-2.3.1
cat <<EOF >> config/server.properties
###################### OPA Properties ######################
authorizer.class.name: com.lbg.kafka.opa.OpaAuthorizer
opa.authorizer.url=http://localhost:8181/v1/data/kafka/authz/allow
opa.authorizer.allow.on.error=false
opa.authorizer.cache.initial.capacity=100
opa.authorizer.cache.maximum.size=100
opa.authorizer.cache.expire.after.ms=600000
EOF
On peut maintenant démarrer Apache ZooKeeper puis le broker Kafka :
bin/zookeeper-server-start.sh config/zookeeper.properties
bin/kafka-server-start.sh config/server.properties
Essayons de créer un topic pour voir ce qui se produit :
bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic MyTopic
Error while executing topic command : org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [Authorization failed.]
[2019-12-07 17:50:56,727] ERROR java.util.concurrent.ExecutionException: org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [Authorization failed.]
at org.apache.kafka.common.internals.KafkaFutureImpl.wrapAndThrow(KafkaFutureImpl.java:45)
at org.apache.kafka.common.internals.KafkaFutureImpl.access$000(KafkaFutureImpl.java:32)
at org.apache.kafka.common.internals.KafkaFutureImpl$SingleWaiter.await(KafkaFutureImpl.java:89)
at org.apache.kafka.common.internals.KafkaFutureImpl.get(KafkaFutureImpl.java:260)
at kafka.admin.TopicCommand$AdminClientTopicService.createTopic(TopicCommand.scala:190)
at kafka.admin.TopicCommand$TopicService.createTopic(TopicCommand.scala:149)
at kafka.admin.TopicCommand$TopicService.createTopic$(TopicCommand.scala:144)
at kafka.admin.TopicCommand$AdminClientTopicService.createTopic(TopicCommand.scala:172)
at kafka.admin.TopicCommand$.main(TopicCommand.scala:60)
at kafka.admin.TopicCommand.main(TopicCommand.scala)
Caused by: org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [Authorization failed.]
(kafka.admin.TopicCommand$)
Notre police de sécurité stricte “deny all” a été appliquée : il nous est impossible de créer un topic. Voyons si on peut le faire après avoir modifié la police pour cette fois ci autoriser toutes les actions :
cat <<EOF > /opt/opa/data/kafka/authz/allow.rego
package kafka.authz
default allow = true
EOF
bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic MyTopic
C’est bon ! Le topic a bien été créé car OPA a appliqué cette nouvelle police.
Cette règle de sécurité est trop simple, nous allons en créer une un peu plus commpliqué qui doit répondre au besoin suivant :
Il est uniquement possible d’écrire dans le topic test_project. Tous les producers qui essayent d’écrire sur un autre topic seront bloqués. TOutes les autres actions sur tous les topics seront authorisées.
package kafka.authz
default allow = false
allow {
not deny
}
deny {
is_produce_operation
not is_topic_test_project
}
is_produce_operation {
input.operation.name == "Write"
}
is_topic_test_project {
input.resource.name == "test_project"
}
Avec cette règle, nous pouvons faire toutes les opérations sur tous les topics sauf l’écriture qui est uniquement autorisée dans le topic test_project :
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic project
>This should be blocked
>[2019-12-09 13:59:10,486] ERROR Error when sending message to topic test with key: null, value: 22 bytes with error: (org.apache.kafka.clients.producer.internals.ErrorLoggingCallback)
org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [project]
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test_project
>This should be allowed
>SUCCESS!
Maintenant vous savez utiliser OPA pour implémenter des règles d’accès aux resources de Kafka.
Quid d’OPA avec mon application ?
Grâce à l’API REST d’Open Policy Agent, il est très aisé d’intégrer ce-dernier à vos applications maison. L’ojbectif est de disposer d’une gestion d’accès simple, portable et centralisée pour votre application sans avoir à réinventer la roue.
Pour cette démo, on dispose d’une WebApp Flask très simple :
from flask import Flask
from flask import request
import requests
app = Flask(__name__)
@app.route('/')
def hello_world():
allowed = False
username = request.args.get('username')
r = requests.post('http://master01.metal.ryba:8181/v1/data/myapp/auth/allow', json={"input": {"user": username}})
allowed = r.json()['result']
if allowed == True:
return 'Hello, World! You are allowed!'
else:
return 'Hello, World! You are not allowed!'
Comme nous pouvons le constater dans le code, la partie autorisation est déléguée à Open Policy Agent qui tourne sur le serveur master01.metal.ryba
sur le port 8181
. L’application peut être démarée avec :
export FLASK_APP=myapp.py
flask run
La police de sécurité est définie de cette façon :
mkdir -p data/myapp/auth
cat <<EOF > data/myapp/auth/allow.rego
package myapp.auth
default allow = false
allow {
user := input.user
user == "leo"
}
EOF
La règle d’accès est simple. Elle doit permettre à un utilisateur d’afficher la ressource demandée ou non selon le paramètre username
qui est envoyé. Essayons :
curl http://127.0.0.1:5000/
Hello, World! You are not allowed!
curl http://127.0.0.1:5000?username=leo
Hello, World! You are allowed!
Ça fonctionne !
Quoi d’autre ?
Nous avons vu ce qu’est Open Policy Agent : comment le service fonctionne et comment l’intégrer à Kafka ou à une application personnalisée. Dans un prochain article, nous essayerons l’intégration d’OPA à Kubernetes.