
Outil d'analyse statique et de visualisation RBAC Kubernetes
Analyse RBAC Kubernetes simplifiée
Krane est un outil simple d'analyse statique RBAC Kubernetes. Il identifie les risques de sécurité potentiels dans la conception RBAC K8s et propose des suggestions pour les atténuer. Le tableau de bord Krane présente la posture de sécurité RBAC actuelle et vous permet de naviguer dans sa définition.
Vous pouvez commencer avec Krane en l'installant via un chart Helm dans votre cluster Kubernetes cible ou en l'exécutant localement avec Docker.
Il est supposé que vous ayez Helm CLI installé sur votre machine.```sh $ helm repo add appvia https://appvia.github.io/krane $ helm repo update $ helm install krane appvia/krane --namespace krane --create-namespace
Suivez la sortie de l'installation du chart Helm pour savoir comment effectuer un port-forward vers le tableau de bord Krane.
### Exécution avec Docker
Il est supposé que vous avez [docker](https://docs.docker.com/get-docker/) en cours d'exécution sur votre machine locale. Installez [docker-compose](https://docs.docker.com/compose/install/#install-compose) si ce n'est pas déjà fait.
Krane dépend de RedisGraph. La stack `docker-compose` définit tout ce qui est nécessaire pour construire et exécuter le service _Krane_ localement. Elle gérera également sa dépendance [RedisGraph](https://oss.redislabs.com/redisgraph/).```
docker-compose up -d
_L'image Docker Krane sera pré-construite automatiquement si elle n'est pas déjà présente sur la machine locale.
Notez que lors de l'exécution locale de docker-compose, Krane ne démarrera pas automatiquement les rapports RBAC report et le tableau de bord dashboard. Au lieu de cela, le conteneur restera en veille pendant 24h par défaut – cette valeur peut être ajustée dans docker-compose.override.yml. Connectez-vous à un conteneur Krane en cours d'exécution pour exécuter des commandes. Le docker-compose local montera également la configuration kube (~/.kube/config) à l'intérieur du conteneur, vous permettant d'exécuter des rapports sur tous les clusters Kubernetes auxquels vous avez déjà accès.
Connectez-vous à un conteneur Krane en cours d'exécution.```sh docker-compose exec krane bash
Une fois dans le conteneur, vous pouvez commencer à utiliser les commandes `krane`. Essayez `krane -help`.```sh
krane -h
Pour inspecter quels services sont en cours d'exécution et les ports associés :``` docker-compose ps
Pour arrêter _Krane_ et ses services dépendants :```
docker-compose down
$ krane --help
NAME:
krane
DESCRIPTION:
Kubernetes RBAC static analysis & visualisation tool
COMMANDS:
dashboard Start K8s RBAC dashboard server
help Display global or [command] help documentation
report Run K8s RBAC report
GLOBAL OPTIONS:
-h, --help
Display help documentation
-v, --version
Display version information
-t, --trace
Display backtrace when an error occurs
AUTHOR:
Marcin Ciszak <[email protected]> - Appvia Ltd <appvia.io>
### Générer un rapport RBAC
#### Avec le contexte `kubectl` local
Pour exécuter un rapport sur un cluster en cours d'exécution, vous devez fournir un contexte _kubectl_```
krane report -k <context>
Vous pouvez également passer l'option -c <cluster-name> si vous prévoyez d'exécuter l'outil contre plusieurs clusters et d'indexer le graphe RBAC séparément pour chaque nom de cluster.
Pour exécuter un rapport sur des fichiers yaml/json RBAC locaux, fournissez un chemin de dossier.``` krane report -d </path/to/rbac-directory>
NOTE: _Krane_ attend que les fichiers suivants (au format YAML ou JSON) soient présents dans le chemin de répertoire spécifié :
- psp
- roles
- clusterroles
- rolebindings
- clusterrolebindings
Si les Pod Security Policies ne sont pas utilisées, vous pouvez contourner l'attente ci-dessus en créant manuellement un fichier `psp` avec le contenu suivant :```json
{
"items": []
}
Note, PodSecurityPolicy a été déprécié dans Kubernetes v1.21 et supprimé de Kubernetes dans v1.25.
Pour exécuter un rapport depuis un conteneur s'exécutant dans un cluster Kubernetes``` krane report --incluster
NOTE: Le compte de service utilisé par _Krane_ nécessitera un accès aux ressources RBAC. Voir [Prerequisites](https://github.com/appvia/krane/blob/HEAD/k8s/one-time/prerequisites.yaml) pour les détails.
#### Dans le pipeline CI/CD
Pour valider la définition RBAC en tant qu'étape dans le pipeline CI/CD```
krane report --ci -d </path/to/rbac-directory>
REMARQUE : Krane s'attend à ce qu'une certaine convention de nommage soit suivie pour les fichiers de ressources RBAC stockés localement. Voir section ci-dessus. Afin d'exécuter les commandes krane, il est recommandé que l'exécuteur CI référence l'image docker quay.io/appvia/krane:latest.
Le mode CI est activé par le drapeau --ci. Krane renverra un code de statut non nul ainsi que les détails des règles de risque enfreintes lorsqu'un ou plusieurs dangers ont été détectés.
Pour afficher l'arborescence des facettes RBAC, le graphe réseau et les derniers résultats de rapport, vous devez d'abord démarrer le serveur de tableau de bord.``` krane dashboard
Cluster flag `-c <cluster-name>` may be passed if you want to run the dashboard against specific cluster name. Dashboard will look for data related to specified cluster name which is cached on the file system.
Command above will start local web server on default port `8000`, and display the dashboard link.
## Architecture
### RBAC Data indexed in a local Graph database
_Krane_ indexes RBAC entities in RedisGraph. This allows us to query network of dependencies efficiently and simply using subset of [CypherQL](https://oss.redislabs.com/redisgraph/cypher_support/) supported by [RedisGraph](https://oss.redislabs.com/redisgraph/).
#### Schema

#### Nodes
The following nodes are created in the Graph for the relevant RBAC objects:
* `Psp` - A PSP node containing attributes around the pod security policy. Only applicable when working with K8s < 1.25.
* `Rule` - Rule node represents access control rule around Kubernetes resources.
* `Role` - Role node represents a given Role or ClusterRole. `kind` attribute defines type of role.
* `Subject` - Subject represents all possible actors in the cluster (`kind`: User, Group and ServiceAccount)
* `Namespace` - Kubernetes Namespace node.
#### Edges
* `:SECURITY` - Defines a link between Rule and Psp nodes. Only applicable when working with K8s < 1.25.
* `:GRANT` - Defines a link between Role and Rule associated with that role.
* `:ASSIGN` - Defines a link between an Actor (Subject) and given Role/ClusterRole (Role node).
* `:RELATION` - Defines a link between two different Actor (Subject) nodes.
* `:SCOPE` - Defines a link between Role and Namespace nodes.
* `:ACCESS` - Defines a link between Subject and Namespace nodes.
* `:AGGREGATE` - Defines a link between ClusterRoles (one ClusterRole aggregates another) `A-(aggregates)->B`
* `:COMPOSITE` - Defines a link between ClusterRoles (one ClusterRole can be aggregated in another) `A<-(is a composite of)-B`
All edges are bidirectional, which means graph can be queried in either direction.
Only exceptions are `:AGGREGATE` and `:COMPOSITE` relations which are uni-directional, though concerned with the same edge nodes.
#### Querying the Graph
In order to query the graph directly you can exec into a running `redisgraph` container, start `redis-cli` and run your arbitrary queries. Follow official [instructions](https://oss.redislabs.com/redisgraph/) for examples of [commands](https://oss.redislabs.com/redisgraph/commands/).
You can also query the Graph from _Krane_ console. First exec into running _Krane_ container, then```ruby
# Start Krane console - this will open interactive ruby shell with Krane code preloaded
console
# Instantiate Graph client
graph = Krane::Clients::RedisGraph.client cluster: 'default'
# Run arbitrary CypherQL query against indexed RBAC Graph
res = graph.query(%Q(
MATCH (r:Rule {resource: "configmaps", verb: "update"})<-[:GRANT]-(ro:Role)<-[:ASSIGN]-(s:Subject)
RETURN s.kind as subject_kind, s.name as subject_name, ro.kind as role_kind, ro.name as role_name))
# Print the results
res.print_resultset
.```
+----------------+--------------------------------+-----------+------------------------------------------------+ | subject_kind | subject_name | role_kind | role_name | +----------------+--------------------------------+-----------+------------------------------------------------+ | ServiceAccount | bootstrap-signer | Role | system:controller:bootstrap-signer | | User | system:kube-controller-manager | Role | system::leader-locking-kube-controller-manager | | ServiceAccount | kube-controller-manager | Role | system::leader-locking-kube-controller-manager | | User | system:kube-scheduler | Role | system::leader-locking-kube-scheduler | | ServiceAccount | kube-scheduler | Role | system::leader-locking-kube-scheduler | +----------------+--------------------------------+-----------+------------------------------------------------+
Note: Example query above will select all Subjects with assigned Roles/ClusterRoles granting access to `update configmaps`.
## Configuration
### RBAC Risk Rules
RBAC risk rules are defined in the [Rules](https://github.com/appvia/krane/blob/HEAD/config/rules.yaml) file. The structure of each rule is largely self-explanatory.
Built-in set can be expanded / overridden by adding extra custom rules to the [Cutom Rules](https://github.com/appvia/krane/blob/HEAD/config/custom-rules.yaml) file.
#### Risk Rule Macros
Macros are "containers" for a set of common/shared attributes, and referenced by one or more risk rules. If you choose to use macro in a given risk rule you would need to reference it by name, e.g. `macro: <macro-name>`. Note that attributes defined in referenced `macro` will take precedence over the same attributes defined on the rule level.
Macro can contain any of the following attributes:
- `query` - [RedisGraph query](#querying-the-graph). Has precedence over `template`. Requires `writer` to be defined.
- `writer` - Writer is a Ruby expression used to format `query` result set. Writer has precedence over `template`.
- `template` - Built-in query/writer template name. If `query` & `writer` are not specified then chosen query generator will be used along with matching writer.
#### Risk Rule attributes
Rule can contain any of the following attributes:
- `id` [Required] Rule id is a unique rule identifier.
- `group_title` [Required] Title applying to all items falling under this risk check.
- `severity` [Required] Severity, as one of :danger, :warning, :info.
- `info` [Required] Textual information about the check and suggestions on how to mitigate the risk.
- `query` [Conditonal] [RedisGraph query](#querying-the-graph). Has precedence over `template`. Requires `writer` to be defined.
- `writer` [Conditonal] Writer is a Ruby expression used to format query result set. Writer has precedence over `template`. Requires `query` to be defined.
- `template` [Conditonal] Built-in query/writer template name. If `query` & `writer` are not specified then chosen query generator will be used along with matching writer.
- Some built-in templates require `match_rules` attribute to be specified on individual rule level in order to build correct query. Templates currently requiring it:
- **_risky-role_** - Builds multi-match graph query based on the access rules specified by `match_rules`. Generated graph query returns the following columns:
- role_name
- role_kind
- namespace_name (an _array_ is returned if multiple items returned)
- `match_rules` [Conditonal] Required when `template` relies on match rules in order to build a query.
- Example:
```yaml
match_rules:
- resources: ['cronjobs']
verbs: ['update']
```
Attributes and values follow [Kubernetes RBAC role specification](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-examples).
- `custom_params` [Optional] List of custom key-value pairs to be evaluated and replaced in a rule `query` and `writer` representation.
- Example:
```yaml
custom_params:
- attrA: valueA
- attrB: valueB
```
Template placeholders for the keys above `{{attrA}}` and `{{attrB}}` will be replaced with `valueA` and `valueB` respectively.
- `threshold` [Optional] Numeric value. When definied this will become available as template placeholder `{{threshold}}` in the `writer` expression.
- `macro` [Optional] Reference to common parameters defined in a named macro.
- `disabled` [Optional] When set to `true` it'll disable given rule and exclude it from evaluation.
By default all rules are enabled.
#### Risk Rule examples
##### Explicit query & writer expression```yaml
- id: verbose-rule-example
group_title: Example rule
severity: :danger
info: Risk description and instructions on how to mitigate it goes here
query: |
MATCH
(s:Subject)-[:ACCESS]->(ns:Namespace)
WHERE
NOT s.name IN {{whitelist_subject_names}}
RETURN
s.kind as subject_kind,
s.name as subject_name,
COLLECT(ns.name) as namespace_names
ORDER BY
subject_kind,
subject_name,
namespace_names DESC
threshold: 2
writer: |
if result.namespace_names.count > {{threshold}}
"#{result.subject_kind} #{result.subject_name} can access namespaces: #{result.namespace_names.join(', ')}"
end
disabled: true
L'exemple ci-dessus définit explicitement une query graph qui est utilisée pour évaluer le risque RBAC, et une expression writer utilisée pour formater l'ensemble des résultats de la requête. La requête sélectionne simplement tous les Subjects (à l'exception de ceux de la liste blanche) et les Namespaces auxquels ils ont accès. Notez que l'ensemble des résultats n'inclura que les Subjects ayant accès à plus de 2 Namespaces (Vous avez remarqué la valeur threshold là-dedans ?). La dernière expression du writer sera capturée comme résultat formaté en sortie.
writer peut accéder à l'élément de l'ensemble des résultats via l'objet result avec des méthodes correspondant aux éléments retournés par la requête, par ex. result.subject_kind, result.subject_name, etc.
Note :
{{threshold}} dans l'expression writer sera remplacé par la valeur du mot-clé threshold de la règle.{{whitelist_subject_names}} représente un champ personnalisé qui sera interpolé avec les valeurs de la liste blanche définies pour un id de règle donné. Si un nom de champ placeholder n'est pas défini dans la liste blanche, il sera remplacé par défaut par un tableau vide ['']. Lisez plus d'informations sur la liste blanche ci-dessous.Les modèles intégrés simplifient considérablement la définition des règles de risque, cependant, ils sont conçus pour extraire un type d'information spécifique et peuvent ne pas convenir à vos règles personnalisées. Si vous vous retrouvez à réutiliser les mêmes expressions query ou writer dans plusieurs règles, vous devriez envisager de les extraire dans une macro et de la référencer dans vos règles personnalisées pour les rendre plus DRY.```yaml
L'exemple ci-dessus montre l'une des règles intégrées. Il fait référence au modèle `risky-role` qui, lors du traitement, étendra la règle en injectant les expressions `query` et `writer` avant le déclenchement de l'évaluation de la règle. `match_rules` sera utilisé pour construire la requête de correspondance appropriée.
### Liste blanche des risques RBAC
La liste blanche optionnelle contient un ensemble de noms d'attributs définis par l'utilisateur et leurs valeurs (autorisées) respectives.
#### Attributs de la liste blanche
Les noms d'attributs et leurs valeurs sont arbitraires. Ils sont définis dans le fichier [Whitelist](https://github.com/appvia/krane/blob/HEAD/config/whitelist.yaml) et divisés en trois sections distinctes :
- `global` - Portée de niveau supérieur. Les attributs personnalisés définis ici s'appliqueront à toutes les règles de risque, quel que soit le nom du cluster.
- `common` - Les attributs personnalisés seront limités à l'`id` d'une règle de risque spécifique, quel que soit le nom du cluster.
- `cluster` (avec une liste imbriquée de noms de clusters) - Les attributs personnalisés s'appliqueront à l'`id` d'une règle de risque spécifique pour un nom de cluster donné.
Chaque [règle de risque](#rbac-risk-rules), lors de l'évaluation, tentera d'interpoler tous les espaces réservés de paramètres utilisés dans la `query`, par exemple `{{your_whitelist_attribute_name}}`. Si un nom de paramètre d'espace réservé (c'est-à-dire un nom entre doubles accolades) correspond à l'un des noms d'attributs de la liste blanche pour l'`id` de cette règle de risque, il sera remplacé par sa valeur calculée. Si aucune valeur n'est trouvée pour un espace réservé donné, il sera remplacé par `['']`.
#### Exemples de liste blanche
L'exemple de liste blanche ci-dessous produit le mappage `placeholder-key => value` suivant pour une [règle de risque](#rbac-risk-rules) dont la valeur de l'attribut `id` correspond à _"some-risk-rule-id"_```
{{whitelist_role_names}} => ['acp:prometheus:operator']
{{whitelist_subject_names}} => ['privileged-psp-user', 'another-user']
Les clés de remplacement ci-dessus, lorsqu'elles sont utilisées dans les requêtes de graphe personnalisées, seront remplacées par leurs valeurs respectives lors de l'évaluation des règles de risque.
rules: global: # global scope - applies to all risk rule and cluster names whitelist_role_names: # custom attribute name - acp:prometheus:operator # custom attribute values
common: # common scope - applies to specific risk rule id regardless of cluster name some-risk-rule-id: # this corresponds to risk rule id defined in config/rules.yaml whitelist_subject_names: # custom attribute name - privileged-psp-user # custom attribute values
cluster: # cluster scope - applies to speciifc risk rule id and cluster name default: # example cluster name some-risk-rule-id: # risk rule id whitelist_subject_names: # custom attribute nane - another-user # custom attribute values
## Déploiement Kubernetes
_Krane_ peut être déployé facilement sur des clusters Kubernetes locaux ou distants.
### Prérequis K8s
Un namespace Kubernetes, un compte de service ainsi que les RBAC appropriés doivent être présents dans le cluster. Voir les [Prérequis](https://github.com/appvia/krane/blob/HEAD/k8s/one-time/prerequisites.yaml) pour référence.
Le point d'entrée par défaut de _Krane_ exécute [bin/in-cluster-run](https://github.com/appvia/krane/blob/HEAD/bin/in-cluster-run) qui attend que l'instance RedisGraph soit disponible avant de démarrer la boucle de _rapport_ RBAC et le serveur web _dashboard_.
Vous pouvez contrôler certains aspects de l'exécution dans le cluster avec les variables d'environnement suivantes :
* `KRANE_REPORT_INTERVAL` - Définit l'intervalle en secondes pour l'exécution du rapport d'analyse statique RBAC. Par défaut : `300` (en secondes, soit 5 minutes).
* `KRANE_REPORT_OUTPUT` - Définit le format de sortie du rapport de risque RBAC. Valeurs possibles : `:json`, `:yaml`, `:none`. Par défaut : `:json`.
### Cluster K8s local ou distant
#### Chart Helm
Avant de commencer, vous aurez besoin des outils suivants :
* [CLI Helm](https://helm.sh/docs/intro/install/)
Installer le chart Helm :```sh
$ helm repo add appvia https://appvia.github.io/krane
$ helm repo update
$ helm install krane appvia/krane --namespace krane --create-namespace
Voir le fichier values.yaml pour les détails des autres options et paramètres configurables.
kubectl create
--context
--namespace krane
-f k8s/redisgraph-service.yaml
-f k8s/redisgraph-deployment.yaml
-f k8s/krane-service.yaml
-f k8s/krane-deployment.yaml
Notez que le service de tableau de bord _Krane_ n'est pas exposé par défaut !```sh
kubectl port-forward svc/krane 8000 \
--context=<docker-desktop> \
--namespace=krane
# Open Krane dashboard at http://localhost:8000
Vous pouvez trouver les exemples de manifests de déploiement dans le répertoire k8s.
Modifiez les manifests selon vos besoins de déploiement en veillant à référencer la version correcte de l'image Docker Krane dans son fichier de déploiement. Consultez le registre Docker Krane pour les tags disponibles, ou utilisez simplement latest.
Si votre cluster K8s est doté du support intégré du contrôleur Compose-on-Kubernetes (docker-desktop le supporte par défaut), alors vous pouvez déployer Krane et ses dépendances avec une seule commande docker stack :```sh
docker stack deploy
--orchestrator kubernetes
--namespace krane
--compose-file docker-compose.yml
--compose-file docker-compose.k8s.yml krane
Remarque : Assurez-vous que votre contexte kube actuel est correctement défini avant d'exécuter la commande ci-dessus !
La pile d'applications devrait maintenant être déployée sur un cluster Kubernetes et tous les services sont prêts et exposés. Notez que _Krane_ démarrera automatiquement sa boucle de rapports et son serveur de tableau de bord.```sh
docker stack services --orchestrator kubernetes --namespace krane krane
La commande ci-dessus produira la sortie suivante :``` ID NAME MODE REPLICAS IMAGE PORTS 0de30651-dd5 krane_redisgraph replicated 1/1 redislabs/redisgraph:1.99.7 *:6379->6379/tcp aa377a5f-62b krane_krane replicated 1/1 quay.io/appvia/krane:latest *:8000->8000/tcp
Vérifiez la posture de sécurité RBAC de votre cluster Kubernetes en visitant http://localhost:8000.
Notez que pour les déploiements de cluster à distance, vous aurez probablement besoin de faire un port-forward du service _Krane_ en premier.```sh
kubectl --context=my-remote-cluster --namespace=krane port-forward svc/krane 8000
Pour supprimer le Stack```sh
docker stack rm krane
--orchestrator kubernetes
--namespace krane
## Notifications
Krane vous informera des anomalies détectées de gravité moyenne et élevée via son intégration Slack.
Pour activer les notifications, spécifiez `webhook_url` et `channel` Slack dans le fichier [config/config.yaml](https://github.com/appvia/krane/blob/HEAD/config/config.yaml), ou définissez les variables d'environnement `SLACK_WEBHOOK_URL` et `SLACK_CHANNEL`. Les variables d'environnement auront priorité sur les valeurs du fichier de configuration.
## Développement local
Cette section décrit les étapes pour activer le développement local.
### Configuration
Installez les dépendances du code _Krane_ avec```sh
./bin/setup
Krane dépend de RedisGraph. docker-compose est le moyen le plus rapide d'exécuter les dépendances de Krane localement.```sh
docker-compose up -d redisgraph
Pour vérifier que le service RedisGraph est opérationnel :```sh
docker-compose ps
Pour arrêter les services :```sh docker-compose down
### Développement
À ce stade, vous devriez pouvoir modifier la base de code de _Krane_ et les résultats des tests en exécutant des commandes dans le shell local.```sh
$ ./bin/krane --help # to get help
$ ./bin/krane report -k docker-desktop # to generate your first report for
# local docker-desktop k8s cluster
...
Pour activer le mode de développement local de l'interface utilisateur du tableau de bord```sh $ cd dashboard $ npm install $ npm start
Cela démarrera automatiquement le serveur Dashboard, ouvrira le navigateur par défaut et surveillera les modifications des fichiers source.
_Krane_ est préconfiguré pour améliorer l'expérience développeur avec [Skaffold](https://skaffold.dev/). Itérer sur le projet et valider l'application en exécutant la pile entière dans un cluster Kubernetes local ou distant est désormais plus facile.
Le rechargement à chaud du code permet de propager automatiquement les modifications locales vers le conteneur en cours d'exécution pour un cycle de développement plus rapide.```sh
skaffold dev --kube-context docker-desktop --namespace krane --port-forward
Exécutez les tests localement avec```sh bundle exec rspec
## Contribuer à Krane
Nous accueillons toutes les contributions de la communauté ! Jetez un œil à notre guide de [contribution](https://github.com/appvia/krane/blob/HEAD/CONTRIBUTING.md) pour plus d'informations sur la façon de commencer. Si vous utilisez _Krane_, le trouvez utile, ou êtes généralement intéressé par la sécurité de Kubernetes, alors faites-le nous savoir en **Starring** et **Watching** ce dépôt. Merci !
## S'impliquer
Rejoignez la discussion sur notre [canal communautaire](https://www.appvia.io/join-the-appvia-community).
Krane est un projet communautaire et nous accueillons vos contributions. Pour signaler un bug, suggérer une amélioration ou demander une nouvelle fonctionnalité, veuillez ouvrir un problème GitHub. Référez-vous à notre guide de [contribution](https://github.com/appvia/krane/blob/HEAD/CONTRIBUTING.md) pour plus d'informations sur la façon dont vous pouvez aider.
## Feuille de route
Consultez notre [Feuille de route](https://github.com/appvia/krane/projects/1) pour plus de détails sur nos plans pour le projet.
## Licence
Auteur : Marcin Ciszak <[email protected]>
Copyright (c) 2019-2020 [Appvia Ltd](https://appvia.io)
Ce projet est distribué sous la [Licence Apache, Version 2.0](https://github.com/appvia/krane/blob/HEAD/LICENSE).