/ansible-content-capture

Ansible Content Caputre is a tool for scanning Ansible contents and generating parsed object data. Topics Resources

Primary LanguagePythonApache License 2.0Apache-2.0

Note: This repository is in prototype phase and under active development with subject to breaking changes.

Ansible Content Capture

Ansible Content Capture is a scanning framework for Ansible contents and it generates graph/intermediate/high-level representation of Ansible data.

It supports scanning for

  • Playbook YAML
  • Task YAML in a role
  • Role
  • Collection (including modules)
  • Any type of Ansible project (e.g. Git repository)

and generates Python objects for each of the scanned contents. These objects are JSON serializable so that other projects/users can dump/load/process them depending on their requirements.

Today, the following projects utilize Ansible Content Capture for the scanning backend.

  • Ansible Risk Insight - A risk evaluation tool for Ansible contents
  • Sage - A framework to process Ansible contents for the scanning results

overview

Object models

Ansible Content Capture scans Ansible contents and generates representation objects and abstract syntax tree (AST) which consists of Ansible-specific nodes like

  • collections (all external dependencies)
  • modules (name, fqcn, spec)
  • playbooks (name, comment, filename)
  • plays (name, comment, calling tasks and roles)
  • projects (including playbooks, roles, dependencies, metadata)
  • roles (including taskfiles, role metadata, variables, default, etc)
  • taskfiles (calling tasks)
  • tasks (task spec, callling module)

Each node is represented by unique identifier, and links between the nodes are created by analayzing Ansible-specific code semantics. For example,

  • call hierarlchy from playbook to role --> taskfile - task --> module
  • variable assignment is inferred in a static-analytics fashion

Installation

Run the following command after git clone.

$ pip install -e .

Getting started

from ansible_content_capture.scanner import AnsibleScanner

scanner = AnsibleScanner()
scanner.run(target_dir="<PATH/TO/PROJECT>")

# Alternatively, you can run the scanning for a YAML string
# scanner.run(raw_yaml="<YAML_STRING>")

An example implementation scan.py is scanning the target directory/file and printing the loaded AST like tree command like the following.

You can refer to it as a reference of a scanning code and how to use the result objects.

# Single Playbook scanning
$ python examples/scan.py examples/single_playbook/playbook.yml

root
└──{"type": "playbook", "filepath": "examples/single_playbook/playbook.yml"}
     └──{"type": "play", "filepath": "examples/single_playbook/playbook.yml"}
          ├──{"type": "task", "filepath": "examples/single_playbook/playbook.yml", "lines": "4 - 8", "name": "Ensure apache is at the latest version", "module": "yum"}
          │    └──{"type": "module", "fqcn": "ansible.builtin.yum"}
          └──{"type": "task", "filepath": "examples/single_playbook/playbook.yml", "lines": "9 - 13", "name": "Ensure apache is running", "module": "service"}
               └──{"type": "module", "fqcn": "ansible.builtin.service"}
# Project scanning
$ python examples/scan.py examples/project

root
└──{"type": "role", "filepath": "roles/common", "default_variables": {"foo": "bar"}}
     └──{"type": "taskfile", "filepath": "roles/common/tasks/main.yml"}
          ├──{"type": "task", "filepath": "roles/common/tasks/main.yml", "lines": "2 - 5", "name": "Install the correct web server for RHEL", "module": "ansible.builtin.import_tasks"}
          │    └──{"type": "taskfile", "filepath": "roles/common/tasks/redhat.yml"}
          │         └──{"type": "task", "filepath": "roles/common/tasks/redhat.yml", "lines": "2 - 6", "name": "Install web server", "module": "ansible.builtin.yum"}
          │              └──{"type": "module", "fqcn": "ansible.builtin.yum"}
          ├──{"type": "task", "filepath": "roles/common/tasks/main.yml", "lines": "6 - 9", "name": "Install the correct web server for Debian", "module": "ansible.builtin.import_tasks"}
          │    └──{"type": "taskfile", "filepath": "roles/common/tasks/debian.yml"}
          │         └──{"type": "task", "filepath": "roles/common/tasks/debian.yml", "lines": "2 - 6", "name": "Install web server", "module": "ansible.builtin.apt"}
          │              └──{"type": "module", "fqcn": "ansible.builtin.apt"}
          └──{"type": "task", "filepath": "roles/common/tasks/main.yml", "lines": "10 - 13", "name": "Print a variable", "module": "debug"}
               └──{"type": "module", "fqcn": "ansible.builtin.debug"}