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()}
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() }
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
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
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
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)
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
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")
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())
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
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()
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})
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)