mirror of
https://github.com/openshift/community.okd.git
synced 2026-03-26 19:03:14 +00:00
openshift adm group sync/prune (#125)
This commit is contained in:
@@ -14,6 +14,8 @@ RUN yum install -y \
|
||||
python3-devel \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
gcc \
|
||||
openldap-devel \
|
||||
&& pip3 install --no-cache-dir --upgrade setuptools pip \
|
||||
&& pip3 install --no-cache-dir \
|
||||
kubernetes \
|
||||
|
||||
@@ -7,3 +7,5 @@ plugin_routing:
|
||||
action:
|
||||
k8s:
|
||||
redirect: kubernetes.core.k8s_info
|
||||
openshift_adm_groups_sync:
|
||||
redirect: kubernetes.core.k8s_info
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
name:
|
||||
- kubernetes>=12.0.0
|
||||
- coverage
|
||||
- python-ldap
|
||||
virtualenv: "{{ virtualenv }}"
|
||||
virtualenv_command: "{{ virtualenv_command }}"
|
||||
virtualenv_site_packages: no
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
ldap_admin_user: "admin"
|
||||
ldap_admin_password: "testing123!"
|
||||
ldap_root: "dc=ansible,dc=redhat"
|
||||
@@ -0,0 +1,186 @@
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
module: openshift_ldap_entry
|
||||
|
||||
short_description: add/remove entry to LDAP Server.
|
||||
|
||||
author:
|
||||
- Aubin Bikouo (@abikouo)
|
||||
|
||||
description:
|
||||
- This module perform basic operations on the LDAP Server (add/remove entries).
|
||||
- Similar to `community.general.ldap_entry` this has been created to avoid dependency with this collection for the test.
|
||||
- This module is not supported outside of testing this collection.
|
||||
|
||||
options:
|
||||
attributes:
|
||||
description:
|
||||
- If I(state=present), attributes necessary to create an entry. Existing
|
||||
entries are never modified. To assert specific attribute values on an
|
||||
existing entry, use M(community.general.ldap_attrs) module instead.
|
||||
type: dict
|
||||
objectClass:
|
||||
description:
|
||||
- If I(state=present), value or list of values to use when creating
|
||||
the entry. It can either be a string or an actual list of
|
||||
strings.
|
||||
type: list
|
||||
elements: str
|
||||
state:
|
||||
description:
|
||||
- The target state of the entry.
|
||||
choices: [present, absent]
|
||||
default: present
|
||||
type: str
|
||||
bind_dn:
|
||||
description:
|
||||
- A DN to bind with. If this is omitted, we'll try a SASL bind with the EXTERNAL mechanism as default.
|
||||
- If this is blank, we'll use an anonymous bind.
|
||||
type: str
|
||||
required: true
|
||||
bind_pw:
|
||||
description:
|
||||
- The password to use with I(bind_dn).
|
||||
type: str
|
||||
dn:
|
||||
required: true
|
||||
description:
|
||||
- The DN of the entry to add or remove.
|
||||
type: str
|
||||
server_uri:
|
||||
description:
|
||||
- A URI to the LDAP server.
|
||||
- The default value lets the underlying LDAP client library look for a UNIX domain socket in its default location.
|
||||
type: str
|
||||
default: ldapi:///
|
||||
|
||||
requirements:
|
||||
- python-ldap
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
'''
|
||||
|
||||
|
||||
RETURN = r'''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.common.text.converters import to_native, to_bytes
|
||||
|
||||
LDAP_IMP_ERR = None
|
||||
try:
|
||||
import ldap
|
||||
import ldap.modlist
|
||||
|
||||
HAS_LDAP = True
|
||||
except ImportError:
|
||||
LDAP_IMP_ERR = traceback.format_exc()
|
||||
HAS_LDAP = False
|
||||
|
||||
|
||||
def argument_spec():
|
||||
args = {}
|
||||
args['attributes'] = dict(default={}, type='dict')
|
||||
args['objectClass'] = dict(type='list', elements='str')
|
||||
args['state'] = dict(default='present', choices=['present', 'absent'])
|
||||
args['bind_dn'] = dict(required=True)
|
||||
args['bind_pw'] = dict(default='', no_log=True)
|
||||
args['dn'] = dict(required=True)
|
||||
args['server_uri'] = dict(default='ldapi:///')
|
||||
return args
|
||||
|
||||
|
||||
class LdapEntry(AnsibleModule):
|
||||
def __init__(self):
|
||||
|
||||
AnsibleModule.__init__(
|
||||
self,
|
||||
argument_spec=argument_spec(),
|
||||
required_if=[('state', 'present', ['objectClass'])],
|
||||
)
|
||||
|
||||
if not HAS_LDAP:
|
||||
self.fail_json(msg=missing_required_lib('python-ldap'), exception=LDAP_IMP_ERR)
|
||||
|
||||
self.__connection = None
|
||||
# Add the objectClass into the list of attributes
|
||||
self.params['attributes']['objectClass'] = (self.params['objectClass'])
|
||||
|
||||
# Load attributes
|
||||
if self.params['state'] == 'present':
|
||||
self.attrs = {}
|
||||
for name, value in self.params['attributes'].items():
|
||||
if isinstance(value, list):
|
||||
self.attrs[name] = list(map(to_bytes, value))
|
||||
else:
|
||||
self.attrs[name] = [to_bytes(value)]
|
||||
|
||||
@property
|
||||
def connection(self):
|
||||
if not self.__connection:
|
||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||
self.__connection = ldap.initialize(self.params['server_uri'])
|
||||
try:
|
||||
self.__connection.simple_bind_s(self.params['bind_dn'], self.params['bind_pw'])
|
||||
except ldap.LDAPError as e:
|
||||
self.fail_json(msg="Cannot bind to the server due to: %s" % e)
|
||||
return self.__connection
|
||||
|
||||
def add(self):
|
||||
""" If self.dn does not exist, returns a callable that will add it. """
|
||||
changed = False
|
||||
msg = "LDAP Entry '%s' already exist." % self.params["dn"]
|
||||
if not self._is_entry_present():
|
||||
modlist = ldap.modlist.addModlist(self.attrs)
|
||||
self.connection.add_s(self.params['dn'], modlist)
|
||||
changed = True
|
||||
msg = "LDAP Entry '%s' successfully created." % self.params["dn"]
|
||||
self.exit_json(changed=changed, msg=msg)
|
||||
|
||||
def delete(self):
|
||||
""" If self.dn exists, returns a callable that will delete it. """
|
||||
changed = False
|
||||
msg = "LDAP Entry '%s' does not exist." % self.params["dn"]
|
||||
if self._is_entry_present():
|
||||
self.connection.delete_s(self.params['dn'])
|
||||
changed = True
|
||||
msg = "LDAP Entry '%s' successfully deleted." % self.params["dn"]
|
||||
self.exit_json(changed=changed, msg=msg)
|
||||
|
||||
def _is_entry_present(self):
|
||||
try:
|
||||
self.connection.search_s(self.params['dn'], ldap.SCOPE_BASE)
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
is_present = False
|
||||
else:
|
||||
is_present = True
|
||||
|
||||
return is_present
|
||||
|
||||
def execute(self):
|
||||
try:
|
||||
if self.params['state'] == 'present':
|
||||
self.add()
|
||||
else:
|
||||
self.delete()
|
||||
except Exception as e:
|
||||
self.fail_json(msg="Entry action failed.", details=to_native(e), exception=traceback.format_exc())
|
||||
|
||||
|
||||
def main():
|
||||
module = LdapEntry()
|
||||
module.execute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,109 @@
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
module: openshift_ldap_entry_info
|
||||
|
||||
short_description: Validate entry from LDAP server.
|
||||
|
||||
author:
|
||||
- Aubin Bikouo (@abikouo)
|
||||
|
||||
description:
|
||||
- This module connect to a ldap server and search for entry.
|
||||
- This module is not supported outside of testing this collection.
|
||||
|
||||
options:
|
||||
bind_dn:
|
||||
description:
|
||||
- A DN to bind with. If this is omitted, we'll try a SASL bind with the EXTERNAL mechanism as default.
|
||||
- If this is blank, we'll use an anonymous bind.
|
||||
type: str
|
||||
required: true
|
||||
bind_pw:
|
||||
description:
|
||||
- The password to use with I(bind_dn).
|
||||
type: str
|
||||
required: True
|
||||
dn:
|
||||
description:
|
||||
- The DN of the entry to test.
|
||||
type: str
|
||||
required: True
|
||||
server_uri:
|
||||
description:
|
||||
- A URI to the LDAP server.
|
||||
- The default value lets the underlying LDAP client library look for a UNIX domain socket in its default location.
|
||||
type: str
|
||||
default: ldapi:///
|
||||
required: True
|
||||
|
||||
requirements:
|
||||
- python-ldap
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
'''
|
||||
|
||||
|
||||
RETURN = r'''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
LDAP_IMP_ERR = None
|
||||
try:
|
||||
import ldap
|
||||
import ldap.modlist
|
||||
HAS_LDAP = True
|
||||
except ImportError:
|
||||
LDAP_IMP_ERR = traceback.format_exc()
|
||||
HAS_LDAP = False
|
||||
|
||||
|
||||
def argument_spec():
|
||||
args = {}
|
||||
args['bind_dn'] = dict(required=True)
|
||||
args['bind_pw'] = dict(required=True, no_log=True)
|
||||
args['dn'] = dict(required=True)
|
||||
args['server_uri'] = dict(required=True)
|
||||
return args
|
||||
|
||||
|
||||
def execute():
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_LDAP:
|
||||
module.fail_json(msg=missing_required_lib("python-ldap"), exception=LDAP_IMP_ERR)
|
||||
|
||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||
connection = ldap.initialize(module.params['server_uri'])
|
||||
try:
|
||||
connection.simple_bind_s(module.params['bind_dn'], module.params['bind_pw'])
|
||||
except ldap.LDAPError as e:
|
||||
module.fail_json(msg="Cannot bind to the server due to: %s" % e)
|
||||
|
||||
try:
|
||||
connection.search_s(module.params['dn'], ldap.SCOPE_BASE)
|
||||
module.exit_json(changed=False, found=True)
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
module.exit_json(changed=False, found=False)
|
||||
|
||||
|
||||
def main():
|
||||
execute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
collections:
|
||||
- community.okd
|
||||
- kubernetes.core
|
||||
@@ -0,0 +1,235 @@
|
||||
- block:
|
||||
- name: Get LDAP definition
|
||||
set_fact:
|
||||
ldap_entries: "{{ lookup('template', 'ad/definition.j2') | from_yaml }}"
|
||||
|
||||
- name: Delete openshift groups if existing
|
||||
community.okd.k8s:
|
||||
state: absent
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: "{{ item }}"
|
||||
with_items:
|
||||
- admins
|
||||
- developers
|
||||
|
||||
- name: Delete existing LDAP Entries
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ item.dn }}"
|
||||
state: absent
|
||||
with_items: "{{ ldap_entries.users + ldap_entries.units | reverse | list }}"
|
||||
|
||||
- name: Create LDAP Entries
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ item.dn }}"
|
||||
attributes: "{{ item.attr }}"
|
||||
objectClass: "{{ item.class }}"
|
||||
with_items: "{{ ldap_entries.units + ldap_entries.users }}"
|
||||
|
||||
- name: Load test configurations
|
||||
set_fact:
|
||||
sync_config: "{{ lookup('template', 'ad/sync-config.j2') | from_yaml }}"
|
||||
|
||||
- name: Synchronize Groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ sync_config }}"
|
||||
check_mode: yes
|
||||
register: result
|
||||
|
||||
- name: Validate Group going to be created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- admins_group
|
||||
- devs_group
|
||||
- '"jane.smith@ansible.org" in {{ admins_group.users }}'
|
||||
- '"jim.adams@ansible.org" in {{ admins_group.users }}'
|
||||
- '"jordanbulls@ansible.org" in {{ devs_group.users }}'
|
||||
- admins_group.users | length == 2
|
||||
- devs_group.users | length == 1
|
||||
vars:
|
||||
admins_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'admins') | first }}"
|
||||
devs_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'developers') | first }}"
|
||||
|
||||
|
||||
- name: Synchronize Groups (Remove check_mode)
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ sync_config }}"
|
||||
register: result
|
||||
|
||||
- name: Validate Group going to be created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Read admins group
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: admins
|
||||
register: result
|
||||
|
||||
- name: Validate group was created
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
- '"jane.smith@ansible.org" in {{ result.resources.0.users }}'
|
||||
- '"jim.adams@ansible.org" in {{ result.resources.0.users }}'
|
||||
|
||||
- name: Read developers group
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: developers
|
||||
register: result
|
||||
|
||||
- name: Validate group was created
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
- '"jordanbulls@ansible.org" in {{ result.resources.0.users }}'
|
||||
|
||||
- name: Define user dn to delete
|
||||
set_fact:
|
||||
user_to_delete: "cn=Jane,ou=engineers,ou=activeD,{{ ldap_root }}"
|
||||
|
||||
- name: Delete 1 admin user
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ user_to_delete }}"
|
||||
state: absent
|
||||
|
||||
- name: Synchronize Openshift groups using allow_groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ sync_config }}"
|
||||
allow_groups:
|
||||
- developers
|
||||
type: openshift
|
||||
register: openshift_sync
|
||||
|
||||
- name: Validate that only developers group was sync
|
||||
assert:
|
||||
that:
|
||||
- openshift_sync is changed
|
||||
- openshift_sync.groups | length == 1
|
||||
- openshift_sync.groups.0.metadata.name == "developers"
|
||||
|
||||
- name: Read admins group
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: admins
|
||||
register: result
|
||||
|
||||
- name: Validate admins group content has not changed
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
- '"jane.smith@ansible.org" in {{ result.resources.0.users }}'
|
||||
- '"jim.adams@ansible.org" in {{ result.resources.0.users }}'
|
||||
|
||||
- name: Synchronize Openshift groups using deny_groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ sync_config }}"
|
||||
deny_groups:
|
||||
- developers
|
||||
type: openshift
|
||||
register: openshift_sync
|
||||
|
||||
- name: Validate that only admins group was sync
|
||||
assert:
|
||||
that:
|
||||
- openshift_sync is changed
|
||||
- openshift_sync.groups | length == 1
|
||||
- openshift_sync.groups.0.metadata.name == "admins"
|
||||
|
||||
- name: Read admins group
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: admins
|
||||
register: result
|
||||
|
||||
- name: Validate admins group contains only 1 user now
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
- result.resources.0.users == ["jim.adams@ansible.org"]
|
||||
|
||||
- name: Set users to delete (delete all developers users)
|
||||
set_fact:
|
||||
user_to_delete: "cn=Jordan,ou=engineers,ou=activeD,{{ ldap_root }}"
|
||||
|
||||
- name: Delete 1 admin user
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ user_to_delete }}"
|
||||
state: absent
|
||||
|
||||
- name: Prune groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ sync_config }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Validate result is changed (only developers group be deleted)
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.groups | length == 1
|
||||
|
||||
- name: Get developers group info
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: developers
|
||||
register: result
|
||||
|
||||
- name: assert group was deleted
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 0
|
||||
|
||||
- name: Get admins group info
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: admins
|
||||
register: result
|
||||
|
||||
- name: assert group was not deleted
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
|
||||
- name: Prune groups once again (idempotency)
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ sync_config }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert nothing was changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
always:
|
||||
- name: Delete openshift groups if existing
|
||||
community.okd.k8s:
|
||||
state: absent
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: "{{ item }}"
|
||||
with_items:
|
||||
- admins
|
||||
- developers
|
||||
@@ -0,0 +1,174 @@
|
||||
- block:
|
||||
- name: Get LDAP definition
|
||||
set_fact:
|
||||
ldap_entries: "{{ lookup('template', 'augmented-ad/definition.j2') | from_yaml }}"
|
||||
|
||||
- name: Delete openshift groups if existing
|
||||
community.okd.k8s:
|
||||
state: absent
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: "{{ item }}"
|
||||
with_items:
|
||||
- banking
|
||||
- insurance
|
||||
|
||||
- name: Delete existing LDAP entries
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ item.dn }}"
|
||||
state: absent
|
||||
with_items: "{{ ldap_entries.users + ldap_entries.groups + ldap_entries.units | reverse | list }}"
|
||||
|
||||
- name: Create LDAP Entries
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ item.dn }}"
|
||||
attributes: "{{ item.attr }}"
|
||||
objectClass: "{{ item.class }}"
|
||||
with_items: "{{ ldap_entries.units + ldap_entries.groups + ldap_entries.users }}"
|
||||
|
||||
- name: Load test configurations
|
||||
set_fact:
|
||||
sync_config: "{{ lookup('template', 'augmented-ad/sync-config.j2') | from_yaml }}"
|
||||
|
||||
- name: Synchronize Groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ sync_config }}"
|
||||
check_mode: yes
|
||||
register: result
|
||||
|
||||
- name: Validate that 'banking' and 'insurance' groups were created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- banking_group
|
||||
- insurance_group
|
||||
- '"james-allan@ansible.org" in {{ banking_group.users }}'
|
||||
- '"gordon-kane@ansible.org" in {{ banking_group.users }}'
|
||||
- '"alice-courtney@ansible.org" in {{ insurance_group.users }}'
|
||||
- banking_group.users | length == 2
|
||||
- insurance_group.users | length == 1
|
||||
vars:
|
||||
banking_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'banking') | first }}"
|
||||
insurance_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'insurance') | first }}"
|
||||
|
||||
|
||||
- name: Synchronize Groups (Remove check_mode)
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ sync_config }}"
|
||||
register: result
|
||||
|
||||
- name: Validate Group going to be created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Define facts for group to create
|
||||
set_fact:
|
||||
ldap_groups:
|
||||
- name: banking
|
||||
users:
|
||||
- "james-allan@ansible.org"
|
||||
- "gordon-kane@ansible.org"
|
||||
- name: insurance
|
||||
users:
|
||||
- "alice-courtney@ansible.org"
|
||||
|
||||
|
||||
- name: Read 'banking' openshift group
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: banking
|
||||
register: result
|
||||
|
||||
- name: Validate group info
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
- '"james-allan@ansible.org" in {{ result.resources.0.users }}'
|
||||
- '"gordon-kane@ansible.org" in {{ result.resources.0.users }}'
|
||||
|
||||
- name: Read 'insurance' openshift group
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: insurance
|
||||
register: result
|
||||
|
||||
- name: Validate group info
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
- 'result.resources.0.users == ["alice-courtney@ansible.org"]'
|
||||
|
||||
- name: Delete employee from 'insurance' group
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "cn=Alice,ou=employee,ou=augmentedAD,{{ ldap_root }}"
|
||||
state: absent
|
||||
|
||||
- name: Prune groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ sync_config }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Validate result is changed (only insurance group be deleted)
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.groups | length == 1
|
||||
|
||||
- name: Get 'insurance' openshift group info
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: insurance
|
||||
register: result
|
||||
|
||||
- name: assert group was deleted
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 0
|
||||
|
||||
- name: Get 'banking' openshift group info
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: banking
|
||||
register: result
|
||||
|
||||
- name: assert group was not deleted
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
|
||||
- name: Prune groups once again (idempotency)
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ sync_config }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert no change was made
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
always:
|
||||
- name: Delete openshift groups if existing
|
||||
community.okd.k8s:
|
||||
state: absent
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: "{{ item }}"
|
||||
with_items:
|
||||
- banking
|
||||
- insurance
|
||||
61
molecule/default/roles/openshift_adm_groups/tasks/main.yml
Normal file
61
molecule/default/roles/openshift_adm_groups/tasks/main.yml
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
- name: Get cluster information
|
||||
kubernetes.core.k8s_cluster_info:
|
||||
register: info
|
||||
|
||||
- name: Create LDAP Pod
|
||||
community.okd.k8s:
|
||||
namespace: "default"
|
||||
wait: yes
|
||||
definition:
|
||||
kind: Pod
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: ldap-pod
|
||||
labels:
|
||||
app: ldap
|
||||
spec:
|
||||
containers:
|
||||
- name: ldap
|
||||
image: bitnami/openldap
|
||||
env:
|
||||
- name: LDAP_ADMIN_USERNAME
|
||||
value: "{{ ldap_admin_user }}"
|
||||
- name: LDAP_ADMIN_PASSWORD
|
||||
value: "{{ ldap_admin_password }}"
|
||||
- name: LDAP_USERS
|
||||
value: "ansible"
|
||||
- name: LDAP_PASSWORDS
|
||||
value: "ansible123"
|
||||
- name: LDAP_ROOT
|
||||
value: "{{ ldap_root }}"
|
||||
ports:
|
||||
- containerPort: 1389
|
||||
register: pod_info
|
||||
|
||||
- name: Set Pod Internal IP
|
||||
set_fact:
|
||||
podIp: "{{ pod_info.result.status.podIP }}"
|
||||
|
||||
- name: Set LDAP Common facts
|
||||
set_fact:
|
||||
ldap_server_uri: "ldap://{{ podIp }}:1389"
|
||||
ldap_bind_dn: "cn={{ ldap_admin_user }},{{ ldap_root }}"
|
||||
ldap_bind_pw: "{{ ldap_admin_password }}"
|
||||
|
||||
- name: Display LDAP Server URI
|
||||
debug:
|
||||
var: ldap_server_uri
|
||||
|
||||
- name: Test existing user from LDAP server
|
||||
openshift_ldap_entry_info:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
dn: "ou=users,{{ ldap_root }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
# ignore_errors: true
|
||||
# register: ping_ldap
|
||||
|
||||
- include_tasks: "tasks/rfc2307.yml"
|
||||
- include_tasks: "tasks/activeDirectory.yml"
|
||||
- include_tasks: "tasks/augmentedActiveDirectory.yml"
|
||||
468
molecule/default/roles/openshift_adm_groups/tasks/rfc2307.yml
Normal file
468
molecule/default/roles/openshift_adm_groups/tasks/rfc2307.yml
Normal file
@@ -0,0 +1,468 @@
|
||||
- block:
|
||||
- name: Get LDAP definition
|
||||
set_fact:
|
||||
ldap_resources: "{{ lookup('template', 'rfc2307/definition.j2') | from_yaml }}"
|
||||
|
||||
- name: Delete openshift groups if existing
|
||||
community.okd.k8s:
|
||||
state: absent
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: "{{ item }}"
|
||||
with_items:
|
||||
- admins
|
||||
- engineers
|
||||
- developers
|
||||
|
||||
- name: Delete existing LDAP entries
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ item.dn }}"
|
||||
state: absent
|
||||
with_items: "{{ ldap_resources.users + ldap_resources.groups + ldap_resources.units | reverse | list }}"
|
||||
|
||||
- name: Create LDAP units
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ item.dn }}"
|
||||
attributes: "{{ item.attr }}"
|
||||
objectClass: "{{ item.class }}"
|
||||
with_items: "{{ ldap_resources.units }}"
|
||||
|
||||
- name: Create LDAP Groups
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ item.dn }}"
|
||||
attributes: "{{ item.attr }}"
|
||||
objectClass: "{{ item.class }}"
|
||||
with_items: "{{ ldap_resources.groups }}"
|
||||
|
||||
- name: Create LDAP users
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ item.dn }}"
|
||||
attributes: "{{ item.attr }}"
|
||||
objectClass: "{{ item.class }}"
|
||||
with_items: "{{ ldap_resources.users }}"
|
||||
|
||||
- name: Load test configurations
|
||||
set_fact:
|
||||
configs: "{{ lookup('template', 'rfc2307/sync-config.j2') | from_yaml }}"
|
||||
|
||||
- name: Synchronize Groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ configs.simple }}"
|
||||
check_mode: yes
|
||||
register: result
|
||||
|
||||
- name: Validate Group going to be created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- admins_group
|
||||
- devs_group
|
||||
- '"jane.smith@ansible.org" in {{ admins_group.users }}'
|
||||
- '"jim.adams@ansible.org" in {{ devs_group.users }}'
|
||||
- '"jordanbulls@ansible.org" in {{ devs_group.users }}'
|
||||
- admins_group.users | length == 1
|
||||
- devs_group.users | length == 2
|
||||
vars:
|
||||
admins_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'admins') | first }}"
|
||||
devs_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'developers') | first }}"
|
||||
|
||||
- name: Synchronize Groups - User defined mapping
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ configs.user_defined }}"
|
||||
check_mode: yes
|
||||
register: result
|
||||
|
||||
- name: Validate Group going to be created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- admins_group
|
||||
- devs_group
|
||||
- '"jane.smith@ansible.org" in {{ admins_group.users }}'
|
||||
- '"jim.adams@ansible.org" in {{ devs_group.users }}'
|
||||
- '"jordanbulls@ansible.org" in {{ devs_group.users }}'
|
||||
- admins_group.users | length == 1
|
||||
- devs_group.users | length == 2
|
||||
vars:
|
||||
admins_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'ansible-admins') | first }}"
|
||||
devs_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'ansible-devs') | first }}"
|
||||
|
||||
- name: Synchronize Groups - Using dn for every query
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ configs.dn_everywhere }}"
|
||||
check_mode: yes
|
||||
register: result
|
||||
|
||||
- name: Validate Group going to be created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- admins_group
|
||||
- devs_group
|
||||
- '"cn=Jane,ou=people,ou=rfc2307,{{ ldap_root }}" in {{ admins_group.users }}'
|
||||
- '"cn=Jim,ou=people,ou=rfc2307,{{ ldap_root }}" in {{ devs_group.users }}'
|
||||
- '"cn=Jordan,ou=people,ou=rfc2307,{{ ldap_root }}" in {{ devs_group.users }}'
|
||||
- admins_group.users | length == 1
|
||||
- devs_group.users | length == 2
|
||||
vars:
|
||||
admins_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'cn=admins,ou=groups,ou=rfc2307,' + ldap_root ) | first }}"
|
||||
devs_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'cn=developers,ou=groups,ou=rfc2307,' + ldap_root ) | first }}"
|
||||
|
||||
- name: Synchronize Groups - Partially user defined mapping
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ configs.partially_user_defined }}"
|
||||
check_mode: yes
|
||||
register: result
|
||||
|
||||
- name: Validate Group going to be created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- admins_group
|
||||
- devs_group
|
||||
- '"jane.smith@ansible.org" in {{ admins_group.users }}'
|
||||
- '"jim.adams@ansible.org" in {{ devs_group.users }}'
|
||||
- '"jordanbulls@ansible.org" in {{ devs_group.users }}'
|
||||
- admins_group.users | length == 1
|
||||
- devs_group.users | length == 2
|
||||
vars:
|
||||
admins_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'ansible-admins') | first }}"
|
||||
devs_group: "{{ result.groups | selectattr('metadata.name', 'equalto', 'developers') | first }}"
|
||||
|
||||
- name: Delete Group 'engineers' if created before
|
||||
community.okd.k8s:
|
||||
state: absent
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: 'engineers'
|
||||
wait: yes
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Synchronize Groups - Partially user defined mapping
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ configs.out_scope }}"
|
||||
check_mode: yes
|
||||
register: result
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Assert group sync failed due to non-existent member
|
||||
assert:
|
||||
that:
|
||||
- result is failed
|
||||
- result.msg.startswith("Entry not found for base='cn=Matthew,ou=people,ou=outrfc2307,{{ ldap_root }}'")
|
||||
|
||||
- name: Define sync configuration with tolerateMemberNotFoundErrors
|
||||
set_fact:
|
||||
config_out_of_scope_tolerate_not_found: "{{ configs.out_scope | combine({'rfc2307': merge_rfc2307 })}}"
|
||||
vars:
|
||||
merge_rfc2307: "{{ configs.out_scope.rfc2307 | combine({'tolerateMemberNotFoundErrors': 'true'}) }}"
|
||||
|
||||
- name: Synchronize Groups - Partially user defined mapping (tolerateMemberNotFoundErrors=true)
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ config_out_of_scope_tolerate_not_found }}"
|
||||
check_mode: yes
|
||||
register: result
|
||||
|
||||
- name: Assert group sync did not fail (tolerateMemberNotFoundErrors=true)
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.groups | length == 1
|
||||
- result.groups.0.metadata.name == 'engineers'
|
||||
- result.groups.0.users == ['Abraham']
|
||||
|
||||
- name: Create Group 'engineers'
|
||||
community.okd.k8s:
|
||||
state: present
|
||||
wait: yes
|
||||
definition:
|
||||
kind: Group
|
||||
apiVersion: "user.openshift.io/v1"
|
||||
metadata:
|
||||
name: engineers
|
||||
users: []
|
||||
|
||||
- name: Try to sync LDAP group with Openshift existing group not created using sync should failed
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ config_out_of_scope_tolerate_not_found }}"
|
||||
check_mode: yes
|
||||
register: result
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Validate group sync failed
|
||||
assert:
|
||||
that:
|
||||
- result is failed
|
||||
- '"openshift.io/ldap.host label did not match sync host" in result.msg'
|
||||
|
||||
- name: Define allow_groups and deny_groups groups
|
||||
set_fact:
|
||||
allow_groups:
|
||||
- "cn=developers,ou=groups,ou=rfc2307,{{ ldap_root }}"
|
||||
deny_groups:
|
||||
- "cn=admins,ou=groups,ou=rfc2307,{{ ldap_root }}"
|
||||
|
||||
- name: Synchronize Groups using allow_groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ configs.simple }}"
|
||||
allow_groups: "{{ allow_groups }}"
|
||||
register: result
|
||||
check_mode: yes
|
||||
|
||||
- name: Validate Group going to be created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.groups | length == 1
|
||||
- result.groups.0.metadata.name == "developers"
|
||||
|
||||
- name: Synchronize Groups using deny_groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ configs.simple }}"
|
||||
deny_groups: "{{ deny_groups }}"
|
||||
register: result
|
||||
check_mode: yes
|
||||
|
||||
- name: Validate Group going to be created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.groups | length == 1
|
||||
- result.groups.0.metadata.name == "developers"
|
||||
|
||||
- name: Synchronize groups, remove check_mode
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ configs.simple }}"
|
||||
register: result
|
||||
|
||||
- name: Validate result is changed
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Read Groups
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: admins
|
||||
register: result
|
||||
|
||||
- name: Validate group was created
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
- '"jane.smith@ansible.org" in {{ result.resources.0.users }}'
|
||||
|
||||
- name: Read Groups
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: developers
|
||||
register: result
|
||||
|
||||
- name: Validate group was created
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
- '"jim.adams@ansible.org" in {{ result.resources.0.users }}'
|
||||
- '"jordanbulls@ansible.org" in {{ result.resources.0.users }}'
|
||||
|
||||
- name: Set users to delete (no admins users anymore and only 1 developer kept)
|
||||
set_fact:
|
||||
users_to_delete:
|
||||
- "cn=Jane,ou=people,ou=rfc2307,{{ ldap_root }}"
|
||||
- "cn=Jim,ou=people,ou=rfc2307,{{ ldap_root }}"
|
||||
|
||||
- name: Delete users from LDAP servers
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ item }}"
|
||||
state: absent
|
||||
with_items: "{{ users_to_delete }}"
|
||||
|
||||
- name: Define sync configuration with tolerateMemberNotFoundErrors
|
||||
set_fact:
|
||||
config_simple_tolerate_not_found: "{{ configs.simple | combine({'rfc2307': merge_rfc2307 })}}"
|
||||
vars:
|
||||
merge_rfc2307: "{{ configs.simple.rfc2307 | combine({'tolerateMemberNotFoundErrors': 'true'}) }}"
|
||||
|
||||
- name: Synchronize groups once again after users deletion
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ config_simple_tolerate_not_found }}"
|
||||
register: result
|
||||
|
||||
- name: Validate result is changed
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Read Groups
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: admins
|
||||
register: result
|
||||
|
||||
- name: Validate admins group does not contains users anymore
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
- result.resources.0.users == []
|
||||
|
||||
- name: Read Groups
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: developers
|
||||
register: result
|
||||
|
||||
- name: Validate group was created
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
- '"jordanbulls@ansible.org" in {{ result.resources.0.users }}'
|
||||
|
||||
- name: Set group to delete
|
||||
set_fact:
|
||||
groups_to_delete:
|
||||
- "cn=developers,ou=groups,ou=rfc2307,{{ ldap_root }}"
|
||||
|
||||
- name: Delete Group from LDAP servers
|
||||
openshift_ldap_entry:
|
||||
bind_dn: "{{ ldap_bind_dn }}"
|
||||
bind_pw: "{{ ldap_bind_pw }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
dn: "{{ item }}"
|
||||
state: absent
|
||||
with_items: "{{ groups_to_delete }}"
|
||||
|
||||
- name: Prune groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ config_simple_tolerate_not_found }}"
|
||||
state: absent
|
||||
register: result
|
||||
check_mode: yes
|
||||
|
||||
- name: Validate that only developers group is candidate for Prune
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.groups | length == 1
|
||||
- result.groups.0.metadata.name == "developers"
|
||||
|
||||
- name: Read Group (validate that check_mode did not performed update in the cluster)
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: developers
|
||||
register: result
|
||||
|
||||
- name: Assert group was found
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
|
||||
- name: Prune using allow_groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ config_simple_tolerate_not_found }}"
|
||||
allow_groups:
|
||||
- developers
|
||||
state: absent
|
||||
register: result
|
||||
check_mode: yes
|
||||
|
||||
- name: assert developers group was candidate for prune
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.groups | length == 1
|
||||
- result.groups.0.metadata.name == "developers"
|
||||
|
||||
- name: Prune using deny_groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ config_simple_tolerate_not_found }}"
|
||||
deny_groups:
|
||||
- developers
|
||||
state: absent
|
||||
register: result
|
||||
check_mode: yes
|
||||
|
||||
- name: assert nothing found candidate for prune
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.groups | length == 0
|
||||
|
||||
- name: Prune groups
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ config_simple_tolerate_not_found }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Validate result is changed
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.groups | length == 1
|
||||
|
||||
- name: Get developers group info
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: developers
|
||||
register: result
|
||||
|
||||
- name: assert group was deleted
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 0
|
||||
|
||||
- name: Get admins group info
|
||||
kubernetes.core.k8s_info:
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: admins
|
||||
register: result
|
||||
|
||||
- name: assert group was not deleted
|
||||
assert:
|
||||
that:
|
||||
- result.resources | length == 1
|
||||
|
||||
- name: Prune groups once again (idempotency)
|
||||
community.okd.openshift_adm_groups_sync:
|
||||
config: "{{ config_simple_tolerate_not_found }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert nothing changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.groups | length == 0
|
||||
|
||||
always:
|
||||
- name: Delete openshift groups if existing
|
||||
community.okd.k8s:
|
||||
state: absent
|
||||
kind: Group
|
||||
version: "user.openshift.io/v1"
|
||||
name: "{{ item }}"
|
||||
with_items:
|
||||
- admins
|
||||
- engineers
|
||||
- developers
|
||||
@@ -0,0 +1,39 @@
|
||||
units:
|
||||
- dn: "ou=activeD,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: activeD
|
||||
- dn: "ou=engineers,ou=activeD,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: engineers
|
||||
users:
|
||||
- dn: cn=Jane,ou=engineers,ou=activeD,{{ ldap_root }}
|
||||
class:
|
||||
- inetOrgPerson
|
||||
attr:
|
||||
cn: Jane
|
||||
sn: Smith
|
||||
displayName: Jane Smith
|
||||
mail: jane.smith@ansible.org
|
||||
employeeType: admins
|
||||
- dn: cn=Jim,ou=engineers,ou=activeD,{{ ldap_root }}
|
||||
class:
|
||||
- inetOrgPerson
|
||||
attr:
|
||||
cn: Jim
|
||||
sn: Adams
|
||||
displayName: Jim Adams
|
||||
mail: jim.adams@ansible.org
|
||||
employeeType: admins
|
||||
- dn: cn=Jordan,ou=engineers,ou=activeD,{{ ldap_root }}
|
||||
class:
|
||||
- inetOrgPerson
|
||||
attr:
|
||||
cn: Jordan
|
||||
sn: Bulls
|
||||
displayName: Jordan Bulls
|
||||
mail: jordanbulls@ansible.org
|
||||
employeeType: developers
|
||||
@@ -0,0 +1,12 @@
|
||||
kind: LDAPSyncConfig
|
||||
apiVersion: v1
|
||||
url: "{{ ldap_server_uri }}"
|
||||
insecure: true
|
||||
activeDirectory:
|
||||
usersQuery:
|
||||
baseDN: "ou=engineers,ou=activeD,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
filter: (objectclass=inetOrgPerson)
|
||||
userNameAttributes: [ mail ]
|
||||
groupMembershipAttributes: [ employeeType ]
|
||||
@@ -0,0 +1,59 @@
|
||||
units:
|
||||
- dn: "ou=augmentedAD,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: augmentedAD
|
||||
- dn: "ou=employee,ou=augmentedAD,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: employee
|
||||
- dn: "ou=category,ou=augmentedAD,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: category
|
||||
groups:
|
||||
- dn: "cn=banking,ou=category,ou=augmentedAD,{{ ldap_root }}"
|
||||
class:
|
||||
- groupOfNames
|
||||
attr:
|
||||
cn: banking
|
||||
description: Banking employees
|
||||
member:
|
||||
- cn=James,ou=employee,ou=augmentedAD,{{ ldap_root }}
|
||||
- cn=Gordon,ou=employee,ou=augmentedAD,{{ ldap_root }}
|
||||
- dn: "cn=insurance,ou=category,ou=augmentedAD,{{ ldap_root }}"
|
||||
class:
|
||||
- groupOfNames
|
||||
attr:
|
||||
cn: insurance
|
||||
description: Insurance employees
|
||||
member:
|
||||
- cn=Alice,ou=employee,ou=augmentedAD,{{ ldap_root }}
|
||||
users:
|
||||
- dn: cn=James,ou=employee,ou=augmentedAD,{{ ldap_root }}
|
||||
class:
|
||||
- inetOrgPerson
|
||||
attr:
|
||||
cn: James
|
||||
sn: Allan
|
||||
mail: james-allan@ansible.org
|
||||
businessCategory: cn=banking,ou=category,ou=augmentedAD,{{ ldap_root }}
|
||||
- dn: cn=Gordon,ou=employee,ou=augmentedAD,{{ ldap_root }}
|
||||
class:
|
||||
- inetOrgPerson
|
||||
attr:
|
||||
cn: Gordon
|
||||
sn: Kane
|
||||
mail: gordon-kane@ansible.org
|
||||
businessCategory: cn=banking,ou=category,ou=augmentedAD,{{ ldap_root }}
|
||||
- dn: cn=Alice,ou=employee,ou=augmentedAD,{{ ldap_root }}
|
||||
class:
|
||||
- inetOrgPerson
|
||||
attr:
|
||||
cn: Alice
|
||||
sn: Courtney
|
||||
mail: alice-courtney@ansible.org
|
||||
businessCategory: cn=insurance,ou=category,ou=augmentedAD,{{ ldap_root }}
|
||||
@@ -0,0 +1,20 @@
|
||||
kind: LDAPSyncConfig
|
||||
apiVersion: v1
|
||||
url: "{{ ldap_server_uri }}"
|
||||
insecure: true
|
||||
augmentedActiveDirectory:
|
||||
groupsQuery:
|
||||
baseDN: "ou=category,ou=augmentedAD,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
pageSize: 0
|
||||
groupUIDAttribute: dn
|
||||
groupNameAttributes: [ cn ]
|
||||
usersQuery:
|
||||
baseDN: "ou=employee,ou=augmentedAD,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
filter: (objectclass=inetOrgPerson)
|
||||
pageSize: 0
|
||||
userNameAttributes: [ mail ]
|
||||
groupMembershipAttributes: [ businessCategory ]
|
||||
@@ -0,0 +1,102 @@
|
||||
units:
|
||||
- dn: "ou=rfc2307,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: rfc2307
|
||||
- dn: "ou=groups,ou=rfc2307,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: groups
|
||||
- dn: "ou=people,ou=rfc2307,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: people
|
||||
- dn: "ou=outrfc2307,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: outrfc2307
|
||||
- dn: "ou=groups,ou=outrfc2307,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: groups
|
||||
- dn: "ou=people,ou=outrfc2307,{{ ldap_root }}"
|
||||
class:
|
||||
- organizationalUnit
|
||||
attr:
|
||||
ou: people
|
||||
groups:
|
||||
- dn: "cn=admins,ou=groups,ou=rfc2307,{{ ldap_root }}"
|
||||
class:
|
||||
- groupOfNames
|
||||
attr:
|
||||
cn: admins
|
||||
description: System Administrators
|
||||
member:
|
||||
- cn=Jane,ou=people,ou=rfc2307,{{ ldap_root }}
|
||||
- dn: "cn=developers,ou=groups,ou=rfc2307,{{ ldap_root }}"
|
||||
class:
|
||||
- groupOfNames
|
||||
attr:
|
||||
cn: developers
|
||||
description: Developers
|
||||
member:
|
||||
- cn=Jim,ou=people,ou=rfc2307,{{ ldap_root }}
|
||||
- cn=Jordan,ou=people,ou=rfc2307,{{ ldap_root }}
|
||||
- dn: "cn=engineers,ou=groups,ou=outrfc2307,{{ ldap_root }}"
|
||||
class:
|
||||
- groupOfNames
|
||||
attr:
|
||||
cn: engineers
|
||||
description: Engineers
|
||||
member:
|
||||
- cn=Jim,ou=people,ou=rfc2307,{{ ldap_root }}
|
||||
- cn=Jordan,ou=people,ou=rfc2307,{{ ldap_root }}
|
||||
- cn=Julia,ou=people,ou=outrfc2307,{{ ldap_root }}
|
||||
- cn=Matthew,ou=people,ou=outrfc2307,{{ ldap_root }}
|
||||
users:
|
||||
- dn: cn=Jane,ou=people,ou=rfc2307,{{ ldap_root }}
|
||||
class:
|
||||
- person
|
||||
- organizationalPerson
|
||||
- inetOrgPerson
|
||||
attr:
|
||||
cn: Jane
|
||||
sn: Smith
|
||||
displayName: Jane Smith
|
||||
mail: jane.smith@ansible.org
|
||||
admin: yes
|
||||
- dn: cn=Jim,ou=people,ou=rfc2307,{{ ldap_root }}
|
||||
class:
|
||||
- person
|
||||
- organizationalPerson
|
||||
- inetOrgPerson
|
||||
attr:
|
||||
cn: Jim
|
||||
sn: Adams
|
||||
displayName: Jim Adams
|
||||
mail: jim.adams@ansible.org
|
||||
- dn: cn=Jordan,ou=people,ou=rfc2307,{{ ldap_root }}
|
||||
class:
|
||||
- person
|
||||
- organizationalPerson
|
||||
- inetOrgPerson
|
||||
attr:
|
||||
cn: Jordan
|
||||
sn: Bulls
|
||||
displayName: Jordan Bulls
|
||||
mail: jordanbulls@ansible.org
|
||||
- dn: cn=Julia,ou=people,ou=outrfc2307,{{ ldap_root }}
|
||||
class:
|
||||
- person
|
||||
- organizationalPerson
|
||||
- inetOrgPerson
|
||||
attr:
|
||||
cn: Julia
|
||||
sn: Abraham
|
||||
displayName: Julia Abraham
|
||||
mail: juliaabraham@ansible.org
|
||||
@@ -0,0 +1,105 @@
|
||||
simple:
|
||||
kind: LDAPSyncConfig
|
||||
apiVersion: v1
|
||||
url: "{{ ldap_server_uri }}"
|
||||
insecure: true
|
||||
rfc2307:
|
||||
groupsQuery:
|
||||
baseDN: "ou=groups,ou=rfc2307,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
filter: (objectclass=groupOfNames)
|
||||
groupUIDAttribute: dn
|
||||
groupNameAttributes: [ cn ]
|
||||
groupMembershipAttributes: [ member ]
|
||||
usersQuery:
|
||||
baseDN: "ou=people,ou=rfc2307,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
userUIDAttribute: dn
|
||||
userNameAttributes: [ mail ]
|
||||
user_defined:
|
||||
kind: LDAPSyncConfig
|
||||
apiVersion: v1
|
||||
url: "{{ ldap_server_uri }}"
|
||||
insecure: true
|
||||
groupUIDNameMapping:
|
||||
"cn=admins,ou=groups,ou=rfc2307,{{ ldap_root }}": ansible-admins
|
||||
"cn=developers,ou=groups,ou=rfc2307,{{ ldap_root }}": ansible-devs
|
||||
rfc2307:
|
||||
groupsQuery:
|
||||
baseDN: "ou=groups,ou=rfc2307,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
filter: (objectclass=groupOfNames)
|
||||
groupUIDAttribute: dn
|
||||
groupNameAttributes: [ cn ]
|
||||
groupMembershipAttributes: [ member ]
|
||||
usersQuery:
|
||||
baseDN: "ou=people,ou=rfc2307,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
userUIDAttribute: dn
|
||||
userNameAttributes: [ mail ]
|
||||
partially_user_defined:
|
||||
kind: LDAPSyncConfig
|
||||
apiVersion: v1
|
||||
url: "{{ ldap_server_uri }}"
|
||||
insecure: true
|
||||
groupUIDNameMapping:
|
||||
"cn=admins,ou=groups,ou=rfc2307,{{ ldap_root }}": ansible-admins
|
||||
rfc2307:
|
||||
groupsQuery:
|
||||
baseDN: "ou=groups,ou=rfc2307,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
filter: (objectclass=groupOfNames)
|
||||
groupUIDAttribute: dn
|
||||
groupNameAttributes: [ cn ]
|
||||
groupMembershipAttributes: [ member ]
|
||||
usersQuery:
|
||||
baseDN: "ou=people,ou=rfc2307,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
userUIDAttribute: dn
|
||||
userNameAttributes: [ mail ]
|
||||
dn_everywhere:
|
||||
kind: LDAPSyncConfig
|
||||
apiVersion: v1
|
||||
url: "{{ ldap_server_uri }}"
|
||||
insecure: true
|
||||
rfc2307:
|
||||
groupsQuery:
|
||||
baseDN: "ou=groups,ou=rfc2307,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
filter: (objectclass=groupOfNames)
|
||||
groupUIDAttribute: dn
|
||||
groupNameAttributes: [ dn ]
|
||||
groupMembershipAttributes: [ member ]
|
||||
usersQuery:
|
||||
baseDN: "ou=people,ou=rfc2307,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
userUIDAttribute: dn
|
||||
userNameAttributes: [ dn ]
|
||||
out_scope:
|
||||
kind: LDAPSyncConfig
|
||||
apiVersion: v1
|
||||
url: "{{ ldap_server_uri }}"
|
||||
insecure: true
|
||||
rfc2307:
|
||||
groupsQuery:
|
||||
baseDN: "ou=groups,ou=outrfc2307,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
filter: (objectclass=groupOfNames)
|
||||
groupUIDAttribute: dn
|
||||
groupNameAttributes: [ cn ]
|
||||
groupMembershipAttributes: [ member ]
|
||||
usersQuery:
|
||||
baseDN: "ou=people,ou=outrfc2307,{{ ldap_root }}"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
userUIDAttribute: dn
|
||||
userNameAttributes: [ sn ]
|
||||
@@ -81,3 +81,6 @@
|
||||
kind: Namespace
|
||||
name: process-test
|
||||
state: absent
|
||||
|
||||
roles:
|
||||
- role: openshift_adm_groups
|
||||
|
||||
466
plugins/module_utils/openshift_groups.py
Normal file
466
plugins/module_utils/openshift_groups.py
Normal file
@@ -0,0 +1,466 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (K8sAnsibleMixin, get_api_client)
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_ldap import (
|
||||
validate_ldap_sync_config,
|
||||
ldap_split_host_port,
|
||||
OpenshiftLDAPRFC2307,
|
||||
OpenshiftLDAPActiveDirectory,
|
||||
OpenshiftLDAPAugmentedActiveDirectory
|
||||
)
|
||||
|
||||
try:
|
||||
import ldap
|
||||
HAS_PYTHON_LDAP = True
|
||||
except ImportError as e:
|
||||
HAS_PYTHON_LDAP = False
|
||||
PYTHON_LDAP_ERROR = e
|
||||
|
||||
try:
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
HAS_KUBERNETES_COLLECTION = True
|
||||
except ImportError as e:
|
||||
HAS_KUBERNETES_COLLECTION = False
|
||||
k8s_collection_import_exception = e
|
||||
K8S_COLLECTION_ERROR = traceback.format_exc()
|
||||
|
||||
try:
|
||||
from kubernetes.dynamic.exceptions import DynamicApiError
|
||||
except ImportError as e:
|
||||
pass
|
||||
|
||||
|
||||
LDAP_OPENSHIFT_HOST_LABEL = "openshift.io/ldap.host"
|
||||
LDAP_OPENSHIFT_URL_ANNOTATION = "openshift.io/ldap.url"
|
||||
LDAP_OPENSHIFT_UID_ANNOTATION = "openshift.io/ldap.uid"
|
||||
LDAP_OPENSHIFT_SYNCTIME_ANNOTATION = "openshift.io/ldap.sync-time"
|
||||
|
||||
|
||||
def connect_to_ldap(module, server_uri, bind_dn=None, bind_pw=None, insecure=True, ca_file=None):
|
||||
if insecure:
|
||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||
elif ca_file:
|
||||
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, ca_file)
|
||||
try:
|
||||
connection = ldap.initialize(server_uri)
|
||||
connection.set_option(ldap.OPT_REFERRALS, 0)
|
||||
|
||||
connection.simple_bind_s(bind_dn, bind_pw)
|
||||
return connection
|
||||
except ldap.LDAPError as e:
|
||||
module.fail_json(msg="Cannot bind to the LDAP server '{0}' due to: {1}".format(server_uri, e))
|
||||
|
||||
|
||||
def validate_group_annotation(definition, host_ip):
|
||||
name = definition['metadata']['name']
|
||||
# Validate LDAP URL Annotation
|
||||
annotate_url = definition['metadata'].get('annotations', {}).get(LDAP_OPENSHIFT_URL_ANNOTATION)
|
||||
if host_ip:
|
||||
if not annotate_url:
|
||||
return "group '{0}' marked as having been synced did not have an '{1}' annotation".format(name, LDAP_OPENSHIFT_URL_ANNOTATION)
|
||||
elif annotate_url != host_ip:
|
||||
return "group '{0}' was not synchronized from: '{1}'".format(name, host_ip)
|
||||
# Validate LDAP UID Annotation
|
||||
annotate_uid = definition['metadata']['annotations'].get(LDAP_OPENSHIFT_UID_ANNOTATION)
|
||||
if not annotate_uid:
|
||||
return "group '{0}' marked as having been synced did not have an '{1}' annotation".format(name, LDAP_OPENSHIFT_UID_ANNOTATION)
|
||||
return None
|
||||
|
||||
|
||||
class OpenshiftLDAPGroups(object):
|
||||
|
||||
kind = "Group"
|
||||
version = "user.openshift.io/v1"
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.cache = {}
|
||||
self.__group_api = None
|
||||
|
||||
@property
|
||||
def k8s_group_api(self):
|
||||
if not self.__group_api:
|
||||
params = dict(
|
||||
kind=self.kind,
|
||||
api_version=self.version,
|
||||
fail=True
|
||||
)
|
||||
self.__group_api = self.module.find_resource(**params)
|
||||
return self.__group_api
|
||||
|
||||
def get_group_info(self, return_list=False, **kwargs):
|
||||
params = dict(
|
||||
kind=self.kind,
|
||||
api_version=self.version,
|
||||
)
|
||||
params.update(kwargs)
|
||||
result = self.module.kubernetes_facts(**params)
|
||||
if len(result["resources"]) == 0:
|
||||
return None
|
||||
if len(result["resources"]) == 1 and not return_list:
|
||||
return result["resources"][0]
|
||||
else:
|
||||
return result["resources"]
|
||||
|
||||
def list_groups(self):
|
||||
allow_groups = self.module.params.get("allow_groups")
|
||||
deny_groups = self.module.params.get("deny_groups")
|
||||
name_mapping = self.module.config.get("groupUIDNameMapping")
|
||||
|
||||
if name_mapping and (allow_groups or deny_groups):
|
||||
|
||||
def _map_group_names(groups):
|
||||
return [name_mapping.get(value, value) for value in groups]
|
||||
|
||||
allow_groups = _map_group_names(allow_groups)
|
||||
deny_groups = _map_group_names(deny_groups)
|
||||
|
||||
host = self.module.host
|
||||
netlocation = self.module.netlocation
|
||||
groups = []
|
||||
if allow_groups:
|
||||
missing = []
|
||||
for grp in allow_groups:
|
||||
if grp in deny_groups:
|
||||
continue
|
||||
resource = self.get_group_info(name=grp)
|
||||
if not resource:
|
||||
missing.append(grp)
|
||||
continue
|
||||
groups.append(resource)
|
||||
|
||||
if missing:
|
||||
self.module.fail_json(
|
||||
msg="The following groups were not found: %s" % ''.join(missing)
|
||||
)
|
||||
else:
|
||||
label_selector = "%s=%s" % (LDAP_OPENSHIFT_HOST_LABEL, host)
|
||||
resources = self.get_group_info(label_selectors=[label_selector], return_list=True)
|
||||
if not resources:
|
||||
return None, "Unable to find Group matching label selector '%s'" % label_selector
|
||||
groups = resources
|
||||
if deny_groups:
|
||||
groups = [item for item in groups if item["metadata"]["name"] not in deny_groups]
|
||||
|
||||
uids = []
|
||||
for grp in groups:
|
||||
err = validate_group_annotation(grp, netlocation)
|
||||
if err and allow_groups:
|
||||
# We raise an error for group part of the allow_group not matching LDAP sync criteria
|
||||
return None, err
|
||||
group_uid = grp['metadata']['annotations'].get(LDAP_OPENSHIFT_UID_ANNOTATION)
|
||||
self.cache[group_uid] = grp
|
||||
uids.append(group_uid)
|
||||
return uids, None
|
||||
|
||||
def get_group_name_for_uid(self, group_uid):
|
||||
if group_uid not in self.cache:
|
||||
return None, "No mapping found for Group uid: %s" % group_uid
|
||||
return self.cache[group_uid]["metadata"]["name"], None
|
||||
|
||||
def make_openshift_group(self, group_uid, group_name, usernames):
|
||||
group = self.get_group_info(name=group_name)
|
||||
if not group:
|
||||
group = {
|
||||
"apiVersion": "user.openshift.io/v1",
|
||||
"kind": "Group",
|
||||
"metadata": {
|
||||
"name": group_name,
|
||||
"labels": {
|
||||
LDAP_OPENSHIFT_HOST_LABEL: self.module.host
|
||||
},
|
||||
"annotations": {
|
||||
LDAP_OPENSHIFT_URL_ANNOTATION: self.module.netlocation,
|
||||
LDAP_OPENSHIFT_UID_ANNOTATION: group_uid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure we aren't taking over an OpenShift group that is already related to a different LDAP group
|
||||
ldaphost_label = group["metadata"].get("labels", {}).get(LDAP_OPENSHIFT_HOST_LABEL)
|
||||
if not ldaphost_label or ldaphost_label != self.module.host:
|
||||
return None, "Group %s: %s label did not match sync host: wanted %s, got %s" % (
|
||||
group_name, LDAP_OPENSHIFT_HOST_LABEL, self.module.host, ldaphost_label
|
||||
)
|
||||
|
||||
ldapurl_annotation = group["metadata"].get("annotations", {}).get(LDAP_OPENSHIFT_URL_ANNOTATION)
|
||||
if not ldapurl_annotation or ldapurl_annotation != self.module.netlocation:
|
||||
return None, "Group %s: %s annotation did not match sync host: wanted %s, got %s" % (
|
||||
group_name, LDAP_OPENSHIFT_URL_ANNOTATION, self.module.netlocation, ldapurl_annotation
|
||||
)
|
||||
|
||||
ldapuid_annotation = group["metadata"].get("annotations", {}).get(LDAP_OPENSHIFT_UID_ANNOTATION)
|
||||
if not ldapuid_annotation or ldapuid_annotation != group_uid:
|
||||
return None, "Group %s: %s annotation did not match LDAP UID: wanted %s, got %s" % (
|
||||
group_name, LDAP_OPENSHIFT_UID_ANNOTATION, group_uid, ldapuid_annotation
|
||||
)
|
||||
|
||||
# Overwrite Group Users data
|
||||
group["users"] = usernames
|
||||
group["metadata"]["annotations"][LDAP_OPENSHIFT_SYNCTIME_ANNOTATION] = datetime.now().isoformat()
|
||||
return group, None
|
||||
|
||||
def create_openshift_groups(self, groups: list):
|
||||
diffs = []
|
||||
results = []
|
||||
changed = False
|
||||
for definition in groups:
|
||||
name = definition["metadata"]["name"]
|
||||
existing = self.get_group_info(name=name)
|
||||
if not self.module.check_mode:
|
||||
try:
|
||||
if existing:
|
||||
method = 'patch'
|
||||
definition = self.k8s_group_api.patch(definition).to_dict()
|
||||
else:
|
||||
method = 'create'
|
||||
definition = self.k8s_group_api.create(definition).to_dict()
|
||||
except DynamicApiError as exc:
|
||||
self.module.fail_json(msg="Failed to %s Group '%s' due to: %s" % (method, name, exc.body))
|
||||
except Exception as exc:
|
||||
self.module.fail_json(msg="Failed to %s Group '%s' due to: %s" % (method, name, to_native(exc)))
|
||||
equals = False
|
||||
if existing:
|
||||
equals, diff = self.module.diff_objects(existing, definition)
|
||||
diffs.append(diff)
|
||||
changed = changed or not equals
|
||||
results.append(definition)
|
||||
return results, diffs, changed
|
||||
|
||||
def delete_openshift_group(self, name: str):
|
||||
result = dict(
|
||||
kind=self.kind,
|
||||
apiVersion=self.version,
|
||||
metadata=dict(
|
||||
name=name
|
||||
)
|
||||
)
|
||||
if not self.module.check_mode:
|
||||
try:
|
||||
result = self.k8s_group_api.delete(name=name).to_dict()
|
||||
except DynamicApiError as exc:
|
||||
self.module.fail_json(msg="Failed to delete Group '{0}' due to: {1}".format(name, exc.body))
|
||||
except Exception as exc:
|
||||
self.module.fail_json(msg="Failed to delete Group '{0}' due to: {1}".format(name, to_native(exc)))
|
||||
return result
|
||||
|
||||
|
||||
class OpenshiftGroupsSync(K8sAnsibleMixin):
|
||||
|
||||
def __init__(self, module):
|
||||
|
||||
self.module = module
|
||||
|
||||
if not HAS_KUBERNETES_COLLECTION:
|
||||
self.module.fail_json(
|
||||
msg="The kubernetes.core collection must be installed",
|
||||
exception=K8S_COLLECTION_ERROR,
|
||||
error=to_native(k8s_collection_import_exception),
|
||||
)
|
||||
|
||||
if not HAS_PYTHON_LDAP:
|
||||
self.fail_json(
|
||||
msg=missing_required_lib('python-ldap'), error=to_native(PYTHON_LDAP_ERROR)
|
||||
)
|
||||
|
||||
super(OpenshiftGroupsSync, self).__init__(self.module)
|
||||
|
||||
self.params = self.module.params
|
||||
self.check_mode = self.module.check_mode
|
||||
self.client = get_api_client(self.module)
|
||||
|
||||
self.__k8s_group_api = None
|
||||
self.__ldap_connection = None
|
||||
self.host = None
|
||||
self.port = None
|
||||
self.netlocation = None
|
||||
self.scheme = None
|
||||
self.config = self.params.get("sync_config")
|
||||
|
||||
@property
|
||||
def k8s_group_api(self):
|
||||
if not self.__k8s_group_api:
|
||||
params = dict(
|
||||
kind="Group",
|
||||
api_version="user.openshift.io/v1",
|
||||
fail=True
|
||||
)
|
||||
self.__k8s_group_api = self.find_resource(**params)
|
||||
return self.__k8s_group_api
|
||||
|
||||
@property
|
||||
def hostIP(self):
|
||||
return self.netlocation
|
||||
|
||||
@property
|
||||
def connection(self):
|
||||
if not self.__ldap_connection:
|
||||
# Create connection object
|
||||
params = dict(
|
||||
module=self,
|
||||
server_uri=self.config.get('url'),
|
||||
bind_dn=self.config.get('bindDN'),
|
||||
bind_pw=self.config.get('bindPassword'),
|
||||
insecure=boolean(self.config.get('insecure')),
|
||||
ca_file=self.config.get('ca')
|
||||
)
|
||||
self.__ldap_connection = connect_to_ldap(**params)
|
||||
return self.__ldap_connection
|
||||
|
||||
def close_connection(self):
|
||||
if self.__ldap_connection:
|
||||
self.__ldap_connection.unbind_s()
|
||||
self.__ldap_connection = None
|
||||
|
||||
def exit_json(self, **kwargs):
|
||||
self.close_connection()
|
||||
self.module.exit_json(**kwargs)
|
||||
|
||||
def fail_json(self, **kwargs):
|
||||
self.close_connection()
|
||||
self.module.fail_json(**kwargs)
|
||||
|
||||
def get_syncer(self):
|
||||
syncer = None
|
||||
if "rfc2307" in self.config:
|
||||
syncer = OpenshiftLDAPRFC2307(self.config, self.connection)
|
||||
elif "activeDirectory" in self.config:
|
||||
syncer = OpenshiftLDAPActiveDirectory(self.config, self.connection)
|
||||
elif "augmentedActiveDirectory" in self.config:
|
||||
syncer = OpenshiftLDAPAugmentedActiveDirectory(self.config, self.connection)
|
||||
else:
|
||||
msg = "No schema-specific config was found, should be one of 'rfc2307', 'activeDirectory', 'augmentedActiveDirectory'"
|
||||
self.fail_json(msg=msg)
|
||||
return syncer
|
||||
|
||||
def synchronize(self):
|
||||
|
||||
sync_group_type = self.module.params.get("type")
|
||||
|
||||
groups_uids = []
|
||||
ldap_openshift_group = OpenshiftLDAPGroups(module=self)
|
||||
|
||||
# Get Synchronize object
|
||||
syncer = self.get_syncer()
|
||||
|
||||
# Determine what to sync : list groups
|
||||
if sync_group_type == "openshift":
|
||||
groups_uids, err = ldap_openshift_group.list_groups()
|
||||
if err:
|
||||
self.fail_json(msg="Failed to list openshift groups", errors=err)
|
||||
else:
|
||||
# List LDAP Group to synchronize
|
||||
groups_uids = self.params.get("allow_groups")
|
||||
if not groups_uids:
|
||||
groups_uids, err = syncer.list_groups()
|
||||
if err:
|
||||
self.module.fail_json(msg=err)
|
||||
deny_groups = self.params.get("deny_groups")
|
||||
if deny_groups:
|
||||
groups_uids = [uid for uid in groups_uids if uid not in deny_groups]
|
||||
|
||||
openshift_groups = []
|
||||
for uid in groups_uids:
|
||||
# Get membership data
|
||||
member_entries, err = syncer.extract_members(uid)
|
||||
if err:
|
||||
self.fail_json(msg=err)
|
||||
|
||||
# Determine usernames for members entries
|
||||
usernames = []
|
||||
for entry in member_entries:
|
||||
name, err = syncer.get_username_for_entry(entry)
|
||||
if err:
|
||||
self.exit_json(
|
||||
msg="Unable to determine username for entry %s: %s" % (entry, err)
|
||||
)
|
||||
if isinstance(name, list):
|
||||
usernames.extend(name)
|
||||
else:
|
||||
usernames.append(name)
|
||||
# Get group name
|
||||
if sync_group_type == "openshift":
|
||||
group_name, err = ldap_openshift_group.get_group_name_for_uid(uid)
|
||||
else:
|
||||
group_name, err = syncer.get_group_name_for_uid(uid)
|
||||
if err:
|
||||
self.exit_json(msg=err)
|
||||
|
||||
# Make Openshift group
|
||||
group, err = ldap_openshift_group.make_openshift_group(uid, group_name, usernames)
|
||||
if err:
|
||||
self.fail_json(msg=err)
|
||||
openshift_groups.append(group)
|
||||
|
||||
# Create Openshift Groups
|
||||
results, diffs, changed = ldap_openshift_group.create_openshift_groups(openshift_groups)
|
||||
self.module.exit_json(changed=True, groups=results)
|
||||
|
||||
def prune(self):
|
||||
ldap_openshift_group = OpenshiftLDAPGroups(module=self)
|
||||
groups_uids, err = ldap_openshift_group.list_groups()
|
||||
if err:
|
||||
self.fail_json(msg="Failed to list openshift groups", errors=err)
|
||||
|
||||
# Get Synchronize object
|
||||
syncer = self.get_syncer()
|
||||
|
||||
changed = False
|
||||
groups = []
|
||||
for uid in groups_uids:
|
||||
# Check if LDAP group exist
|
||||
exists, err = syncer.is_ldapgroup_exists(uid)
|
||||
if err:
|
||||
msg = "Error determining LDAP group existence for group %s: %s" % (uid, err)
|
||||
self.module.fail_json(msg=msg)
|
||||
|
||||
if exists:
|
||||
continue
|
||||
|
||||
# if the LDAP entry that was previously used to create the group doesn't exist, prune it
|
||||
group_name, err = ldap_openshift_group.get_group_name_for_uid(uid)
|
||||
if err:
|
||||
self.module.fail_json(msg=err)
|
||||
|
||||
# Delete Group
|
||||
result = ldap_openshift_group.delete_openshift_group(group_name)
|
||||
groups.append(result)
|
||||
changed = True
|
||||
|
||||
self.exit_json(changed=changed, groups=groups)
|
||||
|
||||
def execute_module(self):
|
||||
# validate LDAP sync configuration
|
||||
error = validate_ldap_sync_config(self.config)
|
||||
if error:
|
||||
self.fail_json(msg="Invalid LDAP Sync config: %s" % error)
|
||||
|
||||
# Split host/port
|
||||
if self.config.get('url'):
|
||||
result, error = ldap_split_host_port(self.config.get('url'))
|
||||
if error:
|
||||
self.fail_json(msg="Failed to parse url='{0}': {1}".format(self.config.get('url'), error))
|
||||
self.netlocation, self.host, self.port = result["netlocation"], result["host"], result["port"]
|
||||
self.scheme = result["scheme"]
|
||||
|
||||
if self.params.get('state') == 'present':
|
||||
self.synchronize()
|
||||
else:
|
||||
self.prune()
|
||||
777
plugins/module_utils/openshift_ldap.py
Normal file
777
plugins/module_utils/openshift_ldap.py
Normal file
@@ -0,0 +1,777 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import os
|
||||
import copy
|
||||
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
try:
|
||||
import ldap
|
||||
except ImportError as e:
|
||||
pass
|
||||
|
||||
|
||||
LDAP_SEARCH_OUT_OF_SCOPE_ERROR = "trying to search by DN for an entry that exists outside of the tree specified with the BaseDN for search"
|
||||
|
||||
|
||||
def validate_ldap_sync_config(config):
|
||||
# Validate url
|
||||
url = config.get('url')
|
||||
if not url:
|
||||
return "url should be non empty attribute."
|
||||
|
||||
# Make sure bindDN and bindPassword are both set, or both unset
|
||||
bind_dn = config.get('bindDN', "")
|
||||
bind_password = config.get('bindPassword', "")
|
||||
if (len(bind_dn) == 0) != (len(bind_password) == 0):
|
||||
return "bindDN and bindPassword must both be specified, or both be empty."
|
||||
|
||||
insecure = boolean(config.get('insecure'))
|
||||
ca_file = config.get('ca')
|
||||
if insecure:
|
||||
if url.startswith('ldaps://'):
|
||||
return "Cannot use ldaps scheme with insecure=true."
|
||||
if ca_file:
|
||||
return "Cannot specify a ca with insecure=true."
|
||||
elif ca_file and not os.path.isfile(ca_file):
|
||||
return "could not read ca file: {0}.".format(ca_file)
|
||||
|
||||
nameMapping = config.get('groupUIDNameMapping', {})
|
||||
for k, v in iteritems(nameMapping):
|
||||
if len(k) == 0 or len(v) == 0:
|
||||
return "groupUIDNameMapping has empty key or value"
|
||||
|
||||
schemas = []
|
||||
schema_list = ('rfc2307', 'activeDirectory', 'augmentedActiveDirectory')
|
||||
for schema in schema_list:
|
||||
if schema in config:
|
||||
schemas.append(schema)
|
||||
|
||||
if len(schemas) == 0:
|
||||
return "No schema-specific config was provided, should be one of %s" % schema_list
|
||||
if len(schemas) > 1:
|
||||
return "Exactly one schema-specific config is required; found (%d) %s" % (len(schemas), ','.join(schemas))
|
||||
|
||||
if schemas[0] == 'rfc2307':
|
||||
return validate_RFC2307(config.get("rfc2307"))
|
||||
elif schemas[0] == 'activeDirectory':
|
||||
return validate_ActiveDirectory(config.get("activeDirectory"))
|
||||
elif schemas[0] == 'augmentedActiveDirectory':
|
||||
return validate_AugmentedActiveDirectory(config.get("augmentedActiveDirectory"))
|
||||
|
||||
|
||||
def validate_ldap_query(qry, isDNOnly=False):
|
||||
|
||||
# validate query scope
|
||||
scope = qry.get('scope')
|
||||
if scope and scope not in ("", "sub", "one", "base"):
|
||||
return "invalid scope %s" % scope
|
||||
|
||||
# validate deref aliases
|
||||
derefAlias = qry.get('derefAliases')
|
||||
if derefAlias and derefAlias not in ("never", "search", "base", "always"):
|
||||
return "not a valid LDAP alias dereferncing behavior: %s", derefAlias
|
||||
|
||||
# validate timeout
|
||||
timeout = qry.get('timeout')
|
||||
if timeout and float(timeout) < 0:
|
||||
return "timeout must be equal to or greater than zero"
|
||||
|
||||
# Validate DN only
|
||||
qry_filter = qry.get('filter', "")
|
||||
if isDNOnly:
|
||||
if len(qry_filter) > 0:
|
||||
return 'cannot specify a filter when using "dn" as the UID attribute'
|
||||
else:
|
||||
# validate filter
|
||||
if len(qry_filter) == 0 or qry_filter[0] != '(':
|
||||
return "filter does not start with an '('"
|
||||
return None
|
||||
|
||||
|
||||
def validate_RFC2307(config):
|
||||
qry = config.get('groupsQuery')
|
||||
if not qry or not isinstance(qry, dict):
|
||||
return "RFC2307: groupsQuery requires a dictionary"
|
||||
error = validate_ldap_query(qry)
|
||||
if not error:
|
||||
return error
|
||||
for field in ('groupUIDAttribute', 'groupNameAttributes', 'groupMembershipAttributes',
|
||||
'userUIDAttribute', 'userNameAttributes'):
|
||||
value = config.get(field)
|
||||
if not value:
|
||||
return "RFC2307: {0} is required.".format(field)
|
||||
|
||||
users_qry = config.get('usersQuery')
|
||||
if not users_qry or not isinstance(users_qry, dict):
|
||||
return "RFC2307: usersQuery requires a dictionary"
|
||||
|
||||
isUserDNOnly = (config.get('userUIDAttribute').strip() == 'dn')
|
||||
return validate_ldap_query(users_qry, isDNOnly=isUserDNOnly)
|
||||
|
||||
|
||||
def validate_ActiveDirectory(config, label="ActiveDirectory"):
|
||||
users_qry = config.get('usersQuery')
|
||||
if not users_qry or not isinstance(users_qry, dict):
|
||||
return "{0}: usersQuery requires as dictionnary".format(label)
|
||||
error = validate_ldap_query(users_qry)
|
||||
if not error:
|
||||
return error
|
||||
|
||||
for field in ('userNameAttributes', 'groupMembershipAttributes'):
|
||||
value = config.get(field)
|
||||
if not value:
|
||||
return "{0}: {1} is required.".format(field, label)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def validate_AugmentedActiveDirectory(config):
|
||||
error = validate_ActiveDirectory(config, label="AugmentedActiveDirectory")
|
||||
if not error:
|
||||
return error
|
||||
for field in ('groupUIDAttribute', 'groupNameAttributes'):
|
||||
value = config.get(field)
|
||||
if not value:
|
||||
return "AugmentedActiveDirectory: {0} is required".format(field)
|
||||
groups_qry = config.get('groupsQuery')
|
||||
if not groups_qry or not isinstance(groups_qry, dict):
|
||||
return "AugmentedActiveDirectory: groupsQuery requires as dictionnary."
|
||||
|
||||
isGroupDNOnly = (config.get('groupUIDAttribute').strip() == 'dn')
|
||||
return validate_ldap_query(groups_qry, isDNOnly=isGroupDNOnly)
|
||||
|
||||
|
||||
def determine_ldap_scope(scope):
|
||||
if scope in ("", "sub"):
|
||||
return ldap.SCOPE_SUBTREE
|
||||
elif scope == 'base':
|
||||
return ldap.SCOPE_BASE
|
||||
elif scope == 'one':
|
||||
return ldap.SCOPE_ONELEVEL
|
||||
return None
|
||||
|
||||
|
||||
def determine_deref_aliases(derefAlias):
|
||||
mapping = {
|
||||
"never": ldap.DEREF_NEVER,
|
||||
"search": ldap.DEREF_SEARCHING,
|
||||
"base": ldap.DEREF_FINDING,
|
||||
"always": ldap.DEREF_ALWAYS,
|
||||
}
|
||||
result = None
|
||||
if derefAlias in mapping:
|
||||
result = mapping.get(derefAlias)
|
||||
return result
|
||||
|
||||
|
||||
def openshift_ldap_build_base_query(config):
|
||||
qry = {}
|
||||
if config.get('baseDN'):
|
||||
qry['base'] = config.get('baseDN')
|
||||
|
||||
scope = determine_ldap_scope(config.get('scope'))
|
||||
if scope:
|
||||
qry['scope'] = scope
|
||||
|
||||
pageSize = config.get('pageSize')
|
||||
if pageSize and int(pageSize) > 0:
|
||||
qry['sizelimit'] = int(pageSize)
|
||||
|
||||
timeout = config.get('timeout')
|
||||
if timeout and int(timeout) > 0:
|
||||
qry['timeout'] = int(timeout)
|
||||
|
||||
filter = config.get('filter')
|
||||
if filter:
|
||||
qry['filterstr'] = filter
|
||||
|
||||
derefAlias = determine_deref_aliases(config.get('derefAliases'))
|
||||
if derefAlias:
|
||||
qry['derefAlias'] = derefAlias
|
||||
return qry
|
||||
|
||||
|
||||
def openshift_ldap_get_attribute_for_entry(entry, attribute):
|
||||
attributes = [attribute]
|
||||
if isinstance(attribute, list):
|
||||
attributes = attribute
|
||||
for k in attributes:
|
||||
if k.lower() == 'dn':
|
||||
return entry[0]
|
||||
v = entry[1].get(k, None)
|
||||
if v:
|
||||
if isinstance(v, list):
|
||||
result = []
|
||||
for x in v:
|
||||
if hasattr(x, 'decode'):
|
||||
result.append(x.decode('utf-8'))
|
||||
else:
|
||||
result.append(x)
|
||||
return result
|
||||
else:
|
||||
return v.decode('utf-8') if hasattr(v, 'decode') else v
|
||||
return ""
|
||||
|
||||
|
||||
def ldap_split_host_port(hostport):
|
||||
"""
|
||||
ldap_split_host_port splits a network address of the form "host:port",
|
||||
"host%zone:port", "[host]:port" or "[host%zone]:port" into host or
|
||||
host%zone and port.
|
||||
"""
|
||||
result = dict(
|
||||
scheme=None, netlocation=None, host=None, port=None
|
||||
)
|
||||
if not hostport:
|
||||
return result, None
|
||||
|
||||
# Extract Scheme
|
||||
netlocation = hostport
|
||||
scheme_l = "://"
|
||||
if "://" in hostport:
|
||||
idx = hostport.find(scheme_l)
|
||||
result["scheme"] = hostport[:idx]
|
||||
netlocation = hostport[idx + len(scheme_l):]
|
||||
result["netlocation"] = netlocation
|
||||
|
||||
if netlocation[-1] == ']':
|
||||
# ipv6 literal (with no port)
|
||||
result["host"] = netlocation
|
||||
|
||||
v = netlocation.rsplit(":", 1)
|
||||
if len(v) != 1:
|
||||
try:
|
||||
result["port"] = int(v[1])
|
||||
except ValueError:
|
||||
return None, "Invalid value specified for port: %s" % v[1]
|
||||
result["host"] = v[0]
|
||||
return result, None
|
||||
|
||||
|
||||
def openshift_ldap_query_for_entries(connection, qry, unique_entry=True):
|
||||
# set deref alias (TODO: need to set a default value to reset for each transaction)
|
||||
derefAlias = qry.pop('derefAlias', None)
|
||||
if derefAlias:
|
||||
ldap.set_option(ldap.OPT_DEREF, derefAlias)
|
||||
try:
|
||||
result = connection.search_ext_s(**qry)
|
||||
if not result or len(result) == 0:
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(qry['base'], qry['filterstr'])
|
||||
if len(result) > 1 and unique_entry:
|
||||
if qry.get('scope') == ldap.SCOPE_BASE:
|
||||
return None, "multiple entries found matching dn={0}: {1}".format(qry['base'], result)
|
||||
else:
|
||||
return None, "multiple entries found matching filter {0}: {1}".format(qry['filterstr'], result)
|
||||
return result, None
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
return None, "search for entry with base dn='{0}' refers to a non-existent entry".format(qry['base'])
|
||||
|
||||
|
||||
def openshift_equal_dn_objects(dn_obj, other_dn_obj):
|
||||
if len(dn_obj) != len(other_dn_obj):
|
||||
return False
|
||||
|
||||
for k, v in enumerate(dn_obj):
|
||||
if len(v) != len(other_dn_obj[k]):
|
||||
return False
|
||||
for j, item in enumerate(v):
|
||||
if not (item == other_dn_obj[k][j]):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def openshift_equal_dn(dn, other):
|
||||
dn_obj = ldap.dn.str2dn(dn)
|
||||
other_dn_obj = ldap.dn.str2dn(other)
|
||||
|
||||
return openshift_equal_dn_objects(dn_obj, other_dn_obj)
|
||||
|
||||
|
||||
def openshift_ancestorof_dn(dn, other):
|
||||
dn_obj = ldap.dn.str2dn(dn)
|
||||
other_dn_obj = ldap.dn.str2dn(other)
|
||||
|
||||
if len(dn_obj) >= len(other_dn_obj):
|
||||
return False
|
||||
# Take the last attribute from the other DN to compare against
|
||||
return openshift_equal_dn_objects(dn_obj, other_dn_obj[len(other_dn_obj) - len(dn_obj):])
|
||||
|
||||
|
||||
class OpenshiftLDAPQueryOnAttribute(object):
|
||||
def __init__(self, qry, attribute):
|
||||
# qry retrieves entries from an LDAP server
|
||||
self.qry = copy.deepcopy(qry)
|
||||
# query_attributes is the attribute for a specific filter that, when conjoined with the common filter,
|
||||
# retrieves the specific LDAP entry from the LDAP server. (e.g. "cn", when formatted with "aGroupName"
|
||||
# and conjoined with "objectClass=groupOfNames", becomes (&(objectClass=groupOfNames)(cn=aGroupName))")
|
||||
self.query_attribute = attribute
|
||||
|
||||
@staticmethod
|
||||
def escape_filter(buffer):
|
||||
"""
|
||||
escapes from the provided LDAP filter string the special
|
||||
characters in the set '(', ')', '*', \\ and those out of the range 0 < c < 0x80, as defined in RFC4515.
|
||||
"""
|
||||
output = []
|
||||
hex_string = "0123456789abcdef"
|
||||
for c in buffer:
|
||||
if ord(c) > 0x7f or c in ('(', ')', '\\', '*') or c == 0:
|
||||
first = ord(c) >> 4
|
||||
second = ord(c) & 0xf
|
||||
output += ['\\', hex_string[first], hex_string[second]]
|
||||
else:
|
||||
output.append(c)
|
||||
return ''.join(output)
|
||||
|
||||
def build_request(self, ldapuid, attributes):
|
||||
params = copy.deepcopy(self.qry)
|
||||
if self.query_attribute.lower() == 'dn':
|
||||
if ldapuid:
|
||||
if not openshift_equal_dn(ldapuid, params['base']) and not openshift_ancestorof_dn(params['base'], ldapuid):
|
||||
return None, LDAP_SEARCH_OUT_OF_SCOPE_ERROR
|
||||
params['base'] = ldapuid
|
||||
params['scope'] = ldap.SCOPE_BASE
|
||||
# filter that returns all values
|
||||
params['filterstr'] = "(objectClass=*)"
|
||||
params['attrlist'] = attributes
|
||||
else:
|
||||
# Builds the query containing a filter that conjoins the common filter given
|
||||
# in the configuration with the specific attribute filter for which the attribute value is given
|
||||
specificFilter = "%s=%s" % (self.escape_filter(self.query_attribute), self.escape_filter(ldapuid))
|
||||
qry_filter = params.get('filterstr', None)
|
||||
if qry_filter:
|
||||
params['filterstr'] = "(&%s(%s))" % (qry_filter, specificFilter)
|
||||
params['attrlist'] = attributes
|
||||
return params, None
|
||||
|
||||
def ldap_search(self, connection, ldapuid, required_attributes, unique_entry=True):
|
||||
query, error = self.build_request(ldapuid, required_attributes)
|
||||
if error:
|
||||
return None, error
|
||||
# set deref alias (TODO: need to set a default value to reset for each transaction)
|
||||
derefAlias = query.pop('derefAlias', None)
|
||||
if derefAlias:
|
||||
ldap.set_option(ldap.OPT_DEREF, derefAlias)
|
||||
|
||||
try:
|
||||
result = connection.search_ext_s(**query)
|
||||
if not result or len(result) == 0:
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(query['base'], query['filterstr'])
|
||||
if unique_entry:
|
||||
if len(result) > 1:
|
||||
return None, "Multiple Entries found matching search criteria: %s (%s)" % (query, result)
|
||||
result = result[0]
|
||||
return result, None
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(query['base'], query['filterstr'])
|
||||
except Exception as err:
|
||||
return None, "Request %s failed due to: %s" % (query, err)
|
||||
|
||||
|
||||
class OpenshiftLDAPQuery(object):
|
||||
def __init__(self, qry):
|
||||
# Query retrieves entries from an LDAP server
|
||||
self.qry = qry
|
||||
|
||||
def build_request(self, attributes):
|
||||
params = copy.deepcopy(self.qry)
|
||||
params['attrlist'] = attributes
|
||||
return params
|
||||
|
||||
def ldap_search(self, connection, required_attributes):
|
||||
query = self.build_request(required_attributes)
|
||||
# set deref alias (TODO: need to set a default value to reset for each transaction)
|
||||
derefAlias = query.pop('derefAlias', None)
|
||||
if derefAlias:
|
||||
ldap.set_option(ldap.OPT_DEREF, derefAlias)
|
||||
|
||||
try:
|
||||
result = connection.search_ext_s(**query)
|
||||
if not result or len(result) == 0:
|
||||
return None, "Entry not found for base='{0}' and filter='{1}'".format(query['base'], query['filterstr'])
|
||||
return result, None
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
return None, "search for entry with base dn='{0}' refers to a non-existent entry".format(query['base'])
|
||||
|
||||
|
||||
class OpenshiftLDAPInterface(object):
|
||||
|
||||
def __init__(self, connection, groupQuery, groupNameAttributes, groupMembershipAttributes,
|
||||
userQuery, userNameAttributes, config):
|
||||
|
||||
self.connection = connection
|
||||
self.groupQuery = copy.deepcopy(groupQuery)
|
||||
self.groupNameAttributes = groupNameAttributes
|
||||
self.groupMembershipAttributes = groupMembershipAttributes
|
||||
self.userQuery = copy.deepcopy(userQuery)
|
||||
self.userNameAttributes = userNameAttributes
|
||||
self.config = config
|
||||
|
||||
self.tolerate_not_found = boolean(config.get('tolerateMemberNotFoundErrors', False))
|
||||
self.tolerate_out_of_scope = boolean(config.get('tolerateMemberOutOfScopeErrors', False))
|
||||
|
||||
self.required_group_attributes = [self.groupQuery.query_attribute]
|
||||
for x in self.groupNameAttributes + self.groupMembershipAttributes:
|
||||
if x not in self.required_group_attributes:
|
||||
self.required_group_attributes.append(x)
|
||||
|
||||
self.required_user_attributes = [self.userQuery.query_attribute]
|
||||
for x in self.userNameAttributes:
|
||||
if x not in self.required_user_attributes:
|
||||
self.required_user_attributes.append(x)
|
||||
|
||||
self.cached_groups = {}
|
||||
self.cached_users = {}
|
||||
|
||||
def get_group_entry(self, uid):
|
||||
"""
|
||||
get_group_entry returns an LDAP group entry for the given group UID by searching the internal cache
|
||||
of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry.
|
||||
"""
|
||||
if uid in self.cached_groups:
|
||||
return self.cached_groups.get(uid), None
|
||||
|
||||
group, err = self.groupQuery.ldap_search(self.connection, uid, self.required_group_attributes)
|
||||
if err:
|
||||
return None, err
|
||||
self.cached_groups[uid] = group
|
||||
return group, None
|
||||
|
||||
def get_user_entry(self, uid):
|
||||
"""
|
||||
get_user_entry returns an LDAP group entry for the given user UID by searching the internal cache
|
||||
of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry.
|
||||
"""
|
||||
if uid in self.cached_users:
|
||||
return self.cached_users.get(uid), None
|
||||
|
||||
entry, err = self.userQuery.ldap_search(self.connection, uid, self.required_user_attributes)
|
||||
if err:
|
||||
return None, err
|
||||
self.cached_users[uid] = entry
|
||||
return entry, None
|
||||
|
||||
def exists(self, ldapuid):
|
||||
group, error = self.get_group_entry(ldapuid)
|
||||
return bool(group), error
|
||||
|
||||
def list_groups(self):
|
||||
group_qry = copy.deepcopy(self.groupQuery.qry)
|
||||
group_qry['attrlist'] = self.required_group_attributes
|
||||
|
||||
groups, err = openshift_ldap_query_for_entries(
|
||||
connection=self.connection,
|
||||
qry=group_qry,
|
||||
unique_entry=False
|
||||
)
|
||||
if err:
|
||||
return None, err
|
||||
|
||||
group_uids = []
|
||||
for entry in groups:
|
||||
uid = openshift_ldap_get_attribute_for_entry(entry, self.groupQuery.query_attribute)
|
||||
if not uid:
|
||||
return None, "Unable to find LDAP group uid for entry %s" % entry
|
||||
self.cached_groups[uid] = entry
|
||||
group_uids.append(uid)
|
||||
return group_uids, None
|
||||
|
||||
def extract_members(self, uid):
|
||||
"""
|
||||
returns the LDAP member entries for a group specified with a ldapGroupUID
|
||||
"""
|
||||
# Get group entry from LDAP
|
||||
group, err = self.get_group_entry(uid)
|
||||
if err:
|
||||
return None, err
|
||||
|
||||
# Extract member UIDs from group entry
|
||||
member_uids = []
|
||||
for attribute in self.groupMembershipAttributes:
|
||||
member_uids += openshift_ldap_get_attribute_for_entry(group, attribute)
|
||||
|
||||
members = []
|
||||
for user_uid in member_uids:
|
||||
entry, err = self.get_user_entry(user_uid)
|
||||
if err:
|
||||
if self.tolerate_not_found and err.startswith("Entry not found"):
|
||||
continue
|
||||
elif err == LDAP_SEARCH_OUT_OF_SCOPE_ERROR:
|
||||
continue
|
||||
return None, err
|
||||
members.append(entry)
|
||||
|
||||
return members, None
|
||||
|
||||
|
||||
class OpenshiftLDAPRFC2307(object):
|
||||
|
||||
def __init__(self, config, ldap_connection):
|
||||
|
||||
self.config = config
|
||||
self.ldap_interface = self.create_ldap_interface(ldap_connection)
|
||||
|
||||
def create_ldap_interface(self, connection):
|
||||
segment = self.config.get("rfc2307")
|
||||
groups_base_qry = openshift_ldap_build_base_query(segment['groupsQuery'])
|
||||
users_base_qry = openshift_ldap_build_base_query(segment['usersQuery'])
|
||||
|
||||
groups_query = OpenshiftLDAPQueryOnAttribute(groups_base_qry, segment['groupUIDAttribute'])
|
||||
users_query = OpenshiftLDAPQueryOnAttribute(users_base_qry, segment['userUIDAttribute'])
|
||||
|
||||
params = dict(
|
||||
connection=connection,
|
||||
groupQuery=groups_query,
|
||||
groupNameAttributes=segment['groupNameAttributes'],
|
||||
groupMembershipAttributes=segment['groupMembershipAttributes'],
|
||||
userQuery=users_query,
|
||||
userNameAttributes=segment['userNameAttributes'],
|
||||
config=segment
|
||||
)
|
||||
return OpenshiftLDAPInterface(**params)
|
||||
|
||||
def get_username_for_entry(self, entry):
|
||||
username = openshift_ldap_get_attribute_for_entry(entry, self.ldap_interface.userNameAttributes)
|
||||
if not username:
|
||||
return None, "The user entry (%s) does not map to a OpenShift User name with the given mapping" % entry
|
||||
return username, None
|
||||
|
||||
def get_group_name_for_uid(self, uid):
|
||||
|
||||
# Get name from User defined mapping
|
||||
groupuid_name_mapping = self.config.get("groupUIDNameMapping")
|
||||
if groupuid_name_mapping and uid in groupuid_name_mapping:
|
||||
return groupuid_name_mapping.get(uid), None
|
||||
elif self.ldap_interface.groupNameAttributes:
|
||||
group, err = self.ldap_interface.get_group_entry(uid)
|
||||
if err:
|
||||
return None, err
|
||||
group_name = openshift_ldap_get_attribute_for_entry(group, self.ldap_interface.groupNameAttributes)
|
||||
if not group_name:
|
||||
error = "The group entry (%s) does not map to an OpenShift Group name with the given name attribute (%s)" % (
|
||||
group, self.ldap_interface.groupNameAttributes
|
||||
)
|
||||
return None, error
|
||||
if isinstance(group_name, list):
|
||||
group_name = group_name[0]
|
||||
return group_name, None
|
||||
else:
|
||||
return None, "No OpenShift Group name defined for LDAP group UID: %s" % uid
|
||||
|
||||
def is_ldapgroup_exists(self, uid):
|
||||
group, err = self.ldap_interface.get_group_entry(uid)
|
||||
if err:
|
||||
if err == LDAP_SEARCH_OUT_OF_SCOPE_ERROR or err.startswith("Entry not found") or "non-existent entry" in err:
|
||||
return False, None
|
||||
return False, err
|
||||
if group:
|
||||
return True, None
|
||||
return False, None
|
||||
|
||||
def list_groups(self):
|
||||
return self.ldap_interface.list_groups()
|
||||
|
||||
def extract_members(self, uid):
|
||||
return self.ldap_interface.extract_members(uid)
|
||||
|
||||
|
||||
class OpenshiftLDAP_ADInterface(object):
|
||||
|
||||
def __init__(self, connection, user_query, group_member_attr, user_name_attr):
|
||||
self.connection = connection
|
||||
self.userQuery = user_query
|
||||
self.groupMembershipAttributes = group_member_attr
|
||||
self.userNameAttributes = user_name_attr
|
||||
|
||||
self.required_user_attributes = self.userNameAttributes or []
|
||||
for attr in self.groupMembershipAttributes:
|
||||
if attr not in self.required_user_attributes:
|
||||
self.required_user_attributes.append(attr)
|
||||
|
||||
self.cache = {}
|
||||
self.cache_populated = False
|
||||
|
||||
def is_entry_present(self, cache_item, entry):
|
||||
for item in cache_item:
|
||||
if item[0] == entry[0]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def populate_cache(self):
|
||||
if not self.cache_populated:
|
||||
self.cache_populated = True
|
||||
entries, err = self.userQuery.ldap_search(self.connection, self.required_user_attributes)
|
||||
if err:
|
||||
return err
|
||||
|
||||
for entry in entries:
|
||||
for group_attr in self.groupMembershipAttributes:
|
||||
uids = openshift_ldap_get_attribute_for_entry(entry, group_attr)
|
||||
if not isinstance(uids, list):
|
||||
uids = [uids]
|
||||
for uid in uids:
|
||||
if uid not in self.cache:
|
||||
self.cache[uid] = []
|
||||
if not self.is_entry_present(self.cache[uid], entry):
|
||||
self.cache[uid].append(entry)
|
||||
return None
|
||||
|
||||
def list_groups(self):
|
||||
err = self.populate_cache()
|
||||
if err:
|
||||
return None, err
|
||||
result = []
|
||||
if self.cache:
|
||||
result = self.cache.keys()
|
||||
return result, None
|
||||
|
||||
def extract_members(self, uid):
|
||||
# ExtractMembers returns the LDAP member entries for a group specified with a ldapGroupUID
|
||||
# if we already have it cached, return the cached value
|
||||
if uid in self.cache:
|
||||
return self.cache[uid], None
|
||||
|
||||
# This happens in cases where we did not list out every group.
|
||||
# In that case, we're going to be asked about specific groups.
|
||||
users_in_group = []
|
||||
for attr in self.groupMembershipAttributes:
|
||||
query_on_attribute = OpenshiftLDAPQueryOnAttribute(self.userQuery.qry, attr)
|
||||
entries, error = query_on_attribute.ldap_search(self.connection, uid, self.required_user_attributes, unique_entry=False)
|
||||
if error and "not found" not in error:
|
||||
return None, error
|
||||
if not entries:
|
||||
continue
|
||||
|
||||
for entry in entries:
|
||||
if not self.is_entry_present(users_in_group, entry):
|
||||
users_in_group.append(entry)
|
||||
|
||||
self.cache[uid] = users_in_group
|
||||
return users_in_group, None
|
||||
|
||||
|
||||
class OpenshiftLDAPActiveDirectory(object):
|
||||
|
||||
def __init__(self, config, ldap_connection):
|
||||
|
||||
self.config = config
|
||||
self.ldap_interface = self.create_ldap_interface(ldap_connection)
|
||||
|
||||
def create_ldap_interface(self, connection):
|
||||
segment = self.config.get("activeDirectory")
|
||||
base_query = openshift_ldap_build_base_query(segment['usersQuery'])
|
||||
user_query = OpenshiftLDAPQuery(base_query)
|
||||
|
||||
return OpenshiftLDAP_ADInterface(
|
||||
connection=connection,
|
||||
user_query=user_query,
|
||||
group_member_attr=segment["groupMembershipAttributes"],
|
||||
user_name_attr=segment["userNameAttributes"],
|
||||
)
|
||||
|
||||
def get_username_for_entry(self, entry):
|
||||
username = openshift_ldap_get_attribute_for_entry(entry, self.ldap_interface.userNameAttributes)
|
||||
if not username:
|
||||
return None, "The user entry (%s) does not map to a OpenShift User name with the given mapping" % entry
|
||||
return username, None
|
||||
|
||||
def get_group_name_for_uid(self, uid):
|
||||
return uid, None
|
||||
|
||||
def is_ldapgroup_exists(self, uid):
|
||||
members, error = self.extract_members(uid)
|
||||
if error:
|
||||
return False, error
|
||||
exists = members and len(members) > 0
|
||||
return exists, None
|
||||
|
||||
def list_groups(self):
|
||||
return self.ldap_interface.list_groups()
|
||||
|
||||
def extract_members(self, uid):
|
||||
return self.ldap_interface.extract_members(uid)
|
||||
|
||||
|
||||
class OpenshiftLDAP_AugmentedADInterface(OpenshiftLDAP_ADInterface):
|
||||
|
||||
def __init__(self, connection, user_query, group_member_attr, user_name_attr, group_qry, group_name_attr):
|
||||
super(OpenshiftLDAP_AugmentedADInterface, self).__init__(
|
||||
connection, user_query, group_member_attr, user_name_attr
|
||||
)
|
||||
self.groupQuery = copy.deepcopy(group_qry)
|
||||
self.groupNameAttributes = group_name_attr
|
||||
|
||||
self.required_group_attributes = [self.groupQuery.query_attribute]
|
||||
for x in self.groupNameAttributes:
|
||||
if x not in self.required_group_attributes:
|
||||
self.required_group_attributes.append(x)
|
||||
|
||||
self.cached_groups = {}
|
||||
|
||||
def get_group_entry(self, uid):
|
||||
"""
|
||||
get_group_entry returns an LDAP group entry for the given group UID by searching the internal cache
|
||||
of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry.
|
||||
"""
|
||||
if uid in self.cached_groups:
|
||||
return self.cached_groups.get(uid), None
|
||||
|
||||
group, err = self.groupQuery.ldap_search(self.connection, uid, self.required_group_attributes)
|
||||
if err:
|
||||
return None, err
|
||||
self.cached_groups[uid] = group
|
||||
return group, None
|
||||
|
||||
def exists(self, ldapuid):
|
||||
# Get group members
|
||||
members, error = self.extract_members(ldapuid)
|
||||
if error:
|
||||
return False, error
|
||||
group_exists = bool(members)
|
||||
|
||||
# Check group Existence
|
||||
entry, error = self.get_group_entry(ldapuid)
|
||||
if error:
|
||||
if "not found" in error:
|
||||
return False, None
|
||||
else:
|
||||
return False, error
|
||||
else:
|
||||
return group_exists and bool(entry), None
|
||||
|
||||
|
||||
class OpenshiftLDAPAugmentedActiveDirectory(OpenshiftLDAPRFC2307):
|
||||
|
||||
def __init__(self, config, ldap_connection):
|
||||
|
||||
self.config = config
|
||||
self.ldap_interface = self.create_ldap_interface(ldap_connection)
|
||||
|
||||
def create_ldap_interface(self, connection):
|
||||
segment = self.config.get("augmentedActiveDirectory")
|
||||
user_base_query = openshift_ldap_build_base_query(segment['usersQuery'])
|
||||
groups_base_qry = openshift_ldap_build_base_query(segment['groupsQuery'])
|
||||
|
||||
user_query = OpenshiftLDAPQuery(user_base_query)
|
||||
groups_query = OpenshiftLDAPQueryOnAttribute(groups_base_qry, segment['groupUIDAttribute'])
|
||||
|
||||
return OpenshiftLDAP_AugmentedADInterface(
|
||||
connection=connection,
|
||||
user_query=user_query,
|
||||
group_member_attr=segment["groupMembershipAttributes"],
|
||||
user_name_attr=segment["userNameAttributes"],
|
||||
group_qry=groups_query,
|
||||
group_name_attr=segment["groupNameAttributes"]
|
||||
)
|
||||
|
||||
def is_ldapgroup_exists(self, uid):
|
||||
return self.ldap_interface.exists(uid)
|
||||
226
plugins/modules/openshift_adm_groups_sync.py
Normal file
226
plugins/modules/openshift_adm_groups_sync.py
Normal file
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
|
||||
module: openshift_adm_groups_sync
|
||||
|
||||
short_description: Sync OpenShift Groups with records from an external provider.
|
||||
|
||||
version_added: "2.1.0"
|
||||
|
||||
author:
|
||||
- Aubin Bikouo (@abikouo)
|
||||
|
||||
description:
|
||||
- In order to sync/prune OpenShift Group records with those from an external provider, determine which Groups you wish to sync
|
||||
and where their records live.
|
||||
- Analogous to `oc adm prune groups` and `oc adm group sync`.
|
||||
- LDAP sync configuration file syntax can be found here
|
||||
U(https://docs.openshift.com/container-platform/4.9/authentication/ldap-syncing.html).
|
||||
- The bindPassword attribute of the LDAP sync configuration is expected to be a string,
|
||||
please use ansible-vault encryption to secure this information.
|
||||
|
||||
extends_documentation_fragment:
|
||||
- kubernetes.core.k8s_auth_options
|
||||
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Determines if the group should be sync when set to C(present) or pruned when set to C(absent).
|
||||
type: str
|
||||
default: present
|
||||
choices: [ absent, present ]
|
||||
type:
|
||||
description:
|
||||
- which groups allow and deny list entries refer to.
|
||||
type: str
|
||||
default: ldap
|
||||
choices: [ ldap, openshift ]
|
||||
sync_config:
|
||||
description:
|
||||
- Provide a valid YAML definition of an LDAP sync configuration.
|
||||
type: dict
|
||||
aliases:
|
||||
- config
|
||||
- src
|
||||
required: True
|
||||
deny_groups:
|
||||
description:
|
||||
- Denied groups, could be openshift group name or LDAP group dn value.
|
||||
- When parameter C(type) is set to I(ldap) this should contains only LDAP group definition
|
||||
like I(cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat).
|
||||
- The elements specified in this list will override the ones specified in C(allow_groups).
|
||||
type: list
|
||||
elements: str
|
||||
allow_groups:
|
||||
description:
|
||||
- Allowed groups, could be openshift group name or LDAP group dn value.
|
||||
- When parameter C(type) is set to I(ldap) this should contains only LDAP group definition
|
||||
like I(cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat).
|
||||
type: list
|
||||
elements: str
|
||||
|
||||
requirements:
|
||||
- python >= 3.6
|
||||
- kubernetes >= 12.0.0
|
||||
- python-ldap
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Prune all orphaned groups
|
||||
- name: Prune all orphan groups
|
||||
openshift_adm_groups_sync:
|
||||
state: absent
|
||||
src: "{{ lookup('file', '/path/to/ldap-sync-config.yaml') | from_yaml }}"
|
||||
|
||||
# Prune all orphaned groups from a list of specific groups specified in allow_groups
|
||||
- name: Prune all orphan groups from a list of specific groups specified in allow_groups
|
||||
openshift_adm_groups_sync:
|
||||
state: absent
|
||||
src: "{{ lookup('file', '/path/to/ldap-sync-config.yaml') | from_yaml }}"
|
||||
allow_groups:
|
||||
- cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat
|
||||
- cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat
|
||||
|
||||
# Sync all groups from an LDAP server
|
||||
- name: Sync all groups from an LDAP server
|
||||
openshift_adm_groups_sync:
|
||||
src:
|
||||
kind: LDAPSyncConfig
|
||||
apiVersion: v1
|
||||
url: ldap://localhost:1390
|
||||
insecure: true
|
||||
bindDN: cn=admin,dc=example,dc=org
|
||||
bindPassword: adminpassword
|
||||
rfc2307:
|
||||
groupsQuery:
|
||||
baseDN: "cn=admins,ou=groups,dc=example,dc=org"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
filter: (objectClass=*)
|
||||
pageSize: 0
|
||||
groupUIDAttribute: dn
|
||||
groupNameAttributes: [ cn ]
|
||||
groupMembershipAttributes: [ member ]
|
||||
usersQuery:
|
||||
baseDN: "ou=users,dc=example,dc=org"
|
||||
scope: sub
|
||||
derefAliases: never
|
||||
pageSize: 0
|
||||
userUIDAttribute: dn
|
||||
userNameAttributes: [ mail ]
|
||||
tolerateMemberNotFoundErrors: true
|
||||
tolerateMemberOutOfScopeErrors: true
|
||||
|
||||
# Sync all groups except the ones from the deny_groups from an LDAP server
|
||||
- name: Sync all groups from an LDAP server using deny_groups
|
||||
openshift_adm_groups_sync:
|
||||
src: "{{ lookup('file', '/path/to/ldap-sync-config.yaml') | from_yaml }}"
|
||||
deny_groups:
|
||||
- cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat
|
||||
- cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat
|
||||
|
||||
# Sync all OpenShift Groups that have been synced previously with an LDAP server
|
||||
- name: Sync all OpenShift Groups that have been synced previously with an LDAP server
|
||||
openshift_adm_groups_sync:
|
||||
src: "{{ lookup('file', '/path/to/ldap-sync-config.yaml') | from_yaml }}"
|
||||
type: openshift
|
||||
"""
|
||||
|
||||
|
||||
RETURN = r"""
|
||||
builds:
|
||||
description:
|
||||
- The groups that were created, updated or deleted
|
||||
returned: success
|
||||
type: list
|
||||
elements: dict
|
||||
sample: [
|
||||
{
|
||||
"apiVersion": "user.openshift.io/v1",
|
||||
"kind": "Group",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"openshift.io/ldap.sync-time": "2021-12-17T12:20:28.125282",
|
||||
"openshift.io/ldap.uid": "cn=developers,ou=groups,ou=rfc2307,dc=ansible,dc=redhat",
|
||||
"openshift.io/ldap.url": "localhost:1390"
|
||||
},
|
||||
"creationTimestamp": "2021-12-17T11:09:49Z",
|
||||
"labels": {
|
||||
"openshift.io/ldap.host": "localhost"
|
||||
},
|
||||
"managedFields": [{
|
||||
"apiVersion": "user.openshift.io/v1",
|
||||
"fieldsType": "FieldsV1",
|
||||
"fieldsV1": {
|
||||
"f:metadata": {
|
||||
"f:annotations": {
|
||||
".": {},
|
||||
"f:openshift.io/ldap.sync-time": {},
|
||||
"f:openshift.io/ldap.uid": {},
|
||||
"f:openshift.io/ldap.url": {}
|
||||
},
|
||||
"f:labels": {
|
||||
".": {},
|
||||
"f:openshift.io/ldap.host": {}
|
||||
}
|
||||
},
|
||||
"f:users": {}
|
||||
},
|
||||
"manager": "OpenAPI-Generator",
|
||||
"operation": "Update",
|
||||
"time": "2021-12-17T11:09:49Z"
|
||||
}],
|
||||
"name": "developers",
|
||||
"resourceVersion": "2014696",
|
||||
"uid": "8dc211cb-1544-41e1-96b1-efffeed2d7d7"
|
||||
},
|
||||
"users": ["jordanbulls@ansible.org"]
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
import copy
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC
|
||||
|
||||
|
||||
def argument_spec():
|
||||
args = copy.deepcopy(AUTH_ARG_SPEC)
|
||||
args.update(
|
||||
dict(
|
||||
state=dict(type='str', choices=['absent', 'present'], default='present'),
|
||||
type=dict(type='str', choices=['ldap', 'openshift'], default='ldap'),
|
||||
sync_config=dict(type='dict', aliases=['config', 'src'], required=True),
|
||||
deny_groups=dict(type='list', elements='str', default=[]),
|
||||
allow_groups=dict(type='list', elements='str', default=[]),
|
||||
)
|
||||
)
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(argument_spec=argument_spec(), supports_check_mode=True)
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_groups import (
|
||||
OpenshiftGroupsSync
|
||||
)
|
||||
|
||||
try:
|
||||
openshift_groups = OpenshiftGroupsSync(module)
|
||||
openshift_groups.execute_module()
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e), exception=traceback.format_exc())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
32
tests/unit/plugins/module_utils/test_ldap_dn.py
Normal file
32
tests/unit/plugins/module_utils/test_ldap_dn.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_ldap import (
|
||||
openshift_equal_dn,
|
||||
openshift_ancestorof_dn
|
||||
)
|
||||
import pytest
|
||||
|
||||
try:
|
||||
import ldap
|
||||
except ImportError:
|
||||
pytestmark = pytest.mark.skip("This test requires the python-ldap library")
|
||||
|
||||
|
||||
def test_equal_dn():
|
||||
|
||||
assert openshift_equal_dn("cn=unit,ou=users,dc=ansible,dc=com", "cn=unit,ou=users,dc=ansible,dc=com")
|
||||
assert not openshift_equal_dn("cn=unit,ou=users,dc=ansible,dc=com", "cn=units,ou=users,dc=ansible,dc=com")
|
||||
assert not openshift_equal_dn("cn=unit,ou=users,dc=ansible,dc=com", "cn=unit,ou=user,dc=ansible,dc=com")
|
||||
assert not openshift_equal_dn("cn=unit,ou=users,dc=ansible,dc=com", "cn=unit,ou=users,dc=ansible,dc=org")
|
||||
|
||||
|
||||
def test_ancestor_of_dn():
|
||||
|
||||
assert not openshift_ancestorof_dn("cn=unit,ou=users,dc=ansible,dc=com", "cn=unit,ou=users,dc=ansible,dc=com")
|
||||
assert not openshift_ancestorof_dn("cn=unit,ou=users,dc=ansible,dc=com", "cn=units,ou=users,dc=ansible,dc=com")
|
||||
assert openshift_ancestorof_dn("ou=users,dc=ansible,dc=com", "cn=john,ou=users,dc=ansible,dc=com")
|
||||
assert openshift_ancestorof_dn("ou=users,dc=ansible,dc=com", "cn=mathew,ou=users,dc=ansible,dc=com")
|
||||
assert not openshift_ancestorof_dn("ou=users,dc=ansible,dc=com", "cn=mathew,ou=users,dc=ansible,dc=org")
|
||||
63
tests/unit/plugins/module_utils/test_ldap_sync_config.py
Normal file
63
tests/unit/plugins/module_utils/test_ldap_sync_config.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.okd.plugins.module_utils.openshift_ldap import (
|
||||
validate_ldap_sync_config,
|
||||
)
|
||||
|
||||
|
||||
def test_missing_url():
|
||||
config = dict(
|
||||
kind="LDAPSyncConfig",
|
||||
apiVersion="v1",
|
||||
insecure=True
|
||||
)
|
||||
err = validate_ldap_sync_config(config)
|
||||
assert err == "url should be non empty attribute."
|
||||
|
||||
|
||||
def test_binddn_and_bindpwd_linked():
|
||||
"""
|
||||
one of bind_dn and bind_pwd cannot be set alone
|
||||
"""
|
||||
config = dict(
|
||||
kind="LDAPSyncConfig",
|
||||
apiVersion="v1",
|
||||
url="ldap://LDAP_SERVICE_IP:389",
|
||||
insecure=True,
|
||||
bindDN="cn=admin,dc=example,dc=org"
|
||||
)
|
||||
|
||||
credentials_error = "bindDN and bindPassword must both be specified, or both be empty."
|
||||
|
||||
assert validate_ldap_sync_config(config) == credentials_error
|
||||
|
||||
config = dict(
|
||||
kind="LDAPSyncConfig",
|
||||
apiVersion="v1",
|
||||
url="ldap://LDAP_SERVICE_IP:389",
|
||||
insecure=True,
|
||||
bindPassword="testing1223"
|
||||
)
|
||||
|
||||
assert validate_ldap_sync_config(config) == credentials_error
|
||||
|
||||
|
||||
def test_insecure_connection():
|
||||
config = dict(
|
||||
kind="LDAPSyncConfig",
|
||||
apiVersion="v1",
|
||||
url="ldaps://LDAP_SERVICE_IP:389",
|
||||
insecure=True,
|
||||
)
|
||||
|
||||
assert validate_ldap_sync_config(config) == "Cannot use ldaps scheme with insecure=true."
|
||||
|
||||
config.update(dict(
|
||||
url="ldap://LDAP_SERVICE_IP:389",
|
||||
ca="path/to/ca/file"
|
||||
))
|
||||
|
||||
assert validate_ldap_sync_config(config) == "Cannot specify a ca with insecure=true."
|
||||
Reference in New Issue
Block a user