1
0
mirror of https://github.com/laurivosandi/certidude synced 2024-12-22 16:25:17 +00:00

Several changes

* OCSP workaround for StrongSwan
* Machine attributes framework
* Scripting support
* Default to nginx frontend
This commit is contained in:
Lauri Võsandi 2017-07-05 18:22:03 +03:00
parent d08a3f9f92
commit 9b5511212e
20 changed files with 211 additions and 93 deletions

View File

@ -8,6 +8,7 @@ import click
import hashlib
from datetime import datetime
from time import sleep
from xattr import listxattr, getxattr
from certidude import authority, mailer
from certidude.auth import login_required, authorize_admin
from certidude.user import User
@ -32,7 +33,6 @@ class SessionResource(object):
@serialize
@login_required
def on_get(self, req, resp):
import xattr
def serialize_requests(g):
for common_name, path, buf, obj, server in g():
@ -50,7 +50,7 @@ class SessionResource(object):
# Extract certificate tags from filesystem
try:
tags = []
for tag in xattr.getxattr(path, "user.xdg.tags").split(","):
for tag in getxattr(path, "user.xdg.tags").split(","):
if "=" in tag:
k, v = tag.split("=", 1)
else:
@ -59,12 +59,17 @@ class SessionResource(object):
except IOError: # No such attribute(s)
tags = None
attributes = {}
for key in listxattr(path):
if key.startswith("user.machine."):
attributes[key[13:]] = getxattr(path, key)
# Extract lease information from filesystem
try:
last_seen = datetime.strptime(xattr.getxattr(path, "user.lease.last_seen"), "%Y-%m-%dT%H:%M:%S.%fZ")
last_seen = datetime.strptime(getxattr(path, "user.lease.last_seen"), "%Y-%m-%dT%H:%M:%S.%fZ")
lease = dict(
inner_address = xattr.getxattr(path, "user.lease.inner_address"),
outer_address = xattr.getxattr(path, "user.lease.outer_address"),
inner_address = getxattr(path, "user.lease.inner_address"),
outer_address = getxattr(path, "user.lease.outer_address"),
last_seen = last_seen,
age = datetime.utcnow() - last_seen
)
@ -80,7 +85,8 @@ class SessionResource(object):
expires = obj.not_valid_after,
sha256sum = hashlib.sha256(buf).hexdigest(),
lease = lease,
tags = tags
tags = tags,
attributes = attributes or None,
)
if req.context.get("user").is_admin():
@ -160,7 +166,7 @@ import ipaddress
class NormalizeMiddleware(object):
def process_request(self, req, resp, *args):
assert not req.get_param("unicode") or req.get_param("unicode") == u"", "Unicode sanity check failed"
req.context["remote_addr"] = ipaddress.ip_address(req.env["REMOTE_ADDR"].decode("utf-8"))
req.context["remote_addr"] = ipaddress.ip_address(req.access_route[0].decode("utf-8"))
def process_response(self, req, resp, resource=None):
# wtf falcon?!
@ -181,6 +187,7 @@ def certidude_app(log_handlers=[]):
app = falcon.API(middleware=NormalizeMiddleware())
app.req_options.auto_parse_form_urlencoded = True
#app.req_options.strip_url_path_trailing_slash = False
# Certificate authority API calls
app.add_route("/api/certificate/", CertificateAuthorityResource())
@ -194,7 +201,7 @@ def certidude_app(log_handlers=[]):
app.add_route("/api/token/", TokenResource())
# Extended attributes for scripting etc.
app.add_route("/api/signed/{cn}/attr/", AttributeResource())
app.add_route("/api/signed/{cn}/attr/", AttributeResource(namespace="machine"))
app.add_route("/api/signed/{cn}/script/", ScriptResource())
# API calls used by pushed events on the JS end
@ -215,18 +222,13 @@ def certidude_app(log_handlers=[]):
from .scep import SCEPResource
app.add_route("/api/scep/", SCEPResource())
if config.OCSP_SUBNETS:
from .ocsp import OCSPResource
app.add_route("/api/ocsp/", OCSPResource())
# Add sink for serving static files
app.add_sink(StaticResource(os.path.join(__file__, "..", "..", "static")))
def log_exceptions(ex, req, resp, params):
logger.debug("Caught exception: %s" % ex)
raise ex
app.add_error_handler(Exception, log_exceptions)
if config.OCSP_SUBNETS:
from .ocsp import OCSPResource
app.add_sink(OCSPResource(), prefix="/api/ocsp")
# Set up log handlers
if config.LOGGING_BACKEND == "sql":

View File

@ -1,14 +1,24 @@
import click
import falcon
import logging
from ipaddress import ip_address
import re
from xattr import setxattr, listxattr, removexattr
from datetime import datetime
from certidude import config, authority
from certidude.decorators import serialize
from certidude import config, authority, push
from certidude.decorators import serialize, csrf_protection
from certidude.firewall import whitelist_subject
from certidude.auth import login_required, login_optional, authorize_admin
from ipaddress import ip_address
logger = logging.getLogger(__name__)
class AttributeResource(object):
def __init__(self, namespace):
self.namespace = namespace
@serialize
@login_required
@authorize_admin
def on_get(self, req, resp, cn):
"""
Return extended attributes stored on the server.
@ -17,22 +27,32 @@ class AttributeResource(object):
Results made available only to lease IP address.
"""
try:
path, buf, cert, attribs = authority.get_attributes(cn)
path, buf, cert, attribs = authority.get_attributes(cn, namespace=self.namespace)
except IOError:
raise falcon.HTTPNotFound()
else:
try:
whitelist = ip_address(attribs.get("user").get("lease").get("inner_address").decode("ascii"))
except AttributeError: # TODO: probably race condition
raise falcon.HTTPForbidden("Forbidden",
"Attributes only accessible to the machine")
if req.context.get("remote_addr") != whitelist:
logger.info("Attribute access denied from %s, expected %s for %s",
req.context.get("remote_addr"),
whitelist,
cn)
raise falcon.HTTPForbidden("Forbidden",
"Attributes only accessible to the machine")
return attribs
@csrf_protection
@whitelist_subject # TODO: sign instead
def on_post(self, req, resp, cn):
try:
path, buf, cert = authority.get_signed(cn)
except IOError:
raise falcon.HTTPNotFound()
else:
for key in req.params:
if not re.match("[a-z0-9_\.]+$", key):
raise falcon.HTTPBadRequest("Invalid key")
valid = set()
for key, value in req.params.items():
identifier = ("user.%s.%s" % (self.namespace, key)).encode("ascii")
setxattr(path, identifier, value.encode("utf-8"))
valid.add(identifier)
for key in listxattr(path):
if not key.startswith("user.%s." % self.namespace):
continue
if key not in valid:
removexattr(path, key)
push.publish("attribute-update", cn)

View File

@ -1,4 +1,3 @@
from __future__ import unicode_literals, division, absolute_import, print_function
import click
import hashlib
import os
@ -14,19 +13,26 @@ from oscrypto import keys, asymmetric, symmetric
from oscrypto.errors import SignatureError
class OCSPResource(object):
def on_post(self, req, resp):
def __call__(self, req, resp):
if req.method == "GET":
_, _, _, tail = req.path.split("/", 3)
body = b64decode(tail)
elif req.method == "POST":
body = req.stream.read(req.content_length or 0)
else:
raise falcon.HTTPMethodNotAllowed()
fh = open(config.AUTHORITY_CERTIFICATE_PATH)
server_certificate = asymmetric.load_certificate(fh.read())
fh.close()
ocsp_req = ocsp.OCSPRequest.load(req.stream.read())
print(ocsp_req["tbs_request"].native)
ocsp_req = ocsp.OCSPRequest.load(body)
now = datetime.now(timezone.utc)
response_extensions = []
try:
for ext in ocsp_req["tbs_request"]["request_extensions"]:
if ext["extn_id"] == "nonce":
if ext["extn_id"].native == "nonce":
response_extensions.append(
ocsp.ResponseDataExtension({
'extn_id': "nonce",
@ -34,6 +40,8 @@ class OCSPResource(object):
'extn_value': ext["extn_value"]
})
)
except ValueError: # https://github.com/wbond/asn1crypto/issues/56
pass
responses = []
for item in ocsp_req["tbs_request"]["request_list"]:

View File

@ -1,4 +1,3 @@
from __future__ import unicode_literals, division, absolute_import, print_function
import click
import hashlib
import os

View File

@ -1,21 +1,37 @@
import falcon
import logging
from certidude import config, authority
from certidude import const, config, authority
from certidude.decorators import serialize
from jinja2 import Environment, FileSystemLoader
from certidude.firewall import whitelist_subject
logger = logging.getLogger(__name__)
env = Environment(loader=FileSystemLoader(config.SCRIPT_DIR), trim_blocks=True)
class ScriptResource():
@whitelist_subject
def on_get(self, req, resp, cn):
try:
path, buf, cert, attribs = authority.get_attributes(cn)
except IOError:
raise falcon.HTTPNotFound()
else:
resp.set_header("Content-Type", "text/x-shellscript")
resp.body = env.get_template(config.SCRIPT_DEFAULT).render(attributes=attribs)
script = config.SCRIPT_DEFAULT
tags = []
for tag in attribs.get("user").get("xdg").get("tags").split(","):
if "=" in tag:
k, v = tag.split("=", 1)
else:
k, v = "other", tag
if k == "script":
script = v
tags.append(dict(id=tag, key=k, value=v))
resp.set_header("Content-Type", "text/x-shellscript")
resp.body = env.get_template(script).render(
authority_name=const.FQDN,
common_name=cn,
tags=tags,
attributes=attribs.get("user").get("machine"))
logger.info("Served script %s for %s at %s" % (script, cn, req.context["remote_addr"]))
# TODO: Assert time is within reasonable range

View File

@ -1,4 +1,4 @@
from __future__ import unicode_literals, division, absolute_import, print_function
from __future__ import division, absolute_import, print_function
import click
import os
import random
@ -57,17 +57,19 @@ def get_revoked(serial):
datetime.utcfromtimestamp(os.stat(path).st_ctime)
def get_attributes(cn):
def get_attributes(cn, namespace=None):
path, buf, cert = get_signed(cn)
attribs = dict()
for key in listxattr(path):
if not key.startswith("user."):
continue
if namespace and not key.startswith("user.%s." % namespace):
continue
value = getxattr(path, key)
current = attribs
if "." in key:
namespace, key = key.rsplit(".", 1)
for component in namespace.split("."):
prefix, key = key.rsplit(".", 1)
for component in prefix.split("."):
if component not in current:
current[component] = dict()
current = current[component]
@ -324,7 +326,6 @@ def sign(common_name, overwrite=False):
def _sign(csr, buf, overwrite=False):
assert buf.startswith("-----BEGIN CERTIFICATE REQUEST-----\n")
assert isinstance(csr, x509.CertificateSigningRequest)
from xattr import getxattr, listxattr, setxattr
common_name, = csr.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
cert_path = os.path.join(config.SIGNED_DIR, "%s.pem" % common_name.value)

View File

@ -1004,10 +1004,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country,
static_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), "static")
certidude_path = sys.argv[0]
# Push server config generation
if os.path.exists("/etc/nginx"):
listen = "127.0.1.1"
port = "8080"
click.echo("Generating: %s" % nginx_config.name)
nginx_config.write(env.get_template("server/nginx.conf").render(vars()))
nginx_config.close()
@ -1017,11 +1013,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country,
if os.path.exists("/etc/nginx/sites-enabled/default"):
os.unlink("/etc/nginx/sites-enabled/default")
os.system("service nginx restart")
else:
click.echo("Directory /etc/nginx does not exist, hence not creating nginx configuration")
click.echo("Remember to install/configure nchan capable nginx instead of regular nginx!")
listen = "0.0.0.0"
port = "80"
if os.path.exists("/etc/systemd"):
if os.path.exists("/etc/systemd/system/certidude.service"):
@ -1284,16 +1275,19 @@ def certidude_cron():
@click.command("serve", help="Run server")
@click.option("-e", "--exit-handler", default=False, is_flag=True, help="Install /api/exit/ handler")
@click.option("-p", "--port", default=80, help="Listen port")
@click.option("-l", "--listen", default="0.0.0.0", help="Listen address")
@click.option("-p", "--port", default=8080, help="Listen port")
@click.option("-l", "--listen", default="127.0.0.1", help="Listen address")
@click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background")
def certidude_serve(port, listen, fork, exit_handler):
import pwd
from setproctitle import setproctitle
from certidude.signer import SignServer
from certidude import authority, const
click.echo("Using configuration from: %s" % const.CONFIG_PATH)
if port == 80:
click.echo("WARNING: Please run Certidude behind nginx, remote address is assumed to be forwarded by nginx!")
click.echo("Using configuration from: %s" % const.CONFIG_PATH)
log_handlers = []

View File

@ -98,4 +98,4 @@ TOKEN_SECRET = cp.get("token", "secret")
# The API call for looking up scripts uses following directory as root
SCRIPT_DIR = os.path.join(os.path.dirname(__file__), "templates", "script")
SCRIPT_DEFAULT = "openwrt.sh"
SCRIPT_DEFAULT = "default.sh"

View File

@ -1,4 +1,5 @@
import falcon
import logging
logger = logging.getLogger("api")
@ -20,7 +21,7 @@ def whitelist_subnets(subnets):
req.env["PATH_INFO"],
req.context.get("user", "unauthenticated user"),
req.context.get("remote_addr"))
raise falcon.HTTPForbidden("Forbidden", "Remote address %s not whitelisted" % remote_addr)
raise falcon.HTTPForbidden("Forbidden", "Remote address %s not whitelisted" % req.context.get("remote_addr"))
return func(self, req, resp, *args, **kwargs)
return wrapped
@ -39,3 +40,19 @@ def whitelist_content_types(*content_types):
return wrapped
return wrapper
def whitelist_subject(func):
def wrapped(self, req, resp, cn, *args, **kwargs):
from ipaddress import ip_address
from certidude import authority
from xattr import getxattr
try:
path, buf, cert = authority.get_signed(cn)
except IOError:
raise falcon.HTTPNotFound()
else:
inner_address = getxattr(path, "user.lease.inner_address").decode("ascii")
if req.context.get("remote_addr") != ip_address(inner_address):
raise falcon.HTTPForbidden("Forbidden", "Remote address %s not whitelisted" % req.context.get("remote_addr"))
return func(self, req, resp, cn, *args, **kwargs)
return wrapped

View File

@ -183,7 +183,8 @@ pre {
padding-bottom: 2px;
}
.tags .tag {
.tags .tag,
.attributes .attribute {
display: inline;
background-size: 24px;
background-position: 0 4px;
@ -195,6 +196,15 @@ pre {
white-space: nowrap;
}
.attribute {
opacity: 0.25;
}
.attribute:hover {
opacity: 1;
}
select {
-webkit-appearance: none;
-moz-appearance: none;
@ -205,7 +215,8 @@ select {
}
.icon.tag { background-image: url("../img/iconmonstr-tag-3.svg"); }
.icon.tag,
.icon.attribute { background-image: url("../img/iconmonstr-tag-3.svg"); }
.icon.critical { background-image: url("../img/iconmonstr-error-4.svg"); }
.icon.error { background-image: url("../img/iconmonstr-error-4.svg"); }
@ -227,5 +238,12 @@ select {
.icon.upload { background-image: url("../img/iconmonstr-upload-17.svg"); }
.icon.dist,
.icon.kernel { background-image: url("../img/iconmonstr-linux-os-1.svg"); }
.icon.if { background-image: url("../img/iconmonstr-sitemap-5.svg"); }
.icon.cpu,
.icon.mem,
.icon.ram { background-image: url("../img/iconmonstr-cpu-1.svg"); }
/* Make sure this is the last one */
.icon.busy{background-image:url("https://software.opensuse.org/assets/ajax-loader-ea46060b6c9f42822a3d58d075c83ea2.gif");}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 17c0 1.104-.896 2-2 2h-11c-1.104 0-2-.896-2-2v-11c0-1.104.896-2 2-2h11c1.104 0 2 .896 2 2v11zm-11 3v3h-1v-3h1zm4 0v3h-1v-3h1zm2 0v3h-1v-3h1zm-4 0v3h-1v-3h1zm6 0v3h-1v-3h1zm-8-20v3h-1v-3h1zm4 0v3h-1v-3h1zm2 0v3h-1v-3h1zm-4 0v3h-1v-3h1zm6 0v3h-1v-3h1zm4 15h3v1h-3v-1zm0-4h3v1h-3v-1zm0-2h3v1h-3v-1zm0 4h3v1h-3v-1zm0-6h3v1h-3v-1zm-20 8h3v1h-3v-1zm0-4h3v1h-3v-1zm0-2h3v1h-3v-1zm0 4h3v1h-3v-1zm0-6h3v1h-3v-1z"/></svg>

After

Width:  |  Height:  |  Size: 507 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20.581 19.049c-.55-.446-.336-1.431-.907-1.917.553-3.365-.997-6.331-2.845-8.232-1.551-1.595-1.051-3.147-1.051-4.49 0-2.146-.881-4.41-3.55-4.41-2.853 0-3.635 2.38-3.663 3.738-.068 3.262.659 4.11-1.25 6.484-2.246 2.793-2.577 5.579-2.07 7.057-.237.276-.557.582-1.155.835-1.652.72-.441 1.925-.898 2.78-.13.243-.192.497-.192.74 0 .75.596 1.399 1.679 1.302 1.461-.13 2.809.905 3.681.905.77 0 1.402-.438 1.696-1.041 1.377-.339 3.077-.296 4.453.059.247.691.917 1.141 1.662 1.141 1.631 0 1.945-1.849 3.816-2.475.674-.225 1.013-.879 1.013-1.488 0-.39-.139-.761-.419-.988zm-9.147-10.465c-.319 0-.583-.258-1-.568-.528-.392-1.065-.618-1.059-1.03 0-.283.379-.37.869-.681.526-.333.731-.671 1.249-.671.53 0 .69.268 1.41.579.708.307 1.201.427 1.201.773 0 .355-.741.609-1.158.868-.613.378-.928.73-1.512.73zm1.665-5.215c.882.141.981 1.691.559 2.454l-.355-.145c.184-.543.181-1.437-.435-1.494-.391-.036-.643.48-.697.922-.153-.064-.32-.11-.523-.127.062-.923.658-1.737 1.451-1.61zm-3.403.331c.676-.168 1.075.618 1.078 1.435l-.31.19c-.042-.343-.195-.897-.579-.779-.411.128-.344 1.083-.115 1.279l-.306.17c-.42-.707-.419-2.133.232-2.295zm-2.115 19.243c-1.963-.893-2.63-.69-3.005-.69-.777 0-1.031-.579-.739-1.127.248-.465.171-.952.11-1.343-.094-.599-.111-.794.478-1.052.815-.346 1.177-.791 1.447-1.124.758-.937 1.523.537 2.15 1.85.407.851 1.208 1.282 1.455 2.225.227.871-.71 1.801-1.896 1.261zm6.987-1.874c-1.384.673-3.147.982-4.466.299-.195-.563-.507-.927-.843-1.293.539-.142.939-.814.46-1.489-.511-.721-1.555-1.224-2.61-2.04-.987-.763-1.299-2.644.045-4.746-.655 1.862-.272 3.578.057 4.069.068-.988.146-2.638 1.496-4.615.681-.998.691-2.316.706-3.14l.62.424c.456.337.838.708 1.386.708.81 0 1.258-.466 1.882-.853.244-.15.613-.302.923-.513.52 2.476 2.674 5.454 2.795 7.15.501-1.032-.142-3.514-.142-3.514.842 1.285.909 2.356.946 3.67.589.241 1.221.869 1.279 1.696l-.245-.028c-.126-.919-2.607-2.269-2.83-.539-1.19.181-.757 2.066-.997 3.288-.11.559-.314 1.001-.462 1.466zm4.846-.041c-.985.38-1.65 1.187-2.107 1.688-.88.966-2.044.503-2.168-.401-.131-.966.36-1.493.572-2.574.193-.987-.023-2.506.431-2.668.295 1.753 2.066 1.016 2.47.538.657 0 .712.222.859.837.092.385.219.709.578 1.09.418.447.29 1.133-.635 1.49zm-8-13.006c-.651 0-1.138-.433-1.534-.769-.203-.171.05-.487.253-.315.387.328.777.675 1.281.675.607 0 1.142-.519 1.867-.805.247-.097.388.285.143.382-.704.277-1.269.832-2.01.832z"/></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M16 6h-8v-6h8v6zm-10 12h-6v6h6v-6zm18 0h-6v6h6v-6zm-11-7v-3h-2v3h-9v5h2v-3h7v3h2v-3h7v3h2v-5h-9zm2 7h-6v6h6v-6z"/></svg>

After

Width:  |  Height:  |  Size: 212 B

View File

@ -181,6 +181,24 @@ function onTagUpdated(e) {
})
}
function onAttributeUpdated(e) {
var cn = e.data;
console.log("Attributes updated", cn);
$.ajax({
method: "GET",
url: "/api/signed/" + cn + "/attr/",
dataType: "json",
success:function(attributes, status, xhr) {
console.info("Updated", cn, "attributes", attributes);
$(".attributes[data-cn='" + cn + "']").html(
nunjucks.render('views/attributes.html', {
certificate: {
common_name: cn,
attributes:attributes }}));
}
})
}
$(document).ready(function() {
console.info("Loading CA, to debug: curl " + window.location.href + " --negotiate -u : -H 'Accept: application/json'");
$.ajax({
@ -228,6 +246,7 @@ $(document).ready(function() {
source.addEventListener("request-signed", onRequestSigned);
source.addEventListener("certificate-revoked", onCertificateRevoked);
source.addEventListener("tag-update", onTagUpdated);
source.addEventListener("attribute-update", onAttributeUpdated);
console.info("Swtiching to requests section");
$("section").hide();

View File

@ -0,0 +1,3 @@
{% for key, value in certificate.attributes %}
<span class="attribute icon {{ key | replace('.', ' ') }}">{{ value }}</span>
{% endfor %}

View File

@ -61,4 +61,8 @@
<div class="status">
{% include 'views/status.html' %}
</div>
<div class="attributes" data-cn="{{ certificate.common_name }}">
{% include 'views/attributes.html' %}
</div>
</li>

View File

@ -0,0 +1,15 @@
#!/bin/sh
# Tags:
{% for tag in tags %}
# {{ tag }}
{% endfor %}
curl http://{{ authority_name }}/api/signed/{{ common_name }}/attr -X POST -d "\
dmi.product_name=$(cat /sys/class/dmi/id/product_name)&\
dmi.product_serial=$(cat /sys/class/dmi/id/product_serial)&\
kernel=$(uname -sr)&\
dist=$(lsb_release -si) $(lsb_release -sr)&\
cpu=$(cat /proc/cpuinfo | grep '^model name' | head -n1 | cut -d ":" -f2 | xargs)&\
mem=$(dmidecode -t 17 | grep Size | cut -d ":" -f 2 | cut -d " " -f 2 | paste -sd+ | bc) MB&\
$(for j in /sys/class/net/[we]*; do echo -en if.$(basename $j).ether=$(cat $j/address)\&; done)"

View File

@ -1,6 +1,6 @@
#!/bin/sh
# This script can executed on a preconfigured OpenWrt box
# This script can be executed on a preconfigured OpenWrt box
# https://lauri.vosandi.com/2017/01/reconfiguring-openwrt-as-dummy-ap.html
# Password protected wireless area
@ -10,13 +10,13 @@ for band in 2ghz 5ghz; do
uci set wireless.lan$band.mode=ap
uci set wireless.lan$band.device=radio$band
uci set wireless.lan$band.encryption=psk2
{% if attributes.protected and attributes.protected.ssid %}
uci set wireless.lan$band.ssid={{ attrbutes.protected.ssid }}
{% if attributes.wireless.protected and attributes.wireless.protected.ssid %}
uci set wireless.lan$band.ssid={{ attrbutes.wireless.protected.ssid }}
{% else %}
uci set wireless.lan$band.ssid=$(uci get system.@system[0].hostname)-protected
{% endif %}
{% if attributes.protected and attributes.protected.psk %}
uci set wireless.lan$band.key={{ attributes.protected.psk }}
{% if attributes.wireless.protected and attributes.wireless.protected.psk %}
uci set wireless.lan$band.key={{ attributes.wireless.protected.psk }}
{% else %}
uci set wireless.lan$band.key=salakala
{% endif %}
@ -29,8 +29,8 @@ for band in 2ghz 5ghz; do
uci set wireless.guest$band.mode=ap
uci set wireless.guest$band.device=radio$band
uci set wireless.guest$band.encryption=none
{% if attributes.public and attributes.public.ssid %}
uci set wireless.guest$band.ssid={{ attrbutes.public.ssid }}
{% if attributes.wireless.public and attributes.wireless.public.ssid %}
uci set wireless.guest$band.ssid={{ attrbutes.wireless.public.ssid }}
{% else %}
uci set wireless.guest$band.ssid=$(uci get system.@system[0].hostname)-public
{% endif %}

View File

@ -23,7 +23,7 @@ server {
root {{static_path}};
location /api/ {
proxy_pass http://127.0.1.1{% if port != 80 %}:{{ port }}{% endif %}/api/;
proxy_pass http://127.0.1.1:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 600;

View File

@ -7,8 +7,7 @@ Environment=PYTHON_EGG_CACHE=/tmp/.cache
PIDFile=/run/certidude/server.pid
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
ExecStart={{ certidude_path }} serve {% if listen %} -l {{listen}}{% endif %}{% if port %} -p {{port}}{% endif %}
ExecStart={{ certidude_path }} serve
[Install]
WantedBy=multi-user.target