diff --git a/README.md b/README.md index 952777a..a76441a 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,43 @@ be manually installed using pip: ## Using this collection -You can either call modules by their Fully Qualified Collection Name (FQCN), such as `infisical.vault.read_secrets`, or you can call modules by their short name if you list the `infisical.vault` collection in the playbook's `collections` keyword: +You can either call modules by their Fully Qualified Collection Name (FQCN), such as `infisical.vault.read_secrets`, or you can call modules by their short name if you list the `infisical.vault` collection in the playbook's `collections` keyword. + +### Authentication + +The Infisical Ansible Collection supports Universal Auth and OIDC for authenticating against Infisical. + +#### Universal Auth +Using Universal Auth for authentication is the most straight-forward way to get started with using the Ansible collection. + +To use Universal Auth, you need to provide the Client ID and Client Secret of your Infisical Machine Identity. + +```yaml +lookup('infisical.vault.read_secrets', auth_method="universal-auth" universal_auth_client_id='', universal_auth_client_secret='' ...rest) +``` + +You can also provide the `auth_method`, `universal_auth_client_id`, and `universal_auth_client_secret` parameters through environment variables: + +| Parameter Name | Environment Variable Name | +| ---------------------------- | ---------------------------------------- | +| auth_method | `INFISICAL_AUTH_METHOD` | +| universal_auth_client_id | `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` | +| universal_auth_client_secret | `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` | + + +#### OIDC Auth +To use OIDC Auth, you'll need to provide the ID of your machine identity, and the OIDC JWT to be used for authentication. + +```yaml +lookup('infisical.vault.read_secrets', auth_method="oidc-auth" identity_id='', jwt='' ...rest) +``` +You can also provide the `auth_method`, `identity_id`, and `jwt` parameters through environment variables: + +| Parameter Name | Environment Variable Name | +| --------------- | ------------------------- | +| auth_method | `INFISICAL_AUTH_METHOD` | +| identity_id | `INFISICAL_IDENTITY_ID` | +| jwt | `INFISICAL_JWT` | ```yaml --- diff --git a/plugins/lookup/read_secrets.py b/plugins/lookup/read_secrets.py index 080c26b..025fe6c 100644 --- a/plugins/lookup/read_secrets.py +++ b/plugins/lookup/read_secrets.py @@ -2,12 +2,29 @@ from ansible.errors import AnsibleError from ansible.plugins.lookup import LookupBase HAS_INFISICAL = False +INFISICAL_VERSION = None + + try: from infisical_sdk import InfisicalSDKClient HAS_INFISICAL = True + except ImportError as e: HAS_INFISICAL = False + +if HAS_INFISICAL: + try: + from importlib.metadata import version + INFISICAL_VERSION = version('infisicalsdk') # Note: package name might differ + except ImportError: + # Fallback for Python < 3.8 + import pkg_resources + INFISICAL_VERSION = pkg_resources.get_distribution('infisicalsdk').version + except Exception: + INFISICAL_VERSION = "unknown" + + DOCUMENTATION = r""" name: read_secrets author: @@ -19,12 +36,24 @@ description: - Secrets can be located either by their name for individual secret loopups or by environment/folder path to return all secrets within the given scope. options: + + auth_method: + description: The method to use to authenticate with Infisical + required: False + type: string + version_added: 1.1.3 + default: universal_auth + choices: + - universal_auth + - oidc_auth + env: + - name: INFISICAL_AUTH_METHOD universal_auth_client_id: description: The Machine Identity Client ID used to authenticate env: - name: UNIVERSAL_AUTH_MACHINE_IDENTITY_CLIENT_ID - name: INFISICAL_UNIVERSAL_AUTH_CLIENT_ID - required: True + required: False type: string version_added: 1.0.0 universal_auth_client_secret: @@ -32,7 +61,7 @@ options: env: - name: UNIVERSAL_AUTH_MACHINE_IDENTITY_CLIENT_SECRET - name: INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET - required: True + required: False type: string version_added: 1.0.0 url: @@ -68,6 +97,21 @@ options: required: False type: bool version_added: 1.0.0 + identity_id: + description: The identity ID of the user that should be authenticated + env: + - name: INFISICAL_MACHINE_IDENTITY_ID + required: False + type: string + version_added: 1.1.3 + jwt: + description: The JWT of the user that should be authenticated + required: False + type: string + version_added: 1.1.3 + env: + - name: INFISICAL_JWT + - name: INFISICAL_OIDC_AUTH_JWT """ EXAMPLES = r""" @@ -83,27 +127,80 @@ vars: """ + +def parse_version_tuple(version_string): + if version_string == "unknown": + return (0, 0, 0) # assume very old version + + try: + parts = version_string.split('.') + # haandle missing parts (example: "1.2" becomes "1.2.0") + while len(parts) < 3: + parts.append('0') + + return tuple(int(part) for part in parts[:3]) # only take first 3 parts + except (ValueError, AttributeError): + return (0, 0, 0) + + +def check_minimum_version(current_version, minimum_version): + """Check if current version meets minimum requirement.""" + current_tuple = parse_version_tuple(current_version) + minimum_tuple = parse_version_tuple(minimum_version) + return current_tuple >= minimum_tuple + + class LookupModule(LookupBase): + + def get_sdk_client(self): + url = self.get_option("url") + client = InfisicalSDKClient(host=url) + + method = self.get_option("auth_method") + + if method == "universal_auth": + + machine_identity_client_id = self.get_option("universal_auth_client_id") + machine_identity_client_secret = self.get_option("universal_auth_client_secret") + + if not machine_identity_client_id or not machine_identity_client_secret: + raise AnsibleError("universal_auth_client_id or universal_auth_client_secret is not set. Please set them to use universal auth.") + + client.auth.universal_auth.login( + machine_identity_client_id, + machine_identity_client_secret + ) + + elif method == "oidc_auth": + + # make sure the infisicalsdk version is at least 1.0.10 + if not check_minimum_version(INFISICAL_VERSION, "1.0.10"): + raise AnsibleError("Please upgrade the infisicalsdk to at least 1.0.10 to use oidc auth.") + + identity_id = self.get_option("identity_id") + jwt = self.get_option("jwt") + + if not identity_id or not jwt: + raise AnsibleError("identity_id or jwt is not set. Please set them to use oidc auth.") + + client.auth.oidc_auth.login( + identity_id, + jwt + ) + else: + raise AnsibleError(f"Invalid auth method. Please use universal_auth or oidc_auth. You provided {method}") + + return client + + + def run(self, terms, variables=None, **kwargs): self.set_options(var_options=variables, direct=kwargs) if not HAS_INFISICAL: raise AnsibleError("Please pip install infisicalsdk to use the infisical_vault lookup module.") - machine_identity_client_id = self.get_option("universal_auth_client_id") - machine_identity_client_secret = self.get_option("universal_auth_client_secret") - url = self.get_option("url") - - # Check if the required environment variables are set - if not machine_identity_client_id or not machine_identity_client_secret: - raise AnsibleError("Please provide the universal_auth_client_id and universal_auth_client_secret") - - client = InfisicalSDKClient(host=url) - - client.auth.universal_auth.login( - machine_identity_client_id, - machine_identity_client_secret - ) + client = self.get_sdk_client() secretName = kwargs.get('secret_name') asDict = kwargs.get('as_dict')