Skip to content

Commit

Permalink
fix ansible integration
Browse files Browse the repository at this point in the history
  • Loading branch information
topher-lo committed Jan 25, 2025
1 parent faa9024 commit 1801f3f
Showing 1 changed file with 78 additions and 36 deletions.
114 changes: 78 additions & 36 deletions registry/tracecat_registry/integrations/ansible.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""Generic interface to Ansible Python API."""

import asyncio
import tempfile
from pathlib import Path
from typing import Annotated, Any

import orjson
Expand All @@ -13,26 +11,33 @@

ansible_secret = RegistrySecret(
name="ansible",
optional_keys=[
keys=[
"ANSIBLE_SSH_KEY",
],
optional_keys=[
"ANSIBLE_PASSWORDS",
],
)
"""Ansible Runner secret.
- name: `ansible`
- optional_keys:
- keys:
- `ANSIBLE_SSH_KEY`
- optional_keys:
- `ANSIBLE_PASSWORDS`
`ANSIBLE_SSH_KEY` should be the private key string, not the path to the file.
`ANSIBLE_PASSWORDS` should be a JSON object mapping password prompts to their responses (e.g. `{"Vault password": "password"}`).
`ANSIBLE_PASSWORDS` should be a JSON object mapping password prompts to their responses:
{
"SSH password:": "sshpass",
"BECOME password": "sudopass",
}
"""


@registry.register(
default_title="Run playbook",
description="Run Ansible playbook given as a list of plays in JSON format.",
description="Run Ansible playbook on a single host given a list of plays in JSON format. Supports SSH host-connection mode only.",
display_group="Ansible",
doc_url="https://docs.ansible.com/ansible/latest/index.html",
namespace="tools.ansible",
Expand All @@ -42,48 +47,85 @@ async def run_playbook(
playbook: Annotated[
list[dict[str, Any]], Field(..., description="List of plays to run")
],
extra_vars: Annotated[
host: Annotated[
str,
Field(description="Host to SSH into and run the playbook on"),
],
host_name: Annotated[
str,
Field(description="Host name to use in the inventory"),
],
user: Annotated[
str,
Field(description="SSH user to connect as"),
],
envvars: Annotated[
dict[str, Any] | None,
Field(description="Environment variables to pass to the playbook"),
] = None,
extravars: Annotated[
dict[str, Any] | None,
Field(description="Extra variables to pass to the playbook"),
] = None,
runner_kwargs: Annotated[
dict[str, Any] | None,
Field(description="Additional keyword arguments to pass to the Ansible runner"),
] = None,
timeout: Annotated[
int,
Field(description="Timeout for the playbook execution in seconds"),
] = 60,
) -> list[dict[str, Any]]:
ssh_key = secrets.get("ANSIBLE_SSH_KEY")
passwords = secrets.get("ANSIBLE_PASSWORDS")

if not ssh_key and not passwords:
extravars = extravars or {}
runner_kwargs = runner_kwargs or {}

if "inventory" in runner_kwargs:
raise ValueError(
"Either `ANSIBLE_SSH_KEY` or `ANSIBLE_PASSWORDS` must be provided"
"`inventory` is not supported in this integration. Please use the `host` and `user` parameters instead."
)

extra_vars = extra_vars or {}
runner_kwargs = runner_kwargs or {}
local_patterns = ["localhost", "127.0.0.1", "::1", "0:0:0:0:0:0:0:1"]
if host in local_patterns:
# Block local connections in initial host connection
raise ValueError(f"Local connections are not supported. Got host: {host}")

# Create inventory config
inventory = {
"all": {
"hosts": {
host_name: {
"ansible_host": host,
"ansible_user": user,
"ansible_connection": "ssh",
}
}
}
}

if ssh_key:
runner_kwargs["ssh_key"] = ssh_key
if passwords:
runner_kwargs["passwords"] = orjson.loads(passwords)

loop = asyncio.get_running_loop()

def run():
runner, result = run_async(
playbook=playbook,
envvars=envvars,
extravars=extravars,
timeout=timeout,
inventory=inventory,
**runner_kwargs,
)
return runner, result

with tempfile.TemporaryDirectory() as temp_dir:
if ssh_key:
ssh_key_path = Path(temp_dir) / "id_rsa"
with ssh_key_path.open("w") as f:
f.write(ssh_key)
runner_kwargs["ssh_key"] = str(ssh_key_path.resolve())

if passwords:
runner_kwargs["passwords"] = orjson.loads(passwords)

loop = asyncio.get_running_loop()

def run():
return run_async(
private_data_dir=temp_dir,
playbook=playbook,
extravars=extra_vars,
**runner_kwargs,
)

_, result = await loop.run_in_executor(None, run)
if isinstance(result, Runner):
return list(result.events)
else:
raise ValueError("Ansible runner returned no result.")
_, result = await loop.run_in_executor(None, run)
if isinstance(result, Runner):
# Events are a generator, so we need to convert to a list
return list(result.events)
else:
raise ValueError("Ansible runner returned no result.")

0 comments on commit 1801f3f

Please sign in to comment.