Exemple #1
0
    def to_template(self):
        """ Dictionary used to create configuration file

        :return     Dictionnary of configuration parameters
        """
        """ First, use to_mongo() internal django function """
        return {'global_config': Cluster.get_global_config()}
Exemple #2
0
 def to_template(self):
     """ Dictionary used to create configuration file
     :return     Dictionnary of configuration parameters
     """
     return {
         'nodes': Node.objects.exclude(name=get_hostname()),
         'global_config': Cluster.get_global_config(),
         'jail_addresses': JAIL_ADDRESSES,
         'databases_path': DATABASES_PATH,
         'defender_policy_list': DefenderPolicy.objects.all(),
         'proxy': get_sanitized_proxy()
     }
Exemple #3
0
    def reload_conf(self):
        """
        Write new PF configuration, if needed
        :return: True / False
        """
        conf_reloaded = super().reload_conf()

        config_model = Cluster.get_global_config()
        """ Check if firehol and vulture netsets exist """
        filepath = DATABASES_PATH + "/firehol_level1.netset"
        if not os_path.isfile(filepath):
            write_conf(logger,
                       [filepath, "", DATABASES_OWNER, DATABASES_PERMS])

        filepath = DATABASES_PATH + "/vulture-v4.netset"
        if not os_path.isfile(filepath):
            write_conf(logger,
                       [filepath, "", DATABASES_OWNER, DATABASES_PERMS])

        filepath = DATABASES_PATH + "/vulture-v6.netset"
        if not os_path.isfile(filepath):
            write_conf(logger,
                       [filepath, "", DATABASES_OWNER, DATABASES_PERMS])
        """ Check if Whitelist and Blacklist has changed """
        wl_bl = {
            'pf.whitelist.conf': config_model.pf_whitelist,
            'pf.blacklist.conf': config_model.pf_blacklist,
        }

        for filename, liste in wl_bl.items():
            file_path = '{}{}'.format(PF_PATH, filename)
            config = "\n".join(liste.split(','))
            md5_config = md5(config.encode('utf-8')).hexdigest().strip()
            md5sum = ""

            try:
                result = check_output(['/sbin/md5', file_path],
                                      stderr=PIPE).decode('utf8')
                md5sum = result.strip().split('= ')[1]

            except CalledProcessError as e:
                stderr = e.stderr.decode('utf8')
                logger.error("Failed to md5sum file '{}' : {}".format(
                    filename, stderr))
            """ If there was an error, bad permissions on file, rewrite-it with correct ones """
            if md5_config != md5sum:
                conf_reloaded = True
                logger.info(
                    'Packet Filter {} need to be rewrite'.format(filename))
                write_conf(logger, [file_path, config, PF_OWNERS, PF_PERMS])

        return conf_reloaded
Exemple #4
0
def configure_node(node_logger):
    """ Generate and write netdata conf files """
    result = ""

    node = Cluster.get_current_node()
    global_config = Cluster.get_global_config()
    """ For each Jinja templates """
    jinja2_env = Environment(loader=FileSystemLoader(JINJA_PATH))
    for template_name in jinja2_env.list_templates():
        """ Perform only "rsyslog_template_*.conf" templates """
        match = re_search("^rsyslog_template_([^\.]+)\.conf$", template_name)
        if not match:
            continue
        template = jinja2_env.get_template(template_name)
        template_path = "{}/05-tpl-01-{}.conf".format(RSYSLOG_PATH,
                                                      match.group(1))
        """ Generate and write the conf depending on all nodes, and current node """
        write_conf(node_logger, [
            template_path,
            template.render({
                'node': node,
                'global_config': global_config
            }), RSYSLOG_OWNER, RSYSLOG_PERMS
        ])
        result += "Rsyslog template '{}' written.\n".format(template_path)
    """ PF configuration for Rsyslog """
    pf_template = jinja2_env.get_template("pf.conf")
    write_conf(node_logger, [
        "{}/pf.conf".format(RSYSLOG_PATH),
        pf_template.render({'mongodb_uri': MongoBase.get_replicaset_uri()}),
        RSYSLOG_OWNER, RSYSLOG_PERMS
    ])
    result += "Rsyslog template 'pf.conf' written.\n"
    """ If this method has been called, there is a reason - a Node has been modified
          so we need to restart Rsyslog because at least PF conf has been changed 
    """
    # if Frontend.objects.filter(enable_logging=True).count() > 0:
    #    node_logger.debug("Logging enabled, reload of Rsyslog needed.")
    restart_service(node_logger)
    node_logger.info("Rsyslog service restarted.")
    result += "Rsyslogd service restarted."

    return result
Exemple #5
0
    def register_authentication(self, sender, recipient):
        chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
        secret_key = get_random_string(self.key_length, chars)

        msg = MIMEText("Vulture authentication\r\nThis is your secret key: {}".format(secret_key))
        msg['Subject'] = 'Vulture OTP Authentication'
        msg['From'] = sender
        msg['To'] = recipient

        config = Cluster.get_global_config()
        smtp_server = config.smtp_server

        if not smtp_server:
            logger.error("VultureMailClient::register_authentication: Cluster SMTP settings not configured")
            raise smtplib.SMTPException("Vulture mail service not configured")

        server = smtplib.SMTP(smtp_server)
        message = "Subject: " + str(msg['Subject']) + "\n\n" + str(msg)
        server.sendmail(msg['from'], recipient, message)
        server.quit()

        return secret_key
Exemple #6
0
def log_in(request, workflow_id=None):
    """ Handle authentication in Vulture Portal
    :param request: Django request object
    :returns: Home page if user auth succeed. Logon page if auth failed
    """
    """ First, try to retrieve concerned objects """
    try:
        workflow = Workflow.objects.get(pk=workflow_id)
    except Exception as e:
        logger.exception(e)
        return HttpResponseForbidden("Injection detected.")

    authentication_classes = {
        'form': POSTAuthentication,
        'basic': BASICAuthentication,
        'kerberos': KERBEROSAuthentication
    }

    global_config = Cluster.get_global_config()
    """ Retrieve token and cookies to instantiate Redis wrapper objects """
    # Retrieve cookies required for authentication
    portal_cookie_name = global_config.portal_cookie_name
    token_name = global_config.public_token
    portal_cookie = request.COOKIES.get(portal_cookie_name, None)
    try:
        # Instantiate authentication object to retrieve application auth_type
        authentication = Authentication(portal_cookie, token_name, workflow)
        # And then instantiate the right authentication class with auth_type ('form','basic','kerberos')
        authentication = authentication_classes[
            authentication.workflow.authentication.auth_type](portal_cookie,
                                                              token_name,
                                                              workflow)
        logger.debug("PORTAL::log_in: Authentication successfully created")

    # Application does not need authentication
    except RedirectionNeededError as e:
        logger.error("PORTAL::log_in: {}".format(str(e)))
        return HttpResponseRedirect(e.redirect_url)

    # Redis connection error
    except RedisConnectionError as e:
        logger.error(
            "PORTAL::log_in: Unable to connect to Redis server : {}".format(
                str(e)))
        return HttpResponseServerError()

    # Token not found while instantiating RedisSession or RedisAppSession
    except TokenNotFoundError as e:
        logger.error("PORTAL::log_in: {}".format(str(e)))

        # Redirect to the same uri, to stock token in Redis via session filter
        return HttpResponseRedirect("")

    # If redis_session.keys['application_id'] does not exists : FORBIDDEN
    except (Workflow.DoesNotExist, ValidationError, InvalidId) as e:
        logger.error(
            "PORTAL::log_in: Application with id '{}' not found".format(
                authentication.redis_session.keys.get('application_id')))
        return HttpResponseForbidden()

    # If assertionError : Ask credentials by portal
    except AssertionError as e:
        logger.error(
            "PORTAL::log_in: AssertionError while trying to create Authentication : "
            .format(e))
        return authentication.ask_credentials_response(request=request)
    """ If user is not authenticated : try to retrieve credentials and authenticate him on backend/fallback-backends """
    # If the user is not authenticated and application need authentication
    if not authentication.is_authenticated():
        try:
            backend_id = authentication.authenticate_sso_acls()
            if not backend_id:
                # Retrieve credentials
                authentication.retrieve_credentials(request)
                logger.debug(
                    "PORTAL::log_in: Credentials successfully retrieved")

                # Authenticate user with credentials retrieven
                authentication_results = authentication.authenticate(request)
                logger.debug(
                    "PORTAL::log_in: Authentication succeed on backend {}".
                    format(authentication.backend_id))

                # Register authentication results in Redis
                portal_cookie, oauth2_token = authentication.register_user(
                    authentication_results)
                logger.debug(
                    "PORTAL::log_in: User {} successfully registered in Redis".
                    format(authentication.credentials[0]))

                if authentication_results['data'].get('password_expired',
                                                      None):
                    logger.info(
                        "PORTAL::log_in: User '{}' must change its password, redirect to self-service portal"
                        .format(authentication.credentials[0]))
                    # FIXME : See w/ JJO for application.auth_portal & get_redirect_uri
                    app_url = authentication.get_url_portal()
                    return response_redirect_with_portal_cookie(
                        app_url + str(token_name) + '/self/change',
                        portal_cookie_name, portal_cookie,
                        app_url.startswith('https'), None)
            # If the user is already authenticated (retrieven with RedisPortalSession ) => SSO
            else:
                portal_cookie, oauth2_token = authentication.register_sso(
                    backend_id)
                logger.info(
                    "PORTAL::log_in: User {} successfully SSO-powered !".
                    format(authentication.credentials[0]))

        except AssertionError as e:
            logger.error(
                "PORTAL::log_in: Bad captcha taped for username '{}' : {}".
                format(authentication.credentials[0], e))
            return authentication.ask_credentials_response(request=request,
                                                           error="Bad captcha")

        except AuthenticationError as e:
            logger.error(
                "PORTAL::log_in: AuthenticationError while trying to authenticate user '{}' : {}"
                .format(authentication.credentials[0], e))
            return authentication.ask_credentials_response(
                request=request, error="Bad credentials")

        except ACLError as e:
            logger.error(
                "PORTAL::log_in: ACLError while trying to authenticate user '{}' : {}"
                .format(authentication.credentials[0], e))
            return authentication.ask_credentials_response(
                request=request, error="Not authorized")

        except (DBAPIError, PyMongoError, LDAPError) as e:
            logger.error(
                "PORTAL::log_in: Repository driver Error while trying to authentication user '{}' : {}"
                .format(authentication.credentials[0], e))
            return authentication.ask_credentials_response(
                request=request,
                error="Connection to repository failed <br> "
                "<b> Contact your administrator </b>")

        except (MultiValueDictKeyError, AttributeError, KeyError) as e:
            # vltprtlsrnm is always empty during the initial redirection. Don't log that
            logger.error(
                "PORTAL::log_in: Error while trying to authentication user '{}' : {}"
                .format(authentication.credentials[0], e))
            return authentication.ask_credentials_response(request=request)

        except REDISWriteError as e:
            logger.error(
                "PORTAL::log_in: RedisWriteError while trying to register user '{}' informations : {}"
                .format(authentication.credentials[0], e))
            return HttpResponseServerError()

        except Exception as e:
            logger.error("ERROR !")
            logger.exception(e)
            return HttpResponseServerError()
    """ If user is not double-authenticated and double-authentication needed : 
            try to retrieve credentials and authenticate him on otp-backend 
    """
    # If the user is authenticated but not double-authenticated and double-authentication required
    if authentication.double_authentication_required():
        logger.info(
            "PORTAL::log_in: Double authentication required for user {}".
            format(authentication.credentials[0]))
        try:
            # Instantiate DOUBLEAuthentication object
            db_authentication = DOUBLEAuthentication(portal_cookie)
            logger.debug(
                "PORTAL::log_in: DoubleAuthentication successfully created")
            # And try to retrieve credentials
            db_authentication.retrieve_credentials(request)
            logger.debug(
                "PORTAL::log_in: DoubleAuthentication credentials successfully retrieved"
            )
            # And use them to authenticate user
            db_authentication.authenticate(request)
            logger.info(
                "PORTAL::log_in: User '{}' successfully double authenticated".
                format(authentication.credentials[0]))

        except AssertionError as e:
            """ If redis_portal_session does not exists or can't retrieve otp key in redis """
            logger.error(
                "PORTAL::log_in: DoubleAuthentication failure for username '{}' : {}"
                .format(authentication.credentials[0], str(e)))
            return authentication.ask_credentials_response(
                request=request,
                portal_cookie_name=portal_cookie_name,
                error="Portal cookie expired")

        except (Application.DoesNotExist, ValidationError, InvalidId) as e:
            """ Invalid POST 'vulture_two_factors_authentication' value """
            logger.error(
                "PORTAL::log_in: Double-authentication failure for username {} : {}"
                .format(authentication.credentials[0], str(e)))
            return HttpResponseForbidden("Intrusion attempt blocked")

        except REDISWriteError as e:
            """ Cannot register double-authentication in Redis : internal server error """
            logger.error(
                "PORTAL::log_in: Failed to write double-authentication results in Redis for username '{}' : {}"
                .format(db_authentication.credentials[0], str(e)))
            return HttpResponseServerError()

        # If authentication failed : create double-authentication key and ask-it
        except CredentialsError as e:
            """ CredentialsError: no OTP credentials provided : ask-them """
            logger.error(
                "PORTAL::log_in: Double-authentication failure for username {} : {}"
                .format(authentication.credentials[0], str(e)))
            try:
                db_authentication.create_authentication()
                return db_authentication.ask_credentials_response(
                    request=request, portal_cookie_name=portal_cookie_name)

            except (OTPError, REDISWriteError, RedisConnectionError) as e:
                """ Error while sending/registering in Redis the OTP informations : display portal"""
                logger.error(
                    "PORTAL::log_in: Failed to create/send double-authentication key : {}"
                    .format(str(e)))
                db_authentication.deauthenticate_user()
                logger.info(
                    "PORTAL::log_in: User '{}' successfully deauthenticated due to db-authentication error"
                    .format(authentication.credentials[0]))
                return authentication.ask_credentials_response(
                    request=request,
                    error="<b> Error sending OTP Key </b> </br> " + str(e))

        except AuthenticationError as e:
            """ Bad OTP key """
            logger.error(
                "PORTAL::log_in: DoubleAuthentication failure for username {} : {}"
                .format(authentication.credentials[0], str(e)))
            try:
                db_authentication.create_authentication()
                db_authentication.authentication_failure()
                logger.debug(
                    "PORTAL:log_in: DoubleAuthentication failure successfully registered in Redis"
                )
                return db_authentication.ask_credentials_response(
                    request=request,
                    portal_cookie_name=portal_cookie_name,
                    error="<b> Bad OTP key </b>")

            except TwoManyOTPAuthFailure as e:
                logger.error(
                    "PORTAL::log_in: Two many OTP authentication failures for username'{}', "
                    "redirecting to portal".format(
                        authentication.credentials[0]))
                db_authentication.deauthenticate_user()
                logger.info(
                    "PORTAL::log_in: User '{}' successfully deauthenticated due to db-authentication error"
                    .format(authentication.credentials[0]))
                return authentication.ask_credentials_response(request=request,
                                                               error=e.message)

            except (OTPError, REDISWriteError, RedisConnectionError) as e:
                logger.error(
                    "PORTAL::log_in: Error while preparing double-authentication : {}"
                    .format(str(e)))
                return db_authentication.ask_credentials_response(
                    request=request,
                    portal_cookie_name=portal_cookie_name,
                    error="<b> Error sending OTP Key </b> </br> " + str(e))

        except OTPError as e:
            """ OTP Error while authenticating given token """
            logger.error(
                "PORTAL::log_in: Double-authentication failure for username {} : {}"
                .format(authentication.credentials[0], str(e)))
            return db_authentication.ask_credentials_response(
                request=request,
                portal_cookie_name=portal_cookie_name,
                error="<b> OTP Error </b> {}".format(str(e)))

        except TwoManyOTPAuthFailure as e:
            logger.error(
                "PORTAL::log_in: Two many OTP authentication failures for username'{}', "
                "redirecting to portal".format(authentication.credentials[0]))
            db_authentication.deauthenticate_user()
            logger.info(
                "PORTAL::log_in: User '{}' successfully deauthenticated due to db-authentication error"
                .format(authentication.credentials[0]))
            return authentication.ask_credentials_response(request=request,
                                                           error=e.message)

    # If we arrive here : the user is authenticated
    #  and double-authenticated if double-authentication needed
    sso_methods = {
        'form': SSOForwardPOST,
        'basic': SSOForwardBASIC,
        'kerberos': SSOForwardKERBEROS
    }
    """ If SSOForward enabled : perform-it """
    if authentication.application.sso_enabled:
        # Try to retrieve credentials from authentication object
        try:
            if not authentication.credentials[
                    0] or not authentication.credentials[1]:
                authentication.get_credentials(request)
            # If we cannot retrieve them, ask credentials
            if not authentication.credentials[
                    0]:  # or not authentication.credentials[1]:
                return authentication.ask_credentials_response(
                    request=request,
                    portal_cookie_name=portal_cookie_name,
                    error="Credentials not found")
            logger.info(
                "PORTAL::log_in: Credentials successfuly retrieven for SSO performing"
            )

        except Exception as e:
            logger.error(
                "PORTAL::log_in: Error while retrieving credentials for SSO : "
            )
            logger.exception(e)
            return authentication.ask_credentials_response(
                request=request,
                portal_cookie_name=portal_cookie_name,
                error="Credentials not found")

        try:
            # Instantiate SSOForward object with sso_forward type
            sso_forward = sso_methods[authentication.application.sso_forward](
                request, authentication.application, authentication)
            logger.info("PORTAL::log_in: SSOForward successfully created")
            # Get credentials needed for sso forward : AutoLogon or Learning
            sso_data, profiles_to_stock, url = sso_forward.retrieve_credentials(
                request)
            logger.info(
                "PORTAL::log_in: SSOForward credentials successfully retrieven"
            )
            # If credentials retrieven needs to be stocked
            for profile_name, profile_value in profiles_to_stock.items():
                sso_forward.stock_sso_field(authentication.credentials[0],
                                            profile_name, profile_value)

            # Use 'sso_data' and 'url' to authenticate user on application
            response = sso_forward.authenticate(
                sso_data,
                post_url=url,
                redis_session=authentication.redis_session)
            logger.info("PORTAL::log_in: SSOForward performing success")
            # Generate response depending on application.sso_forward options
            final_response = sso_forward.generate_response(
                request, response, authentication.get_redirect_url())
            logger.info(
                "PORTAL::log_in: SSOForward response successfuly generated")

            # If the user has not yet a portal cookie : give-it
            if not request.COOKIES.get(portal_cookie_name, None) or \
                    not authentication.redis_base.hgetall(request.COOKIES.get(portal_cookie_name, None)):
                final_response = set_portal_cookie(
                    final_response, portal_cookie_name, portal_cookie,
                    authentication.get_redirect_url())

            return final_response

        # If learning credentials cannot be retrieven : ask them
        except CredentialsMissingError as e:
            logger.error(
                "PORTAL::log_in: Learning credentials missing : asking-them")
            return authentication.ask_learning_credentials(
                request=request,
                portal_cookie_name=None if request.POST.get(
                    portal_cookie_name, None) else portal_cookie_name,
                fields=e.fields_missing)

        # If KerberosBackend object cannot be retrieven from mongo with the backend_id that the user is authenticated on
        except InvalidId:
            logger.error(
                "PORTAL::log_in: The user is authenticated on a not Kerberos backend, cannot do SSOForward"
            )

        except (RequestsConnectionError, OpenSSLError) as e:
            logger.error(
                "PORTAL::log_in: ConnectionError while trying to SSO to backend : "
            )
            logger.exception(e)

        except Exception as e:
            logger.error(
                "PORTAL::log_in: Unexpected error while trying to perform SSO Forward :"
            )
            logger.exception(e)
    """ If no response has been returned yet : redirect to the asked-uri/default-uri with portal_cookie """
    redirection_url = authentication.get_redirect_url()
    logger.info(
        "PORTAL::log_in: Redirecting user to '{}'".format(redirection_url))
    try:
        kerberos_token_resp = authentication_results['data']['token_resp']
    except:
        kerberos_token_resp = None
    return response_redirect_with_portal_cookie(
        redirection_url, portal_cookie_name, portal_cookie,
        redirection_url.startswith('https'), kerberos_token_resp)
Exemple #7
0
def security_update(node_logger=None):
    """
    :return: Update Vulture's security databases
    """
    # Get proxy first
    proxies = get_proxy()
    """ Every node needs to be up2date """
    try:
        logger.info("Crontab::security_update: calling pkg update...")
        res = subprocess.check_output([
            "/usr/local/bin/sudo", "/usr/sbin/pkg", "-ohttp_proxy={}".format(
                proxies.get('http', "")), "-ohttps_proxy={}".format(
                    proxies.get('https', "")), "-oftp_proxy={}".format(
                        proxies.get('ftp', "")), "update"
        ],
                                      stderr=subprocess.PIPE).decode("utf-8")
        if "All repositories are up to date" not in res:
            logger.error("Crontab::security_update: Unable to update pkg")
        else:
            logger.info(
                "Crontab::security_update: All repositories are up to date")
    except subprocess.CalledProcessError as e:
        logger.error("Failed to update pkg packages : {}".format(
            str(e.stderr.decode('utf8'))))
    except Exception as e:
        logger.error("Failed to update pkg packages : {}".format(str(e)))
    """ Do we have something urgent to update ? """
    try:
        logger.info("Crontab::security_update: calling pkg upgrade...")
        res = subprocess.check_output([
            "/usr/local/bin/sudo", "/usr/sbin/pkg", "-ohttp_proxy={}".format(
                proxies.get('http', "")), "-ohttps_proxy={}".format(
                    proxies.get('https', "")), "-oftp_proxy={}".format(
                        proxies.get('ftp', "")), "audit", "-F"
        ],
                                      stderr=subprocess.PIPE).decode('utf8')
        if "0 problem" in res:
            logger.info("Crontab::security_update: No vulnerability found.")
        elif "is vulnerable" in res:
            logger.info(
                "Crontab::security_update: Security problem found : {}".format(
                    res))
            security_alert(
                "Security problem found on node {}".format(get_hostname()),
                "danger", res)
    except subprocess.CalledProcessError as e:
        if e.stdout.decode("utf-8").startswith("0 problem"):
            logger.info("Crontab::security_update: No vulnerability found.")
        elif "is vulnerable" in e.stdout.decode("utf-8"):
            logger.info(
                "Crontab::security_update: Security problem found : {}".format(
                    e.stdout.decode('utf-8')))
            security_alert(
                "Security problem found on node {}".format(get_hostname()),
                "danger", e.stdout.decode("utf-8"))
        else:
            logger.error(
                "Crontab::security_update: Failed to retrieve vulnerabilities : "
                "{}".format(str(e)))
    except Exception as e:
        logger.error(
            "Crontab::security_update: Failed to retrieve vulnerabilities : {}"
            .format(e))

    # If it is the master node, retrieve the databases
    if Cluster.get_current_node().is_master_mongo:
        # Retrieve predator_token
        predator_token = Cluster.get_global_config().predator_apikey
        """ If we are the master node, download newest reputation databases """
        try:
            logger.info("Crontab::security_update: get Vulture's ipsets...")
            infos = requests.get(IPSET_VULTURE + "index.json",
                                 headers={
                                     'Authorization': predator_token
                                 },
                                 proxies=proxies,
                                 timeout=5).json()
        except Exception as e:
            logger.error(
                "Crontab::security_update: Unable to download Vulture's ipsets: {}"
                .format(e))
            return False

        infos.append({
            'filename':
            "GeoLite2-Country.mmdb",
            'label':
            "Geolite2 Country",
            'description':
            "Maxmind DB's Geoip country database",
            'type':
            "GeoIP",
            'url':
            "https://updates.maxmind.com/geoip/databases/GeoLite2-Country/update"
        })
        infos.append({
            'filename':
            "GeoLite2-City.mmdb",
            'label':
            "Geolite2 City",
            'description':
            "Maxmind DB's Geoip city database",
            'type':
            "GeoIP",
            'url':
            "https://updates.maxmind.com/geoip/databases/GeoLite2-City/update"
        })
        infos.append({
            'filename': "firehol_level1.netset",
            'label': "Firehol Level 1 netset",
            'description': "Firehol IPSET Level 1",
            'type': "ipv4_netset",
            'url': IPSET_VULTURE + "firehol_level1.netset"
        })
        infos.append({
            'filename': "vulture-v4.netset",
            'label': "Vulture Cloud IPv4",
            'description': "Vulture Cloud IPv4",
            'type': "ipv4_netset",
            'url': IPSET_VULTURE + "firehol_level1.netset"
        })
        infos.append({
            'filename': "vulture-v6.netset",
            'label': "Vulture Cloud IPv6",
            'description': "Vulture Cloud IPv6",
            'type': "ipv6_netset",
            'url': IPSET_VULTURE + "vulture-v6.netset"
        })

        for info in infos:
            filename = info['filename']
            label = info['label']
            description = info['description']
            entry_type = info['type']
            url = info.get('url', IPSET_VULTURE + filename)
            nb_netset = info.get('nb_netset', 0)
            nb_unique = info.get('nb_unique', 0)
            """ Create/update object """
            try:
                reputation_ctx = ReputationContext.objects.get(
                    filename=filename)
            except Exception as e:
                reputation_ctx = ReputationContext(filename=filename)
            reputation_ctx.name = label
            reputation_ctx.url = url
            reputation_ctx.db_type = entry_type
            reputation_ctx.label = label
            reputation_ctx.description = description
            reputation_ctx.nb_netset = nb_netset
            reputation_ctx.nb_unique = nb_unique
            reputation_ctx.internal = True
            # Use predator_apikey only for predator requests
            if "predator.vultureproject.org" in reputation_ctx.url:
                reputation_ctx.custom_headers = {
                    'Authorization': predator_token
                }
            else:
                reputation_ctx.custom_headers = {}
            reputation_ctx.save()
            logger.info("Reputation context {} created.".format(label))

    # On ALL nodes, write databases on disk
    # All internal reputation contexts are retrieved and created if needed
    # We can now download and write all reputation contexts
    for reputation_ctx in ReputationContext.objects.all():
        try:
            content = reputation_ctx.download()
        except Exception as e:
            logger.error(
                "Security_update::error: Failed to download reputation database '{}' : {}"
                .format(reputation_ctx.name, e))
            continue
        try:
            tmp_filename = "{}{}".format("/tmp/", get_random_string())
            with open(tmp_filename, "wb") as f:
                f.write(content)
            """ Immediatly reload the rsyslog service to prevent crash on MMDB access """
            # Filename is a variable of us (not injectable)
            reload_rsyslog = subprocess.run([
                '/usr/local/bin/sudo /bin/mv {} {}'
                '&& /usr/local/bin/sudo /usr/sbin/jexec '
                'rsyslog /usr/sbin/service rsyslogd reload'.format(
                    tmp_filename, reputation_ctx.absolute_filename)
            ],
                                            stdout=subprocess.PIPE,
                                            stderr=subprocess.PIPE,
                                            shell=True)
            if reload_rsyslog.returncode == 1:
                if "rsyslogd not running" in reload_rsyslog.stderr.decode(
                        'utf8'):
                    logger.info(
                        "Crontab::security_update: Database written and rsyslogd not runing."
                    )
                else:
                    logger.error(
                        "Crontab::security_update: It seems that the database cannot be written : {}"
                        .format(e))
            elif reload_rsyslog.returncode == 0:
                logger.info(
                    "Crontab::security_update: Database written and rsyslogd reloaded."
                )
            else:
                logger.error(
                    "Crontab::security_update: Database write failure : "
                    "stdout={}, stderr={}".format(
                        reload_rsyslog.stdout.decode('utf8'),
                        reload_rsyslog.stderr.decode('utf8')))
            logger.info(
                "Crontab::security_update: Reputation database named '{}' (file '{}') successfully written."
                .format(reputation_ctx.name, reputation_ctx.absolute_filename))
        except Exception as e:
            logger.error(
                "Security_update::error: Failed to write reputation database '{}' : {}"
                .format(reputation_ctx.name, e))

    logger.info("Security_update done.")

    return True
Exemple #8
0
def cluster_create(admin_user=None, admin_password=None):
    """
    Initialize a new cluster from scratch
    This requires a valid hostname and existing certificates

    :param logger: A valid logger
    :return: True / False
    """

    """ We're comming from CLI: Abort if cluster already exists """
    try:
        Cluster.objects.get()
        logger.error("Error: Cluster already exists")
        return False
    except Exception:
        pass

    """ Create Cluster object in database """
    cluster = Cluster()

    """ Create the local node """
    node, new_node = Node.objects.get_or_create(
        name=get_hostname(),
        management_ip=get_management_ip()
    )

    if new_node:
        logger.info("Registering new node '{}'".format(node.name))
        node.internet_ip = node.management_ip
        node.save()

    """ Read network config and store it into mongo """
    """ No rights to do that in jail - API request """
    node.api_request('toolkit.network.network.refresh_nic')
    """ Read ZFS file systems """
    node.api_request('system.zfs.zfs.refresh')

    """ Obtain Certificates and store it into mongoDB """
    with open("/var/db/pki/ca.pem") as f:
        ca_cert = f.read()

    with open("/var/db/pki/ca.key") as f:
        ca_key = f.read()

    with open("/var/db/pki/node.cert") as f:
        cert = f.read()

    with open("/var/db/pki/node.key") as f:
        key = f.read()

    internal_ca = X509Certificate(
        is_vulture_ca=True,
        is_ca=True,
        is_external=False,
        status='V',
        cert=ca_cert,
        key=ca_key,
        serial=1
    )

    ca_name = internal_ca.explose_dn()['CN']
    internal_ca.name = ca_name
    internal_ca.save()

    node_cert = X509Certificate(
        is_vulture_ca=False,
        is_ca=False,
        is_external=False,
        status='V',
        cert=cert,
        key=key,
        chain=ca_cert,
        serial=2
    )

    node_name = node_cert.explose_dn()['CN']
    node_cert.name = node_name
    node_cert.save()

    """ Save cluster here, in case of error """
    cluster.save()

    """ Generate random API Key for cluster management and NMAP """
    """ Config object can not exists yet """
    system_config = cluster.get_global_config()
    system_config.cluster_api_key = get_random_string(
        16, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.,+')
    system_config.portal_cookie_name = get_random_string(
        8, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
    system_config.public_token = get_random_string(16, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
    system_config.set_logs_ttl()
    system_config.save()

    for name in ('Administrator', 'Log Viewer'):
        Group.objects.get_or_create(
            name=name
        )

    """ Delete any existing user """
    User.objects.all().delete()

    if admin_user and admin_password:
        user = User.objects.create_superuser(admin_user, 'changeme@localhost', admin_password)
        user.save()

    time.sleep(5)
    """ Tell local sentinel to monitor local redis server """
    c = RedisBase(get_management_ip(), 26379)
    try:
        # It may failed if Redis / Sentinel is already configured
        c.sentinel_monitor()
    except Exception as e:
        logger.error("Install::Sentinel monitor: Error: ")
        logger.exception(e)

    """ Update uri of internal Log Forwarder """
    logfwd = LogOMMongoDB.objects.get()
    logfwd.uristr = "mongodb://{}:9091/?replicaset=Vulture&ssl=true".format(get_hostname())
    logfwd.x509_certificate = node_cert
    logfwd.save()

    """ Create default TLSProfile """
    tls_profile = TLSProfile(name="Default TLS profile", x509_certificate=node_cert)  # All vars by default
    tls_profile.save()

    """ Download reputation databases before crontab """
    node.api_request("gui.crontab.feed.security_update")

    """ And configure + restart netdata """
    logger.debug("API call to netdata configure_node")
    node.api_request('services.netdata.netdata.configure_node')

    logger.debug("API call to restart netdata service")
    node.api_request('services.netdata.netdata.restart_service')

    """ No need to restart rsyslog because the conf is not complete """
    logger.debug("API call to configure rsyslog")
    node.api_request("services.rsyslogd.rsyslog.build_conf")
    node.api_request("services.rsyslogd.rsyslog.configure_node")
    node.api_request("services.rsyslogd.rsyslog.restart_service")

    logger.debug("API call to configure HAProxy")
    node.api_request("services.haproxy.haproxy.configure_node")

    logger.debug("API call to write default Darwin filters conf")
    for policy in DarwinPolicy.objects.all():
        node.api_request("services.darwin.darwin.write_policy_conf", policy.pk)

    logger.debug("API call to configure Darwin default policy")
    node.api_request("services.darwin.darwin.build_conf")

    logger.debug("API call to configure Apache GUI")
    node.api_request("services.apache.apache.reload_conf")

    logger.debug("API call to configure Logrotate")
    node.api_request("services.logrotate.logrotate.reload_conf")
Exemple #9
0
def handle_disconnect(request, workflow_id=None):
    """ Handle User Disconnection
    If we are here, mod_vulture has already delete the application session in redis
    According to the configuration of App, this handler will:
     - Display a "Logout Message"
     - Destroy the portal session
     - Redirect to the application (ie: display the Login portal)

    :param request: Django request object
    :returns: Self-service portal
    """
    global_config = Cluster.get_global_config()
    """ Try to find the application with the requested URI """
    try:
        workflow = Workflow.objects.get(pk=workflow_id)
    except:
        logger.error(
            "DISCONNECT::handle_disconnect: Unable to find workflow having id '{}'"
            .format(workflow_id))
        return HttpResponseForbidden("Invalid Workflow.")
    """ Get portal_cookie name from cluster """
    portal_cookie_name = global_config.portal_cookie_name
    """ Get portal cookie value (if exists) """
    portal_cookie = request.COOKIES.get(portal_cookie_name, None)
    if portal_cookie:
        logger.debug(
            "DISCONNECT::handle_disconnect: portal_cookie Found: {}".format(
                portal_cookie))
    else:
        logger.error(
            "DISCONNECT::handle_disconnect: portal_cookie not found !")
        return HttpResponseForbidden("Access Denied.")
    """ Connect to Redis """
    r = REDISBase()
    if not r:
        logger.info("PORTAL::self: Unable to connect to REDIS !")
        return HttpResponseServerError()

    portal_session = REDISPortalSession(r, portal_cookie)
    """ The user do not have a portal session: Access is forbidden """
    if not portal_session.exists():
        return HttpResponseForbidden("Invalid session.")

    # FIXME
    """ Destroy portal session if needed """
    if workflow.app_disconnect_portal:
        logger.info(
            "DISCONNECT::handle_disconnect: portal session '{}' has been destroyed"
            .format(portal_cookie))
        portal_session.destroy()

    # FIXME
    """ Display Logout message if needed (otherwise redirect to application) """
    if workflow.app_display_logout_message:
        template = workflow.template
        style = '<link rel="stylesheet" type="text/css" href="/' + str(
            global_config.public_token) + '/templates/portal_%s.css">' % (str(
                template.id))
        logger.debug(
            "DISCONNECT::handle_disconnect: Display template '{}'".format(
                template.name))
        return render_to_response("portal_%s_html_logout.conf" %
                                  (str(template.id)), {
                                      'style': style,
                                      'app_url': workflow.get_redirect_uri()
                                  },
                                  context_instance=RequestContext(request))
    else:
        logger.debug(
            "DISCONNECT::handle_disconnect: Redirecting to redirect_uri '{}'".
            format(workflow.get_redirect_uri()))
        return HttpResponseRedirect(workflow.get_redirect_uri())
Exemple #10
0
 def generate_conf(self):
     """ Render the conf with Jinja template and self.to_template() method
     :return     The generated configuration as string, or raise
     """
     # The following var is only used by error, do not forget to adapt if needed
     template_name = JINJA_PATH + JINJA_TEMPLATE
     try:
         jinja2_env = Environment(loader=FileSystemLoader(JINJA_PATH))
         template = jinja2_env.get_template(JINJA_TEMPLATE)
         return template.render({'conf': self.to_template_external(), 'global_config': Cluster.get_global_config()})
     # In ALL exceptions, associate an error message
     # The exception instantiation MUST be IN except statement, to retrieve traceback in __init__
     except TemplateNotFound:
         exception = ServiceJinjaError("The following file cannot be found : '{}'".format(template_name), "haproxy")
     except TemplatesNotFound:
         exception = ServiceJinjaError("The following files cannot be found : '{}'".format(template_name), "haproxy")
     except (TemplateAssertionError, TemplateRuntimeError):
         exception = ServiceJinjaError("Unknown error in template generation: {}".format(template_name), "haproxy")
     except UndefinedError:
         exception = ServiceJinjaError("A variable is undefined while trying to render the following template: "
                                       "{}".format(template_name), "haproxy")
     except TemplateSyntaxError:
         exception = ServiceJinjaError("Syntax error in the template: '{}'".format(template_name), "haproxy")
     # If there was an exception, raise a more general exception with the message and the traceback
     raise exception
Exemple #11
0
def config_edit(request, api=False, update=False):
    config_model = Cluster.get_global_config()

    if hasattr(request, "JSON") and api:
        if update:
            request.JSON = {**config_model.to_dict(), **request.JSON}
        request_data = request.JSON
    else:
        request_data = request.POST

    # Verify if attribute has changed HERE, config_model will be modified after (at form.is_valid())
    has_customer_changed = request_data.get(
        'customer_name') != config_model.customer_name
    has_portal_cookie_name_changed = request_data.get(
        'portal_cookie_name') != config_model.portal_cookie_name
    ssh_authorized_key_changed = request_data.get(
        'ssh_authorized_key') != config_model.ssh_authorized_key
    logs_ttl_changed = request_data.get('logs_ttl') != config_model.logs_ttl

    form = ConfigForm(request_data or None,
                      instance=config_model,
                      error_class=DivErrorList)

    def render_form(**kwargs):
        save_error = kwargs.get('save_error')
        if api:
            if form.errors:
                return JsonResponse(form.errors.get_json_data(), status=400)
            if save_error:
                return JsonResponse({'error': save_error[0]}, status=500)
        return render(request, 'config/config.html', {'form': form, **kwargs})

    if request.method in ("POST", "PUT", "PATCH") and form.is_valid():
        config = form.save(commit=False)
        config.save()
        """ Write .ssh/authorized_keys if any change detected """
        if ssh_authorized_key_changed:
            params = [
                "/usr/home/vlt-adm/.ssh/authorized_keys",
                request_data.get('ssh_authorized_key'), 'vlt-adm:wheel', '600'
            ]
            for node in Node.objects.all():
                try:
                    node.api_request('system.config.models.write_conf',
                                     config=params)
                except Exception as e:
                    raise VultureSystemConfigError(
                        "on node '{}'.\nRequest failure.".format(node.name))
        """ If customer name has changed, rewrite rsyslog templates """
        error = ""
        if has_customer_changed:
            api_res = Cluster.api_request(
                "services.rsyslogd.rsyslog.configure_node")
            if not api_res.get('status'):
                error = api_res.get('message')
        if has_portal_cookie_name_changed:
            api_res = Cluster.api_request(
                "services.haproxy.haproxy.configure_node")
            if not api_res.get('status'):
                error = api_res.get('message')
        if logs_ttl_changed:
            res, mess = config_model.set_logs_ttl()
            if not res:
                return render_form(save_error=mess)
        if error:
            return render_form(save_error=error)
        if api:
            return build_response_config("system.config.api", [])

        return HttpResponseRedirect('/system/config/')

    # If request PATCH or PUT & form not valid - return error
    if api:
        logger.error("Config api form error : {}".format(
            form.errors.get_json_data()))
        return JsonResponse(form.errors.get_json_data(), status=400)

    return render_form()
Exemple #12
0
def pf_whitelist_blacklist(request, list_type=None):
    """
        Add or Delete an IP from PacketFilter Whitelist or Blacklist
        JSON Parameters:
            ip_address: IPv4/IPv6
            action: (str: add/delete)
    """

    #FIXME: Use decorators
    try:
        ip_address = request.JSON.get('ip_address')
        action = request.JSON.get('action')
    except:
        #We are coming from the GUI
        list_type = request.POST.get("list_type")
        ip_address = request.POST.get("ip_address")
        action = request.POST.get("action")

    if list_type not in ('blacklist', 'whitelist'):
        return JsonResponse({
            'status': False,
            'error': _('Invalid Packet Filter list')
        })

    if action not in ('add', 'del'):
        return JsonResponse({'status': False, 'error': _('Invalid action')})

    if not ip_address:
        return JsonResponse({
            'status': False,
            'error': _('No ip address specified')
        })

    config_model = Cluster.get_global_config()
    if list_type == "whitelist":
        pf_list = config_model.pf_whitelist.split(',')
    else:
        pf_list = config_model.pf_blacklist.split(',')

    if action == "add":
        if ip_address not in pf_list:
            pf_list.append(ip_address)
        else:
            return JsonResponse({
                'status': False,
                'error': _('address already present in list')
            })
    elif action == "del":
        if ip_address in pf_list:
            pf_list.remove(ip_address)
        else:
            return JsonResponse({
                'status': False,
                'error': _('address not present in list')
            })

    try:
        ConfigForm.validate_ip_list(pf_list)
        if list_type == "whitelist":
            config_model.pf_whitelist = ",".join(pf_list)
        else:
            config_model.pf_blacklist = ",".join(pf_list)

    except Exception as e:
        return JsonResponse({'status': False, 'error': str(e)})

    config_model.save()
    return JsonResponse({'status': True})
Exemple #13
0
    def post(self, request, portal_id, repo_id, action=None):
        try:
            portal = UserAuthentication.objects.get(pk=portal_id)
            ldap_repo = get_repo(portal, repo_id)

            if action and action not in ("resend_registration",
                                         "reset_password", "lock", "unlock",
                                         "reset_otp"):
                return JsonResponse(
                    {
                        "status": False,
                        "error": _("Invalid action")
                    },
                    status=400)

            elif not action:
                config = Cluster.get_global_config()
                assert config.smtp_server, "SMTP server is not properly configured."
                try:
                    # This method simply raises if an error occur
                    test_smtp_server(config.smtp_server
                                     ), "SMTP server seems to be unavailable."
                except Exception as e:
                    raise Exception(
                        "SMTP server is not properly configured: {}".format(
                            str(e)))

                user = {ldap_repo.user_attr: request.JSON['username']}

                attrs = {}
                if ldap_repo.user_account_locked_attr:
                    attr = ldap_repo.get_user_account_locked_attr
                    attrs[attr] = request.JSON.get('is_locked')

                if ldap_repo.user_change_password_attr:
                    attr = ldap_repo.get_user_change_password_attr
                    attrs[attr] = request.JSON.get('need_change_password')

                if ldap_repo.user_mobile_attr:
                    attrs[ldap_repo.user_mobile_attr] = request.JSON.get(
                        'mobile')

                if ldap_repo.user_email_attr:
                    attrs[ldap_repo.user_email_attr] = request.JSON.get(
                        'email')

                # Variable needed to send user's registration
                user_mail = request.JSON.get('email')

                for key, value in MAPPING_ATTRIBUTES.items():
                    attrs[value["internal_key"]] = request.JSON.get(key)

                group_name = None
                if portal.update_group_registration:
                    group_name = f"{ldap_repo.group_attr}={portal.group_registration}"

                ldap_response, user_id = tools.create_user(
                    ldap_repo, user[ldap_repo.user_attr],
                    request.JSON.get('userPassword'), attrs, group_name)
            else:
                # We get an action
                # !! If we get a DN, extract username to search in LDAP configured scope (for segregation regards) !!
                user = request.JSON['id']
                if "," in user:
                    user = user.split(",")[0]
                if "=" in user:
                    user = user.split('=')[1]
                # We will need user' email for registration and reset
                user_id, user_mail = tools.find_user_email(ldap_repo, user)
                logger.info(f"User's email found : {user_mail}")

            if not action or action == "resend_registration":
                if not perform_email_registration(
                        logger,
                        f"https://{portal.external_fqdn}/",
                        portal.name,
                        portal.portal_template,
                        user_mail,
                        repo_id=repo_id,
                        expire=72 * 3600):
                    logger.error(
                        f"Failed to send registration email to '{user_mail}'")
                    return JsonResponse(
                        {
                            'status': False,
                            'error':
                            _("Fail to send user's registration email")
                        },
                        status=500)
                else:
                    logger.info(f"Registration email re-sent to '{user_mail}'")

            elif action == "reset_password":
                if not perform_email_reset(logger,
                                           f"https://{portal.external_fqdn}/",
                                           portal.name,
                                           portal.portal_template,
                                           user_mail,
                                           repo_id=repo_id,
                                           expire=3600):
                    logger.error(
                        f"Failed to send reset password email to '{user_mail}'"
                    )
                    return JsonResponse(
                        {
                            'status': False,
                            'error':
                            _("Fail to send user's reset password email")
                        },
                        status=500)
                else:
                    logger.info(f"Reset password email sent to '{user_mail}'")

            elif action == "reset_otp":
                try:
                    if not portal.otp_repository:
                        logger.error(
                            f"IDP::Reset_otp: TOTP not configured for portal {portal}"
                        )
                        return JsonResponse({
                            'status':
                            False,
                            'error':
                            _("TOTP not configured on portal")
                        })
                    otp_profile = TOTPProfile.objects.get(
                        auth_repository=ldap_repo,
                        totp_repository=portal.otp_repository,
                        login=user)
                    otp_profile.delete()
                except TOTPProfile.DoesNotExist:
                    logger.error(
                        f"TOTP Profile not found for repo='{ldap_repo}', "
                        f"otp_repo='{portal.otp_repository}', user='******'")
                    return JsonResponse(
                        {
                            'status': False,
                            'error': _("TOTP Profile not found")
                        },
                        status=404)
                except Exception as e:
                    logger.exception(e)
                    logger.error(f"Failed to reset otp for user '{user}'")
                    return JsonResponse(
                        {
                            'status': False,
                            'error': _("Fail to reset otp")
                        },
                        status=500)
                else:
                    logger.info(f"Reset otp done for '{user}'")

            elif action in ("lock", "unlock"):
                user_dn = request.JSON["id"]
                to_lock = action == "lock"
                ldap_response, user_id = tools.lock_unlock_user(ldap_repo,
                                                                user_dn,
                                                                lock=to_lock)

            return JsonResponse({
                "status": True,
                "user_id": user_id
            },
                                status=201)
        except KeyError as err:
            logger.debug(err)
            return JsonResponse({
                "status": False,
                "error": _("Invalid call")
            },
                                status=400)

        except NotUniqueError as e:
            return JsonResponse(
                {
                    "status": False,
                    "error": _("User already exist"),
                    "user_id": str(e)
                },
                status=409)
        except UserAuthentication.DoesNotExist:
            return JsonResponse(
                {
                    "status": False,
                    "error": _("Portal does not exist")
                },
                status=404)
        except Exception as err:
            logger.critical(err, exc_info=1)
            if settings.DEV_MODE:
                raise

            return JsonResponse({
                "status": False,
                "error": str(err)
            },
                                status=500)