Пример #1
0
def check_cloud(config, toolbox=STANDARD_TOOLBOX):
    """
    Checks the cloud section config by checking
    1) The config is valid
    2) We can /ping and /check Duo with the provided api host and credentials

    Args:
        config (ConfigDict): Configuration for cloud section
        toolbox (ConnectivityTestToolbox): Toolbox for doing connectivity tests

    Returns:
        CloudResult: the results of checking the cloud config
    """
    ca_certs = config.get_str('http_ca_certs_file',
                              const.DEFAULT_HTTP_CERTS_FILE)
    ca_certs_path = util.resolve_file_path(ca_certs)
    http_proxy = None
    http_proxy_host = config.get_str('http_proxy_host', '')
    if http_proxy_host:
        http_proxy_port = config.get_int('http_proxy_port', 80)
        http_proxy = (http_proxy_host, http_proxy_port)

    api_host = config.get_str('api_host', '')
    ping_result = base_duo_check.perform_duo_ping(toolbox, api_host, '', '', ca_certs_path, http_proxy=http_proxy)
    time_drift_result = base_duo_check.perform_time_drift(
        toolbox, ping_result, api_host, '', '', ca_certs_path, http_proxy=http_proxy)
    # T51092: validate the credentials once an entry point for doing so
    # exists in apiserv. Also update tests exercising this method to account
    # for the additional validation.

    result = CloudResult(ping_result, time_drift_result)
    return result
Пример #2
0
def check_http_proxy(config, toolbox=STANDARD_TOOLBOX):
    """
    Checks an http_proxy section config by testing
      1) The config is valid
      2) We can listen via TCP on the configured port
      3) We can /ping Duo at the provided api host

    Args:
        config: A ConfigDict for an http_proxy module
        toolbox: a ConnectivityTestToolbox for doing individual connectivity tests

    Returns:
        HttpProxyResult with the results of the testing
    """
    config_result = validate_http_proxy_config(config)

    port = config.get_int("port", const.DEFAULT_HTTP_PORT)
    interface = config.get_str("interface", "")
    can_listen_result = yield toolbox.test_listen_tcp(port,
                                                      interface=interface)

    api_host = config.get_str("api_host", "")
    ca_certs = config.get_str("http_ca_certs_file",
                              const.DEFAULT_HTTP_CERTS_FILE)
    ca_certs_path = util.resolve_file_path(ca_certs)

    can_ping_result = base_duo_check.perform_duo_ping(toolbox, api_host, "",
                                                      "", ca_certs_path)
    time_drift_result = base_duo_check.perform_time_drift(
        toolbox, can_ping_result, api_host, "", "", ca_certs_path)

    result = HttpProxyResult(config_result, can_listen_result, can_ping_result,
                             time_drift_result)
    defer.returnValue(result)
Пример #3
0
def check_main(config, toolbox=STANDARD_TOOLBOX):
    """
    Test a main section configuration

    Check if the specified http certificates file (defaulted to conf/ca-bundle.crt) is
    working (can be read, valid certs, etc.)

    Args:
        config (ConfigDict): the main section configuration
        toolbox (ConnectivityTestToolbox): the testing toolbox

    Returns:
        MainSectionResult with the results of the testing

    """
    http_ca_certs = config.get_str("http_ca_certs_file",
                                   const.DEFAULT_HTTP_CERTS_FILE)
    http_ca_certs_file = util.resolve_file_path(http_ca_certs)

    cert_result = toolbox.test_ssl_certs(http_ca_certs_file)

    http_proxy_host = config.get_str("http_proxy_host", "")
    http_proxy_port = config.get_int("http_proxy_port", 80)

    if http_proxy_host:
        http_proxy_result = yield toolbox.test_connect_with_http_proxy(
            http_proxy_host, http_proxy_port)
    else:
        http_proxy_result = NOT_APPLICABLE_TEST_RESULT

    defer.returnValue(MainSectionResult(cert_result, http_proxy_result))
Пример #4
0
def check_radius_server(config, toolbox=STANDARD_TOOLBOX):
    """
    Checks a radius_server section config by checking
      1) The config is valid
      2) We can /ping and /check Duo with the provided api host and credentials
      3) We can listen on the specified port + interface

    Args:
        config: A ConfigDict with the section config
        toolbox: a ConnectivityTestToolbox for doing individual connectivity tests

    Returns:
        RadiusServerResult with the results of the testing

    """
    config_result = validate_radius_server_config(config)

    ikey = config.get_str("ikey", "")
    skey = config.get_protected_str("skey_protected", "skey", "")
    api_host = config.get_str("api_host", "")

    ca_certs = config.get_str("http_ca_certs_file",
                              const.DEFAULT_HTTP_CERTS_FILE)
    ca_certs_path = util.resolve_file_path(ca_certs)
    http_proxy = None
    http_proxy_host = config.get_str("http_proxy_host", "")
    if http_proxy_host:
        http_proxy_port = config.get_int("http_proxy_port", 80)
        http_proxy = (http_proxy_host, http_proxy_port)

    ping_result = base_duo_check.perform_duo_ping(toolbox,
                                                  api_host,
                                                  "",
                                                  "",
                                                  ca_certs_path,
                                                  http_proxy=http_proxy)
    time_drift_result = base_duo_check.perform_time_drift(
        toolbox,
        ping_result,
        api_host,
        "",
        "",
        ca_certs_path,
        http_proxy=http_proxy)
    check_result = base_duo_check.perform_credentials_check(
        toolbox, api_host, skey, ikey, ca_certs_path, http_proxy=http_proxy)

    port = config.get_int("port", const.DEFAULT_RADIUS_PORT)
    interface = config.get_str("interface", "")
    listen_result = yield toolbox.test_listen_udp(port, interface)

    result = RadiusServerResult(config_result, ping_result, time_drift_result,
                                check_result, listen_result)
    defer.returnValue(result)
Пример #5
0
def check_file(filename_to_check):
    """
    Check if the specified file exists and is readable.
    Args:
        filename_to_check (str):  the filename to check, relative or absolute.
            If it is a relative path, we try the check twice- once as normal,
            and once with a prepended "conf/".
    Returns:
        bool: True if the file can be opened for reading; False otherwise
    """
    try:
        # Covers absolute paths as well as relative paths with prepended conf/
        open(filename_to_check, 'r')
        return True
    except (OSError, IOError):
        try:
            # Covers relative paths without conf/ by tacking it onto filepath
            open(util.resolve_file_path(filename_to_check), 'r')
            return True
        except (OSError, IOError):
            return False
Пример #6
0
def check_ldap_server_auto(config, toolbox=STANDARD_TOOLBOX):
    """Check an ldap_server_auto section config by checking:
      1) The config is valid
      2) We can /ping and /check Duo with the provided api host and credentials
      3) SSL creds are fine, if they are specified
      4) We can listen on the specified port + interface (plain TCP or SSL)
    Args:
        config (ConfigDict): config for this [ldap_server_auto] section
        toolbox (ConnectivityTestToolbox): for doing individual connectivity tests
    Returns:
        nested result (dict) of testing
    """
    ikey = config.get_str('ikey', '')
    skey = config.get_protected_str('skey_protected', 'skey', '')
    api_host = config.get_str('api_host', '')

    ca_certs = config.get_str('http_ca_certs_file', const.DEFAULT_HTTP_CERTS_FILE)
    ca_certs_path = util.resolve_file_path(ca_certs)
    http_proxy = None
    http_proxy_host = config.get_str('http_proxy_host', '')
    if http_proxy_host:
        http_proxy_port = config.get_int('http_proxy_port', 80)
        http_proxy = (http_proxy_host, http_proxy_port)

    ping_result, time_drift_result, validate_result = _call_duo(
        ikey, skey, api_host, ca_certs_path, toolbox, http_proxy=http_proxy)

    ssl_key_path = config.get_str('ssl_key_path', '')
    ssl_cert_path = config.get_str('ssl_cert_path', '')
    cipher_list = config.get_str('cipher_list', '')
    minimum_tls_version = config.get_str('minimum_tls_version', '')

    if ssl_key_path and ssl_cert_path:
        ssl_key_path = util.resolve_file_path(ssl_key_path)
        ssl_cert_path = util.resolve_file_path(ssl_cert_path)
        ssl_result = toolbox.test_ssl_credentials(
            ssl_key_path,
            ssl_cert_path,
            cipher_list,
            minimum_tls_version,
        )
        port = config.get_int('ssl_port', const.DEFAULT_LDAPS_PORT)
    else:
        ssl_result = NOT_APPLICABLE_TEST_RESULT
        port = config.get_int('port', const.DEFAULT_LDAP_PORT)

    interface = config.get_str('interface', '')

    ssl_creds = {
        'ssl_key_path': ssl_key_path,
        'ssl_cert_path': ssl_cert_path,
        'cipher_list': cipher_list,
    }

    if not (ssl_key_path and ssl_cert_path):
        listen_result = yield toolbox.test_listen_tcp(port, interface)
    elif ssl_result.is_successful():
        listen_result = yield _listen_ssl(port, interface, toolbox, ssl_creds)
    else:
        listen_result = UnmetPrerequisiteSkippedTestResult('listen', 'ssl configuration')

    result = LdapServerResult(ping_result, time_drift_result, validate_result, ssl_result, listen_result)
    defer.returnValue(result)
Пример #7
0
def create_application(args=None, twistd_user=None, log_group=None):
    home_dir = util.get_home_dir()
    os.chdir(home_dir)
    is_logging_insecure = False

    # parse command-line args, if appropriate
    primary_only_time = None
    if args:
        option_parser = argparse.ArgumentParser()

        option_parser.add_argument(
            "--primary-only",
            type=int,
            nargs="?",
            help=
            "This option disables secondary authentication for the specified number of minutes (default 60)",
            default=None,
            const=60,
        )
        option_parser.add_argument(
            "--logging-insecure",
            action="store_true",
            help=
            "This option enables debug, and prints logs containing passwords and possibly other secrets.",
            default=False,
        )
        options = option_parser.parse_args()
        is_logging_insecure = options.logging_insecure
        primary_only_time = options.primary_only

    config_filename = os.path.join("conf", "authproxy.cfg")
    configuration = config_provider.get_config(config_filename)

    if primary_only_time is not None:
        if primary_only_time > 240:
            print(
                "Primary only mode can only be enabled for a maximum of 4 hours (240 minutes)"
            )
            sys.exit(2)
        else:
            PrimaryOnlyManager.enable_primary_only(primary_only_time)

    main_config = configuration.get_main_section_config()
    if main_config:
        log.msg("Main Configuration:")
        log.config(main_config)

    fips_mode = main_config.get_bool("fips_mode", False)
    if fips_mode:
        fips_manager.enable()

    # Set up our observers
    if is_logging_insecure:
        observers = [textFileLogObserver(sys.stdout)]
    else:
        observers = log_observation.get_observers(main_config, twistd_user,
                                                  log_group)

    for observer in observers:
        globalLogPublisher.addObserver(observer)

    # Global debug mode
    if is_logging_insecure:
        debug_mode = True
    else:
        debug_mode = main_config.get_bool("debug", False)

    http.set_debug(debug_mode)
    http.set_is_logging_insecure(is_logging_insecure)

    # Create main application.
    application = Application("duoauthproxy")
    LogReadyService().setServiceParent(application)

    fips_mode = fips_manager.status()
    if fips_mode:
        log.msg("FIPS mode {0} is enabled with {1}".format(
            fips_mode, fips_manager.get_openssl_version()))
    else:
        log.msg("FIPS mode is not enabled")

    # get ca certs file
    http_ca_certs_file = main_config.get_str("http_ca_certs_file", "")
    if http_ca_certs_file:
        http_ca_certs_file = util.resolve_file_path(http_ca_certs_file)
    else:
        http_ca_certs_file = os.path.join("conf",
                                          const.DEFAULT_HTTP_CERTS_FILE)

    # read ca certs
    if not os.path.isfile(http_ca_certs_file):
        http_ca_certs_file = os.path.join("conf", http_ca_certs_file)
    with open(http_ca_certs_file, "r") as bundle_fp:
        http.set_ca_certs(ssl_verify.load_ca_bundle(bundle_fp))

    # get proxy settings
    http_proxy_host = main_config.get_str("http_proxy_host", "")
    http_proxy_port = main_config.get_int("http_proxy_port", 80)
    if http_proxy_host:
        http.set_proxy(http_proxy_host, http_proxy_port)

    sections = section.parse_sections(configuration, is_logging_insecure)
    module_factory = section.ModuleFactory(sections, application)
    modules_by_type = module_factory.make_modules()

    if not any(modules_by_type.values()):
        raise config_error.ConfigError("No integrations in config file.")

    # Setup forwarding/server pairs by port
    for port, interface in modules_by_type.get("server", []):
        server_networks = {}
        server_names = {}
        for section_name, server_module, server_config in modules_by_type[
                "server"][(port, interface)]:
            client_name = configuration.get_section_client(section_name)

            if not client_name:
                if server_module.Module.no_client:
                    modules_by_type["client"]["no_client"] = None
                    client_name = "no_client"
                else:
                    raise config_error.ConfigError(
                        'Neither module %s or main has "client" value' %
                        section_name)

            if section_name.startswith(
                    "ldap_server_auto"
            ) and not client_name.startswith("ad_client"):
                raise config_error.ConfigError(
                    "ad_client is required by ldap_server_auto. No ad_client found in config file. "
                )

            if client_name != "radius_client" and server_config.get_str(
                    "pass_through_attr_names", ""):
                raise config_error.ConfigError(
                    "Can only pass through radius attributes if using a radius client"
                )
            server_instance = server_module.Module(
                server_config, modules_by_type["client"][client_name],
                section_name)
            server_instance.setServiceParent(application)

            if section_name.startswith("radius_server_"):
                server_networks[server_instance] = parse_radius_secrets(
                    server_config).keys()
                server_names[server_instance] = section_name

        if server_names:
            forward_module = forward_serv
            forward_instance = forward_module.Module(
                port=port,
                servers=server_networks,
                server_names=server_names,
                interface=interface,
                debug=debug_mode,
            )
            forward_instance.setServiceParent(application)

    # set user-agent
    sections = ",".join(sorted(set(configuration.list_sections())))
    user_agent = "duoauthproxy/{0} ({1}; Python{2}; {3})".format(
        get_version(), platform.platform(), platform.python_version(),
        sections)
    http.set_user_agent(user_agent)

    # Authproxy uses globalLogPublisher to emit events. Defining a no-op emitter will squelch the creation
    # of the unwatned twistd default logging mechanisms.
    def no_op_emitter(eventDict):
        pass

    application.setComponent(ILogObserver, no_op_emitter)

    return application
Пример #8
0
def create_application(args=None, twistd_user=None, log_group=None):
    home_dir = util.get_home_dir()
    os.chdir(home_dir)
    is_logging_insecure = False

    if syslog is not None:
        facility_dict = {
            'LOG_KERN': pySyslog.LOG_KERN,
            'LOG_USER': pySyslog.LOG_USER,
            'LOG_MAIL': pySyslog.LOG_MAIL,
            'LOG_DAEMON': pySyslog.LOG_DAEMON,
            'LOG_AUTH': pySyslog.LOG_AUTH,
            'LOG_LPR': pySyslog.LOG_LPR,
            'LOG_NEWS': pySyslog.LOG_NEWS,
            'LOG_UUCP': pySyslog.LOG_UUCP,
            'LOG_CRON': pySyslog.LOG_CRON,
            'LOG_SYSLOG': pySyslog.LOG_SYSLOG,
            'LOG_LOCAL0': pySyslog.LOG_LOCAL0,
            'LOG_LOCAL1': pySyslog.LOG_LOCAL1,
            'LOG_LOCAL2': pySyslog.LOG_LOCAL2,
            'LOG_LOCAL3': pySyslog.LOG_LOCAL3,
            'LOG_LOCAL4': pySyslog.LOG_LOCAL4,
            'LOG_LOCAL5': pySyslog.LOG_LOCAL5,
            'LOG_LOCAL6': pySyslog.LOG_LOCAL6,
            'LOG_LOCAL7': pySyslog.LOG_LOCAL7
        }

    # parse command-line args, if appropriate
    primary_only_time = None
    if args:
        option_parser = argparse.ArgumentParser()

        option_parser.add_argument(
            "--primary-only", type=int, nargs='?',
            help="This option disables secondary authentication for the specified number of minutes (default 60)",
            default=None, const=60
        )
        option_parser.add_argument(
            "--logging-insecure",
            action="store_true",
            help="This option enables debug, and prints logs containing passwords and possibly other secrets.",
            default=False
        )
        options = option_parser.parse_args()
        is_logging_insecure = options.logging_insecure
        primary_only_time = options.primary_only

    config_filename = os.path.join('conf', 'authproxy.cfg')
    configuration = config_provider.get_config(config_filename)

    if primary_only_time is not None:
        if primary_only_time > 240:
            print("Primary only mode can only be enabled for a maximum of 4 hours (240 minutes)")
            sys.exit(2)
        else:
            PrimaryOnlyManager.enable_primary_only(primary_only_time)

    main_config = configuration.get_main_section_config()
    if main_config:
        log.msg('Main Configuration:')
        log.config(main_config)

    fips_mode = main_config.get_bool('fips_mode', False)
    if fips_mode:
        fips_manager.enable()

    # handle log configuration
    log_to_file = main_config.get_bool('log_file', False)
    log_stdout = main_config.get_bool('log_stdout', False)
    log_syslog = main_config.get_bool('log_syslog', False)
    log_auth_events = main_config.get_bool('log_auth_events', False)
    log_sso_events = main_config.get_bool('log_sso_events', True)

    if is_logging_insecure:
        globalLogPublisher.addObserver(textFileLogObserver(sys.stdout))
    else:
        if log_to_file or not (log_to_file or log_syslog or log_stdout):
            log_dir = main_config.get_str('log_dir', 'log')
            log_max_size = main_config.get_int('log_max_size', 10 * (1 << 20))
            log_max_files = main_config.get_int('log_max_files', 6)
            if log_max_files == 0:
                # we need to pass None explicitly if we want there to be no limit
                # 0 would just mean no logfiles would get kept...
                log_max_files = None

            log_file = create_log_file('authproxy.log', log_dir, log_max_size,
                                       log_max_files, twistd_user, log_group)
            log_file_observer = textFileLogObserver(log_file)

            if log_auth_events:
                auth_log_file = create_log_file('authevents.log', log_dir, log_max_size, log_max_files, twistd_user, log_group)
                auth_observer = FileLogObserver(auth_log_file, log.format_auth_event)
            else:
                auth_observer = log.no_op_observer

            if log_sso_events:
                sso_log_file = create_log_file('ssoevents.log', log_dir, log_max_size, log_max_files, twistd_user, log_group)
                sso_observer = FileLogObserver(sso_log_file, log.format_sso_event)
            else:
                sso_observer = log.no_op_observer

            auth_filtering_observer = FilteringLogObserver(auth_observer,
                                                           [log.auth_type_predicate],
                                                           log.no_op_observer)

            globalLogPublisher.addObserver(auth_filtering_observer)

            sso_filtering_observer = FilteringLogObserver(sso_observer,
                                                          [log.sso_type_predicate],
                                                          log.no_op_observer)

            globalLogPublisher.addObserver(sso_filtering_observer)

            # the default authproxy.log
            log_file_observer = FilteringLogObserver(log_file_observer,
                                                     [log.only_default_log_predicate],
                                                     log.no_op_observer)

            globalLogPublisher.addObserver(log_file_observer)

        if log_stdout:
            std_out_observer = textFileLogObserver(sys.stdout)
            std_out_filter = FilteringLogObserver(log.no_op_observer,
                                                  [log.auth_type_predicate],
                                                  std_out_observer)
            globalLogPublisher.addObserver(std_out_filter)
        if log_syslog:
            if syslog is None:
                raise config_error.ConfigError('syslog not supported on Windows')
            syslog_facilitystr = main_config.get_str('syslog_facility', 'LOG_USER')
            syslog_facility = facility_dict.get(syslog_facilitystr, None)
            if syslog_facility is None:
                raise config_error.ConfigError('Unknown syslog_facility: {0}'.format(syslog_facilitystr))
            syslog_observer = syslog.SyslogObserver('Authproxy', facility=syslog_facility)
            wrapped_syslog_observer = LegacyLogObserverWrapper(syslog_observer.emit)
            syslog_filtering_observer = FilteringLogObserver(log.no_op_observer,
                                                             [log.auth_type_predicate],
                                                             wrapped_syslog_observer)
            globalLogPublisher.addObserver(syslog_filtering_observer)

    # Global debug mode
    if is_logging_insecure:
        debug_mode = True
    else:
        debug_mode = main_config.get_bool('debug', False)

    http.set_debug(debug_mode)
    http.set_is_logging_insecure(is_logging_insecure)

    # Create main application.
    application = Application('duoauthproxy')
    LogReadyService().setServiceParent(application)

    fips_mode = fips_manager.status()
    if fips_mode:
        log.msg("FIPS mode {0} is enabled with {1}".format(fips_mode, fips_manager.get_openssl_version()))
    else:
        log.msg("FIPS mode is not enabled")

    # get ca certs file
    http_ca_certs_file = main_config.get_str('http_ca_certs_file', '')
    if http_ca_certs_file:
        http_ca_certs_file = util.resolve_file_path(http_ca_certs_file)
    else:
        http_ca_certs_file = os.path.join('conf', const.DEFAULT_HTTP_CERTS_FILE)

    # read ca certs
    if not os.path.isfile(http_ca_certs_file):
        http_ca_certs_file = os.path.join('conf', http_ca_certs_file)
    with open(http_ca_certs_file, 'r') as bundle_fp:
        http.set_ca_certs(ssl_verify.load_ca_bundle(bundle_fp))

    # get proxy settings
    http_proxy_host = main_config.get_str('http_proxy_host', '')
    http_proxy_port = main_config.get_int('http_proxy_port', 80)
    if http_proxy_host:
        http.set_proxy(http_proxy_host, http_proxy_port)

    sections = section.parse_sections(configuration, is_logging_insecure)
    module_factory = section.ModuleFactory(sections, application)
    modules_by_type = module_factory.make_modules()

    if not any(modules_by_type.values()):
        raise config_error.ConfigError('No integrations in config file.')

    # Setup forwarding/server pairs by port
    for port, interface in modules_by_type.get('server', []):
        server_networks = {}
        server_names = {}
        for section_name, server_module, server_config in modules_by_type['server'][(port, interface)]:
            client_name = configuration.get_section_client(section_name)

            if not client_name:
                if server_module.Module.no_client:
                    modules_by_type['client']['no_client'] = None
                    client_name = 'no_client'
                else:
                    raise config_error.ConfigError('Neither module %s or main has "client" value' % section_name)

            if section_name.startswith('ldap_server_auto') and not client_name.startswith('ad_client'):
                raise config_error.ConfigError('ad_client is required by ldap_server_auto. No ad_client found in config file. ')

            if client_name != 'radius_client' \
                    and server_config.get_str('pass_through_attr_names', ''):
                raise config_error.ConfigError('Can only pass through radius attributes if using a radius client')
            server_instance = server_module.Module(server_config,
                                                   modules_by_type['client'][client_name],
                                                   section_name)
            server_instance.setServiceParent(application)

            if section_name.startswith('radius_server_'):
                server_networks[server_instance] = parse_radius_secrets(server_config).keys()
                server_names[server_instance] = section_name

        if server_names:
            forward_module = forward_serv
            forward_instance = forward_module.Module(
                port=port,
                servers=server_networks,
                server_names=server_names,
                interface=interface,
                debug=debug_mode,
            )
            forward_instance.setServiceParent(application)

    # set user-agent
    sections = ','.join(sorted(set(configuration.list_sections())))
    user_agent = "duoauthproxy/{0} ({1}; Python{2}; {3})".format(
        get_version(),
        platform.platform(),
        platform.python_version(),
        sections)
    http.set_user_agent(user_agent)

    # Authproxy uses globalLogPublisher to emit events. Defining a no-op emitter will squelch the creation
    # of the unwatned twistd default logging mechanisms.
    def no_op_emitter(eventDict):
        pass

    application.setComponent(ILogObserver, no_op_emitter)

    return application