From ef16bac80f01d9c84b9a0e2c2f72426043d5a79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauri=20V=C3=B5sandi?= Date: Sun, 20 May 2018 13:46:27 +0000 Subject: [PATCH] Several updates #6 * Preliminary advanced snippets for claiming token * Better frontend mouse click event handling * Token overwrites now toggleable via config * Disable compression for OpenVPN snippets * Make sure image builder scripts are included in .whl package * Token mechanism tests * Various bugfixes --- certidude/api/token.py | 6 +- certidude/api/utils/firewall.py | 8 +- certidude/cli.py | 9 +- certidude/config.py | 3 +- certidude/static/js/certidude.js | 89 ++++++++++++------- certidude/static/snippets/ios.mobileconfig | 17 ++-- .../snippets/networkmanager-openvpn.conf | 29 ++++++ .../snippets/networkmanager-strongswan.conf | 23 +++++ certidude/static/snippets/request-client.ps1 | 47 ++++++++++ .../static/snippets/strongswan-client.sh | 2 +- .../static/snippets/strongswan-patching.sh | 2 +- certidude/static/snippets/update-trust.ps1 | 4 + certidude/static/snippets/windows.ps1 | 36 ++------ certidude/static/views/enroll.html | 30 +++++++ certidude/static/views/request.html | 4 +- certidude/templates/openvpn-client.conf | 1 - certidude/templates/server/builder.conf | 14 +-- certidude/templates/server/server.conf | 8 +- certidude/tokens.py | 7 +- setup.py | 6 +- tests/test_cli.py | 82 +++++++++++++++-- 21 files changed, 311 insertions(+), 116 deletions(-) create mode 100644 certidude/static/snippets/networkmanager-openvpn.conf create mode 100644 certidude/static/snippets/networkmanager-strongswan.conf create mode 100644 certidude/static/snippets/request-client.ps1 create mode 100644 certidude/static/snippets/update-trust.ps1 diff --git a/certidude/api/token.py b/certidude/api/token.py index 12c3c6c..d4e48f3 100644 --- a/certidude/api/token.py +++ b/certidude/api/token.py @@ -24,9 +24,6 @@ class TokenResource(AuthorityHandler): AuthorityHandler.__init__(self, authority) self.manager = manager - def on_get(self, req, resp): - return - def on_put(self, req, resp): try: username, mail, created, expires, profile = self.manager.consume(req.get_param("token", required=True)) @@ -36,7 +33,8 @@ class TokenResource(AuthorityHandler): header, _, der_bytes = pem.unarmor(body) csr = CertificationRequest.load(der_bytes) common_name = csr["certification_request_info"]["subject"].native["common_name"] - assert common_name == username or common_name.startswith(username + "@"), "Invalid common name %s" % common_name + if not common_name.startswith(username + "@"): + raise falcon.HTTPBadRequest("Bad requst", "Invalid common name %s" % common_name) try: _, resp.body = self.authority._sign(csr, body, profile=config.PROFILES.get(profile), overwrite=config.TOKEN_OVERWRITE_PERMITTED) diff --git a/certidude/api/utils/firewall.py b/certidude/api/utils/firewall.py index 9d34dee..b63f47f 100644 --- a/certidude/api/utils/firewall.py +++ b/certidude/api/utils/firewall.py @@ -109,8 +109,9 @@ def authenticate(optional=False): if kerberized: if not req.auth.startswith("Negotiate "): - raise falcon.HTTPBadRequest("Bad request", - "Bad header, expected Negotiate") + raise falcon.HTTPUnauthorized("Unauthorized", + "Bad header, expected Negotiate", + ["Negotiate"]) os.environ["KRB5_KTNAME"] = config.KERBEROS_KEYTAB @@ -158,7 +159,8 @@ def authenticate(optional=False): else: if not req.auth.startswith("Basic "): - raise falcon.HTTPBadRequest("Bad request", "Bad header, expected Basic") + raise falcon.HTTPUnauthorized("Forbidden", "Bad header, expected Basic", ("Basic",)) + basic, token = req.auth.split(" ", 1) user, passwd = b64decode(token).decode("ascii").split(":", 1) diff --git a/certidude/cli.py b/certidude/cli.py index f8df4ab..97295be 100755 --- a/certidude/cli.py +++ b/certidude/cli.py @@ -581,7 +581,7 @@ def certidude_enroll(fork, renew, no_wait, kerberos, skip_self): nm_config.add_section("vpn") nm_config.set("vpn", "service-type", "org.freedesktop.NetworkManager.openvpn") nm_config.set("vpn", "connection-type", "tls") - nm_config.set("vpn", "comp-lzo", "yes") + nm_config.set("vpn", "comp-lzo", "no") nm_config.set("vpn", "cert-pass-flags", "0") nm_config.set("vpn", "tap-dev", "no") nm_config.set("vpn", "remote-cert-tls", "server") # Assert TLS Server flag of X.509 certificate @@ -717,7 +717,7 @@ def certidude_setup_openvpn_server(authority, common_name, config, subnet, route config.write("ca %s\n" % paths.get("authority_path")) config.write("crl-verify %s\n" % paths.get("revocations_path")) config.write("dh %s\n" % paths.get("dhparam_path")) - config.write("comp-lzo\n") + config.write(";comp-lzo\n") config.write("user nobody\n") config.write("group nogroup\n") config.write("persist-tun\n") @@ -822,7 +822,7 @@ def certidude_setup_openvpn_client(authority, remote, common_name, config, proto config.write("cert %s\n" % paths.get("certificate_path")) config.write("ca %s\n" % paths.get("authority_path")) config.write("crl-verify %s\n" % paths.get("revocations_path")) - config.write("comp-lzo\n") + config.write(";comp-lzo\n") config.write("user nobody\n") config.write("group nogroup\n") config.write("persist-tun\n") @@ -1081,9 +1081,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi else: raise ValueError("Fully qualified hostname not specified as common name, make sure hostname -f works") - # Generate secret for tokens - token_url = "https://" + common_name + "/#action=enroll&token=%(token)s&router=%(router)s&protocol=ovpn" - template_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates", "profile") click.echo("Using templates from %s" % template_path) diff --git a/certidude/config.py b/certidude/config.py index f7ee9c3..755dbe1 100644 --- a/certidude/config.py +++ b/certidude/config.py @@ -100,6 +100,7 @@ TOKEN_URL = cp.get("token", "url") TOKEN_BACKEND = cp.get("token", "backend") TOKEN_LIFETIME = timedelta(minutes=cp.getint("token", "lifetime")) # Convert minutes to seconds TOKEN_DATABASE = cp.get("token", "database") +TOKEN_OVERWRITE_PERMITTED = cp.getboolean("token", "overwrite permitted") # TODO: Check if we don't have base or servers # The API call for looking up scripts uses following directory as root @@ -125,7 +126,5 @@ cp2 = configparser.RawConfigParser() cp2.readfp(open(const.BUILDER_CONFIG_PATH, "r")) IMAGE_BUILDER_PROFILES = [(j, cp2.get(j, "title"), cp2.get(j, "rename")) for j in cp2.sections() if cp2.getboolean(j, "enabled")] -TOKEN_OVERWRITE_PERMITTED=True - SERVICE_PROTOCOLS = set([j.lower() for j in cp.get("service", "protocols").split(" ") if j]) SERVICE_ROUTERS = cp.get("service", "routers") diff --git a/certidude/static/js/certidude.js b/certidude/static/js/certidude.js index 8a9dd33..678357b 100644 --- a/certidude/static/js/certidude.js +++ b/certidude/static/js/certidude.js @@ -6,6 +6,24 @@ const DEVICE_KEYWORDS = ["Android", "iPhone", "iPad", "Windows", "Ubuntu", "Fedo jQuery.timeago.settings.allowFuture = true; +function onRejectRequest(e, common_name, sha256sum) { + $(this).button('loading'); + $.ajax({ + url: "/api/request/" + common_name + "/?sha256sum=" + sha256sum, + type: "delete" + }); +} + +function onSignRequest(e, common_name, sha256sum) { + e.preventDefault(); + $(e.target).button('loading'); + $.ajax({ + url: "/api/request/" + common_name + "/?sha256sum=" + sha256sum, + type: "post" + }); + return false; +} + function normalizeCommonName(j) { return j.replace("@", "--").split(".").join("-"); // dafuq ?! } @@ -27,24 +45,6 @@ function onKeyGen() { window.keys = forge.pki.rsa.generateKeyPair(KEY_SIZE); console.info('Key-pair created.'); - // Device identifier - var dig = forge.md.sha384.create(); - dig.update(window.navigator.userAgent); - - var prefix = "unknown"; - for (i in DEVICE_KEYWORDS) { - var keyword = DEVICE_KEYWORDS[i]; - if (window.navigator.userAgent.indexOf(keyword) >= 0) { - prefix = keyword.toLowerCase(); - break; - } - } - - window.identifier = prefix + "-" + dig.digest().toHex().substring(0, 5); - console.info("Device identifier:", identifier); - - window.common_name = query.subject + "@" + identifier; - window.csr = forge.pki.createCertificationRequest(); csr.publicKey = keys.publicKey; csr.setSubject([{ @@ -82,17 +82,19 @@ function onKeyGen() { $(".option.any").show(); } -function onEnroll(encoding) { - console.info("Service name:", query.title); +function blobToUuid(blob) { var md = forge.md.md5.create(); - md.update(query.title); + md.update(blob); var digest = md.digest().toHex(); - var service_uuid = digest.substring(0, 8) + "-" + + return digest.substring(0, 8) + "-" + digest.substring(8, 12) + "-" + digest.substring(12, 16) + "-" + digest.substring(16,20) + "-" + - digest.substring(20) - console.info("Service UUID:", service_uuid); + digest.substring(20); +} + +function onEnroll(encoding) { + console.info("Service name:", query.title); console.info("User agent:", window.navigator.userAgent); var xhr = new XMLHttpRequest(); @@ -108,8 +110,8 @@ function onEnroll(encoding) { var a = document.createElement("a"); var cert = forge.pki.certificateFromPem(xhr2.responseText); console.info("Got signed certificate:", xhr2.responseText); - var p12 = forge.pkcs12.toPkcs12Asn1( - keys.privateKey, [cert, ca], "", {algorithm: '3des'}); + var p12 = forge.asn1.toDer(forge.pkcs12.toPkcs12Asn1( + keys.privateKey, [cert, ca], "", {algorithm: '3des'})).getBytes(); switch(encoding) { case 'p12': @@ -119,13 +121,13 @@ function onEnroll(encoding) { break case 'sswan': var buf = JSON.stringify({ - uuid: service_uuid, + uuid: blobToUuid(query.title), name: query.title, type: "ikev2-cert", 'ike-proposal': 'aes256-sha384-prfsha384-modp2048', 'esp-proposal': 'aes128gcm16-modp2048', remote: { addr: query.router }, - local: { p12: forge.util.encode64(forge.asn1.toDer(p12).getBytes()) } + local: { p12: forge.util.encode64(p12) } }); console.info("Buf is:", buf); var mimetype = "application/vnd.strongswan.profile" @@ -142,14 +144,18 @@ function onEnroll(encoding) { a.download = query.title + ".ovpn"; break case 'mobileconfig': - var p12 = forge.pkcs12.toPkcs12Asn1( - keys.privateKey, [cert, ca], "1234", {algorithm: '3des'}); + var p12 = forge.asn1.toDer(forge.pkcs12.toPkcs12Asn1( + keys.privateKey, [cert, ca], "1234", {algorithm: '3des'})).getBytes(); var buf = nunjucks.render('snippets/ios.mobileconfig', { session: session, + service_uuid: blobToUuid(query.title), + conf_uuid: blobToUuid(query.title + " conf1"), title: query.title, common_name: common_name, gateway: query.router, - p12: forge.util.encode64(forge.asn1.toDer(p12).getBytes()), + p12_uuid: blobToUuid(p12), + p12: forge.util.encode64(p12), + ca_uuid: blobToUuid(forge.pki.certificateToAsn1(ca)).getBytes()), ca: forge.util.encode64(forge.asn1.toDer(forge.pki.certificateToAsn1(ca)).getBytes()) }); var mimetype = "application/x-apple-aspen-config"; @@ -179,6 +185,7 @@ function onEnroll(encoding) { } function onHashChanged() { + window.query = {}; var a = location.hash.substring(1).split('&'); for (var i = 0; i < a.length; i++) { @@ -200,6 +207,23 @@ function onHashChanged() { $("#view-dashboard").html(env.render('views/error.html', { message: msg })); }, success: function(blob) { + // Device identifier + var dig = forge.md.sha384.create(); + dig.update(window.navigator.userAgent); + + var prefix = "unknown"; + for (i in DEVICE_KEYWORDS) { + var keyword = DEVICE_KEYWORDS[i]; + if (window.navigator.userAgent.indexOf(keyword) >= 0) { + prefix = keyword.toLowerCase(); + break; + } + } + + window.identifier = prefix + "-" + dig.digest().toHex().substring(0, 5); + window.common_name = query.subject + "@" + identifier; + console.info("Device identifier:", identifier); + window.session = { authority: { hostname: window.location.hostname, @@ -221,7 +245,8 @@ function onHashChanged() { } else { if (query.action == "enroll") { $("#view-dashboard").html(env.render('views/enroll.html', { - session:session, + common_name: common_name, + session: session, token: query.token, })); var options = document.querySelectorAll(".option"); diff --git a/certidude/static/snippets/ios.mobileconfig b/certidude/static/snippets/ios.mobileconfig index 36ad9f5..282afa2 100644 --- a/certidude/static/snippets/ios.mobileconfig +++ b/certidude/static/snippets/ios.mobileconfig @@ -6,7 +6,7 @@ {{ gateway }} PayloadIdentifier - org.example.vpn2 + {{ gateway }} PayloadUUID {{ service_uuid }} PayloadType @@ -17,9 +17,9 @@ PayloadIdentifier - org.example.vpn2.conf1 + {{ gateway }}.conf1 PayloadUUID - 29e4456d-3f03-4f15-b46f-4225d89465b7 + {{ conf_uuid }} PayloadType com.apple.vpn.managed PayloadVersion @@ -63,14 +63,14 @@ EnablePFS 1 PayloadCertificateUUID - d60488c6-328e-4944-9c8d-61db8095c865 + {{ p12_uuid }} PayloadIdentifier - ee.k-space.ca2.client + {{ common_name }} PayloadUUID - d60488c6-328e-4944-9c8d-61db8095c865 + {{ p12_uuid }} PayloadType com.apple.security.pkcs12 PayloadVersion @@ -80,14 +80,13 @@ PayloadIdentifier - org.example.ca + {{ session.authority.certificate.common_name }} PayloadUUID - 64988b2c-33e0-4adf-a432-6fbcae543408 + {{ ca_uuid }} PayloadType com.apple.security.root PayloadVersion 1 - PayloadContent {{ ca }} diff --git a/certidude/static/snippets/networkmanager-openvpn.conf b/certidude/static/snippets/networkmanager-openvpn.conf new file mode 100644 index 0000000..db64541 --- /dev/null +++ b/certidude/static/snippets/networkmanager-openvpn.conf @@ -0,0 +1,29 @@ +[connection] +certidude managed = true +id = {{ session.service.title }} +uuid = {{ uuid }} +type = vpn + +[vpn] +service-type = org.freedesktop.NetworkManager.openvpn +connection-type = tls +cert-pass-flags 0 +tap-dev = no +remote-cert-tls = server +remote = {{ routers[0] }} +key = {% if key_path %}{{ key_path }}{% else %}/etc/certidude/authority/{{ session.authority.hostname }}/host_key.pem{% endif %} +cert = {% if certificate_path %}{{ certificate_path }}{% else %}/etc/certidude/authority/{{ session.authority.hostname }}/host_cert.pem{% endif %} +ca = {% if authority_path %}{{ authority_path }}{% else %}/etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem{% endif %} +tls-cipher = TLS-{% if authority_public_key.algorithm == "ec" %}ECDHE-ECDSA{% else %}DHE-RSA{% endif %}-WITH-AES-256-GCM-SHA384 +cipher = AES-128-GCM +auth = SHA384 +{% if port %};port = {{ port }}{% else %};port = 1194{% endif %} +{% if not proto or not proto.startswith('tcp') %};{% endif %}proto-tcp = yes + +[ipv4] +# Route only pushed subnets to tunnel +never-default = true +method = auto + +[ipv6] +method = auto diff --git a/certidude/static/snippets/networkmanager-strongswan.conf b/certidude/static/snippets/networkmanager-strongswan.conf new file mode 100644 index 0000000..548dccf --- /dev/null +++ b/certidude/static/snippets/networkmanager-strongswan.conf @@ -0,0 +1,23 @@ +[connection] +certidude managed = true +id = {{ session.service.title }} +uuid = {{ uuid }} +type = {{ vpn }} + +[vpn] +service-type = org.freedesktop.NetworkManager.strongswan +encap = no +virtual = yes +method = key +ipcomp = no +address = {{ session.service.routers[0] }} +userkey = {% if key_path %}{{ key_path }}{% else %}/etc/certidude/authority/{{ session.authority.hostname }}/host_key.pem{% endif %} +usercert = {% if certificate_path %}{{ certificate_path }}{% else %}/etc/certidude/authority/{{ session.authority.hostname }}/host_cert.pem{% endif %} +certificate = {% if authority_path %}{{ authority_path }}{% else %}/etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem{% endif %} +ike = aes256-sha384-prfsha384-{% if session.authority.certificate.algorithm == "ec" %}ecp384{% else %}modp2048{% endif %} +esp = aes128gcm16-aes128gmac-{% if session.authority.certificate.algorithm == "ec" %}ecp384{% else %}modp2048{% endif %} +proposal = yes + +[ipv4] +method = auto +;route1 = 0.0.0.0/0 diff --git a/certidude/static/snippets/request-client.ps1 b/certidude/static/snippets/request-client.ps1 new file mode 100644 index 0000000..9bb758b --- /dev/null +++ b/certidude/static/snippets/request-client.ps1 @@ -0,0 +1,47 @@ +# Generate keypair and submit CSR +{% if common_name %}$NAME = "{{ common_name }}" +{% else %}$NAME = $env:computername.toLower() +{% endif %} +@" +[NewRequest] +Subject = "CN=$NAME" +Exportable = FALSE +KeySpec = 1 +KeyUsage = 0xA0 +MachineKeySet = True +ProviderType = 12 +RequestType = PKCS10 +{% if session.authority.certificate.algorithm == "ec" %}ProviderName = "Microsoft Software Key Storage Provider" +KeyAlgorithm = ECDSA_P384 +{% else %}ProviderName = "Microsoft RSA SChannel Cryptographic Provider" +KeyLength = 2048 +{% endif %}"@ | Out-File req.inf +C:\Windows\system32\certreq.exe -new -f -q req.inf host_csr.pem +Invoke-WebRequest `{% if token %} + -Uri 'https://{{ session.authority.hostname }}:8443/api/token/?token={{ token }}' ` + -Method PUT `{% else %} + -Uri 'https://{{ session.authority.hostname }}:8443/api/request/?wait=yes&autosign=yes' ` + -Method POST `{% endif %} + -TimeoutSec 900 ` + -InFile host_csr.pem ` + -ContentType application/pkcs10 ` + -MaximumRedirection 3 -OutFile host_cert.pem + +# Import certificate +Import-Certificate -FilePath host_cert.pem -CertStoreLocation Cert:\LocalMachine\My +{# + +On Windows 7 the Import-Certificate cmdlet is missing, +but certutil.exe can be used instead: + +C:\Windows\system32\certutil.exe -addstore My host_cert.pem + +Everything seems to work except after importing the certificate +it is not properly associated with the private key, +that means "You have private key that corresponds to this certificate" is not +shown under "Valid from ... to ..." in MMC. +This results in error code 13806 during IKEv2 handshake and error message +"IKE failed to find valid machine certificate" + +#} + diff --git a/certidude/static/snippets/strongswan-client.sh b/certidude/static/snippets/strongswan-client.sh index 2c3bd43..2567159 100644 --- a/certidude/static/snippets/strongswan-client.sh +++ b/certidude/static/snippets/strongswan-client.sh @@ -25,4 +25,4 @@ EOF echo ": {% if session.authority.certificate.algorithm == "ec" %}ECDSA{% else %}RSA{% endif %} {{ session.authority.hostname }}.pem" > /etc/ipsec.secrets -ipsec restart apparmor +ipsec restart diff --git a/certidude/static/snippets/strongswan-patching.sh b/certidude/static/snippets/strongswan-patching.sh index 6538b75..44257f0 100644 --- a/certidude/static/snippets/strongswan-patching.sh +++ b/certidude/static/snippets/strongswan-patching.sh @@ -14,4 +14,4 @@ chcon --type=home_cert_t /etc/certidude/authority/{{ session.authority.hostname cat << EOF > /etc/apparmor.d/local/usr.lib.ipsec.charon /etc/certidude/authority/** r, EOF -systemctl restart +systemctl restart apparmor diff --git a/certidude/static/snippets/update-trust.ps1 b/certidude/static/snippets/update-trust.ps1 new file mode 100644 index 0000000..6cd6b5c --- /dev/null +++ b/certidude/static/snippets/update-trust.ps1 @@ -0,0 +1,4 @@ +# Install CA certificate +@" +{{ session.authority.certificate.blob }}"@ | Out-File ca_cert.pem +Import-Certificate -FilePath ca_cert.pem -CertStoreLocation Cert:\LocalMachine\Root diff --git a/certidude/static/snippets/windows.ps1 b/certidude/static/snippets/windows.ps1 index a286050..d7f8ed6 100644 --- a/certidude/static/snippets/windows.ps1 +++ b/certidude/static/snippets/windows.ps1 @@ -1,37 +1,8 @@ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -# Install CA certificate -@" -{{ session.authority.certificate.blob }}"@ | Out-File ca_cert.pem -{% if session.authority.certificate.algorithm == "ec" %} -Import-Certificate -FilePath ca_cert.pem -CertStoreLocation Cert:\LocalMachine\Root -{% else %} -C:\Windows\system32\certutil.exe -addstore Root ca_cert.pem -{% endif %} +{% include "snippets/update-trust.ps1" %} -# Generate keypair and submit CSR -$hostname = $env:computername.ToLower() -@" -[NewRequest] -Subject = "CN=$hostname" -Exportable = FALSE -KeySpec = 1 -KeyUsage = 0xA0 -MachineKeySet = True -ProviderType = 12 -RequestType = PKCS10 -{% if session.authority.certificate.algorithm == "ec" %}ProviderName = "Microsoft Software Key Storage Provider" -KeyAlgorithm = ECDSA_P384 -{% else %}ProviderName = "Microsoft RSA SChannel Cryptographic Provider" -KeyLength = 2048 -{% endif %}"@ | Out-File req.inf -C:\Windows\system32\certreq.exe -new -f -q req.inf host_csr.pem -Invoke-WebRequest -TimeoutSec 900 -Uri 'https://{{ session.authority.hostname }}:8443/api/{% if token %}token/?uuid={{ token }}{% else %}request/?wait=yes&autosign=yes{% endif %}' -InFile host_csr.pem -ContentType application/pkcs10 -Method POST -MaximumRedirection 3 -OutFile host_cert.pem - -# Import certificate -{% if session.authority.certificate.algorithm == "ec" %}Import-Certificate -FilePath host_cert.pem -CertStoreLocation Cert:\LocalMachine\My -{% else %}C:\Windows\system32\certutil.exe -addstore My host_cert.pem -{% endif %} +{% include "snippets/request-client.ps1" %} {% for router in session.service.routers %} # Set up IPSec VPN tunnel to {{ router }} @@ -40,9 +11,12 @@ Add-VpnConnection ` -Name "IPSec to {{ router }}" ` -ServerAddress {{ router }} ` -AuthenticationMethod MachineCertificate ` + -EncryptionLevel Maximum ` -SplitTunneling ` -TunnelType ikev2 ` -PassThru -AllUserConnection + +# Harden VPN configuration Set-VpnConnectionIPsecConfiguration ` -ConnectionName "IPSec to {{ router }}" ` -AuthenticationTransformConstants GCMAES128 ` diff --git a/certidude/static/views/enroll.html b/certidude/static/views/enroll.html index ba91fea..d6fb4ab 100644 --- a/certidude/static/views/enroll.html +++ b/certidude/static/views/enroll.html @@ -77,6 +77,36 @@ sudo systemctl restart network-manager +
+
+
+

Ubuntu 18.04+ (advanced)

+

Copy-paste follownig to terminal as root user:

+
{% include "snippets/request-client.sh" %}
+cat << EOF > '/etc/NetworkManager/system-connections/OpenVPN to {{ session.service.title }}'
+{% include "snippets/networkmanager-openvpn.conf" %}EOF
+
+nmcli con reload
+
+
+
+
+ +
+
+
+

Ubuntu 18.04+ (advanced)

+

Copy-paste follownig to terminal as root user:

+
{% include "snippets/request-client.sh" %}
+cat << EOF > '/etc/NetworkManager/system-connections/IPSec to {{ session.service.title }}'
+{% include "snippets/networkmanager-strongswan.conf" %}EOF
+
+nmcli con reload
+
+
+
+
+
diff --git a/certidude/static/views/request.html b/certidude/static/views/request.html index 4b44222..706a8bc 100644 --- a/certidude/static/views/request.html +++ b/certidude/static/views/request.html @@ -19,11 +19,11 @@