mirror of
https://github.com/laurivosandi/certidude
synced 2024-12-22 08:15:18 +00:00
Several updates #5
* Better 'systemctl stop certidude' signal handling * Add 502.json for better bad gateway error handling * Generate UUID for .sswan and .mobileconfig files from service name * More detailed token list view in admin interface * Improved testcases
This commit is contained in:
parent
c6d117b9cf
commit
ad1f9c2338
4
.gitignore
vendored
4
.gitignore
vendored
@ -69,3 +69,7 @@ lextab.py
|
|||||||
yacctab.py
|
yacctab.py
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
*~
|
*~
|
||||||
|
certidude/static/coverage/
|
||||||
|
*.tar
|
||||||
|
*.bz2
|
||||||
|
*.gz
|
||||||
|
16
README.rst
16
README.rst
@ -301,8 +301,8 @@ Clone the repository:
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
git clone https://github.com/laurivosandi/certidude
|
git clone https://github.com/laurivosandi/certidude /srv/certidude
|
||||||
cd certidude
|
cd /srv/certidude
|
||||||
|
|
||||||
Install dependencies as shown above and additionally:
|
Install dependencies as shown above and additionally:
|
||||||
|
|
||||||
@ -316,15 +316,17 @@ To install the package from the source tree:
|
|||||||
|
|
||||||
pip3 install -e .
|
pip3 install -e .
|
||||||
|
|
||||||
To run tests and measure code coverage grab a clean VM or container:
|
To run tests and measure code coverage grab a clean VM or container,
|
||||||
|
set hostname to ca.example.lan, export environment variable COVERAGE_PROCESS_START globally and run:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
pip3 install codecov pytest-cov
|
pip3 install codecov pytest-cov
|
||||||
rm .coverage*
|
rm /tmp/.coverage*
|
||||||
COVERAGE_FILE=/tmp/.coverage TRAVIS=1 coverage run --parallel-mode --source certidude -m py.test tests --capture=sys
|
COVERAGE_PROCESS_START=/srv/certidude/.coveragerc py.test tests --capture=sys
|
||||||
coverage combine
|
coverage combine
|
||||||
coverage report
|
coverage report
|
||||||
|
coverage html -i
|
||||||
|
|
||||||
To uninstall:
|
To uninstall:
|
||||||
|
|
||||||
@ -342,7 +344,7 @@ vanilla Ubuntu 16.04 or container:
|
|||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
rm -fv /var/cache/apt/archives/*.deb /var/cache/certidude/wheels/*.whl
|
rm -fv /var/cache/apt/archives/*.deb /var/cache/certidude/wheels/*.whl
|
||||||
apt install --download-only python3-pip
|
apt install python3-pip
|
||||||
pip3 wheel --wheel-dir=/var/cache/certidude/wheels -r requirements.txt
|
pip3 wheel --wheel-dir=/var/cache/certidude/wheels -r requirements.txt
|
||||||
pip3 wheel --wheel-dir=/var/cache/certidude/wheels .
|
pip3 wheel --wheel-dir=/var/cache/certidude/wheels .
|
||||||
tar -cf certidude-client.tar /var/cache/certidude/wheels
|
tar -cf certidude-client.tar /var/cache/certidude/wheels
|
||||||
@ -363,6 +365,8 @@ Transfer certidude-server.tar or certidude-client.tar to the target machine and
|
|||||||
|
|
||||||
Proceed to bootstrap authority without installing packages or assembling assets:
|
Proceed to bootstrap authority without installing packages or assembling assets:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
certidude setup authority --skip-packages --skip-assets [--elliptic-curve] [--organization "Mycorp LLC"]
|
certidude setup authority --skip-packages --skip-assets [--elliptic-curve] [--organization "Mycorp LLC"]
|
||||||
|
|
||||||
Note it's highly recommended to enable nginx PPA in the target machine
|
Note it's highly recommended to enable nginx PPA in the target machine
|
||||||
|
@ -231,7 +231,7 @@ def authorize_server(func):
|
|||||||
def wrapped(resource, req, resp, *args, **kwargs):
|
def wrapped(resource, req, resp, *args, **kwargs):
|
||||||
buf = req.get_header("X-SSL-CERT")
|
buf = req.get_header("X-SSL-CERT")
|
||||||
if not buf:
|
if not buf:
|
||||||
logger.info("No TLS certificate presented to access administrative API call")
|
logger.info("No TLS certificate presented to access administrative API call from %s" % req.context.get("remote_addr"))
|
||||||
raise falcon.HTTPForbidden("Forbidden", "Machine not authorized to perform the operation")
|
raise falcon.HTTPForbidden("Forbidden", "Machine not authorized to perform the operation")
|
||||||
|
|
||||||
header, _, der_bytes = pem.unarmor(buf.replace("\t", "").encode("ascii"))
|
header, _, der_bytes = pem.unarmor(buf.replace("\t", "").encode("ascii"))
|
||||||
|
@ -24,6 +24,16 @@ from glob import glob
|
|||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
from oscrypto import asymmetric
|
from oscrypto import asymmetric
|
||||||
|
|
||||||
|
try:
|
||||||
|
import coverage
|
||||||
|
cov = coverage.process_startup()
|
||||||
|
if cov:
|
||||||
|
click.echo("Enabling coverage tracking")
|
||||||
|
else:
|
||||||
|
click.echo("Coverage tracking not requested")
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# http://www.mad-hacking.net/documentation/linux/security/ssl-tls/creating-ca.xml
|
# http://www.mad-hacking.net/documentation/linux/security/ssl-tls/creating-ca.xml
|
||||||
@ -55,7 +65,7 @@ def setup_client(prefix="client_", dh=False):
|
|||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
rpm("openssl")
|
rpm("openssl")
|
||||||
apt("openssl")
|
apt("openssl")
|
||||||
cmd = "openssl", "dhparam", "-out", path, ("1024" if os.getenv("TRAVIS") else "2048")
|
cmd = "openssl", "dhparam", "-out", path, str(const.KEY_SIZE)
|
||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
arguments["dhparam_path"] = path
|
arguments["dhparam_path"] = path
|
||||||
|
|
||||||
@ -1008,10 +1018,11 @@ def certidude_setup_openvpn_networkmanager(authority, remote, common_name, **pat
|
|||||||
@click.option("--outbox", default="smtp://smtp.%s" % const.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % const.DOMAIN)
|
@click.option("--outbox", default="smtp://smtp.%s" % const.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % const.DOMAIN)
|
||||||
@click.option("--skip-assets", is_flag=True, help="Don't attempt to assemble JS/CSS/font assets")
|
@click.option("--skip-assets", is_flag=True, help="Don't attempt to assemble JS/CSS/font assets")
|
||||||
@click.option("--skip-packages", is_flag=True, help="Don't attempt to install apt/pip/npm packages")
|
@click.option("--skip-packages", is_flag=True, help="Don't attempt to install apt/pip/npm packages")
|
||||||
|
@click.option("--packages-only", is_flag=True, help="Install only apt/pip/npm packages")
|
||||||
@click.option("--elliptic-curve", "-e", is_flag=True, help="Generate EC instead of RSA keypair")
|
@click.option("--elliptic-curve", "-e", is_flag=True, help="Generate EC instead of RSA keypair")
|
||||||
@click.option("--subordinate", is_flag=True, help="Set up subordinate CA instead of root CA")
|
@click.option("--subordinate", is_flag=True, help="Set up subordinate CA instead of root CA")
|
||||||
@fqdn_required
|
@fqdn_required
|
||||||
def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_config, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, title, skip_assets, skip_packages, elliptic_curve, subordinate):
|
def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_config, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, title, skip_assets, skip_packages, elliptic_curve, subordinate, packages_only):
|
||||||
assert subprocess.check_output(["/usr/bin/lsb_release", "-cs"]) in (b"trusty\n", b"xenial\n", b"bionic\n"), "Only Ubuntu 16.04 supported at the moment"
|
assert subprocess.check_output(["/usr/bin/lsb_release", "-cs"]) in (b"trusty\n", b"xenial\n", b"bionic\n"), "Only Ubuntu 16.04 supported at the moment"
|
||||||
assert os.getuid() == 0 and os.getgid() == 0, "Authority can be set up only by root"
|
assert os.getuid() == 0 and os.getgid() == 0, "Authority can be set up only by root"
|
||||||
|
|
||||||
@ -1058,6 +1069,9 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
|
|||||||
if not os.path.exists("/usr/bin/node"):
|
if not os.path.exists("/usr/bin/node"):
|
||||||
os.symlink("/usr/bin/nodejs", "/usr/bin/node")
|
os.symlink("/usr/bin/nodejs", "/usr/bin/node")
|
||||||
|
|
||||||
|
if packages_only:
|
||||||
|
return
|
||||||
|
|
||||||
# Generate secret for tokens
|
# Generate secret for tokens
|
||||||
token_url = "https://" + const.FQDN + "/#action=enroll&token=%(token)s&router=%(router)s&protocol=ovpn"
|
token_url = "https://" + const.FQDN + "/#action=enroll&token=%(token)s&router=%(router)s&protocol=ovpn"
|
||||||
|
|
||||||
@ -1150,8 +1164,9 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
|
|||||||
with open("/etc/systemd/system/certidude.service", "w") as fh:
|
with open("/etc/systemd/system/certidude.service", "w") as fh:
|
||||||
fh.write(env.get_template("server/systemd.service").render(vars()))
|
fh.write(env.get_template("server/systemd.service").render(vars()))
|
||||||
click.echo("File /etc/systemd/system/certidude.service created")
|
click.echo("File /etc/systemd/system/certidude.service created")
|
||||||
|
os.system("systemctl daemon-reload")
|
||||||
else:
|
else:
|
||||||
click.echo("Not systemd based OS, don't know how to set up initscripts")
|
raise NotImplementedError("Not systemd based OS, don't know how to set up initscripts")
|
||||||
|
|
||||||
# Set umask to 0022
|
# Set umask to 0022
|
||||||
os.umask(0o022)
|
os.umask(0o022)
|
||||||
@ -1231,7 +1246,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
|
|||||||
os.umask(0o177) # 600
|
os.umask(0o177) # 600
|
||||||
|
|
||||||
if not os.path.exists(dhparam_path):
|
if not os.path.exists(dhparam_path):
|
||||||
cmd = "openssl", "dhparam", "-out", dhparam_path, ("1024" if os.getenv("TRAVIS") else str(const.KEY_SIZE))
|
cmd = "openssl", "dhparam", "-out", dhparam_path, str(const.KEY_SIZE)
|
||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
if os.path.exists(tls_config.name):
|
if os.path.exists(tls_config.name):
|
||||||
@ -1362,15 +1377,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
|
|||||||
assert os.stat("/etc/nginx/sites-available/certidude.conf").st_mode == 0o100600
|
assert os.stat("/etc/nginx/sites-available/certidude.conf").st_mode == 0o100600
|
||||||
assert os.stat("/etc/certidude/server.conf").st_mode == 0o100600
|
assert os.stat("/etc/certidude/server.conf").st_mode == 0o100600
|
||||||
|
|
||||||
click.echo("Enabling and starting Certidude backend")
|
|
||||||
os.system("systemctl enable certidude")
|
|
||||||
os.system("systemctl restart certidude")
|
|
||||||
click.echo("Enabling and starting nginx")
|
|
||||||
os.system("systemctl enable nginx")
|
|
||||||
os.system("systemctl start nginx")
|
|
||||||
os.system("systemctl reload nginx")
|
|
||||||
click.echo()
|
|
||||||
|
|
||||||
click.echo("To enable e-mail notifications install Postfix as sattelite system and set mailer address in %s" % const.SERVER_CONFIG_PATH)
|
click.echo("To enable e-mail notifications install Postfix as sattelite system and set mailer address in %s" % const.SERVER_CONFIG_PATH)
|
||||||
click.echo()
|
click.echo()
|
||||||
click.echo("Use following commands to inspect the newly created files:")
|
click.echo("Use following commands to inspect the newly created files:")
|
||||||
@ -1383,6 +1389,15 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
|
|||||||
click.echo()
|
click.echo()
|
||||||
click.echo(" echo 'select * from log;' | sqlite3 /var/lib/certidude/meta/db.sqlite")
|
click.echo(" echo 'select * from log;' | sqlite3 /var/lib/certidude/meta/db.sqlite")
|
||||||
click.echo(" echo 'select * from token;' | sqlite3 /var/lib/certidude/meta/db.sqlite")
|
click.echo(" echo 'select * from token;' | sqlite3 /var/lib/certidude/meta/db.sqlite")
|
||||||
|
click.echo()
|
||||||
|
click.echo("Enabling Certidude backend and nginx...")
|
||||||
|
os.system("systemctl enable certidude")
|
||||||
|
os.system("systemctl enable nginx")
|
||||||
|
click.echo("To (re)start services:")
|
||||||
|
click.echo()
|
||||||
|
click.echo(" systemctl restart certidude")
|
||||||
|
click.echo(" systemctl restart nginx")
|
||||||
|
click.echo()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@ -1598,14 +1613,6 @@ def certidude_serve(port, listen, fork):
|
|||||||
with open(const.SERVER_PID_PATH, "w") as pidfile:
|
with open(const.SERVER_PID_PATH, "w") as pidfile:
|
||||||
pidfile.write("%d\n" % pid)
|
pidfile.write("%d\n" % pid)
|
||||||
|
|
||||||
def cleanup_handler(*args):
|
|
||||||
push.publish("server-stopped")
|
|
||||||
logger.debug("Shutting down Certidude")
|
|
||||||
sys.exit(0) # TODO: use another code, needs test refactor
|
|
||||||
|
|
||||||
import signal
|
|
||||||
signal.signal(signal.SIGTERM, cleanup_handler) # Handle SIGTERM from systemd
|
|
||||||
|
|
||||||
push.publish("server-started")
|
push.publish("server-started")
|
||||||
logger.debug("Started Certidude at %s", const.FQDN)
|
logger.debug("Started Certidude at %s", const.FQDN)
|
||||||
|
|
||||||
@ -1613,7 +1620,10 @@ def certidude_serve(port, listen, fork):
|
|||||||
try:
|
try:
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
cleanup_handler() # FIXME
|
click.echo("Caught Ctrl-C, exiting...")
|
||||||
|
push.publish("server-stopped")
|
||||||
|
logger.debug("Shutting down Certidude")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
@click.command("yubikey", help="Set up Yubikey as client authentication token")
|
@click.command("yubikey", help="Set up Yubikey as client authentication token")
|
||||||
@ -1676,7 +1686,7 @@ def certidude_token_list():
|
|||||||
token_manager = TokenManager(config.TOKEN_DATABASE)
|
token_manager = TokenManager(config.TOKEN_DATABASE)
|
||||||
cols = "uuid", "expires", "subject", "state"
|
cols = "uuid", "expires", "subject", "state"
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
for token in token_manager.list(expired=True, used=True, token=True):
|
for token in token_manager.list(expired=True, used=True):
|
||||||
token["state"] = "used" if token.get("used") else ("valid" if token.get("expires") > now else "expired")
|
token["state"] = "used" if token.get("used") else ("valid" if token.get("expires") > now else "expired")
|
||||||
print(";".join([str(token.get(col)) for col in cols]))
|
print(";".join([str(token.get(col)) for col in cols]))
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import socket
|
|||||||
import sys
|
import sys
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
KEY_SIZE = 1024 if os.getenv("TRAVIS") else 4096
|
KEY_SIZE = 1024 if os.getenv("COVERAGE_PROCESS_START") else 4096
|
||||||
CURVE_NAME = "secp384r1"
|
CURVE_NAME = "secp384r1"
|
||||||
RE_FQDN = "^(([a-z0-9]|[a-z0-9][a-z0-9\-_]*[a-z0-9])\.)+([a-z0-9]|[a-z0-9][a-z0-9\-_]*[a-z0-9])?$"
|
RE_FQDN = "^(([a-z0-9]|[a-z0-9][a-z0-9\-_]*[a-z0-9])\.)+([a-z0-9]|[a-z0-9][a-z0-9\-_]*[a-z0-9])?$"
|
||||||
RE_HOSTNAME = "^[a-z0-9]([a-z0-9\-_]{0,61}[a-z0-9])?$"
|
RE_HOSTNAME = "^[a-z0-9]([a-z0-9\-_]{0,61}[a-z0-9])?$"
|
||||||
|
4
certidude/static/502.json
Normal file
4
certidude/static/502.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"title": "502 Bad Gateway",
|
||||||
|
"description": "It seems the server had bit of a hiccup, perhaps this helps: systemctl restart certidude && journalctl -f"
|
||||||
|
}
|
@ -40,7 +40,7 @@ function onKeyGen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.identifier = prefix + "-" + dig.digest().toHex().substring(0, 8);
|
window.identifier = prefix + "-" + dig.digest().toHex().substring(0, 5);
|
||||||
console.info("Device identifier:", identifier);
|
console.info("Device identifier:", identifier);
|
||||||
|
|
||||||
window.common_name = query.subject + "@" + identifier;
|
window.common_name = query.subject + "@" + identifier;
|
||||||
@ -79,9 +79,21 @@ function onKeyGen() {
|
|||||||
options[i].style.display = "block";
|
options[i].style.display = "block";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$(".option.any").show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEnroll(encoding) {
|
function onEnroll(encoding) {
|
||||||
|
console.info("Service name:", query.title);
|
||||||
|
var md = forge.md.md5.create();
|
||||||
|
md.update(query.title);
|
||||||
|
var digest = md.digest().toHex();
|
||||||
|
var service_uuid = 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);
|
||||||
|
|
||||||
console.info("User agent:", window.navigator.userAgent);
|
console.info("User agent:", window.navigator.userAgent);
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', "/api/certificate");
|
xhr.open('GET', "/api/certificate");
|
||||||
@ -103,62 +115,45 @@ function onEnroll(encoding) {
|
|||||||
case 'p12':
|
case 'p12':
|
||||||
var buf = forge.asn1.toDer(p12).getBytes();
|
var buf = forge.asn1.toDer(p12).getBytes();
|
||||||
var mimetype = "application/x-pkcs12"
|
var mimetype = "application/x-pkcs12"
|
||||||
a.download = query.router + ".p12";
|
a.download = query.title + ".p12";
|
||||||
break
|
break
|
||||||
case 'sswan':
|
case 'sswan':
|
||||||
var buf = JSON.stringify({
|
var buf = JSON.stringify({
|
||||||
uuid: "a061d140-d3f9-4db7-b2f8-32d6703f4618",
|
uuid: service_uuid,
|
||||||
name: identifier,
|
name: query.title,
|
||||||
type: "ikev2-cert",
|
type: "ikev2-cert",
|
||||||
'ike-proposal': 'aes256-sha384-prfsha384-modp2048',
|
'ike-proposal': 'aes256-sha384-prfsha384-modp2048',
|
||||||
'esp-proposal': 'aes128gcm16-aes128gmac-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(forge.asn1.toDer(p12).getBytes()) }
|
||||||
});
|
});
|
||||||
console.info("Buf is:", buf);
|
console.info("Buf is:", buf);
|
||||||
var mimetype = "application/vnd.strongswan.profile"
|
var mimetype = "application/vnd.strongswan.profile"
|
||||||
a.download = query.router + ".sswan";
|
a.download = query.title + ".sswan";
|
||||||
break
|
break
|
||||||
case 'ovpn':
|
case 'ovpn':
|
||||||
var buf = nunjucks.render('snippets/openvpn-client.conf', {
|
var buf = nunjucks.render('snippets/openvpn-client.conf', {
|
||||||
session: {
|
session: session,
|
||||||
authority: {
|
|
||||||
certificate: {
|
|
||||||
common_name: "Certidude at " + window.location.hostname,
|
|
||||||
algorithm: "rsa"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
service: {
|
|
||||||
protocols: query.protocols.split(","),
|
|
||||||
routers: [query.router],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
key: forge.pki.privateKeyToPem(keys.privateKey),
|
key: forge.pki.privateKeyToPem(keys.privateKey),
|
||||||
cert: xhr2.responseText,
|
cert: xhr2.responseText,
|
||||||
ca: xhr.responseText
|
ca: xhr.responseText
|
||||||
});
|
});
|
||||||
var mimetype = "application/x-openvpn-profile";
|
var mimetype = "application/x-openvpn-profile";
|
||||||
a.download = query.router + ".ovpn";
|
a.download = query.title + ".ovpn";
|
||||||
break
|
break
|
||||||
case 'mobileconfig':
|
case 'mobileconfig':
|
||||||
var p12 = forge.pkcs12.toPkcs12Asn1(
|
var p12 = forge.pkcs12.toPkcs12Asn1(
|
||||||
keys.privateKey, [cert, ca], "1234", {algorithm: '3des'});
|
keys.privateKey, [cert, ca], "1234", {algorithm: '3des'});
|
||||||
var buf = nunjucks.render('snippets/ios.mobileconfig', {
|
var buf = nunjucks.render('snippets/ios.mobileconfig', {
|
||||||
session: {
|
session: session,
|
||||||
authority: {
|
title: query.title,
|
||||||
certificate: {
|
|
||||||
common_name: "Certidude at " + window.location.hostname,
|
|
||||||
algorithm: "rsa"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
common_name: common_name,
|
common_name: common_name,
|
||||||
gateway: query.router,
|
gateway: query.router,
|
||||||
p12: forge.util.encode64(forge.asn1.toDer(p12).getBytes()),
|
p12: forge.util.encode64(forge.asn1.toDer(p12).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";
|
||||||
a.download = query.router + ".mobileconfig";
|
a.download = query.title + ".mobileconfig";
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
a.href = "data:" + mimetype + ";base64," + forge.util.encode64(buf);
|
a.href = "data:" + mimetype + ";base64," + forge.util.encode64(buf);
|
||||||
@ -193,17 +188,42 @@ function onHashChanged() {
|
|||||||
|
|
||||||
console.info("Hash is now:", query);
|
console.info("Hash is now:", query);
|
||||||
|
|
||||||
if (window.location.protocol != "https:") {
|
$.get({
|
||||||
$.get("/api/certificate/", function(blob) {
|
method: "GET",
|
||||||
$("#view-dashboard").html(env.render('views/insecure.html', { window: window,
|
url: "/api/certificate",
|
||||||
session: { authority: {
|
error: function(response) {
|
||||||
|
if (response.responseJSON) {
|
||||||
|
var msg = response.responseJSON
|
||||||
|
} else {
|
||||||
|
var msg = { title: "Error " + response.status, description: response.statusText }
|
||||||
|
}
|
||||||
|
$("#view-dashboard").html(env.render('views/error.html', { message: msg }));
|
||||||
|
},
|
||||||
|
success: function(blob) {
|
||||||
|
window.session = {
|
||||||
|
authority: {
|
||||||
hostname: window.location.hostname,
|
hostname: window.location.hostname,
|
||||||
certificate: { blob: blob }}}
|
certificate: {
|
||||||
}));
|
common_name: "Certidude at " + window.location.hostname,
|
||||||
});
|
algorithm: "rsa",
|
||||||
|
blob: blob
|
||||||
|
}
|
||||||
|
},
|
||||||
|
service: {
|
||||||
|
title: query.title ? query.title : query.router,
|
||||||
|
protocols: query.protocols ? query.protocols.split(",") : null,
|
||||||
|
routers: query.router ? [query.router] : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.location.protocol != "https:") {
|
||||||
|
$("#view-dashboard").html(env.render('views/insecure.html', {session:session}));
|
||||||
} 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,
|
||||||
|
token: query.token,
|
||||||
|
}));
|
||||||
var options = document.querySelectorAll(".option");
|
var options = document.querySelectorAll(".option");
|
||||||
for (i = 0; i < options.length; i++) {
|
for (i = 0; i < options.length; i++) {
|
||||||
options[i].style.display = "none";
|
options[i].style.display = "none";
|
||||||
@ -215,6 +235,9 @@ function onHashChanged() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function onTagClicked(e) {
|
function onTagClicked(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<key>PayloadIdentifier</key>
|
<key>PayloadIdentifier</key>
|
||||||
<string>org.example.vpn2</string>
|
<string>org.example.vpn2</string>
|
||||||
<key>PayloadUUID</key>
|
<key>PayloadUUID</key>
|
||||||
<string>9f93912b-5fd2-4455-99fd-13b9a47b4581</string>
|
<string>{{ service_uuid }}</string>
|
||||||
<key>PayloadType</key>
|
<key>PayloadType</key>
|
||||||
<string>Configuration</string>
|
<string>Configuration</string>
|
||||||
<key>PayloadVersion</key>
|
<key>PayloadVersion</key>
|
||||||
|
@ -26,7 +26,7 @@ KeyAlgorithm = ECDSA_P384
|
|||||||
KeyLength = 2048
|
KeyLength = 2048
|
||||||
{% endif %}"@ | Out-File req.inf
|
{% endif %}"@ | Out-File req.inf
|
||||||
C:\Windows\system32\certreq.exe -new -f -q req.inf host_csr.pem
|
C:\Windows\system32\certreq.exe -new -f -q req.inf host_csr.pem
|
||||||
Invoke-WebRequest -TimeoutSec 900 -Uri 'https://{{ session.authority.hostname }}:8443/api/request/?wait=yes&autosign=yes' -InFile host_csr.pem -ContentType application/pkcs10 -Method POST -MaximumRedirection 3 -OutFile host_cert.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
|
# Import certificate
|
||||||
{% if session.authority.certificate.algorithm == "ec" %}Import-Certificate -FilePath host_cert.pem -CertStoreLocation Cert:\LocalMachine\My
|
{% if session.authority.certificate.algorithm == "ec" %}Import-Certificate -FilePath host_cert.pem -CertStoreLocation Cert:\LocalMachine\My
|
||||||
|
@ -231,14 +231,9 @@ curl http://{{ session.authority.hostname }}/api/revoked/?wait=yes -L -H "Accept
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>Issued tokens:</p>
|
<p>Issued tokens:</p>
|
||||||
<ul>
|
<ul class="list-group">
|
||||||
{% for token in session.authority.tokens %}
|
{% for token in session.authority.tokens %}
|
||||||
<li>
|
{% include "views/token.html" %}
|
||||||
<a href="mailto:{{ token.mail }}">{{ token.subject }}</a>
|
|
||||||
{% if token.issuer %}{% if token.issuer != token.subject %}by {{ token.issuer }}{% else %}by himself{% endif %}{% else %}via shell{% endif %},
|
|
||||||
expires
|
|
||||||
<time class="timeago" datetime="{{ token.expires }}">{{ token.expires }}</time>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -103,7 +103,17 @@ systemctl restart NetworkManager</code></pre>
|
|||||||
or
|
or
|
||||||
<pre><code>C:\ProgramData\Microsoft\Network\Connections\Pbk</code></pre></p>
|
<pre><code>C:\ProgramData\Microsoft\Network\Connections\Pbk</code></pre></p>
|
||||||
<a href="javascript:onEnroll('p12');" class="btn btn-primary">Fetch PKCS#12 container</a>
|
<a href="javascript:onEnroll('p12');" class="btn btn-primary">Fetch PKCS#12 container</a>
|
||||||
<a href="#" class="btn btn-secondary">Fetch VPN profile</a>
|
<a href="#" class="btn btn-secondary">Fetch IPSec IKEv2 VPN profile</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 mt-3 option windows ikev2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-block">
|
||||||
|
<h3 class="card-title">Windows</h3>
|
||||||
|
<p>To configure IPSec IKEv2 tunnel on Windows, open PowerShell as administrator and copy-paste following:</p>
|
||||||
|
<div class="highlight"><pre class="code"><code>{% include "snippets/windows.ps1" %}</code></pre></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -179,7 +189,7 @@ systemctl restart NetworkManager</code></pre>
|
|||||||
Where password for the certificate is prompted, enter 1234.
|
Where password for the certificate is prompted, enter 1234.
|
||||||
Hit <i>Done</i>. Go to <i>Settings</i>, open VPN submenu and tap on the VPN profile to connect.
|
Hit <i>Done</i>. Go to <i>Settings</i>, open VPN submenu and tap on the VPN profile to connect.
|
||||||
</p>
|
</p>
|
||||||
<a href="javascript:onEnroll('mobileconfig');" class="btn btn-primary">Fetch VPN profile</a>
|
<a href="javascript:onEnroll('mobileconfig');" class="btn btn-primary">Fetch IPSec IKEv2 VPN profile</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -231,8 +241,8 @@ systemctl restart NetworkManager</code></pre>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<div class="col-sm-12 mt-3 option any">
|
||||||
<a href="javascript:onShowAll();">I did't find an appropriate option for me, show all options</a>
|
<a href="javascript:$('.option').show();">I did't find an appropriate option for me, show all options</a>
|
||||||
-->
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
10
certidude/static/views/token.html
Normal file
10
certidude/static/views/token.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<li id="token-{{ token.subject }}-{{ token.uuid }}" class="list-group-item filterable" data-keywords="">
|
||||||
|
<span>
|
||||||
|
<i class="fas fa-ticket-alt"></i>
|
||||||
|
{{ token.uuid }}...
|
||||||
|
<a href="mailto:{{ token.mail }}">{{ token.subject }}</a>
|
||||||
|
{% if token.issuer %}{% if token.issuer != token.subject %}by {{ token.issuer }}{% else %}by himself{% endif %}{% else %}via shell{% endif %},
|
||||||
|
expires
|
||||||
|
<time class="timeago" datetime="{{ token.expires }}">{{ token.expires }}</time>
|
||||||
|
</span>
|
||||||
|
</li>
|
@ -21,6 +21,9 @@ ssl_certificate {{ directory }}/signed/{{ common_name }}.pem;
|
|||||||
ssl_certificate_key {{ directory }}/self_key.pem;
|
ssl_certificate_key {{ directory }}/self_key.pem;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
|
# Uncomment following to automatically redirect to HTTPS
|
||||||
|
#rewrite ^/$ https://$server_name$request_uri? permanent;
|
||||||
|
|
||||||
# Section for serving insecure HTTP, note that this is suitable for
|
# Section for serving insecure HTTP, note that this is suitable for
|
||||||
# OCSP, SCEP, CRL-s etc which is already covered by PKI protection mechanisms.
|
# OCSP, SCEP, CRL-s etc which is already covered by PKI protection mechanisms.
|
||||||
# This also solves the chicken-and-egg problem of deploying the certificates
|
# This also solves the chicken-and-egg problem of deploying the certificates
|
||||||
@ -33,9 +36,6 @@ server {
|
|||||||
proxy_pass http://127.0.1.1:8080/api/;
|
proxy_pass http://127.0.1.1:8080/api/;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Path to static files
|
|
||||||
root {{static_path}};
|
|
||||||
|
|
||||||
# Path to compiled assets
|
# Path to compiled assets
|
||||||
location /assets/ {
|
location /assets/ {
|
||||||
alias {{ assets_dir }}/;
|
alias {{ assets_dir }}/;
|
||||||
@ -64,8 +64,9 @@ server {
|
|||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# Uncomment following to enable HTTPS
|
# Path to static files
|
||||||
#rewrite ^/$ https://$server_name$request_uri? permanent;
|
root {{static_path}};
|
||||||
|
error_page 502 /502.json;
|
||||||
|
|
||||||
access_log /var/log/nginx/certidude-plaintext-access.log;
|
access_log /var/log/nginx/certidude-plaintext-access.log;
|
||||||
error_log /var/log/nginx/certidude-plaintext-error.log;
|
error_log /var/log/nginx/certidude-plaintext-error.log;
|
||||||
@ -94,9 +95,6 @@ server {
|
|||||||
proxy_pass http://127.0.1.1:8080/api/;
|
proxy_pass http://127.0.1.1:8080/api/;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Path to static files
|
|
||||||
root {{static_path}};
|
|
||||||
|
|
||||||
# Path to compiled assets
|
# Path to compiled assets
|
||||||
location /assets/ {
|
location /assets/ {
|
||||||
alias {{ assets_dir }}/;
|
alias {{ assets_dir }}/;
|
||||||
@ -121,6 +119,10 @@ server {
|
|||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
# Path to static files
|
||||||
|
root {{static_path}};
|
||||||
|
error_page 502 /502.json;
|
||||||
|
|
||||||
access_log /var/log/nginx/certidude-frontend-access.log;
|
access_log /var/log/nginx/certidude-frontend-access.log;
|
||||||
error_log /var/log/nginx/certidude-frontend-error.log;
|
error_log /var/log/nginx/certidude-frontend-error.log;
|
||||||
}
|
}
|
||||||
@ -151,6 +153,10 @@ server {
|
|||||||
nchan_subscriber longpoll;
|
nchan_subscriber longpoll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Path to static files
|
||||||
|
root {{static_path}};
|
||||||
|
error_page 502 /502.json;
|
||||||
|
|
||||||
access_log /var/log/nginx/certidude-mutual-auth-access.log;
|
access_log /var/log/nginx/certidude-mutual-auth-access.log;
|
||||||
error_log /var/log/nginx/certidude-mutual-auth-error.log;
|
error_log /var/log/nginx/certidude-mutual-auth-error.log;
|
||||||
}
|
}
|
||||||
@ -173,7 +179,6 @@ server {
|
|||||||
|
|
||||||
access_log /var/log/nginx/certidude-push-access.log;
|
access_log /var/log/nginx/certidude-push-access.log;
|
||||||
error_log /var/log/nginx/certidude-push-error.log;
|
error_log /var/log/nginx/certidude-push-error.log;
|
||||||
|
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -160,10 +160,10 @@ admin subnets = 0.0.0.0/0
|
|||||||
|
|
||||||
[logging]
|
[logging]
|
||||||
# Disable logging
|
# Disable logging
|
||||||
backend =
|
;backend =
|
||||||
|
|
||||||
# Use SQLite backend
|
# Use SQLite backend
|
||||||
;backend = sql
|
backend = sql
|
||||||
database = sqlite://{{ directory }}/meta/db.sqlite
|
database = sqlite://{{ directory }}/meta/db.sqlite
|
||||||
|
|
||||||
[signature]
|
[signature]
|
||||||
@ -239,7 +239,7 @@ name = Certidude at {{ common_name }}
|
|||||||
{% if domain %}
|
{% if domain %}
|
||||||
address = certificates@{{ domain }}
|
address = certificates@{{ domain }}
|
||||||
{% else %}
|
{% else %}
|
||||||
address = certificates@exaple.com
|
address = certificates@example.com
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
[tagging]
|
[tagging]
|
||||||
|
@ -3,10 +3,14 @@ Description=Certidude server
|
|||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
EnvironmentFile=/etc/environment
|
||||||
|
Environment=LANG=C.UTF-8
|
||||||
Environment=PYTHON_EGG_CACHE=/tmp/.cache
|
Environment=PYTHON_EGG_CACHE=/tmp/.cache
|
||||||
PIDFile=/run/certidude/server.pid
|
PIDFile=/run/certidude/server.pid
|
||||||
ExecStop=/bin/kill -s TERM $MAINPID
|
KillSignal=SIGINT
|
||||||
ExecStart={{ certidude_path }} serve
|
ExecStart={{ certidude_path }} serve
|
||||||
|
TimeoutSec=15
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@ -58,11 +58,8 @@ class TokenManager(RelationalMixin):
|
|||||||
"url": url,
|
"url": url,
|
||||||
}
|
}
|
||||||
|
|
||||||
def list(self, expired=False, used=False, token=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"
|
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"
|
||||||
if token:
|
|
||||||
stmt += ", uuid"
|
|
||||||
stmt += " from token"
|
|
||||||
where = []
|
where = []
|
||||||
args = []
|
args = []
|
||||||
if not expired:
|
if not expired:
|
||||||
@ -73,7 +70,6 @@ class TokenManager(RelationalMixin):
|
|||||||
if where:
|
if where:
|
||||||
stmt = stmt + " where " + (" and ".join(where))
|
stmt = stmt + " where " + (" and ".join(where))
|
||||||
stmt += " order by expires"
|
stmt += " order by expires"
|
||||||
|
|
||||||
return self.iterfetch(stmt, *args)
|
return self.iterfetch(stmt, *args)
|
||||||
|
|
||||||
def purge(self, all=False):
|
def purge(self, all=False):
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import coverage
|
||||||
import pwd
|
import pwd
|
||||||
from asn1crypto import pem, x509
|
from asn1crypto import pem, x509
|
||||||
from oscrypto import asymmetric
|
from oscrypto import asymmetric
|
||||||
@ -14,6 +15,8 @@ import shutil
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
coverage.process_startup()
|
||||||
|
|
||||||
UA_FEDORA_FIREFOX = "Mozilla/5.0 (X11; Fedora; Linux x86_64) " \
|
UA_FEDORA_FIREFOX = "Mozilla/5.0 (X11; Fedora; Linux x86_64) " \
|
||||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36"
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36"
|
||||||
|
|
||||||
@ -69,6 +72,8 @@ def clean_client():
|
|||||||
"/etc/certidude/authority/ca.example.lan/server_req.pem",
|
"/etc/certidude/authority/ca.example.lan/server_req.pem",
|
||||||
"/etc/certidude/authority/ca.example.lan/client_cert.pem",
|
"/etc/certidude/authority/ca.example.lan/client_cert.pem",
|
||||||
"/etc/certidude/authority/ca.example.lan/server_cert.pem",
|
"/etc/certidude/authority/ca.example.lan/server_cert.pem",
|
||||||
|
"/etc/NetworkManager/system-connections/IPSec to ipsec.example.lan",
|
||||||
|
"/etc/NetworkManager/system-connections/OpenVPN to vpn.example.lan",
|
||||||
]
|
]
|
||||||
for path in files:
|
for path in files:
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
@ -93,14 +98,6 @@ def clean_server():
|
|||||||
|
|
||||||
os.umask(0o22)
|
os.umask(0o22)
|
||||||
|
|
||||||
if os.path.exists("/run/certidude/server.pid"):
|
|
||||||
with open("/run/certidude/server.pid") as fh:
|
|
||||||
try:
|
|
||||||
os.kill(int(fh.read()), 15)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if os.path.exists("/var/lib/certidude"):
|
if os.path.exists("/var/lib/certidude"):
|
||||||
shutil.rmtree("/var/lib/certidude")
|
shutil.rmtree("/var/lib/certidude")
|
||||||
if os.path.exists("/run/certidude"):
|
if os.path.exists("/run/certidude"):
|
||||||
@ -125,11 +122,14 @@ def clean_server():
|
|||||||
"/tmp/key.pem",
|
"/tmp/key.pem",
|
||||||
"/tmp/req.pem",
|
"/tmp/req.pem",
|
||||||
"/tmp/cert.pem",
|
"/tmp/cert.pem",
|
||||||
|
"/usr/bin/node",
|
||||||
]
|
]
|
||||||
|
|
||||||
for filename in files:
|
for filename in files:
|
||||||
if os.path.exists(filename):
|
try:
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# Remove OpenVPN stuff
|
# Remove OpenVPN stuff
|
||||||
if os.path.exists("/etc/openvpn"):
|
if os.path.exists("/etc/openvpn"):
|
||||||
@ -142,6 +142,7 @@ def clean_server():
|
|||||||
# Remove Samba stuff
|
# Remove Samba stuff
|
||||||
os.system("rm -Rfv /var/lib/samba/*")
|
os.system("rm -Rfv /var/lib/samba/*")
|
||||||
assert not os.path.exists("/var/lib/samba/private/secrets.keytab")
|
assert not os.path.exists("/var/lib/samba/private/secrets.keytab")
|
||||||
|
assert not os.path.exists("/etc/krb5.keytab")
|
||||||
|
|
||||||
# Restore initial resolv.conf
|
# Restore initial resolv.conf
|
||||||
shutil.copyfile("/etc/resolv.conf.orig", "/etc/resolv.conf")
|
shutil.copyfile("/etc/resolv.conf.orig", "/etc/resolv.conf")
|
||||||
@ -227,6 +228,8 @@ def test_cli_setup_authority():
|
|||||||
|
|
||||||
# Make sure nginx is running
|
# Make sure nginx is running
|
||||||
assert os.system("nginx -t") == 0, "invalid nginx configuration"
|
assert os.system("nginx -t") == 0, "invalid nginx configuration"
|
||||||
|
os.system("systemctl restart certidude")
|
||||||
|
os.system("systemctl restart nginx")
|
||||||
assert os.path.exists("/run/nginx.pid"), "nginx wasn't started up properly"
|
assert os.path.exists("/run/nginx.pid"), "nginx wasn't started up properly"
|
||||||
|
|
||||||
# Make sure we generated legit CA certificate
|
# Make sure we generated legit CA certificate
|
||||||
@ -623,7 +626,7 @@ def test_cli_setup_authority():
|
|||||||
assert r.text == "[]", r.text
|
assert r.text == "[]", r.text
|
||||||
|
|
||||||
# Test script without tags
|
# Test script without tags
|
||||||
r = client().simulate_get("/api/signed/test/script/")
|
r = requests.get("http://ca.example.lan/api/signed/test/script/")
|
||||||
assert r.status_code == 200, r.text # script render ok
|
assert r.status_code == 200, r.text # script render ok
|
||||||
assert "# No tags" in r.text, r.text
|
assert "# No tags" in r.text, r.text
|
||||||
|
|
||||||
@ -648,19 +651,17 @@ def test_cli_setup_authority():
|
|||||||
headers={"Authorization":admintoken})
|
headers={"Authorization":admintoken})
|
||||||
assert r.status_code == 200, r.text
|
assert r.status_code == 200, r.text
|
||||||
assert "Revoked " in inbox.pop(), inbox
|
assert "Revoked " in inbox.pop(), inbox
|
||||||
"""
|
|
||||||
|
|
||||||
# Log can be read only by admin
|
# Log can be read only by admin
|
||||||
r = client().simulate_get("/api/log/")
|
r = requests.get("http://ca.example.lan/api/log/?limit=100")
|
||||||
assert r.status_code == 401, r.text
|
assert r.status_code == 401, r.text
|
||||||
r = client().simulate_get("/api/log/",
|
r = requests.get("http://ca.example.lan/api/log/?limit=100",
|
||||||
headers={"Authorization":usertoken})
|
headers={"Authorization":usertoken})
|
||||||
assert r.status_code == 403, r.text
|
assert r.status_code == 403, r.text
|
||||||
r = client().simulate_get("/api/log/",
|
r = requests.get("http://ca.example.lan/api/log/?limit=100",
|
||||||
headers={"Authorization":admintoken})
|
headers={"Authorization":admintoken})
|
||||||
assert r.status_code == 200, r.text
|
assert r.status_code == 200, r.text
|
||||||
assert r.headers.get('content-type') == "application/json; charset=UTF-8"
|
assert r.headers.get('content-type') == "application/json; charset=UTF-8"
|
||||||
"""
|
|
||||||
|
|
||||||
# Test session API call
|
# Test session API call
|
||||||
r = client().simulate_get("/api/")
|
r = client().simulate_get("/api/")
|
||||||
@ -698,7 +699,7 @@ def test_cli_setup_authority():
|
|||||||
clean_client()
|
clean_client()
|
||||||
|
|
||||||
result = runner.invoke(cli, ["setup", "nginx", "-cn", "www", "ca.example.lan"])
|
result = runner.invoke(cli, ["setup", "nginx", "-cn", "www", "ca.example.lan"])
|
||||||
assert result.exception # FQDN required
|
assert result.exception
|
||||||
|
|
||||||
result = runner.invoke(cli, ["setup", "nginx", "-cn", "www.example.lan", "ca.example.lan"])
|
result = runner.invoke(cli, ["setup", "nginx", "-cn", "www.example.lan", "ca.example.lan"])
|
||||||
assert not result.exception, result.output
|
assert not result.exception, result.output
|
||||||
@ -819,8 +820,25 @@ def test_cli_setup_authority():
|
|||||||
assert not result.exception, result.output
|
assert not result.exception, result.output
|
||||||
assert not os.path.exists("/run/certidude/ca.example.lan.pid"), result.output
|
assert not os.path.exists("/run/certidude/ca.example.lan.pid"), result.output
|
||||||
assert "Writing certificate to:" in result.output, result.output
|
assert "Writing certificate to:" in result.output, result.output
|
||||||
|
assert os.path.exists("/etc/NetworkManager/system-connections/OpenVPN to vpn.example.lan")
|
||||||
|
|
||||||
|
|
||||||
|
# Issue token, needs legit router ^
|
||||||
|
os.system("certidude token issue userbot")
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Test image builder ###
|
||||||
|
########################
|
||||||
|
|
||||||
|
r = client().simulate_get("/api/build/ar150-mfp-sysupgrade/mfp-gl-ar150-squashfs-sysupgrade.bin")
|
||||||
|
assert r.status_code == 401, r.text
|
||||||
|
r = client().simulate_get("/api/build/ar150-mfp-sysupgrade/mfp-gl-ar150-squashfs-sysupgrade.bin",
|
||||||
|
headers={"Authorization":usertoken})
|
||||||
|
assert r.status_code == 403, r.text
|
||||||
|
r = client().simulate_get("/api/build/ar150-mfp-sysupgrade/mfp-gl-ar150-squashfs-sysupgrade.bin",
|
||||||
|
headers={"Authorization":admintoken})
|
||||||
|
assert r.status_code == 200, r.text
|
||||||
|
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
### Token mechanism ###
|
### Token mechanism ###
|
||||||
@ -995,6 +1013,7 @@ def test_cli_setup_authority():
|
|||||||
|
|
||||||
with open("/etc/certidude/client.conf", "a") as fh:
|
with open("/etc/certidude/client.conf", "a") as fh:
|
||||||
fh.write("autosign = false\n")
|
fh.write("autosign = false\n")
|
||||||
|
fh.write("system wide = yes\n")
|
||||||
|
|
||||||
result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait"])
|
result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait"])
|
||||||
assert not result.exception, result.output
|
assert not result.exception, result.output
|
||||||
@ -1048,7 +1067,7 @@ def test_cli_setup_authority():
|
|||||||
assert not result.exception, result.output
|
assert not result.exception, result.output
|
||||||
assert not os.path.exists("/run/certidude/ca.example.lan.pid"), result.output
|
assert not os.path.exists("/run/certidude/ca.example.lan.pid"), result.output
|
||||||
assert "Writing certificate to:" in result.output, result.output
|
assert "Writing certificate to:" in result.output, result.output
|
||||||
|
assert os.path.exists("/etc/NetworkManager/system-connections/IPSec to ipsec.example.lan")
|
||||||
|
|
||||||
######################################
|
######################################
|
||||||
### Test revocation on client side ###
|
### Test revocation on client side ###
|
||||||
@ -1115,20 +1134,30 @@ def test_cli_setup_authority():
|
|||||||
### Switch to Kerberos/LDAP auth ###
|
### Switch to Kerberos/LDAP auth ###
|
||||||
####################################
|
####################################
|
||||||
|
|
||||||
|
assert os.path.exists("/run/certidude/server.pid")
|
||||||
|
pid_certidude = int(open("/run/certidude/server.pid").read())
|
||||||
os.system("systemctl stop certidude")
|
os.system("systemctl stop certidude")
|
||||||
|
assert not os.path.exists("/run/certidude/server.pid")
|
||||||
|
|
||||||
# Install packages
|
# Install packages
|
||||||
clean_server()
|
clean_server()
|
||||||
|
|
||||||
# Bootstrap domain controller here,
|
# Bootstrap domain controller here,
|
||||||
# Samba startup takes some time
|
# Samba startup takes some time
|
||||||
|
assert not os.path.exists("/var/lib/samba/private/secrets.keytab")
|
||||||
|
assert not os.path.exists("/etc/krb5.keytab")
|
||||||
|
|
||||||
os.system("samba-tool domain provision --server-role=dc --domain=EXAMPLE --realm=EXAMPLE.LAN --host-name=ca")
|
os.system("samba-tool domain provision --server-role=dc --domain=EXAMPLE --realm=EXAMPLE.LAN --host-name=ca")
|
||||||
|
assert not os.path.exists("/run/samba/samba.pid")
|
||||||
os.system("systemctl restart samba-ad-dc")
|
os.system("systemctl restart samba-ad-dc")
|
||||||
os.system("samba-tool user add userbot S4l4k4l4 --given-name='User' --surname='Bot'")
|
os.system("samba-tool user add userbot S4l4k4l4 --given-name='User' --surname='Bot'")
|
||||||
os.system("samba-tool user add adminbot S4l4k4l4 --given-name='Admin' --surname='Bot'")
|
os.system("samba-tool user add adminbot S4l4k4l4 --given-name='Admin' --surname='Bot'")
|
||||||
os.system("samba-tool group addmembers 'Domain Admins' adminbot")
|
os.system("samba-tool group addmembers 'Domain Admins' adminbot")
|
||||||
os.system("samba-tool user setpassword administrator --newpassword=S4l4k4l4")
|
os.system("samba-tool user setpassword administrator --newpassword=S4l4k4l4")
|
||||||
|
try:
|
||||||
os.symlink("/var/lib/samba/private/secrets.keytab", "/etc/krb5.keytab")
|
os.symlink("/var/lib/samba/private/secrets.keytab", "/etc/krb5.keytab")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
os.chmod("/var/lib/samba/private/secrets.keytab", 0o644) # To allow access to certidude server
|
os.chmod("/var/lib/samba/private/secrets.keytab", 0o644) # To allow access to certidude server
|
||||||
if os.path.exists("/etc/krb5.conf"): # Remove the one from krb5-user package
|
if os.path.exists("/etc/krb5.conf"): # Remove the one from krb5-user package
|
||||||
os.unlink("/etc/krb5.conf")
|
os.unlink("/etc/krb5.conf")
|
||||||
@ -1140,14 +1169,15 @@ def test_cli_setup_authority():
|
|||||||
|
|
||||||
# Samba bind 636 late (probably generating keypair)
|
# Samba bind 636 late (probably generating keypair)
|
||||||
# so LDAPS connections below will fail
|
# so LDAPS connections below will fail
|
||||||
timeout = 0
|
timeout = 30
|
||||||
while timeout < 30:
|
while timeout > 0:
|
||||||
if os.path.exists("/var/lib/samba/private/tls/cert.pem"):
|
if os.path.exists("/var/lib/samba/private/tls/cert.pem"):
|
||||||
break
|
break
|
||||||
sleep(1)
|
sleep(1)
|
||||||
timeout += 1
|
timeout -= 1
|
||||||
else:
|
else:
|
||||||
assert False, "Samba startup timed out"
|
assert False, "Samba startup timed out"
|
||||||
|
assert os.path.exists("/run/samba/samba.pid")
|
||||||
|
|
||||||
# (re)auth against DC
|
# (re)auth against DC
|
||||||
assert os.system("kdestroy") == 0
|
assert os.system("kdestroy") == 0
|
||||||
@ -1175,7 +1205,7 @@ def test_cli_setup_authority():
|
|||||||
# - CRL disabled
|
# - CRL disabled
|
||||||
|
|
||||||
assert not os.path.exists("/var/lib/certidude/ca_key.pem")
|
assert not os.path.exists("/var/lib/certidude/ca_key.pem")
|
||||||
assert os.system("certidude setup authority --skip-packages") == 0
|
assert os.system("certidude setup authority --skip-packages -o 'Demola LLC'") == 0
|
||||||
assert os.path.exists("/var/lib/certidude/ca_key.pem")
|
assert os.path.exists("/var/lib/certidude/ca_key.pem")
|
||||||
assert os.path.exists("/etc/cron.hourly/certidude")
|
assert os.path.exists("/etc/cron.hourly/certidude")
|
||||||
|
|
||||||
@ -1203,6 +1233,13 @@ def test_cli_setup_authority():
|
|||||||
|
|
||||||
# Start certidude backend
|
# Start certidude backend
|
||||||
assert os.system("systemctl restart certidude") == 0
|
assert os.system("systemctl restart certidude") == 0
|
||||||
|
|
||||||
|
cov_finished = False
|
||||||
|
for path in os.listdir("/tmp/"):
|
||||||
|
if path.startswith(".coverage.ca.%d." % pid_certidude):
|
||||||
|
cov_finished = True
|
||||||
|
assert cov_finished, "Didn't find %d in %s" % (pid_certidude, os.listdir("/tmp"))
|
||||||
|
|
||||||
assert_cleanliness()
|
assert_cleanliness()
|
||||||
|
|
||||||
# Apply /etc/certidude/server.conf changes
|
# Apply /etc/certidude/server.conf changes
|
||||||
@ -1285,7 +1322,7 @@ def test_cli_setup_authority():
|
|||||||
admintoken = "Basic YWRtaW5ib3Q6UzRsNGs0bDQ="
|
admintoken = "Basic YWRtaW5ib3Q6UzRsNGs0bDQ="
|
||||||
|
|
||||||
with open("/etc/ldap/ldap.conf", "w") as fh:
|
with open("/etc/ldap/ldap.conf", "w") as fh:
|
||||||
fh.write("TLS_REQCERT never\n") # TODO: Correct way
|
fh.write("TLS_CACERT /var/lib/samba/private/tls/ca.pem")
|
||||||
|
|
||||||
# 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/",
|
||||||
@ -1355,8 +1392,15 @@ def test_cli_setup_authority():
|
|||||||
result = runner.invoke(cli, ['expire'])
|
result = runner.invoke(cli, ['expire'])
|
||||||
assert not result.exception, result.output
|
assert not result.exception, result.output
|
||||||
|
|
||||||
|
pid_certidude = int(open("/run/certidude/server.pid").read())
|
||||||
assert os.system("systemctl stop certidude") == 0
|
assert os.system("systemctl stop certidude") == 0
|
||||||
|
|
||||||
|
cov_finished = False
|
||||||
|
for path in os.listdir("/tmp/"):
|
||||||
|
if path.startswith(".coverage.ca.%d." % pid_certidude):
|
||||||
|
cov_finished = True
|
||||||
|
assert cov_finished
|
||||||
|
|
||||||
assert open("/etc/apparmor.d/local/usr.lib.ipsec.charon").read() == \
|
assert open("/etc/apparmor.d/local/usr.lib.ipsec.charon").read() == \
|
||||||
"/etc/certidude/authority/ca.example.lan/client_key.pem r,\n" + \
|
"/etc/certidude/authority/ca.example.lan/client_key.pem r,\n" + \
|
||||||
"/etc/certidude/authority/ca.example.lan/ca_cert.pem r,\n" + \
|
"/etc/certidude/authority/ca.example.lan/ca_cert.pem r,\n" + \
|
||||||
@ -1367,6 +1411,9 @@ def test_cli_setup_authority():
|
|||||||
os.system("service openvpn stop")
|
os.system("service openvpn stop")
|
||||||
os.system("ipsec stop")
|
os.system("ipsec stop")
|
||||||
|
|
||||||
|
os.system("certidude token list")
|
||||||
|
os.system("certidude token purge")
|
||||||
|
|
||||||
clean_server()
|
clean_server()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
Loading…
Reference in New Issue
Block a user