Exemplo n.º 1
0
    def _select_cipher_list(cipher_list=None):
        """
        Selects either a passed in cipher list or uses the default openssl cipher list
        """
        if cipher_list:
            return AcceptableCiphers.fromOpenSSLCipherString(cipher_list)

        if fips_manager.status():
            return AcceptableCiphers.fromOpenSSLCipherString('TLSv1.2:kRSA:!eNULL:!aNULL')

        return AcceptableCiphers.fromOpenSSLCipherString(':'.join(DEFAULT_CIPHER_LIST))
Exemplo n.º 2
0
    def _select_minimum_tls_version(minimum_tls_version=None):
        """
        Retreives a minimum TLS version to be used for a SSL Context
        """
        if fips_manager.status():
            return TLSVersion.TLSv1_2

        if minimum_tls_version and minimum_tls_version not in TLS_VERSIONS:
            raise ConfigError("incorrect minimum_tls_version")

        return TLS_VERSIONS.get(minimum_tls_version, DEFAULT_MINIMUM_TLS_VERSION)
Exemplo n.º 3
0
def create_context_factory(
    hostnames=None,
    privateKey=None,
    certificate=None,
    caCerts=None,
    verifyDepth=9,
    verbose=False,
):
    """ Create an appropriate SSL context factory for the given CA certs
    and hostname.

    @param hostnames: the hostname (or list of hostnames) to match against.
        Pass 'None' to disable hostname validation
    @param privateKey: client private key, if the server requires it
    @param certificate: client certificate, if the server requires it
    @param caCerts: a list of CA root certs (of type
        OpenSSL.SSL.X509) against which servers should be
        validated. Pass 'None' to entirely disable certificate
        validation
    @param verifyDepth: maximum verify depth
    @param verbose: More detailed log messages for invalid certificates
        (Note: no effect if hostname validation is not enabled)
    """
    if fips_manager.status():
        minimum_ssl_version = ssl.TLSVersion.TLSv1_2
    else:
        minimum_ssl_version = None

    kwargs = {
        "certificate": certificate,
        "raiseMinimumTo": minimum_ssl_version,
        "privateKey": privateKey,
        "verify": False,
    }
    if not caCerts:
        return ssl.CertificateOptions(**kwargs)
    else:
        kwargs.update(
            {
                "verify": True,
                "caCerts": caCerts,
                "verifyDepth": verifyDepth,
                "verifyOnce": False,
                "requireCertificate": True,
            }
        )
        if hostnames:
            return HostnameVerifySSLContextFactory(hostnames, verbose=verbose, **kwargs)
        else:
            return ssl.CertificateOptions(**kwargs)
Exemplo n.º 4
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
Exemplo n.º 5
0
    def perform_bind(
        self,
        auth_type: str,
        dn: str,
        username: str,
        password: str,
        domain: str,
        workstation: str,
        permit_implicit=False,
    ):
        bind_context: Optional[BaseContext] = None
        tls_enabled = self.factory.transport_type in const.AD_TRANSPORTS_WITH_SSL
        if auth_type == const.AD_AUTH_TYPE_PLAIN:
            password_bytes = password.encode()
            yield self.perform_bind_plain(dn, password_bytes)
            # Bind context for plain will always be None
        elif auth_type == const.AD_AUTH_TYPE_NTLM_V1:
            yield ADClientProtocol.perform_bind_ntlm(
                self,
                username,
                password,
                domain,
                workstation,
                ntlm_version=1,
                peercert=None,
            )
            # Bind context for NTLMv1 will always be None
        elif auth_type == const.AD_AUTH_TYPE_NTLM_V2:
            peercert = yield self._get_peercert()
            session_key = yield ADClientProtocol.perform_bind_ntlm(
                self,
                username,
                password,
                domain,
                workstation,
                ntlm_version=2,
                peercert=peercert,
            )
            # NTLMv2 returns session key
            if not tls_enabled:
                if not fips_manager.status():
                    bind_context = NTLMContext(session_key)
                else:
                    raise SignAndSealNotSupported(
                        "Sign and seal using md5 for security is not FIPS compliant, "
                        "Must use secure transport (LDAPS/STARTTLS)"
                        "Read more about FIPS configuration: https://duo.com/docs/authproxy-reference#fips-mode"
                    )
        elif auth_type == const.AD_AUTH_TYPE_SSPI:
            # Use the host to build the targetspn. If it's a hostname
            # and the targetspn maps to a valid service provider, then
            # Kerberos will be used. Otherwise SSPI will use NTLM.
            targetspn = "ldap/{}".format(self.transport.connector.host)
            ca = yield self.perform_bind_sspi(username, password, domain,
                                              permit_implicit, targetspn)
            if self.debug:
                ca_package_info = ca.ctxt.QueryContextAttributes(
                    sspicon.SECPKG_ATTR_PACKAGE_INFO)

                log.msg("SSPI security package {data}", data=ca_package_info)
            if not tls_enabled:
                if not fips_manager.status():
                    bind_context = SSPIContext(ca)
                else:
                    raise SignAndSealNotSupported(
                        "Sign and seal using md5 for security is not FIPS compliant, "
                        "Must use secure transport (LDAPS/STARTTLS)"
                        "Read more about FIPS configuration: https://duo.com/docs/authproxy-reference#fips-mode"
                    )
        if not self.bound:
            self.bound = True
            if bind_context:
                if self.debug:
                    log.msg(
                        "Sign and Seal has been negotiated on this connection")
                wrap_protocol(self,
                              SignSealEncoder,
                              SignSealDecoder,
                              context=bind_context)

        defer.returnValue(bind_context)
Exemplo n.º 6
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