mirror of
https://github.com/laurivosandi/certidude
synced 2024-12-22 08:15:18 +00:00
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
This commit is contained in:
parent
6299d468c0
commit
ef16bac80f
@ -24,9 +24,6 @@ class TokenResource(AuthorityHandler):
|
|||||||
AuthorityHandler.__init__(self, authority)
|
AuthorityHandler.__init__(self, authority)
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
|
|
||||||
def on_get(self, req, resp):
|
|
||||||
return
|
|
||||||
|
|
||||||
def on_put(self, req, resp):
|
def on_put(self, req, resp):
|
||||||
try:
|
try:
|
||||||
username, mail, created, expires, profile = self.manager.consume(req.get_param("token", required=True))
|
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)
|
header, _, der_bytes = pem.unarmor(body)
|
||||||
csr = CertificationRequest.load(der_bytes)
|
csr = CertificationRequest.load(der_bytes)
|
||||||
common_name = csr["certification_request_info"]["subject"].native["common_name"]
|
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:
|
try:
|
||||||
_, resp.body = self.authority._sign(csr, body, profile=config.PROFILES.get(profile),
|
_, resp.body = self.authority._sign(csr, body, profile=config.PROFILES.get(profile),
|
||||||
overwrite=config.TOKEN_OVERWRITE_PERMITTED)
|
overwrite=config.TOKEN_OVERWRITE_PERMITTED)
|
||||||
|
@ -109,8 +109,9 @@ def authenticate(optional=False):
|
|||||||
|
|
||||||
if kerberized:
|
if kerberized:
|
||||||
if not req.auth.startswith("Negotiate "):
|
if not req.auth.startswith("Negotiate "):
|
||||||
raise falcon.HTTPBadRequest("Bad request",
|
raise falcon.HTTPUnauthorized("Unauthorized",
|
||||||
"Bad header, expected Negotiate")
|
"Bad header, expected Negotiate",
|
||||||
|
["Negotiate"])
|
||||||
|
|
||||||
os.environ["KRB5_KTNAME"] = config.KERBEROS_KEYTAB
|
os.environ["KRB5_KTNAME"] = config.KERBEROS_KEYTAB
|
||||||
|
|
||||||
@ -158,7 +159,8 @@ def authenticate(optional=False):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
if not req.auth.startswith("Basic "):
|
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)
|
basic, token = req.auth.split(" ", 1)
|
||||||
user, passwd = b64decode(token).decode("ascii").split(":", 1)
|
user, passwd = b64decode(token).decode("ascii").split(":", 1)
|
||||||
|
|
||||||
|
@ -581,7 +581,7 @@ def certidude_enroll(fork, renew, no_wait, kerberos, skip_self):
|
|||||||
nm_config.add_section("vpn")
|
nm_config.add_section("vpn")
|
||||||
nm_config.set("vpn", "service-type", "org.freedesktop.NetworkManager.openvpn")
|
nm_config.set("vpn", "service-type", "org.freedesktop.NetworkManager.openvpn")
|
||||||
nm_config.set("vpn", "connection-type", "tls")
|
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", "cert-pass-flags", "0")
|
||||||
nm_config.set("vpn", "tap-dev", "no")
|
nm_config.set("vpn", "tap-dev", "no")
|
||||||
nm_config.set("vpn", "remote-cert-tls", "server") # Assert TLS Server flag of X.509 certificate
|
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("ca %s\n" % paths.get("authority_path"))
|
||||||
config.write("crl-verify %s\n" % paths.get("revocations_path"))
|
config.write("crl-verify %s\n" % paths.get("revocations_path"))
|
||||||
config.write("dh %s\n" % paths.get("dhparam_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("user nobody\n")
|
||||||
config.write("group nogroup\n")
|
config.write("group nogroup\n")
|
||||||
config.write("persist-tun\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("cert %s\n" % paths.get("certificate_path"))
|
||||||
config.write("ca %s\n" % paths.get("authority_path"))
|
config.write("ca %s\n" % paths.get("authority_path"))
|
||||||
config.write("crl-verify %s\n" % paths.get("revocations_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("user nobody\n")
|
||||||
config.write("group nogroup\n")
|
config.write("group nogroup\n")
|
||||||
config.write("persist-tun\n")
|
config.write("persist-tun\n")
|
||||||
@ -1081,9 +1081,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Fully qualified hostname not specified as common name, make sure hostname -f works")
|
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")
|
template_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates", "profile")
|
||||||
click.echo("Using templates from %s" % template_path)
|
click.echo("Using templates from %s" % template_path)
|
||||||
|
|
||||||
|
@ -100,6 +100,7 @@ TOKEN_URL = cp.get("token", "url")
|
|||||||
TOKEN_BACKEND = cp.get("token", "backend")
|
TOKEN_BACKEND = cp.get("token", "backend")
|
||||||
TOKEN_LIFETIME = timedelta(minutes=cp.getint("token", "lifetime")) # Convert minutes to seconds
|
TOKEN_LIFETIME = timedelta(minutes=cp.getint("token", "lifetime")) # Convert minutes to seconds
|
||||||
TOKEN_DATABASE = cp.get("token", "database")
|
TOKEN_DATABASE = cp.get("token", "database")
|
||||||
|
TOKEN_OVERWRITE_PERMITTED = cp.getboolean("token", "overwrite permitted")
|
||||||
# TODO: Check if we don't have base or servers
|
# TODO: Check if we don't have base or servers
|
||||||
|
|
||||||
# The API call for looking up scripts uses following directory as root
|
# 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"))
|
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")]
|
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_PROTOCOLS = set([j.lower() for j in cp.get("service", "protocols").split(" ") if j])
|
||||||
SERVICE_ROUTERS = cp.get("service", "routers")
|
SERVICE_ROUTERS = cp.get("service", "routers")
|
||||||
|
@ -6,6 +6,24 @@ const DEVICE_KEYWORDS = ["Android", "iPhone", "iPad", "Windows", "Ubuntu", "Fedo
|
|||||||
|
|
||||||
jQuery.timeago.settings.allowFuture = true;
|
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) {
|
function normalizeCommonName(j) {
|
||||||
return j.replace("@", "--").split(".").join("-"); // dafuq ?!
|
return j.replace("@", "--").split(".").join("-"); // dafuq ?!
|
||||||
}
|
}
|
||||||
@ -27,24 +45,6 @@ function onKeyGen() {
|
|||||||
window.keys = forge.pki.rsa.generateKeyPair(KEY_SIZE);
|
window.keys = forge.pki.rsa.generateKeyPair(KEY_SIZE);
|
||||||
console.info('Key-pair created.');
|
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();
|
window.csr = forge.pki.createCertificationRequest();
|
||||||
csr.publicKey = keys.publicKey;
|
csr.publicKey = keys.publicKey;
|
||||||
csr.setSubject([{
|
csr.setSubject([{
|
||||||
@ -82,17 +82,19 @@ function onKeyGen() {
|
|||||||
$(".option.any").show();
|
$(".option.any").show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEnroll(encoding) {
|
function blobToUuid(blob) {
|
||||||
console.info("Service name:", query.title);
|
|
||||||
var md = forge.md.md5.create();
|
var md = forge.md.md5.create();
|
||||||
md.update(query.title);
|
md.update(blob);
|
||||||
var digest = md.digest().toHex();
|
var digest = md.digest().toHex();
|
||||||
var service_uuid = digest.substring(0, 8) + "-" +
|
return digest.substring(0, 8) + "-" +
|
||||||
digest.substring(8, 12) + "-" +
|
digest.substring(8, 12) + "-" +
|
||||||
digest.substring(12, 16) + "-" +
|
digest.substring(12, 16) + "-" +
|
||||||
digest.substring(16,20) + "-" +
|
digest.substring(16,20) + "-" +
|
||||||
digest.substring(20)
|
digest.substring(20);
|
||||||
console.info("Service UUID:", service_uuid);
|
}
|
||||||
|
|
||||||
|
function onEnroll(encoding) {
|
||||||
|
console.info("Service name:", query.title);
|
||||||
|
|
||||||
console.info("User agent:", window.navigator.userAgent);
|
console.info("User agent:", window.navigator.userAgent);
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
@ -108,8 +110,8 @@ function onEnroll(encoding) {
|
|||||||
var a = document.createElement("a");
|
var a = document.createElement("a");
|
||||||
var cert = forge.pki.certificateFromPem(xhr2.responseText);
|
var cert = forge.pki.certificateFromPem(xhr2.responseText);
|
||||||
console.info("Got signed certificate:", xhr2.responseText);
|
console.info("Got signed certificate:", xhr2.responseText);
|
||||||
var p12 = forge.pkcs12.toPkcs12Asn1(
|
var p12 = forge.asn1.toDer(forge.pkcs12.toPkcs12Asn1(
|
||||||
keys.privateKey, [cert, ca], "", {algorithm: '3des'});
|
keys.privateKey, [cert, ca], "", {algorithm: '3des'})).getBytes();
|
||||||
|
|
||||||
switch(encoding) {
|
switch(encoding) {
|
||||||
case 'p12':
|
case 'p12':
|
||||||
@ -119,13 +121,13 @@ function onEnroll(encoding) {
|
|||||||
break
|
break
|
||||||
case 'sswan':
|
case 'sswan':
|
||||||
var buf = JSON.stringify({
|
var buf = JSON.stringify({
|
||||||
uuid: service_uuid,
|
uuid: blobToUuid(query.title),
|
||||||
name: query.title,
|
name: query.title,
|
||||||
type: "ikev2-cert",
|
type: "ikev2-cert",
|
||||||
'ike-proposal': 'aes256-sha384-prfsha384-modp2048',
|
'ike-proposal': 'aes256-sha384-prfsha384-modp2048',
|
||||||
'esp-proposal': 'aes128gcm16-modp2048',
|
'esp-proposal': 'aes128gcm16-modp2048',
|
||||||
remote: { addr: query.router },
|
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);
|
console.info("Buf is:", buf);
|
||||||
var mimetype = "application/vnd.strongswan.profile"
|
var mimetype = "application/vnd.strongswan.profile"
|
||||||
@ -142,14 +144,18 @@ function onEnroll(encoding) {
|
|||||||
a.download = query.title + ".ovpn";
|
a.download = query.title + ".ovpn";
|
||||||
break
|
break
|
||||||
case 'mobileconfig':
|
case 'mobileconfig':
|
||||||
var p12 = forge.pkcs12.toPkcs12Asn1(
|
var p12 = forge.asn1.toDer(forge.pkcs12.toPkcs12Asn1(
|
||||||
keys.privateKey, [cert, ca], "1234", {algorithm: '3des'});
|
keys.privateKey, [cert, ca], "1234", {algorithm: '3des'})).getBytes();
|
||||||
var buf = nunjucks.render('snippets/ios.mobileconfig', {
|
var buf = nunjucks.render('snippets/ios.mobileconfig', {
|
||||||
session: session,
|
session: session,
|
||||||
|
service_uuid: blobToUuid(query.title),
|
||||||
|
conf_uuid: blobToUuid(query.title + " conf1"),
|
||||||
title: query.title,
|
title: query.title,
|
||||||
common_name: common_name,
|
common_name: common_name,
|
||||||
gateway: query.router,
|
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())
|
ca: forge.util.encode64(forge.asn1.toDer(forge.pki.certificateToAsn1(ca)).getBytes())
|
||||||
});
|
});
|
||||||
var mimetype = "application/x-apple-aspen-config";
|
var mimetype = "application/x-apple-aspen-config";
|
||||||
@ -179,6 +185,7 @@ function onEnroll(encoding) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onHashChanged() {
|
function onHashChanged() {
|
||||||
|
|
||||||
window.query = {};
|
window.query = {};
|
||||||
var a = location.hash.substring(1).split('&');
|
var a = location.hash.substring(1).split('&');
|
||||||
for (var i = 0; i < a.length; i++) {
|
for (var i = 0; i < a.length; i++) {
|
||||||
@ -200,6 +207,23 @@ function onHashChanged() {
|
|||||||
$("#view-dashboard").html(env.render('views/error.html', { message: msg }));
|
$("#view-dashboard").html(env.render('views/error.html', { message: msg }));
|
||||||
},
|
},
|
||||||
success: function(blob) {
|
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 = {
|
window.session = {
|
||||||
authority: {
|
authority: {
|
||||||
hostname: window.location.hostname,
|
hostname: window.location.hostname,
|
||||||
@ -221,7 +245,8 @@ function onHashChanged() {
|
|||||||
} else {
|
} else {
|
||||||
if (query.action == "enroll") {
|
if (query.action == "enroll") {
|
||||||
$("#view-dashboard").html(env.render('views/enroll.html', {
|
$("#view-dashboard").html(env.render('views/enroll.html', {
|
||||||
session:session,
|
common_name: common_name,
|
||||||
|
session: session,
|
||||||
token: query.token,
|
token: query.token,
|
||||||
}));
|
}));
|
||||||
var options = document.querySelectorAll(".option");
|
var options = document.querySelectorAll(".option");
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<string>{{ gateway }}</string>
|
<string>{{ gateway }}</string>
|
||||||
<!-- This is a reverse-DNS style unique identifier used to detect duplicate profiles -->
|
<!-- This is a reverse-DNS style unique identifier used to detect duplicate profiles -->
|
||||||
<key>PayloadIdentifier</key>
|
<key>PayloadIdentifier</key>
|
||||||
<string>org.example.vpn2</string>
|
<string>{{ gateway }}</string>
|
||||||
<key>PayloadUUID</key>
|
<key>PayloadUUID</key>
|
||||||
<string>{{ service_uuid }}</string>
|
<string>{{ service_uuid }}</string>
|
||||||
<key>PayloadType</key>
|
<key>PayloadType</key>
|
||||||
@ -17,9 +17,9 @@
|
|||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>PayloadIdentifier</key>
|
<key>PayloadIdentifier</key>
|
||||||
<string>org.example.vpn2.conf1</string>
|
<string>{{ gateway }}.conf1</string>
|
||||||
<key>PayloadUUID</key>
|
<key>PayloadUUID</key>
|
||||||
<string>29e4456d-3f03-4f15-b46f-4225d89465b7</string>
|
<string>{{ conf_uuid }}</string>
|
||||||
<key>PayloadType</key>
|
<key>PayloadType</key>
|
||||||
<string>com.apple.vpn.managed</string>
|
<string>com.apple.vpn.managed</string>
|
||||||
<key>PayloadVersion</key>
|
<key>PayloadVersion</key>
|
||||||
@ -63,14 +63,14 @@
|
|||||||
<key>EnablePFS</key>
|
<key>EnablePFS</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>PayloadCertificateUUID</key>
|
<key>PayloadCertificateUUID</key>
|
||||||
<string>d60488c6-328e-4944-9c8d-61db8095c865</string>
|
<string>{{ p12_uuid }}</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>PayloadIdentifier</key>
|
<key>PayloadIdentifier</key>
|
||||||
<string>ee.k-space.ca2.client</string>
|
<string>{{ common_name }}</string>
|
||||||
<key>PayloadUUID</key>
|
<key>PayloadUUID</key>
|
||||||
<string>d60488c6-328e-4944-9c8d-61db8095c865</string>
|
<string>{{ p12_uuid }}</string>
|
||||||
<key>PayloadType</key>
|
<key>PayloadType</key>
|
||||||
<string>com.apple.security.pkcs12</string>
|
<string>com.apple.security.pkcs12</string>
|
||||||
<key>PayloadVersion</key>
|
<key>PayloadVersion</key>
|
||||||
@ -80,14 +80,13 @@
|
|||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>PayloadIdentifier</key>
|
<key>PayloadIdentifier</key>
|
||||||
<string>org.example.ca</string>
|
<string>{{ session.authority.certificate.common_name }}</string>
|
||||||
<key>PayloadUUID</key>
|
<key>PayloadUUID</key>
|
||||||
<string>64988b2c-33e0-4adf-a432-6fbcae543408</string>
|
<string>{{ ca_uuid }}</string>
|
||||||
<key>PayloadType</key>
|
<key>PayloadType</key>
|
||||||
<string>com.apple.security.root</string>
|
<string>com.apple.security.root</string>
|
||||||
<key>PayloadVersion</key>
|
<key>PayloadVersion</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<!-- This is the Base64 (PEM) encoded CA certificate -->
|
|
||||||
<key>PayloadContent</key>
|
<key>PayloadContent</key>
|
||||||
<data>{{ ca }}</data>
|
<data>{{ ca }}</data>
|
||||||
</dict>
|
</dict>
|
||||||
|
29
certidude/static/snippets/networkmanager-openvpn.conf
Normal file
29
certidude/static/snippets/networkmanager-openvpn.conf
Normal file
@ -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
|
23
certidude/static/snippets/networkmanager-strongswan.conf
Normal file
23
certidude/static/snippets/networkmanager-strongswan.conf
Normal file
@ -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
|
47
certidude/static/snippets/request-client.ps1
Normal file
47
certidude/static/snippets/request-client.ps1
Normal file
@ -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"
|
||||||
|
|
||||||
|
#}
|
||||||
|
|
@ -25,4 +25,4 @@ EOF
|
|||||||
|
|
||||||
echo ": {% if session.authority.certificate.algorithm == "ec" %}ECDSA{% else %}RSA{% endif %} {{ session.authority.hostname }}.pem" > /etc/ipsec.secrets
|
echo ": {% if session.authority.certificate.algorithm == "ec" %}ECDSA{% else %}RSA{% endif %} {{ session.authority.hostname }}.pem" > /etc/ipsec.secrets
|
||||||
|
|
||||||
ipsec restart apparmor
|
ipsec restart
|
||||||
|
@ -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
|
cat << EOF > /etc/apparmor.d/local/usr.lib.ipsec.charon
|
||||||
/etc/certidude/authority/** r,
|
/etc/certidude/authority/** r,
|
||||||
EOF
|
EOF
|
||||||
systemctl restart
|
systemctl restart apparmor
|
||||||
|
4
certidude/static/snippets/update-trust.ps1
Normal file
4
certidude/static/snippets/update-trust.ps1
Normal file
@ -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
|
@ -1,37 +1,8 @@
|
|||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
|
||||||
# Install CA certificate
|
{% include "snippets/update-trust.ps1" %}
|
||||||
@"
|
|
||||||
{{ 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 %}
|
|
||||||
|
|
||||||
# Generate keypair and submit CSR
|
{% include "snippets/request-client.ps1" %}
|
||||||
$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 %}
|
|
||||||
|
|
||||||
{% for router in session.service.routers %}
|
{% for router in session.service.routers %}
|
||||||
# Set up IPSec VPN tunnel to {{ router }}
|
# Set up IPSec VPN tunnel to {{ router }}
|
||||||
@ -40,9 +11,12 @@ Add-VpnConnection `
|
|||||||
-Name "IPSec to {{ router }}" `
|
-Name "IPSec to {{ router }}" `
|
||||||
-ServerAddress {{ router }} `
|
-ServerAddress {{ router }} `
|
||||||
-AuthenticationMethod MachineCertificate `
|
-AuthenticationMethod MachineCertificate `
|
||||||
|
-EncryptionLevel Maximum `
|
||||||
-SplitTunneling `
|
-SplitTunneling `
|
||||||
-TunnelType ikev2 `
|
-TunnelType ikev2 `
|
||||||
-PassThru -AllUserConnection
|
-PassThru -AllUserConnection
|
||||||
|
|
||||||
|
# Harden VPN configuration
|
||||||
Set-VpnConnectionIPsecConfiguration `
|
Set-VpnConnectionIPsecConfiguration `
|
||||||
-ConnectionName "IPSec to {{ router }}" `
|
-ConnectionName "IPSec to {{ router }}" `
|
||||||
-AuthenticationTransformConstants GCMAES128 `
|
-AuthenticationTransformConstants GCMAES128 `
|
||||||
|
@ -77,6 +77,36 @@ sudo systemctl restart network-manager
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 mt-3 option ubuntu linux openvpn advanced">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-block">
|
||||||
|
<h3 class="card-title">Ubuntu 18.04+ (advanced)</h3>
|
||||||
|
<p class="card-text">Copy-paste follownig to terminal as root user:</p>
|
||||||
|
<pre><code>{% 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
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 mt-3 option ubuntu linux ikev2 advanced">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-block">
|
||||||
|
<h3 class="card-title">Ubuntu 18.04+ (advanced)</h3>
|
||||||
|
<p class="card-text">Copy-paste follownig to terminal as root user:</p>
|
||||||
|
<pre><code>{% 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
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="col-sm-12 mt-3 option fedora linux openvpn">
|
<div class="col-sm-12 mt-3 option fedora linux openvpn">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
@ -19,11 +19,11 @@
|
|||||||
<button type="button" class="btn btn-secondary" data-toggle="collapse" data-target="#details-{{ request.sha256sum }}"><i class="fa fa-list"></i> Details</button>
|
<button type="button" class="btn btn-secondary" data-toggle="collapse" data-target="#details-{{ request.sha256sum }}"><i class="fa fa-list"></i> Details</button>
|
||||||
<button type="button" class="btn btn-danger"
|
<button type="button" class="btn btn-danger"
|
||||||
data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i> Rejecting..."
|
data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i> Rejecting..."
|
||||||
onclick="javascript:$(this).button('loading');$.ajax({url:'/api/request/{{request.common_name}}/?sha256sum={{ request.sha256sum }}',type:'delete'});">
|
onclick="onRejectRequest(event, '{{ request.common_name }}', '{{ request.sha256sum }}');">
|
||||||
<i class="fa fa-trash"></i> Reject</button>
|
<i class="fa fa-trash"></i> Reject</button>
|
||||||
<button type="button" class="btn btn-success"
|
<button type="button" class="btn btn-success"
|
||||||
data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i> Processing Order"
|
data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i> Processing Order"
|
||||||
onclick="javascript:$(this).button('loading');$.ajax({url:'/api/request/{{request.common_name}}/?sha256sum={{ request.sha256sum }}',type:'post'});">
|
onclick="onSignRequest(event, '{{ request.common_name }}', '{{ request.sha256sum }}');">
|
||||||
<i class="fa fa-thumbs-up"></i> Sign</button>
|
<i class="fa fa-thumbs-up"></i> Sign</button>
|
||||||
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<span class="sr-only">Toggle Dropdown</span>
|
<span class="sr-only">Toggle Dropdown</span>
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
client
|
client
|
||||||
|
|
||||||
# OpenVPN gateway(s)
|
# OpenVPN gateway(s)
|
||||||
comp-lzo
|
|
||||||
nobind
|
nobind
|
||||||
;proto udp
|
;proto udp
|
||||||
;port 1194
|
;port 1194
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
enabled = yes
|
enabled = yes
|
||||||
|
|
||||||
# Path to filesystem overlay used
|
# Path to filesystem overlay used
|
||||||
overlay = {{ doc_path }}/overlay
|
overlay = {{ builder_path }}/overlay
|
||||||
|
|
||||||
# Hostname or regex to match the IPSec gateway included in the image
|
# Hostname or regex to match the IPSec gateway included in the image
|
||||||
router = ^(router|vpn|gw|gateway)\d*\.
|
router = ^(router|vpn|gw|gateway)\d*\.
|
||||||
@ -51,7 +51,7 @@ rename = wdr4300v1_tp_recovery.bin
|
|||||||
[tpl-archer-c7-factory]
|
[tpl-archer-c7-factory]
|
||||||
enabled = no
|
enabled = no
|
||||||
title = TP-Link Archer C7 (Access Point), TFTP-friendly
|
title = TP-Link Archer C7 (Access Point), TFTP-friendly
|
||||||
command = {{ doc_path }}/builder/ap.sh
|
command = {{ builder_path }}/ap.sh
|
||||||
model = archer-c7-v2
|
model = archer-c7-v2
|
||||||
filename = archer-c7-v2-squashfs-factory-eu.bin
|
filename = archer-c7-v2-squashfs-factory-eu.bin
|
||||||
rename = ArcherC7v2_tp_recovery.bin
|
rename = ArcherC7v2_tp_recovery.bin
|
||||||
@ -59,7 +59,7 @@ rename = ArcherC7v2_tp_recovery.bin
|
|||||||
[cf-e380ac-factory]
|
[cf-e380ac-factory]
|
||||||
enabled = no
|
enabled = no
|
||||||
title = Comfast E380AC (Access Point), TFTP-friendly
|
title = Comfast E380AC (Access Point), TFTP-friendly
|
||||||
command = {{ doc_path }}/builder/ap.sh
|
command = {{ builder_path }}/ap.sh
|
||||||
model = cf-e380ac-v2
|
model = cf-e380ac-v2
|
||||||
filename = cf-e380ac-v2-squashfs-factory.bin
|
filename = cf-e380ac-v2-squashfs-factory.bin
|
||||||
rename = firmware_auto.bin
|
rename = firmware_auto.bin
|
||||||
@ -86,7 +86,7 @@ rename = ap-tl-wdr4300-v1-squashfs-sysupgrade.bin
|
|||||||
[tpl-archer-c7-sysupgrade]
|
[tpl-archer-c7-sysupgrade]
|
||||||
;enabled = yes
|
;enabled = yes
|
||||||
title = TP-Link Archer C7 (Access Point)
|
title = TP-Link Archer C7 (Access Point)
|
||||||
command = {{ doc_path }}/builder/ap.sh
|
command = {{ builder_path }}/ap.sh
|
||||||
model = archer-c7-v2
|
model = archer-c7-v2
|
||||||
filename = archer-c7-v2-squashfs-factory-eu.bin
|
filename = archer-c7-v2-squashfs-factory-eu.bin
|
||||||
rename = ap-archer-c7-v2-squashfs-factory-eu.bin
|
rename = ap-archer-c7-v2-squashfs-factory-eu.bin
|
||||||
@ -94,7 +94,7 @@ rename = ap-archer-c7-v2-squashfs-factory-eu.bin
|
|||||||
[cf-e380ac-sysupgrade]
|
[cf-e380ac-sysupgrade]
|
||||||
;enabled = yes
|
;enabled = yes
|
||||||
title = Comfast E380AC (Access Point)
|
title = Comfast E380AC (Access Point)
|
||||||
command = {{ doc_path }}/builder/ap.sh
|
command = {{ builder_path }}/ap.sh
|
||||||
model = cf-e380ac-v2
|
model = cf-e380ac-v2
|
||||||
filename = cf-e380ac-v2-squashfs-factory.bin
|
filename = cf-e380ac-v2-squashfs-factory.bin
|
||||||
rename = ap-cf-e380ac-v2-squashfs-factory.bin
|
rename = ap-cf-e380ac-v2-squashfs-factory.bin
|
||||||
@ -102,7 +102,7 @@ rename = ap-cf-e380ac-v2-squashfs-factory.bin
|
|||||||
[ar150-mfp-sysupgrade]
|
[ar150-mfp-sysupgrade]
|
||||||
;enabled = yes
|
;enabled = yes
|
||||||
title = GL.iNet GL-AR150 (MFP)
|
title = GL.iNet GL-AR150 (MFP)
|
||||||
command = {{ doc_path }}/builder/mfp.sh
|
command = {{ builder_path }}/mfp.sh
|
||||||
model = gl-ar150
|
model = gl-ar150
|
||||||
filename = ar71xx-generic-gl-ar150-squashfs-sysupgrade.bin
|
filename = ar71xx-generic-gl-ar150-squashfs-sysupgrade.bin
|
||||||
rename = mfp-gl-ar150-squashfs-sysupgrade.bin
|
rename = mfp-gl-ar150-squashfs-sysupgrade.bin
|
||||||
@ -110,7 +110,7 @@ rename = mfp-gl-ar150-squashfs-sysupgrade.bin
|
|||||||
[ar150-cam-sysupgrade]
|
[ar150-cam-sysupgrade]
|
||||||
;enabled = yes
|
;enabled = yes
|
||||||
title = GL.iNet GL-AR150 (IP Camera)
|
title = GL.iNet GL-AR150 (IP Camera)
|
||||||
command = {{ doc_path }}/builder/ipcam.sh
|
command = {{ builder_path }}/ipcam.sh
|
||||||
model = gl-ar150
|
model = gl-ar150
|
||||||
filename = ar71xx-generic-gl-ar150-squashfs-sysupgrade.bin
|
filename = ar71xx-generic-gl-ar150-squashfs-sysupgrade.bin
|
||||||
rename = cam-gl-ar150-squashfs-sysupgrade.bin
|
rename = cam-gl-ar150-squashfs-sysupgrade.bin
|
||||||
|
@ -263,13 +263,17 @@ backend = sql
|
|||||||
# Database path for SQL backend
|
# Database path for SQL backend
|
||||||
database = sqlite://{{ directory }}/meta/db.sqlite
|
database = sqlite://{{ directory }}/meta/db.sqlite
|
||||||
|
|
||||||
# URL format
|
# URL format, router and protocols are substituted from the [service] section below
|
||||||
url = {{ token_url }}
|
url = https://{{ common_name }}/#action=enroll&title=certidude.rocks&token=%(token)s&subject=%(subject_username)s&router=%(router)s&protocols=%(protocols)s
|
||||||
|
|
||||||
# Token lifetime in minutes, 48 hours by default.
|
# Token lifetime in minutes, 48 hours by default.
|
||||||
# Note that code tolerates 5 minute clock skew.
|
# Note that code tolerates 5 minute clock skew.
|
||||||
lifetime = 2880
|
lifetime = 2880
|
||||||
|
|
||||||
|
# Whether token allows overwriting certificate with same CN
|
||||||
|
;overwrite permitted = yes
|
||||||
|
overwrite permitted = no
|
||||||
|
|
||||||
|
|
||||||
[script]
|
[script]
|
||||||
# Path to the folder with scripts that can be served to the clients, set none to disable scripting
|
# Path to the folder with scripts that can be served to the clients, set none to disable scripting
|
||||||
|
@ -11,7 +11,7 @@ class TokenManager(RelationalMixin):
|
|||||||
def consume(self, uuid):
|
def consume(self, uuid):
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
retval = self.get(
|
retval = self.get(
|
||||||
"select subject, mail, created, expires, profile from token where uuid = ? and created < ? and ? < expires and used is null",
|
"select subject, mail, created, expires, profile from token where uuid = ? and created <= ? and ? <= expires and used is null",
|
||||||
uuid,
|
uuid,
|
||||||
now + const.CLOCK_SKEW_TOLERANCE,
|
now + const.CLOCK_SKEW_TOLERANCE,
|
||||||
now - const.CLOCK_SKEW_TOLERANCE)
|
now - const.CLOCK_SKEW_TOLERANCE)
|
||||||
@ -53,10 +53,7 @@ class TokenManager(RelationalMixin):
|
|||||||
context.update(locals())
|
context.update(locals())
|
||||||
|
|
||||||
mailer.send("token.md", to=subject_mail, **context)
|
mailer.send("token.md", to=subject_mail, **context)
|
||||||
return {
|
return token
|
||||||
"token": token,
|
|
||||||
"url": url,
|
|
||||||
}
|
|
||||||
|
|
||||||
def list(self, expired=False, used=False):
|
def list(self, expired=False, used=False):
|
||||||
stmt = "select created as 'created[timestamp]', expires as 'expires[timestamp]', used as 'used[timestamp]', issuer, mail, subject, substr(uuid, 0, 8) as uuid from token"
|
stmt = "select created as 'created[timestamp]', expires as 'expires[timestamp]', used as 'used[timestamp]', issuer, mail, subject, substr(uuid, 0, 8) as uuid from token"
|
||||||
|
6
setup.py
6
setup.py
@ -14,7 +14,8 @@ setup(
|
|||||||
url = "http://github.com/laurivosandi/certidude",
|
url = "http://github.com/laurivosandi/certidude",
|
||||||
packages=[
|
packages=[
|
||||||
"certidude",
|
"certidude",
|
||||||
"certidude.api"
|
"certidude.api",
|
||||||
|
"certidude.api.utils"
|
||||||
],
|
],
|
||||||
long_description=open("README.rst").read(),
|
long_description=open("README.rst").read(),
|
||||||
# Include here only stuff required to run certidude client
|
# Include here only stuff required to run certidude client
|
||||||
@ -24,6 +25,7 @@ setup(
|
|||||||
"configparser",
|
"configparser",
|
||||||
"certbuilder",
|
"certbuilder",
|
||||||
"csrbuilder",
|
"csrbuilder",
|
||||||
|
"crlbuilder",
|
||||||
"jinja2",
|
"jinja2",
|
||||||
],
|
],
|
||||||
scripts=[
|
scripts=[
|
||||||
@ -31,7 +33,7 @@ setup(
|
|||||||
],
|
],
|
||||||
include_package_data = True,
|
include_package_data = True,
|
||||||
package_data={
|
package_data={
|
||||||
"certidude": ["certidude/templates/*"],
|
"certidude": ["certidude/templates/*", "certidude/static/*", "certidude/builder/*"],
|
||||||
},
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import coverage
|
import coverage
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
import pwd
|
import pwd
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
from asn1crypto import pem, x509
|
from asn1crypto import pem, x509
|
||||||
from oscrypto import asymmetric
|
from oscrypto import asymmetric
|
||||||
from csrbuilder import CSRBuilder, pem_armor_csr
|
from csrbuilder import CSRBuilder, pem_armor_csr
|
||||||
@ -9,11 +15,6 @@ from importlib import reload
|
|||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from time import sleep
|
from time import sleep
|
||||||
import json
|
|
||||||
import pytest
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
coverage.process_startup()
|
coverage.process_startup()
|
||||||
|
|
||||||
@ -65,6 +66,8 @@ def clean_client():
|
|||||||
files = [
|
files = [
|
||||||
"/etc/certidude/client.conf",
|
"/etc/certidude/client.conf",
|
||||||
"/etc/certidude/services.conf",
|
"/etc/certidude/services.conf",
|
||||||
|
"/etc/certidude/client.conf.d/ca.conf",
|
||||||
|
"/etc/certidude/services.conf.d/ca.conf",
|
||||||
"/etc/certidude/authority/ca.example.lan/ca_cert.pem",
|
"/etc/certidude/authority/ca.example.lan/ca_cert.pem",
|
||||||
"/etc/certidude/authority/ca.example.lan/client_key.pem",
|
"/etc/certidude/authority/ca.example.lan/client_key.pem",
|
||||||
"/etc/certidude/authority/ca.example.lan/server_key.pem",
|
"/etc/certidude/authority/ca.example.lan/server_key.pem",
|
||||||
@ -826,6 +829,35 @@ def test_cli_setup_authority():
|
|||||||
# Issue token, needs legit router ^
|
# Issue token, needs legit router ^
|
||||||
os.system("certidude token issue userbot")
|
os.system("certidude token issue userbot")
|
||||||
|
|
||||||
|
|
||||||
|
clean_client()
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.makedirs("/etc/certidude/client.conf.d")
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
os.makedirs("/etc/certidude/services.conf.d")
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
with open("/etc/certidude/client.conf.d/ca.conf", "w") as fh:
|
||||||
|
fh.write("[ca.example.lan]\n")
|
||||||
|
fh.write("trigger = interface up\n")
|
||||||
|
fh.write("system wide = true\n")
|
||||||
|
fh.write("common name = roadwarrior5\n")
|
||||||
|
fh.write("autosign = false\n")
|
||||||
|
with open("/etc/certidude/services.conf.d/ca.conf", "w") as fh:
|
||||||
|
fh.write("[OpenVPN to vpn.example.lan]\n")
|
||||||
|
fh.write("authority = ca.example.lan\n")
|
||||||
|
fh.write("remote = vpn.example.lan\n")
|
||||||
|
fh.write("service = network-manager/openvpn\n")
|
||||||
|
fh.write("[IPSec to ipsec.example.lan]\n")
|
||||||
|
fh.write("authority = ca.example.lan\n")
|
||||||
|
fh.write("remote = ipsec.example.lan\n")
|
||||||
|
fh.write("service = network-manager/strongswan\n")
|
||||||
|
|
||||||
|
assert os.system("certidude enroll --skip-self") == 0
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# Test image builder ###
|
# Test image builder ###
|
||||||
########################
|
########################
|
||||||
@ -849,7 +881,40 @@ def test_cli_setup_authority():
|
|||||||
headers={"content-type": "application/x-www-form-urlencoded", "Authorization":admintoken})
|
headers={"content-type": "application/x-www-form-urlencoded", "Authorization":admintoken})
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
# TODO: check consume
|
from certidude.tokens import TokenManager
|
||||||
|
from certidude.user import User
|
||||||
|
token_manager = TokenManager(config.TOKEN_DATABASE)
|
||||||
|
token = token_manager.issue(None, User.objects.get("userbot"))
|
||||||
|
assert re.match("[A-Za-z0-9]{32}$", token), token
|
||||||
|
|
||||||
|
# TODO: submit garbage instead CSR
|
||||||
|
|
||||||
|
# Invalid common name
|
||||||
|
r = client().simulate_put("/api/token/",
|
||||||
|
body = generate_csr("random"),
|
||||||
|
query_string = "token=%s" % token)
|
||||||
|
assert r.status_code == 400, r.text
|
||||||
|
|
||||||
|
# Unknown token
|
||||||
|
token = token_manager.issue(None, User.objects.get("userbot"))
|
||||||
|
r = client().simulate_put("/api/token/",
|
||||||
|
body = generate_csr("userbot@random"),
|
||||||
|
query_string = "token=WpPQAgbnak84QgWjbMY4230JHi0hVYJP")
|
||||||
|
assert r.status_code == 403, r.text
|
||||||
|
|
||||||
|
# Correct token
|
||||||
|
r = client().simulate_put("/api/token/",
|
||||||
|
body = generate_csr("userbot@random"),
|
||||||
|
query_string = "token=%s" % token)
|
||||||
|
assert r.status_code == 200, r.text
|
||||||
|
|
||||||
|
# Overwrite prohibited
|
||||||
|
token = token_manager.issue(None, User.objects.get("userbot"))
|
||||||
|
r = client().simulate_put("/api/token/",
|
||||||
|
body = generate_csr("userbot@random"),
|
||||||
|
query_string = "token=%s" % token)
|
||||||
|
assert r.status_code == 409, r.text
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#################################
|
#################################
|
||||||
@ -1317,7 +1382,7 @@ def test_cli_setup_authority():
|
|||||||
### LDAP auth ###
|
### LDAP auth ###
|
||||||
#################
|
#################
|
||||||
|
|
||||||
# Test LDAP bind auth fallback
|
# TODO: Test LDAP bind auth fallback
|
||||||
usertoken = "Basic dXNlcmJvdDpTNGw0azRsNA=="
|
usertoken = "Basic dXNlcmJvdDpTNGw0azRsNA=="
|
||||||
admintoken = "Basic YWRtaW5ib3Q6UzRsNGs0bDQ="
|
admintoken = "Basic YWRtaW5ib3Q6UzRsNGs0bDQ="
|
||||||
|
|
||||||
@ -1327,7 +1392,7 @@ def test_cli_setup_authority():
|
|||||||
# curl http://ca.example.lan/api/ -u adminbot:S4l4k4l4 -H "User-agent: Android" -H "Referer: http://ca.example.lan"
|
# curl http://ca.example.lan/api/ -u adminbot:S4l4k4l4 -H "User-agent: Android" -H "Referer: http://ca.example.lan"
|
||||||
r = requests.get("http://ca.example.lan/api/",
|
r = requests.get("http://ca.example.lan/api/",
|
||||||
headers={"Authorization":usertoken, "User-Agent": "Android", "Referer":"http://ca.example.lan/"})
|
headers={"Authorization":usertoken, "User-Agent": "Android", "Referer":"http://ca.example.lan/"})
|
||||||
assert r.status_code == 400, r.text
|
assert r.status_code == 401, r.text
|
||||||
assert "expected Negotiate" in r.text, r.text
|
assert "expected Negotiate" in r.text, r.text
|
||||||
|
|
||||||
|
|
||||||
@ -1413,6 +1478,7 @@ def test_cli_setup_authority():
|
|||||||
|
|
||||||
os.system("certidude token list")
|
os.system("certidude token list")
|
||||||
os.system("certidude token purge")
|
os.system("certidude token purge")
|
||||||
|
os.system("certidude token purge -a")
|
||||||
|
|
||||||
clean_server()
|
clean_server()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user