aker-gateway / Aker

SSH bastion/jump host/jumpserver
Other
568 stars 81 forks source link

[RFE] Integration with Identity provider (FreeIPA) #2

Closed anazmy closed 7 years ago

anazmy commented 7 years ago

Relaying on local config file to populate users , their privileges etc.. wont work for big scale environments.

Aker needs to integrate with a central Identity provider - for starters FreeIPA - to :

anazmy commented 7 years ago

The approach should be like this :

anazmy commented 7 years ago

Since FreeIPA has no property to define "ssh port" for every host , I will relay on a "single" config value to define "ssh port" for all hosts .

anazmy commented 7 years ago

Just a POC

#!/usr/bin/env python

import argparse
import json

from ipalib import api, errors, output, util
from ipalib import Command, Str, Flag, Int
from types import NoneType
from ipalib.cli import to_cli
from ipalib import _, ngettext
from ipapython.dn import DN
from ipalib.plugable import Registry
import pyhbac

def initialize():
    '''
    This function initializes the FreeIPA/IPA API. This function requires
    no arguments. A kerberos key must be present in the users keyring in
    order for this to work.
    '''

    api.bootstrap(context='cli')
    api.finalize()
    try:
        api.Backend.rpcclient.connect()
    except AttributeError:
        api.Backend.xmlclient.connect() #FreeIPA < 4.0 compatibility

    return api

def list_hosts(api):
    '''
    This function prints a list of all hosts. This function requires
    one argument, the FreeIPA/IPA API object.
    '''

    ssh_port = 22
    result = api.Command.host_find()['result']
    members = []
    for host in result:
        hostname = host['fqdn']
        if isinstance(hostname, (tuple, list)):
            hostname = hostname[0]
            inventory.append(hostname)
        #print(hostname)

    return None

def convert_to_ipa_rule(rule):
    # convert a dict with a rule to an pyhbac rule
    ipa_rule = pyhbac.HbacRule(rule['cn'][0])
    ipa_rule.enabled = rule['ipaenabledflag'][0]
    # Following code attempts to process rule systematically
    structure = \
        (('user',       'memberuser',    'user',    'group',        ipa_rule.users),
         ('host',       'memberhost',    'host',    'hostgroup',    ipa_rule.targethosts),
         ('sourcehost', 'sourcehost',    'host',    'hostgroup',    ipa_rule.srchosts),
         ('service',    'memberservice', 'hbacsvc', 'hbacsvcgroup', ipa_rule.services),
        )
    for element in structure:
        category = '%scategory' % (element[0])
        if (category in rule and rule[category][0] == u'all') or (element[0] == 'sourcehost'):
            # rule applies to all elements
            # sourcehost is always set to 'all'
            element[4].category = set([pyhbac.HBAC_CATEGORY_ALL])
        else:
            # rule is about specific entities
            # Check if there are explicitly listed entities
            attr_name = '%s_%s' % (element[1], element[2])
            if attr_name in rule:
                element[4].names = rule[attr_name]
            # Now add groups of entities if they are there
            attr_name = '%s_%s' % (element[1], element[3])
            if attr_name in rule:
                element[4].groups = rule[attr_name]
    if 'externalhost' in rule:
            ipa_rule.srchosts.names.extend(rule['externalhost']) #pylint: disable=E1101
    return ipa_rule

def hbactest():
    print "ATTN: Listing allowed hosts"
    user = "power1"
    hbacset = []
    rules = []
    sizelimit = None
    hbacset = api.Command.hbacrule_find(sizelimit=sizelimit)['result']
    for rule in hbacset:
        ipa_rule = convert_to_ipa_rule(rule)
        if ipa_rule.enabled:
            #print ipa_rule.name
            rules.append(ipa_rule)      

    allowed_hosts = []
    for ipa_rule in rules:
        for host in inventory:
            request = pyhbac.HbacRequest()

            #Build request user/usergroups
            try:
                request.user.name = user
                search_result = api.Command.user_show(request.user.name)['result']
                groups = search_result['memberof_group']
                if 'memberofindirect_group' in search_result:
                    groups += search_result['memberofindirect_group']
                request.user.groups = sorted(set(groups))
            except:
                pass

            # Add sshd service + service groups it belongs to
            try:
                request.service.name = "sshd"
                service_result = api.Command.hbacsvc_show(request.service.name)['result']
                if 'memberof_hbacsvcgroup' in service_result:
                    request.service.groups = service_result['memberof_hbacsvcgroup']
            except:
                pass    

            try:
                request.targethost.name = canonicalize(host)
                tgthost_result = api.Command.host_show(request.targethost.name)['result']
                groups = tgthost_result['memberof_hostgroup']
                if 'memberofindirect_hostgroup' in tgthost_result:
                    groups += tgthost_result['memberofindirect_hostgroup']
                request.targethost.groups = sorted(set(groups))                
            except:
                pass
            try:
                res = request.evaluate([ipa_rule])
                if res == pyhbac.HBAC_EVAL_ALLOW:
                    print host
            except pyhbac.HbacError as (code, rule_name):
                if code == pyhbac.HBAC_EVAL_ERROR:
                    error_rules.append(rule_name)
                    print('Native IPA HBAC rule "%s" parsing error: %s' % \
                        (rule_name, pyhbac.hbac_result_string(code)))
            except (TypeError, IOError) as (info):
                print('Native IPA HBAC module error: %s' % (info))

def canonicalize(host):
    """
    Canonicalize the host name -- add default IPA domain if that is missing
    """
    if host.find('.') == -1:
        return u'%s.%s' % (host, self.env.domain)
    return host 

if __name__ == '__main__':
    api = initialize()
    inventory = []
    list_hosts(api)
    hbactest()
anazmy commented 7 years ago

testing this seems like paramiko is not supporting kerberos using latest gssapi per paramiko/paramiko#584 so I moved strategy a bit to use FreeIPA centralized ssh keys , and it worked fine so far , need some polishing to the code , one gotcha to take in consideration though, aker-gateway server(s) listed in the user ssh allowed hosts, maybe we can remove them via aker config ?

leochan007 commented 6 years ago

@anazmy hi there. when run the POC with latest 4.6.0 ipalib.

get this:

Traceback (most recent call last): File "test.py", line 6, in from ipalib import api, errors, output, util File "/usr/lib/python2.7/site-packages/ipalib/init.py", line 919, in from ipalib import plugable File "/usr/lib/python2.7/site-packages/ipalib/plugable.py", line 44, in from ipalib.util import classproperty File "/usr/lib/python2.7/site-packages/ipalib/util.py", line 59, in from ipalib.install import sysrestore ImportError: No module named install

it seems the ipalib's bug...

any suggestions? thx.

anazmy commented 6 years ago

@leochan007 it's not clear what you're trying to do, also I didn't test ipa 4.6 yet.

Can you please report a new issue here with exact details of your setup and debug logging as well if possible.

leochan007 commented 6 years ago

@anazmy 4.5.0 is Ok. i mean the client side.