# 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