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
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)
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))
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)
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
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)
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
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