Dynamic inventory plugin — queries Bastion for managed devices, groups by OS and compliance state, bastion_* host vars, zero credentials in inventory. Credential lookup plugin — resolves short-lived credentials from Bastion's CredentialResolver at execution time. Graceful degradation when broker unavailable. Chronicle callback plugin — reports playbook lifecycle events (started, task completed, completed) to Chronicle. Optionally triggers compliance re-evaluation after playbook completion. Shared BastionClient for all plugins using stdlib urllib. Signed-off-by: Tyler King <tking@guildhouse.dev>
86 lines
2.9 KiB
Python
86 lines
2.9 KiB
Python
# Copyright 2026 Guildhouse Dev
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""Bastion credential lookup plugin for Ansible.
|
|
|
|
Resolves short-lived credentials from Bastion's CredentialResolver
|
|
at playbook execution time. Replaces static credentials in
|
|
Ansible Vault with dynamic, scoped, time-limited credentials.
|
|
|
|
SECURITY: Credentials are resolved per-host at execution time.
|
|
They are NOT cached, NOT written to facts, NOT stored in any
|
|
persistent structure. Ansible holds them only for the duration
|
|
of the connection to that specific host.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
DOCUMENTATION = """
|
|
name: guildhouse.bastion.credential
|
|
short_description: Resolve credentials from Bastion
|
|
description:
|
|
- Acquires short-lived credentials from Bastion CredentialResolver
|
|
- Zero credential storage -- resolved at execution time only
|
|
options:
|
|
host:
|
|
description: Target hostname to resolve credential for
|
|
required: true
|
|
type:
|
|
description: Credential type (kerberos, oauth, ssh_cert, auto)
|
|
required: false
|
|
default: auto
|
|
ac_id:
|
|
description: Authorization Context ID for governed resolution
|
|
required: false
|
|
env:
|
|
- name: BASTION_AC_ID
|
|
"""
|
|
|
|
# OS type to credential type mapping for auto-detection
|
|
_AUTO_TYPE_MAP = {
|
|
"windows": "kerberos",
|
|
"linux": "ssh_cert",
|
|
"macos": "ssh_cert",
|
|
}
|
|
|
|
|
|
def resolve_credential(host, credential_type="auto", ac_id=None, host_vars=None):
|
|
"""Resolve a credential for the specified host.
|
|
|
|
1. Determine credential type (auto-detect from bastion_os_type
|
|
host var, or use explicit type)
|
|
2. Call Bastion broker's credential resolution endpoint
|
|
3. Return the credential value
|
|
|
|
NOTE: Until the broker exposes a /credentials/resolve HTTP
|
|
endpoint, this returns None with a warning, allowing Ansible
|
|
to fall back to its own credential resolution.
|
|
"""
|
|
if credential_type == "auto" and host_vars:
|
|
os_type = host_vars.get("bastion_os_type", "").lower()
|
|
credential_type = _AUTO_TYPE_MAP.get(os_type, "oauth")
|
|
|
|
# Import here to avoid hard dependency on bastion_client at
|
|
# module load time (Ansible loads all lookup plugins eagerly).
|
|
try:
|
|
from ansible_collection.guildhouse.bastion.plugins.module_utils.bastion_client import BastionClient
|
|
client = BastionClient()
|
|
result = client.resolve_credential(credential_type, host, ac_id)
|
|
if result is not None:
|
|
return result
|
|
except ImportError:
|
|
pass
|
|
except Exception as e:
|
|
logger.warning("Bastion credential resolution failed for %s: %s", host, e)
|
|
|
|
logger.debug(
|
|
"Bastion credential resolution not available for %s "
|
|
"(type=%s). Falling back to Ansible credential resolution.",
|
|
host, credential_type,
|
|
)
|
|
return None
|