diff --git a/README-idp.md b/README-idp.md new file mode 100644 index 000000000..7a5d2d737 --- /dev/null +++ b/README-idp.md @@ -0,0 +1,108 @@ +Idp module +============ + +Description +----------- + +The idp module allows to ensure presence and absence of idps. + +Features +-------- + +* Idp management + + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipaidp module. + + +Requirements +------------ + +**Controller** +* Ansible version: 2.8+ + +**Node** +* Supported FreeIPA version (see above) + + +Usage +===== + +Example inventory file + +```ini +[ipaserver] +ipaserver.test.local +``` + + +Example playbook to make sure idp "NAME" is present: + +```yaml +--- +- name: Playbook to manage IPA idp. + hosts: ipaserver + become: false + + tasks: + - ipaidp: + ipaadmin_password: SomeADMINpassword + name: NAME + # Add needed parameters here +``` + + +Example playbook to make sure idp "NAME" is absent: + +```yaml +--- +- name: Playbook to manage IPA idp. + hosts: ipaserver + become: false + + tasks: + - ipaidp: + ipaadmin_password: SomeADMINpassword + name: NAME + state: absent +``` + + +MORE EXAMPLE PLAYBOOKS HERE + + +Variables +--------- + +Variable | Description | Required +-------- | ----------- | -------- +`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no +`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no +`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no +`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to true. (bool) | false +`name` \| `cn` | The list of idp name strings. | yes +auth_uri \| ipaidpauthendpoint | OAuth 2.0 authorization endpoint string. | no +dev_auth_uri \| ipaidpdevauthendpoint | Device authorization endpoint string. | no +token_uri \| ipaidptokenendpoint | Token endpoint string. | no +userinfo_uri \| ipaidpuserinfoendpoint | User information endpoint string. | no +keys_uri \| ipaidpkeysendpoint | JWKS endpoint string. | no +issuer_url \| ipaidpissuerurl | The Identity Provider OIDC URL string. | no +client_id \| ipaidpclientid | OAuth 2.0 client identifier string. | no +secret \| ipaidpclientsecret | OAuth 2.0 client secret string. | no +scope \| ipaidpscope | OAuth 2.0 scope string. Multiple scopes separated by space. | no +idp_user_id \| ipaidpsub | Attribute string for user identity in OAuth 2.0 userinfo. | no +provider \| ipaidpprovider | Pre-defined template string. Choices: ["google","github","microsoft","okta","keycloak"] | no +organization \| ipaidporg | Organization ID string or Realm name for IdP provider templates. | no +base_url \| ipaidpbaseurl | Base URL string for IdP provider templates. | no +rename \| new_name | New name for the Identity Provider server object. Only with `state: renamed`. | no +delete_continue \| continue | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no +`state` | The state to ensure. It can be one of `present`, `absent`, `renamed`, default: `present`. | no + + +Authors +======= + +Thomas Woerner diff --git a/README.md b/README.md index 6bc009ebe..c288e41a9 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Features * Modules for hbacsvcgroup management * Modules for host management * Modules for hostgroup management +* Modules for idp management * Modules for idrange management * Modules for location management * Modules for netgroup management @@ -450,6 +451,7 @@ Modules in plugin/modules * [ipahbacsvcgroup](README-hbacsvcgroup.md) * [ipahost](README-host.md) * [ipahostgroup](README-hostgroup.md) +* [idp](README-idp.md) * [idrange](README-idrange.md) * [ipalocation](README-location.md) * [ipanetgroup](README-netgroup.md) diff --git a/playbooks/idp/idp-absent.yml b/playbooks/idp/idp-absent.yml new file mode 100644 index 000000000..e912c3cf1 --- /dev/null +++ b/playbooks/idp/idp-absent.yml @@ -0,0 +1,11 @@ +--- +- name: Idp absent example + hosts: ipaserver + become: no + + tasks: + - name: Ensure idp NAME is absent + ipaidp: + ipaadmin_password: SomeADMINpassword + name: NAME + state: absent diff --git a/playbooks/idp/idp-present.yml b/playbooks/idp/idp-present.yml new file mode 100644 index 000000000..0ff381202 --- /dev/null +++ b/playbooks/idp/idp-present.yml @@ -0,0 +1,11 @@ +--- +- name: Idp present example + hosts: ipaserver + become: no + + tasks: + - name: Ensure idp NAME is present + ipaidp: + ipaadmin_password: SomeADMINpassword + name: NAME + # Add needed parameters here diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py index 30e0dce80..12627f034 100644 --- a/plugins/module_utils/ansible_freeipa_module.py +++ b/plugins/module_utils/ansible_freeipa_module.py @@ -30,7 +30,7 @@ "kinit_password", "kinit_keytab", "run", "DN", "VERSION", "paths", "tasks", "get_credentials_if_valid", "Encoding", "DNSName", "getargspec", "certificate_loader", - "write_certificate_list", "boolean"] + "write_certificate_list", "boolean", "template_str"] import os # ansible-freeipa requires locale to be C, IPA requires utf-8. @@ -90,6 +90,7 @@ def getargspec(func): except ImportError: from ipapython.ipautil import kinit_password, kinit_keytab from ipapython.ipautil import run + from ipapython.ipautil import template_str from ipapython.dn import DN from ipapython.version import VERSION from ipaplatform.paths import paths diff --git a/plugins/modules/ipaidp.py b/plugins/modules/ipaidp.py new file mode 100644 index 000000000..298a2b59e --- /dev/null +++ b/plugins/modules/ipaidp.py @@ -0,0 +1,522 @@ +# -*- coding: utf-8 -*- + +# Authors: +# Thomas Woerner +# +# Copyright (C) 2023 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.0", + "supported_by": "community", + "status": ["preview"], +} + +DOCUMENTATION = """ +--- +module: ipaidp +short_description: Manage FreeIPA idp +description: Manage FreeIPA idp +extends_documentation_fragment: + - ipamodule_base_docs +# - ipamodule_base_docs.delete_continue +options: + name: + description: The list of idp name strings. + required: true + type: list + elements: str + aliases: ["cn"] + auth_uri: + description: OAuth 2.0 authorization endpoint + required: false + type: str + aliases: ["ipaidpauthendpoint"] + dev_auth_uri: + description: Device authorization endpoint + required: false + type: str + aliases: ["ipaidpdevauthendpoint"] + token_uri: + description: Token endpoint + required: false + type: str + aliases: ["ipaidptokenendpoint"] + userinfo_uri: + description: User information endpoint + required: false + type: str + aliases: ["ipaidpuserinfoendpoint"] + keys_uri: + description: JWKS endpoint + required: false + type: str + aliases: ["ipaidpkeysendpoint"] + issuer_url: + description: The Identity Provider OIDC URL + required: false + type: str + aliases: ["ipaidpissuerurl"] + client_id: + description: OAuth 2.0 client identifier + required: false + type: str + aliases: ["ipaidpclientid"] + secret: + description: OAuth 2.0 client secret + required: false + type: str + no_log: true + aliases: ["ipaidpclientsecret"] + scope: + description: OAuth 2.0 scope. Multiple scopes separated by space + required: false + type: str + aliases: ["ipaidpscope"] + idp_user_id: + description: Attribute for user identity in OAuth 2.0 userinfo + required: false + type: str + aliases: ["ipaidpsub"] + provider: + description: Pre-defined template + required: false + type: str + choices: ["google","github","microsoft","okta","keycloak"] + aliases: ["ipaidpprovider"] + organization: + description: Organization ID or Realm name for IdP provider templates + required: false + type: str + aliases: ["ipaidporg"] + base_url: + description: Base URL for IdP provider templates + required: false + type: str + aliases: ["ipaidpbaseurl"] + rename: + description: | + New name the Identity Provider server object. Only with state: renamed. + required: false + type: str + aliases: ["new_name"] + delete_continue: + description: + Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. + required: false + type: bool + aliases: ["continue"] + state: + description: The state to ensure. + choices: ["present", "absent", "renamed"] + default: present + type: str +author: + - Thomas Woerner (@t-woerner) +""" + +EXAMPLES = """ +# Ensure idp my-keycloak-idp is present +- ipaidp: + name: my-keycloak-idp + provider: keycloak + organization: main + base_url: keycloak.idm.example.com:8443/auth + client_id: my-client-id + +# Ensure idp my-keycloak-idp is absent +- ipaidp: + name: my-keycloak-idp + delete_continue: true + state: absent + +# Ensure idp my-google-idp is present +- ipaidp: + name: my-google-idp + auth_uri: https://accounts.google.com/o/oauth2/auth + dev_auth_uri: https://oauth2.googleapis.com/device/code + token_uri: https://oauth2.googleapis.com/token + userinfo_uri: https://openidconnect.googleapis.com/v1/userinfo + client_id: my-client-id + scope: "profile email" +""" + +RETURN = """ +""" + + +from ansible.module_utils.ansible_freeipa_module import \ + IPAAnsibleModule, compare_args_ipa, template_str +from ansible.module_utils import six +from copy import deepcopy +import string +from itertools import chain + +if six.PY3: + unicode = str + +# Copy from FreeIPA ipaserver/plugins/idp.py +idp_providers = { + 'google': { + 'ipaidpauthendpoint': + 'https://accounts.google.com/o/oauth2/auth', + 'ipaidpdevauthendpoint': + 'https://oauth2.googleapis.com/device/code', + 'ipaidptokenendpoint': + 'https://oauth2.googleapis.com/token', + 'ipaidpuserinfoendpoint': + 'https://openidconnect.googleapis.com/v1/userinfo', + 'ipaidpkeysendpoint': + 'https://www.googleapis.com/oauth2/v3/certs', + 'ipaidpscope': 'openid email', + 'ipaidpsub': 'email'}, + 'github': { + 'ipaidpauthendpoint': + 'https://github.com/login/oauth/authorize', + 'ipaidpdevauthendpoint': + 'https://github.com/login/device/code', + 'ipaidptokenendpoint': + 'https://github.com/login/oauth/access_token', + 'ipaidpuserinfoendpoint': + 'https://api.github.com/user', + 'ipaidpscope': 'user', + 'ipaidpsub': 'login'}, + 'microsoft': { + 'ipaidpauthendpoint': + 'https://login.microsoftonline.com/${ipaidporg}/oauth2/v2.0/' + 'authorize', + 'ipaidpdevauthendpoint': + 'https://login.microsoftonline.com/${ipaidporg}/oauth2/v2.0/' + 'devicecode', + 'ipaidptokenendpoint': + 'https://login.microsoftonline.com/${ipaidporg}/oauth2/v2.0/' + 'token', + 'ipaidpuserinfoendpoint': + 'https://graph.microsoft.com/oidc/userinfo', + 'ipaidpkeysendpoint': + 'https://login.microsoftonline.com/common/discovery/v2.0/keys', + 'ipaidpscope': 'openid email', + 'ipaidpsub': 'email', + }, + 'okta': { + 'ipaidpauthendpoint': + 'https://${ipaidpbaseurl}/oauth2/v1/authorize', + 'ipaidpdevauthendpoint': + 'https://${ipaidpbaseurl}/oauth2/v1/device/authorize', + 'ipaidptokenendpoint': + 'https://${ipaidpbaseurl}/oauth2/v1/token', + 'ipaidpuserinfoendpoint': + 'https://${ipaidpbaseurl}/oauth2/v1/userinfo', + 'ipaidpscope': 'openid email', + 'ipaidpsub': 'email'}, + 'keycloak': { + 'ipaidpauthendpoint': + 'https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/' + 'openid-connect/auth', + 'ipaidpdevauthendpoint': + 'https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/' + 'openid-connect/auth/device', + 'ipaidptokenendpoint': + 'https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/' + 'openid-connect/token', + 'ipaidpuserinfoendpoint': + 'https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/' + 'openid-connect/userinfo', + 'ipaidpscope': 'openid email', + 'ipaidpsub': 'email'}, +} + + +def find_idp(module, name): + """Find if a idp with the given name already exist.""" + try: + _result = module.ipa_command("idp_show", name, {"all": True}) + except Exception: # pylint: disable=broad-except + # An exception is raised if idp name is not found. + return None + else: + return _result["result"] + + +def gen_args(auth_uri, dev_auth_uri, token_uri, userinfo_uri, keys_uri, + issuer_url, client_id, secret, scope, idp_user_id, organization, + base_url): + _args = {} + if auth_uri is not None: + _args["ipaidpauthendpoint"] = auth_uri + if dev_auth_uri is not None: + _args["ipaidpdevauthendpoint"] = dev_auth_uri + if token_uri is not None: + _args["ipaidptokenendpoint"] = token_uri + if userinfo_uri is not None: + _args["ipaidpuserinfoendpoint"] = userinfo_uri + if keys_uri is not None: + _args["ipaidpkeysendpoint"] = keys_uri + if issuer_url is not None: + _args["ipaidpissuerurl"] = issuer_url + if client_id is not None: + _args["ipaidpclientid"] = client_id + if secret is not None: + _args["ipaidpclientsecret"] = secret + if scope is not None: + _args["ipaidpscope"] = scope + if idp_user_id is not None: + _args["ipaidpsub"] = idp_user_id + if organization is not None: + _args["ipaidporg"] = organization + if base_url is not None: + _args["ipaidpbaseurl"] = base_url + return _args + + +# Copied and adapted from FreeIPA ipaserver/plugins/idp.py +def convert_provider_to_endpoints(module, _args, provider): + """Convert provider option to auth-uri and token-uri,..""" + if provider not in idp_providers: + module.fail_json(msg="Provider '%s' is unknown" % provider) + + # For each string in the template check if a variable + # is required, it is provided as an option + points = deepcopy(idp_providers[provider]) + _r = string.Template.pattern + for (_k, _v) in points.items(): + # build list of variables to be replaced + subs = list(chain.from_iterable( + (filter(None, _s) for _s in _r.findall(_v)))) + if subs: + for _s in subs: + if _s not in _args: + module.fail_json(msg="Parameter '%s' is missing" % _s) + points[_k] = template_str(_v, _args) + _args.update(points) + + +def main(): + ansible_module = IPAAnsibleModule( + argument_spec=dict( + # general + name=dict(type="list", elements="str", required=True, + aliases=["cn"]), + # present + auth_uri=dict(required=False, type="str", default=None, + aliases=["ipaidpauthendpoint"]), + dev_auth_uri=dict(required=False, type="str", default=None, + aliases=["ipaidpdevauthendpoint"]), + token_uri=dict(required=False, type="str", default=None, + aliases=["ipaidptokenendpoint"]), + userinfo_uri=dict(required=False, type="str", default=None, + aliases=["ipaidpuserinfoendpoint"]), + keys_uri=dict(required=False, type="str", default=None, + aliases=["ipaidpkeysendpoint"]), + issuer_url=dict(required=False, type="str", default=None, + aliases=["ipaidpissuerurl"]), + client_id=dict(required=False, type="str", default=None, + aliases=["ipaidpclientid"]), + secret=dict(required=False, type="str", default=None, + aliases=["ipaidpclientsecret"], no_log=True), + scope=dict(required=False, type="str", default=None, + aliases=["ipaidpscope"]), + idp_user_id=dict(required=False, type="str", default=None, + aliases=["ipaidpsub"]), + provider=dict(required=False, type="str", default=None, + aliases=["ipaidpprovider"], + choices=["google", "github", "microsoft", "okta", + "keycloak"]), + organization=dict(required=False, type="str", default=None, + aliases=["ipaidporg"]), + base_url=dict(required=False, type="str", default=None, + aliases=["ipaidpbaseurl"]), + rename=dict(required=False, type="str", default=None, + aliases=["new_name"]), + delete_continue=dict(required=False, type="bool", default=None, + aliases=['continue']), + # state + state=dict(type="str", default="present", + choices=["present", "absent", "renamed"]), + ), + supports_check_mode=True, + # mutually_exclusive=[], + # required_one_of=[] + # ipa_module_options=["delete_continue"], + ) + + ansible_module._ansible_debug = True + + # Get parameters + + # general + names = ansible_module.params_get("name") + + # present + auth_uri = ansible_module.params_get("auth_uri") + dev_auth_uri = ansible_module.params_get("dev_auth_uri") + token_uri = ansible_module.params_get("token_uri") + userinfo_uri = ansible_module.params_get("userinfo_uri") + keys_uri = ansible_module.params_get("keys_uri") + issuer_url = ansible_module.params_get("issuer_url") + client_id = ansible_module.params_get("client_id") + secret = ansible_module.params_get("secret") + scope = ansible_module.params_get("scope") + idp_user_id = ansible_module.params_get("idp_user_id") + provider = ansible_module.params_get("provider") + organization = ansible_module.params_get("organization") + base_url = ansible_module.params_get("base_url") + rename = ansible_module.params_get("rename") + + delete_continue = ansible_module.params_get("delete_continue") + + # state + state = ansible_module.params_get("state") + + # Check parameters + + invalid = [] + + if state == "present": + if len(names) != 1: + ansible_module.fail_json( + msg="Only one idp can be added at a time.") + if provider: + if any([auth_uri, dev_auth_uri, token_uri, userinfo_uri, + keys_uri]): + ansible_module.fail_json( + msg="Cannot specify both individual endpoints and IdP " + "provider") + if provider not in idp_providers: + ansible_module.fail_json( + msg="Provider '%s' is unknown" % provider) + else: + if not auth_uri: + ansible_module.fail_json( + msg="auth_uri is required without provider") + if not dev_auth_uri: + ansible_module.fail_json( + msg="dev_auth_uri is required without provider") + if not token_uri: + ansible_module.fail_json( + msg="token_uri is required without provider") + if not userinfo_uri: + ansible_module.fail_json( + msg="userinfo_uri is required without provider") + invalid = ["rename", "delete_continue"] + + if state == "renamed": + if len(names) != 1: + ansible_module.fail_json( + msg="Only one permission can be renamed at a time.") + invalid = ["auth_uri", "dev_auth_uri", "token_uri", "userinfo_uri", + "keys_uri", "issuer_url", "client_id", "secret", "scope", + "idp_user_id", "provider", "organization", "base_url", + "delete_continue"] + + if state == "absent": + if len(names) < 1: + ansible_module.fail_json(msg="No name given.") + invalid = ["auth_uri", "dev_auth_uri", "token_uri", "userinfo_uri", + "keys_uri", "issuer_url", "client_id", "secret", "scope", + "idp_user_id", "provider", "organization", "base_url", + "rename"] + + ansible_module.params_fail_used_invalid(invalid, state) + + # Init + + changed = False + exit_args = {} + + # Connect to IPA API + with ansible_module.ipa_connect(): + + if not ansible_module.ipa_command_exists("idp_add"): + ansible_module.fail_json( + msg="Managing idp is not supported by your IPA version") + + commands = [] + for name in names: + # Make sure idp exists + res_find = find_idp(ansible_module, name) + + # Create command + if state == "present": + + # Generate args + args = gen_args(auth_uri, dev_auth_uri, token_uri, + userinfo_uri, keys_uri, issuer_url, client_id, + secret, scope, idp_user_id, organization, + base_url) + + if provider is not None: + convert_provider_to_endpoints(ansible_module, args, + provider) + + # Found the idp + if res_find is not None: + # The parameters ipaidpprovider, ipaidporg and + # ipaidpbaseurl are only available for idp-add to create + # then endpoints using provider, Therefore we have to + # remove them from args. + for arg in ["ipaidpprovider", "ipaidporg", + "ipaidpbaseurl"]: + if arg in args: + del args[arg] + + # For all settings is args, check if there are + # different settings in the find result. + # If yes: modify + if not compare_args_ipa(ansible_module, args, + res_find): + commands.append([name, "idp_mod", args]) + else: + commands.append([name, "idp_add", args]) + + elif state == "absent": + if res_find is not None: + _args = {} + if delete_continue is not None: + _args = {"continue": delete_continue} + commands.append([name, "idp_del", _args]) + + elif state == "renamed": + if not rename: + ansible_module.fail_json(msg="No rename value given.") + + if res_find is None: + ansible_module.fail_json( + msg="No idp found to be renamed: '%s'" % (name)) + + if name != rename: + commands.append( + [name, "idp_mod", {"rename": rename}]) + + else: + ansible_module.fail_json(msg="Unkown state '%s'" % state) + + # Execute commands + + ansible_module.warn("commands: %s" % repr(commands)) + changed = ansible_module.execute_ipa_commands(commands) + + # Done + + ansible_module.exit_json(changed=changed, **exit_args) + + +if __name__ == "__main__": + main() diff --git a/tests/idp/test_idp.yml b/tests/idp/test_idp.yml new file mode 100644 index 000000000..25bc3cb26 --- /dev/null +++ b/tests/idp/test_idp.yml @@ -0,0 +1,131 @@ +--- +- name: Test idp + hosts: "{{ ipa_test_host | default('ipaserver') }}" + # It is normally not needed to set "become" to "true" for a module test. + # Only set it to true if it is needed to execute commands as root. + become: false + # Enable "gather_facts" only if "ansible_facts" variable needs to be used. + gather_facts: false + module_defaults: + ipaidp: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + + tasks: + + # CHECK IF WE HAVE IDP SUPPORT + + - name: Verify if ipd management is supported + ansible.builtin.shell: + cmd: | + echo SomeADMINpassword | kinit -c {{ krb5ccname }} admin + RESULT=$(KRB5CCNAME={{ krb5ccname }} ipa command-show idp_add) + kdestroy -A -c {{ krb5ccname }} + echo $RESULT + vars: + krb5ccname: "__check_command_idp_add__" + register: check_command_idp_add + + - name: Run tests for idp + when: not "idp_add" in check_command_idp_add.stderr + block: + + # CLEANUP TEST ITEMS + + - name: Ensure idp my-keycloak-idp and my-github-idp are absent + ipaidp: + name: + - my-keycloak-idp + - my-github-idp + - my-google-idp + delete_continue: true + state: absent + + # CREATE TEST ITEMS + + # TESTS + + - name: Ensure idp my-keycloak-idp is present + ipaidp: + name: my-keycloak-idp + provider: keycloak + organization: main + base_url: keycloak.idm.example.com:8443/auth + client_id: my-client-id + register: result + failed_when: not result.changed or result.failed + + - name: Ensure idp my-keycloak-idp is present, again + ipaidp: + name: my-keycloak-idp + provider: keycloak + organization: main + base_url: keycloak.idm.example.com:8443/auth + client_id: my-client-id + register: result + failed_when: result.changed or result.failed + + - name: Ensure idp my-keycloak-idp is absent + ipaidp: + name: my-keycloak-idp + delete_continue: true + state: absent + + - name: Ensure idp my-keycloak-idp is failing with missing parameters + ipaidp: + name: my-keycloak-idp + provider: keycloak + client_id: my-client-id + register: result + failed_when: result.changed or not result.failed + + - name: Ensure idp my-github-idp is present + ipaidp: + name: my-github-idp + provider: github + client_id: my-github-client-id + register: result + failed_when: not result.changed or result.failed + + - name: Ensure idp my-github-idp is present, again + ipaidp: + name: my-github-idp + provider: github + client_id: my-github-client-id + register: result + failed_when: result.changed or result.failed + + - name: Ensure idp my-google-idp is present + ipaidp: + name: my-google-idp + auth_uri: https://accounts.google.com/o/oauth2/auth + dev_auth_uri: https://oauth2.googleapis.com/device/code + token_uri: https://oauth2.googleapis.com/token + userinfo_uri: https://openidconnect.googleapis.com/v1/userinfo + client_id: my-google-client-id + scope: "profile email" + register: result + failed_when: not result.changed or result.failed + + - name: Ensure idp my-google-idp is present, again + ipaidp: + name: my-google-idp + auth_uri: https://accounts.google.com/o/oauth2/auth + dev_auth_uri: https://oauth2.googleapis.com/device/code + token_uri: https://oauth2.googleapis.com/token + userinfo_uri: https://openidconnect.googleapis.com/v1/userinfo + client_id: my-google-client-id + scope: "profile email" + register: result + failed_when: result.changed or result.failed + + # CLEANUP TEST ITEMS + + - name: Ensure idp my-keycloak-idp and my-github-idp are absent + ipaidp: + name: + - my-keycloak-idp + - my-github-idp + - my-google-idp + delete_continue: true + state: absent diff --git a/tests/idp/test_idp_client_context.yml b/tests/idp/test_idp_client_context.yml new file mode 100644 index 000000000..991058171 --- /dev/null +++ b/tests/idp/test_idp_client_context.yml @@ -0,0 +1,40 @@ +--- +- name: Test idp + hosts: ipaclients, ipaserver + # It is normally not needed to set "become" to "true" for a module test. + # Only set it to true if it is needed to execute commands as root. + become: false + # Enable "gather_facts" only if "ansible_facts" variable needs to be used. + gather_facts: false + + tasks: + - name: Include FreeIPA facts. + include_tasks: ../env_freeipa_facts.yml + + # Test will only be executed if host is not a server. + - name: Execute with server context in the client. + ipaidp: + ipaadmin_password: SomeADMINpassword + ipaapi_context: server + name: ThisShouldNotWork + register: result + failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*")) + when: ipa_host_is_client + +# Import basic module tests, and execute with ipa_context set to 'client'. +# If ipaclients is set, it will be executed using the client, if not, +# ipaserver will be used. +# +# With this setup, tests can be executed against an IPA client, against +# an IPA server using "client" context, and ensure that tests are executed +# in upstream CI. + +- name: Test idp using client context, in client host. + import_playbook: test_idp.yml + when: groups['ipaclients'] + vars: + ipa_test_host: ipaclients + +- name: Test idp using client context, in server host. + import_playbook: test_idp.yml + when: groups['ipaclients'] is not defined or not groups['ipaclients']