def server_config(): """ Returns the OpenVPN server configuration generated from the application settings. :return: str """ conf = render_to_string( "openvpn/server.conf", { "root_dir": settings.BASE_DIR, "bind_address": net.interface_ip(config.get("vpn_interface")), "bind_port": config.get("vpn_port"), "ca_crt": config.get("ca_crt"), "crl_file": settings.PKI_CRL_FILE, "dh_params": config.get("vpn_dh_params"), "domain": config.get("domain"), "log_file": settings.OPENVPN_LOG_FILE, "managment_socket": settings.OPENVPN_MANAGEMENT_SOCKET, "nameservers": config.get_list("vpn_nameservers"), "protocol": config.get("vpn_protocol"), "redirect_gateway": config.get_bool("vpn_redirect_gateway", False), "routes": config.get_list("vpn_routes"), "server_crt": config.get("vpn_crt"), "server_key": config.get("vpn_key"), "status_file": settings.OPENVPN_STATUS_FILE, "subnet": config.get("vpn_subnet"), "tls_auth_key": config.get("vpn_tls_auth_key"), }) return strings.remove_empty_lines(conf)
def send(recipient, subject, body): """ Sends an e-mail with the given details by calling an application task. :return: bool """ sender = config.get("smtp_reply_address", config.get("smtp_username")) tasks.send_email(recipient, subject, body, sender) return True
def is_configured(): """ Returns whether the application settings are configured to send e-mails. :return: bool """ return not (not config.get("smtp_host") or not config.get("smtp_port") or not config.get("smtp_username") or not config.get("smtp_password"))
def certificate_authority(): """ Return's the application certificate authority keypair. :return: KeyPair """ return load_keypair( crt=config.get("ca_crt"), key=config.get("ca_key"), )
def base_context_processor(request): """ The base UI context processor. :return: dict """ return { "error": request.session.pop("error", None), "form": request.session.pop("form", {}), "oauth2_provider": config.get("oauth2_provider", None), "organization": config.get("app_organization", "Mangle"), }
def _render_rules(): """ Renders and returns the web application firewall rules template. :return: str """ return render_to_string( template_name="firewall/web.rules", context={ "http_port": config.get("app_http_port", 80), "https_port": config.get("app_https_port", 443), }, )
def create(self, request, *args, **kwargs): """ Creates a user account for all of the submitted e-mail addresses. :return: Response """ serializer = serializers.UserInviteSerializer(data=request.data) users = serializer.save() # contains the e-mail and password that is returned to the UI emails_passwords = [] # send e-mail notifications if desired for user in users: emails_passwords.append({ "email": user.email, "password": user.temp_password, }) if request.data.get("notify", False): organization = config.get("app_organization") mail.send_template( recipient=user.email, subject="Welcome to the {} VPN!".format(organization), template="mail/Welcome.html", data={ "email": user.email, "organization": organization, "password": user.temp_password, "url": config.url(), }) return Response(emails_passwords, status=status.HTTP_201_CREATED)
def render_rules(): """ Returns the base OpenVPN rules. :return: str """ return render_to_string( template_name="firewall/vpn.rules", context={ "interface": config.get("vpn_interface"), "nat_interface": config.get("vpn_nat_interface"), "local_addrs": net.ip_addresses(), "port": config.get("vpn_port"), "protocol": config.get("vpn_protocol"), "nameservers": config.get_list("vpn_nameservers"), "subnet": config.get("vpn_subnet"), }, )
def get_disposition(self, device): """ Returns the 'Content-Disposition' header value for the given device configuration file download. :return: str """ org = config.get("app_organization") return 'attachment; filename="{} - {}.ovpn"'.format(org, device.name)
def init(): """ Sets the Django SMTP settings from the application settings and returns whether the application is ready to send e-mails. :return: None """ config.reload() if not is_configured(): return False settings.EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' settings.EMAIL_USE_TLS = config.get("smtp_tls", True) settings.EMAIL_HOST = config.get("smtp_host") settings.EMAIL_PORT = config.get("smtp_port") settings.EMAIL_HOST_USER = config.get("smtp_username") settings.EMAIL_HOST_PASSWORD = config.get("smtp_password") return True
def client_config(crt, key, os="windows"): """ Returns an OpenVPN client configuration for the given client certificate and private key. If `is_linux` is True then the proper DNS scripts commands are added. :return: str """ conf = render_to_string( "openvpn/client.conf", { "ca_crt": config.get("ca_crt"), "client_crt": crt, "client_key": key, "hostname": config.get("vpn_hostname"), "os": os, "port": config.get("vpn_port"), "protocol": config.get("vpn_protocol"), "tls_auth_key": config.get("vpn_tls_auth_key"), }) return strings.remove_empty_lines(conf)
def create_web_vhost(): """ Creates the web application Nginx virtual host file. :return: None """ content = render_to_string( "install/nginx/mangle-web.conf", { "hostname": config.get("app_hostname"), "http_port": config.get("app_http_port", 80), "https_port": config.get("app_https_port", 443), "root_dir": settings.BASE_DIR, "ssl_crt": settings.WEB_SSL_CRT_FILE, "ssl_key": settings.WEB_SSL_KEY_FILE, "ssl_dh": settings.WEB_SSL_DH_FILE, "wsgi_socket": settings.WEB_WSGI_SOCKET, }) fs.write_file(settings.WEB_VHOST_FILE, content, 0o755) bash.run("systemctl", "restart", "nginx")
def web_post_stop(): """ Deletes the web application firewall rules. :return: None """ rules = config.get("web_firewall_rules", "") for rule in rules.split("\n"): iptables.run(rule.replace("-A", "-D")) config.delete("web_firewall_rules")
def redirect_login(request): """ Returns the login URL based on the current authentication backend. :return: Any """ backend = config.get("auth_backend", "oauth2") if backend == "oauth2": url, state = oauth2.get_provider().get_login_url() request.session["oauth_state"] = state return redirect(url)
def get_provider(): """ Returns the OAuth2 provider from the application settings. :return: OAuth2Provider """ provider = config.get("oauth2_provider", "google") if provider == "google": return GoogleOAuth2Provider() raise ValueError("unknown oauth2 provider: " + provider)
def mfa_url(self): """ Returns the user's two-factor authenticaiton provisioning URL. :return: str """ organization = config.get("app_organization", "Mangle") return pyotp.TOTP(self.mfa_secret).provisioning_uri( name=self.email, issuer_name="{} VPN".format(organization), )
def save(self, **kwargs): """ Updates the application settings and SSL certificate and private key if provided. :return: None """ old_http_port = config.get("app_http_port") old_https_port = config.get("app_https_port") # the SSL certificate and private key should not be stored in the # database as settings so remove them from the validated_data dict # prior to saving ssl_crt = self.initial_data.pop("app_ssl_crt", None) ssl_key = self.initial_data.pop("app_ssl_key", None) super().save(**kwargs) if ssl_crt and ssl_key: fs.write_file(settings.WEB_SSL_CRT_FILE, ssl_crt, 0o600) fs.write_file(settings.WEB_SSL_KEY_FILE, ssl_key, 0o600) bash.run("systemctl", "reload", "nginx") if (old_http_port != self.validated_data["app_http_port"] or old_https_port != self.validated_data["app_https_port"]): # update HTTP and HTTPs listen directives http = "sed -i 's/{} default_server/{} default_server/g' /etc/nginx/conf.d/mangle.conf" bash.run(http.format(old_http_port, self.validated_data["app_http_port"])) https = "sed -i 's/{} ssl/{} ssl/g' /etc/nginx/conf.d/mangle.conf" bash.run(https.format(old_https_port, self.validated_data["app_https_port"])) # update the port number in redirect directive bash.run("sed -i 's/$host:{}/$host:{}/g' /etc/nginx/conf.d/mangle.conf".format( old_https_port, self.validated_data["app_https_port"] )) # restart Nginx and web application bash.run("systemctl", "restart", "nginx") bash.run("systemctl", "restart", "mangle-web")
def password(self, request, pk=None): """ Resets the user's password and forces them to change it. :return: Response """ user = self.get_object() password = user.reset_password() user.save() # send e-mail notifying user mail.send_template(recipient=user.email, subject="{} VPN Password Reset".format( config.get("app_organization")), template="mail/Password.html", data={ "email": user.email, "organization": config.get("app_organization"), "password": password, "url": config.url(), }) return Response({"password": password})
def get(self, request): """ Returns general API information. :return: Response """ return Response({ "app_organization": config.get("app_organization", "Mangle"), "app_version": version(), "vpn_restart_pending": config.get_bool("vpn_restart_pending", False), "update_available": self.update_available(), })
def settings(self): """ Returns all of the serial :return: """ settings = {} for name in self.fields: settings[name] = config.get(name, "") settings["interfaces"] = {} for iface in net.interface_names("lo", "tun"): settings["interfaces"][iface] = net.interface_ip(iface) return settings
def vpn_post_stop(): """ Deletes the OpenVPN firewall rules. :return: None """ rules = config.get("vpn_firewall_rules", "") for rule in rules.split("\n"): iptables.run(rule.replace("-A", "-D")) # delete the application chains iptables.delete_chain("filter", "MangleVPN") iptables.delete_chain("filter", "MangleVPN_Clients") config.delete("vpn_firewall_rules") # delete all of the group chains for group in models.Group.objects.filter(is_enabled=True).all(): group.delete_firewall_chain() # delete all clients to avoid potential stale clients persisting through # dirty restarts or reboots models.Client.objects.all().delete()
from datetime import datetime, timedelta from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dh, rsa from mangle.common import config KEY_SIZE = config.get("pki_key_size", 2048) """int: the size of generated keys. Defines the number of bits that will be used when generating RSA private keys and Diffie-Hellman parameters. Read from the application settings but defaults to 2048 bits. """ def certificate_authority(): """ Return's the application certificate authority keypair. :return: KeyPair """ return load_keypair( crt=config.get("ca_crt"), key=config.get("ca_key"), ) def create_certificate_authority(): """ Creates and sets the application certificate authority keys.