Skip to content

Commit

Permalink
Issue 5521 - RFE - split pass through auth cli
Browse files Browse the repository at this point in the history
Bug Description: The pass through auth cli previously
was a "merge" of both ldap pass through and pam pass through. These
two do not share any commonality, and actually conflict on each other.
This caused a lot of confusion, especially in documentation where it
wasn't clear how to use either feature as a result.

Fix Description: Split the cli into two seperate plugins with their own
config domains. This clarifies the situation for users, and makes it far
easier to configure the various pass through layers.

fixes: #5521

Author: William Brown <william@blackhats.net.au>

Review by: @mreynolds389 @droideck (Thanks!)
  • Loading branch information
Firstyear committed Dec 6, 2022
1 parent 156aa1e commit 8a6e7be
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 299 deletions.
6 changes: 4 additions & 2 deletions src/lib389/lib389/cli_conf/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
from lib389.cli_conf.plugins import dna as cli_dna
from lib389.cli_conf.plugins import linkedattr as cli_linkedattr
from lib389.cli_conf.plugins import managedentries as cli_managedentries
from lib389.cli_conf.plugins import passthroughauth as cli_passthroughauth
from lib389.cli_conf.plugins import pampassthrough as cli_pampassthrough
from lib389.cli_conf.plugins import ldappassthrough as cli_ldappassthrough
from lib389.cli_conf.plugins import retrochangelog as cli_retrochangelog
from lib389.cli_conf.plugins import automember as cli_automember
from lib389.cli_conf.plugins import posix_winsync as cli_posix_winsync
Expand Down Expand Up @@ -110,9 +111,10 @@ def create_parser(subparsers):
cli_accountpolicy.create_parser(subcommands)
cli_attruniq.create_parser(subcommands)
cli_dna.create_parser(subcommands)
cli_ldappassthrough.create_parser(subcommands)
cli_linkedattr.create_parser(subcommands)
cli_managedentries.create_parser(subcommands)
cli_passthroughauth.create_parser(subcommands)
cli_pampassthrough.create_parser(subcommands)
cli_retrochangelog.create_parser(subcommands)
cli_posix_winsync.create_parser(subcommands)
cli_contentsync.create_parser(subcommands)
Expand Down
156 changes: 156 additions & 0 deletions src/lib389/lib389/cli_conf/plugins/ldappassthrough.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# --- BEGIN COPYRIGHT BLOCK ---
# Copyright (C) 2020 Red Hat, Inc.
# Copyright (C) 2022 William Brown <william@blackhats.net.au>
# All rights reserved.
#
# License: GPL (version 3 or any later version).
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---

import json
import ldap
from lib389.plugins import (PassThroughAuthenticationPlugin)

from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add, generic_show, generic_enable, generic_disable, generic_status


def _get_url_next_num(url_attrs):
existing_nums = list(map(lambda url: int(url.split('nsslapd-pluginarg')[1]),
[i for i, _ in url_attrs.items()]))
if len(existing_nums) > 0:
existing_nums.sort()
full_num_list = list(range(existing_nums[-1]+2))
if not full_num_list:
next_num_list = ["0"]
else:
next_num_list = list(filter(lambda x: x not in existing_nums, full_num_list))
else:
next_num_list = ["0"]

return next_num_list[0]

def _get_url_attr(url_attrs, this_url):
for attr, val in url_attrs.items():
if val.lower() == this_url.lower():
return attr

return "nsslapd-pluginarg0"

def _validate_url(url):
failed = False
if len(url.split(" ")) == 2:
link = url.split(" ")[0]
params = url.split(" ")[1]
else:
link = url
params = ""

if (":" not in link) or ("//" not in link) or ("/" not in link) or (params and "," not in params):
failed = False

if not ldap.dn.is_dn(link.split("/")[-1]):
raise ValueError("Subtree is an invalid DN")

if params and len(params.split(",")) != 6 and not all(map(str.isdigit, params.split(","))):
failed = False

if failed:
raise ValueError("URL should be in one of the next formats (all parameters after a space should be digits): "
"'ldap|ldaps://authDS/subtree maxconns,maxops,timeout,ldver,connlifetime,startTLS' or "
"'ldap|ldaps://authDS/subtree'")
return url


def pta_list(inst, basedn, log, args):
log = log.getChild('pta_list')
plugin = PassThroughAuthenticationPlugin(inst)
urls = plugin.get_urls()
if args.json:
log.info(json.dumps({"type": "list",
"items": [{"id": id, "url": value} for id, value in urls.items()]},
indent=4))
else:
if len(urls) > 0:
for _, value in urls.items():
log.info(value)
else:
log.info("No Pass Through Auth URLs were found")


def pta_add(inst, basedn, log, args):
log = log.getChild('pta_add')
new_url_l = _validate_url(args.URL.lower())
plugin = PassThroughAuthenticationPlugin(inst)
url_attrs = plugin.get_urls()
urls = list(map(lambda url: url.lower(),
[i for _, i in url_attrs.items()]))
next_num = _get_url_next_num(url_attrs)
if new_url_l in urls:
raise ldap.ALREADY_EXISTS("Entry %s already exists" % args.URL)
plugin.add("nsslapd-pluginarg%s" % next_num, args.URL)


def pta_edit(inst, basedn, log, args):
log = log.getChild('pta_edit')
plugin = PassThroughAuthenticationPlugin(inst)
url_attrs = plugin.get_urls()
urls = list(map(lambda url: url.lower(),
[i for _, i in url_attrs.items()]))
_validate_url(args.NEW_URL.lower())
old_url_l = args.OLD_URL.lower()
if old_url_l not in urls:
raise ValueError("URL %s doesn't exist." % args.OLD_URL)
else:
for attr, value in url_attrs.items():
if value.lower() == old_url_l:
plugin.remove(attr, old_url_l)
break
plugin.add("%s" % _get_url_attr(url_attrs, old_url_l), args.NEW_URL)


def pta_del(inst, basedn, log, args):
log = log.getChild('pta_del')
plugin = PassThroughAuthenticationPlugin(inst)
url_attrs = plugin.get_urls()
urls = list(map(lambda url: url.lower(),
[i for _, i in url_attrs.items()]))
old_url_l = args.URL.lower()
if old_url_l not in urls:
raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.URL)

plugin.remove_all("%s" % _get_url_attr(url_attrs, old_url_l))
log.info("Successfully deleted %s", args.URL)


def create_parser(subparsers):
passthroughauth_parser = subparsers.add_parser('ldap-pass-through-auth',
help='Manage and configure LDAP Pass-Through Authentication Plugin')
subcommands = passthroughauth_parser.add_subparsers(help='action')

add_generic_plugin_parsers(subcommands, PassThroughAuthenticationPlugin)

list_urls = subcommands.add_parser('list', help='Lists LDAP URLs')
list_urls.set_defaults(func=pta_list)

# url = subcommands.add_parser('url', help='Manage PTA LDAP URL configurations')
# subcommands_url = url.add_subparsers(help='action')

add_url = subcommands.add_parser('add', help='Add an LDAP url to the config entry')
add_url.add_argument('URL',
help='The full LDAP URL in format '
'"ldap|ldaps://authDS/subtree maxconns,maxops,timeout,ldver,connlifetime,startTLS". '
'If one optional parameter is specified the rest should be specified too')
add_url.set_defaults(func=pta_add)

edit_url = subcommands.add_parser('modify', help='Edit the LDAP pass through config entry')
edit_url.add_argument('OLD_URL', help='The full LDAP URL you get from the "list" command')
edit_url.add_argument('NEW_URL',
help='Sets the full LDAP URL in format '
'"ldap|ldaps://authDS/subtree maxconns,maxops,timeout,ldver,connlifetime,startTLS". '
'If one optional parameter is specified the rest should be specified too.')
edit_url.set_defaults(func=pta_edit)

delete_url = subcommands.add_parser('delete', help='Delete a URL from the config entry')
delete_url.add_argument('URL', help='The full LDAP URL you get from the "list" command')
delete_url.set_defaults(func=pta_del)

133 changes: 133 additions & 0 deletions src/lib389/lib389/cli_conf/plugins/pampassthrough.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# --- BEGIN COPYRIGHT BLOCK ---
# Copyright (C) 2022 William Brown <william@blackhats.net.au>
# All rights reserved.
#
# License: GPL (version 3 or any later version).
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---

import json
import ldap
from lib389.plugins import (PAMPassThroughAuthPlugin,
PAMPassThroughAuthConfigs, PAMPassThroughAuthConfig)

from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add


arg_to_attr_pam = {
'exclude_suffix': 'pamExcludeSuffix',
'include_suffix': 'pamIncludeSuffix',
'missing_suffix': 'pamMissingSuffix',
'filter': 'pamFilter',
'id_attr': 'pamIDAttr',
'id_map_method': 'pamIDMapMethod',
'fallback': 'pamFallback',
'secure': 'pamSecure',
'service': 'pamService'
}


def pam_pta_list(inst, basedn, log, args):
log = log.getChild('pam_pta_list')
configs = PAMPassThroughAuthConfigs(inst)
result = []
result_json = []
for config in configs.list():
if args.json:
result_json.append(json.loads(config.get_all_attrs_json()))
else:
result.append(config.rdn)
if args.json:
log.info(json.dumps({"type": "list", "items": result_json}, indent=4))
else:
if len(result) > 0:
for i in result:
log.info(i)
else:
log.info("No PAM Pass Through Auth plugin config instances")


def pam_pta_add(inst, basedn, log, args):
log = log.getChild('pam_pta_add')
plugin = PAMPassThroughAuthPlugin(inst)
props = {'cn': args.NAME}
generic_object_add(PAMPassThroughAuthConfig, inst, log, args, arg_to_attr_pam, basedn=plugin.dn, props=props)


def pam_pta_edit(inst, basedn, log, args):
log = log.getChild('pam_pta_edit')
configs = PAMPassThroughAuthConfigs(inst)
config = configs.get(args.NAME)
generic_object_edit(config, log, args, arg_to_attr_pam)


def pam_pta_show(inst, basedn, log, args):
log = log.getChild('pam_pta_show')
configs = PAMPassThroughAuthConfigs(inst)
config = configs.get(args.NAME)

if not config.exists():
raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)
if args and args.json:
o_str = config.get_all_attrs_json()
log.info(o_str)
else:
log.info(config.display())


def pam_pta_del(inst, basedn, log, args):
log = log.getChild('pam_pta_del')
configs = PAMPassThroughAuthConfigs(inst)
config = configs.get(args.NAME)
config.delete()
log.info("Successfully deleted the %s", config.dn)


def _add_parser_args_pam(parser):
parser.add_argument('--exclude-suffix', nargs='+',
help='Specifies a suffix to exclude from PAM authentication (pamExcludeSuffix)')
parser.add_argument('--include-suffix', nargs='+',
help='Sets a suffix to include for PAM authentication (pamIncludeSuffix)')
parser.add_argument('--missing-suffix', choices=['ERROR', 'ALLOW', 'IGNORE', 'delete', ''],
help='Identifies how to handle missing include or exclude suffixes (pamMissingSuffix)')
parser.add_argument('--filter',
help='Sets an LDAP filter to use to identify specific entries within '
'the included suffixes for which to use PAM pass-through authentication (pamFilter)')
parser.add_argument('--id-attr',
help='Contains the attribute name which is used to hold the PAM user ID (pamIDAttr)')
parser.add_argument('--id_map_method',
help='Sets the method to use to map the LDAP bind DN to a PAM identity (pamIDMapMethod)')
parser.add_argument('--fallback', choices=['TRUE', 'FALSE'], type=str.upper,
help='Sets whether to fallback to regular LDAP authentication '
'if PAM authentication fails (pamFallback)')
parser.add_argument('--secure', choices=['TRUE', 'FALSE'], type=str.upper,
help='Requires secure TLS connection for PAM authentication (pamSecure)')
parser.add_argument('--service',
help='Contains the service name to pass to PAM (pamService)')


def create_parser(subparsers):
passthroughauth_parser = subparsers.add_parser('pam-pass-through-auth',
help='Manage and configure Pass-Through Authentication plugins '
'(LDAP URLs and PAM)')
subcommands = passthroughauth_parser.add_subparsers(help='action')

add_generic_plugin_parsers(subcommands, PAMPassThroughAuthPlugin)

list_pam = subcommands.add_parser('list', help='Lists PAM configurations')
list_pam.set_defaults(func=pam_pta_list)

pam = subcommands.add_parser('config', help='Manage PAM PTA configurations.')
pam.add_argument('NAME', help='The PAM PTA configuration name')
subcommands_pam = pam.add_subparsers(help='action')

add = subcommands_pam.add_parser('add', help='Add the config entry')
add.set_defaults(func=pam_pta_add)
_add_parser_args_pam(add)
edit = subcommands_pam.add_parser('set', help='Edit the config entry')
edit.set_defaults(func=pam_pta_edit)
_add_parser_args_pam(edit)
show = subcommands_pam.add_parser('show', help='Display the config entry')
show.set_defaults(func=pam_pta_show)
delete = subcommands_pam.add_parser('delete', help='Delete the config entry')
delete.set_defaults(func=pam_pta_del)
Loading

0 comments on commit 8a6e7be

Please sign in to comment.