diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..6e2a80e --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,50 @@ +FROM ubuntu:latest +LABEL version="0.4" description="Mosquitto and OwnTracks Recorder" +MAINTAINER Jan-Piet Mens + +RUN apt-get install -y software-properties-common && \ + apt-add-repository ppa:mosquitto-dev/mosquitto-ppa && \ + apt-get update && \ + apt-get install -y \ + build-essential \ + git-core \ + libmosquitto-dev \ + libcurl3 \ + libcurl4-openssl-dev \ + liblua5.2-dev \ + mosquitto \ + mosquitto-clients \ + supervisor \ + wget \ + && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN groupadd --system owntracks && \ + adduser --system --disabled-password --disabled-login owntracks + +# data volume +RUN mkdir -p -m 775 /owntracks && \ + chown owntracks:owntracks /owntracks +VOLUME /owntracks + +# Recorder +RUN mkdir -p /usr/local/src /var/log/supervisor +WORKDIR /usr/local/src +RUN git clone https://github.com/owntracks/recorder.git +WORKDIR /usr/local/src/recorder +COPY config.mk /usr/local/src/recorder/config.mk +RUN make && make install +RUN chown owntracks /usr/local/bin/ocat /usr/local/sbin/ot-recorder && \ + chgrp owntracks /usr/local/bin/ocat /usr/local/sbin/ot-recorder && \ + chmod 7111 /usr/local/bin/ocat /usr/local/sbin/ot-recorder + + +COPY launcher.sh /usr/local/sbin/launcher.sh +COPY generate-CA.sh /usr/local/sbin/generate-CA.sh +RUN chmod 755 /usr/local/sbin/launcher.sh /usr/local/sbin/generate-CA.sh +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY mosquitto.conf mosquitto.acl /etc/mosquitto/ + +EXPOSE 1883 8883 8083 +CMD ["/usr/local/sbin/launcher.sh"] diff --git a/docker/build.sh b/docker/build.sh new file mode 100755 index 0000000..ed9c215 --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,3 @@ +docker build --rm -t owntracks/backend:v01 . + +# docker push owntracks/backend:v01 diff --git a/docker/generate-CA.sh b/docker/generate-CA.sh new file mode 100644 index 0000000..03e66e5 --- /dev/null +++ b/docker/generate-CA.sh @@ -0,0 +1,291 @@ +#!/bin/bash +#(@)generate-CA.sh - Create CA key-pair and server key-pair signed by CA + +# Copyright (c) 2013-2016 Jan-Piet Mens +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of mosquitto nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# +# Usage: +# ./generate-CA.sh creates ca.crt and server.{key,crt} +# ./generate-CA.sh hostname creates hostname.{key,crt} +# ./generate-CA.sh client email creates email.{key,crt} +# +# Set the following optional environment variables before invocation +# to add the specified IP addresses and/or hostnames to the subjAltName list +# These contain white-space-separated values +# +# IPLIST="172.13.14.15 192.168.1.1" +# HOSTLIST="a.example.com b.example.com" + +set -e + +export LANG=C + +kind=server + +if [ $# -ne 2 ]; then + kind=server + host=$(hostname -f) + if [ -n "$1" ]; then + host="$1" + fi +else + kind=client + CLIENT="$2" +fi + +[ -z "$USER" ] && USER=root + +DIR=${TARGET:='.'} +# A space-separated list of alternate hostnames (subjAltName) +# may be empty "" +ALTHOSTNAMES=${HOSTLIST} +ALTADDRESSES=${IPLIST} +CA_ORG='/O=OwnTracks.org/OU=generate-CA/emailAddress=nobody@example.net' +CA_DN="/CN=An MQTT broker${CA_ORG}" +CACERT=${DIR}/ca +SERVER="${DIR}/${host}" +SERVER_DN="/CN=${host}$CA_ORG" +keybits=2048 +openssl=$(which openssl) +MOSQUITTOUSER=${MOSQUITTOUSER:=$USER} + +# Signature Algorithm. To find out which are supported by your +# version of OpenSSL, run `openssl dgst -help` and set your +# signature algorithm here. For example: +# +# defaultmd="-sha256" +# +defaultmd="-sha512" + +function maxdays() { + nowyear=$(date +%Y) + years=$(expr 2032 - $nowyear) + days=$(expr $years '*' 365) + + echo $days +} + +function getipaddresses() { + /sbin/ifconfig | + grep -v tunnel | + sed -En '/inet6? /p' | + sed -Ee 's/inet6? (addr:)?//' | + awk '{print $1;}' | + sed -e 's/[%/].*//' | + egrep -v '(::1|127\.0\.0\.1)' # omit loopback to add it later +} + + +function addresslist() { + + ALIST="" + for a in $(getipaddresses); do + ALIST="${ALIST}IP:$a," + done + ALIST="${ALIST}IP:127.0.0.1,IP:::1," + + for ip in $(echo ${ALTADDRESSES}); do + ALIST="${ALIST}IP:${ip}," + done + for h in $(echo ${ALTHOSTNAMES}); do + ALIST="${ALIST}DNS:$h," + done + ALIST="${ALIST}DNS:localhost" + echo $ALIST + +} + +days=$(maxdays) + +if [ -n "$CAKILLFILES" ]; then + rm -f $CACERT.??? $SERVER.??? $CACERT.srl +fi + +if [ ! -f $CACERT.crt ]; then + + # ____ _ + # / ___| / \ + # | | / _ \ + # | |___ / ___ \ + # \____/_/ \_\ + # + + # Create un-encrypted (!) key + $openssl req -newkey rsa:${keybits} -x509 -nodes $defaultmd -days $days -extensions v3_ca -keyout $CACERT.key -out $CACERT.crt -subj "${CA_DN}" + echo "Created CA certificate in $CACERT.crt" + $openssl x509 -in $CACERT.crt -nameopt multiline -subject -noout + + chmod 400 $CACERT.key + chmod 444 $CACERT.crt + chown $MOSQUITTOUSER $CACERT.* + echo "Warning: the CA key is not encrypted; store it safely!" +fi + + +if [ $kind == 'server' ]; then + + # ____ + # / ___| ___ _ ____ _____ _ __ + # \___ \ / _ \ '__\ \ / / _ \ '__| + # ___) | __/ | \ V / __/ | + # |____/ \___|_| \_/ \___|_| + # + + if [ ! -f $SERVER.key ]; then + echo "--- Creating server key and signing request" + $openssl genrsa -out $SERVER.key $keybits + $openssl req -new $defaultmd \ + -out $SERVER.csr \ + -key $SERVER.key \ + -subj "${SERVER_DN}" + chmod 400 $SERVER.key + chown $MOSQUITTOUSER $SERVER.key + fi + + if [ -f $SERVER.csr -a ! -f $SERVER.crt ]; then + + # There's no way to pass subjAltName on the CLI so + # create a cnf file and use that. + + CNF=`mktemp /tmp/cacnf.XXXXXXXX` || { echo "$0: can't create temp file" >&2; exit 1; } + sed -e 's/^.*%%% //' > $CNF <<\!ENDconfig + %%% [ JPMextensions ] + %%% basicConstraints = critical,CA:false + %%% nsCertType = server + %%% keyUsage = nonRepudiation, digitalSignature, keyEncipherment + %%% nsComment = "Broker Certificate" + %%% subjectKeyIdentifier = hash + %%% authorityKeyIdentifier = keyid,issuer:always + %%% subjectAltName = $ENV::SUBJALTNAME + %%% # issuerAltName = issuer:copy + %%% ## nsCaRevocationUrl = http://mqttitude.org/carev/ + %%% ## nsRevocationUrl = http://mqttitude.org/carev/ + %%% certificatePolicies = ia5org,@polsection + %%% + %%% [polsection] + %%% policyIdentifier = 1.3.5.8 + %%% CPS.1 = "http://localhost" + %%% userNotice.1 = @notice + %%% + %%% [notice] + %%% explicitText = "This CA is for a local MQTT broker installation only" + %%% organization = "OwnTracks" + %%% noticeNumbers = 1 + +!ENDconfig + + SUBJALTNAME="$(addresslist)" + export SUBJALTNAME # Use environment. Because I can. ;-) + + echo "--- Creating and signing server certificate" + $openssl x509 -req $defaultmd \ + -in $SERVER.csr \ + -CA $CACERT.crt \ + -CAkey $CACERT.key \ + -CAcreateserial \ + -CAserial "${DIR}/ca.srl" \ + -out $SERVER.crt \ + -days $days \ + -extfile ${CNF} \ + -extensions JPMextensions + + rm -f $CNF + chmod 444 $SERVER.crt + chown $MOSQUITTOUSER $SERVER.crt + fi +else + # ____ _ _ _ + # / ___| (_) ___ _ __ | |_ + # | | | | |/ _ \ '_ \| __| + # | |___| | | __/ | | | |_ + # \____|_|_|\___|_| |_|\__| + # + + if [ ! -f $CLIENT.key ]; then + echo "--- Creating client key and signing request" + $openssl genrsa -out $CLIENT.key $keybits + + CNF=`mktemp /tmp/cacnf-req.XXXXXXXX` || { echo "$0: can't create temp file" >&2; exit 1; } + # Mosquitto's use_identity_as_username takes the CN attribute + # so we're populating that with the client's name + sed -e 's/^.*%%% //' > $CNF <&2; exit 1; } + sed -e 's/^.*%%% //' > $CNF <<\!ENDClientconfig + %%% [ JPMclientextensions ] + %%% basicConstraints = critical,CA:false + %%% subjectAltName = email:copy + %%% nsCertType = client,email + %%% extendedKeyUsage = clientAuth,emailProtection + %%% keyUsage = digitalSignature, keyEncipherment, keyAgreement + %%% nsComment = "Client Broker Certificate" + %%% subjectKeyIdentifier = hash + %%% authorityKeyIdentifier = keyid,issuer:always + +!ENDClientconfig + + SUBJALTNAME="$(addresslist)" + export SUBJALTNAME # Use environment. Because I can. ;-) + + echo "--- Creating and signing client certificate" + $openssl x509 -req $defaultmd \ + -in $CLIENT.csr \ + -CA $CACERT.crt \ + -CAkey $CACERT.key \ + -CAcreateserial \ + -CAserial "${DIR}/ca.srl" \ + -out $CLIENT.crt \ + -days $days \ + -extfile ${CNF} \ + -extensions JPMclientextensions + + rm -f $CNF + chmod 444 $CLIENT.crt + fi +fi diff --git a/docker/launcher.sh b/docker/launcher.sh new file mode 100644 index 0000000..0395c15 --- /dev/null +++ b/docker/launcher.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# launcher.sh +# This will be started when the container starts + +set -e + +echo -- "--- BEGIN OWNTRACKS LAUNCHER ---" + + +mkdir -p /owntracks/recorder/store +mkdir -p /owntracks/recorder/store/last + +chown -R owntracks:owntracks /owntracks/recorder +/usr/local/sbin/ot-recorder --initialize + +mkdir -p /owntracks/certs + +if [ -d /owntracks/certs ]; then + cd /owntracks/certs + + # We prefer the the environment's (-e) MQTTHOSTNAME value. + # Note, that generate-CA.sh will also consume $IPLIST and + # $HOSTLIST, both of which may contain space-separated values. + + host=${MQTTHOSTNAME:=$(hostname)} + echo "*** Using $host as hostname for server certificate" + /usr/local/sbin/generate-CA.sh ${host} + ln -sf ${host}.crt mosquitto.crt + ln -sf ${host}.key mosquitto.key + chown mosquitto mosquitto.crt + chown mosquitto mosquitto.key + +fi + +# --- for Mosquitto's persistence +mkdir -p /owntracks/mosquitto +chown mosquitto:mosquitto /owntracks/mosquitto + +# Prime Mosquitto's configuration in volume if it doesn't yet exist there. +# Mosquitto will launch with that, allowing the admin to modify config +# if necessary/desired. + +if [ ! -f /owntracks/mosquitto/mosquitto.conf ]; then + cp /etc/mosquitto/mosquitto.conf /owntracks/mosquitto/mosquitto.conf +fi +if [ ! -f /owntracks/mosquitto/mosquitto.acl ]; then + cp /etc/mosquitto/mosquitto.acl /owntracks/mosquitto/mosquitto.acl +fi + +exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf diff --git a/docker/mosquitto.acl b/docker/mosquitto.acl new file mode 100644 index 0000000..5c3f3ca --- /dev/null +++ b/docker/mosquitto.acl @@ -0,0 +1,9 @@ +# This affects access control for clients with no username. +topic read $SYS/# + +# This only affects clients with username "roger". +user roger +topic foo/bar + +# This affects all clients. +pattern write $SYS/broker/connection/%c/state diff --git a/docker/mosquitto.conf b/docker/mosquitto.conf new file mode 100644 index 0000000..90bd434 --- /dev/null +++ b/docker/mosquitto.conf @@ -0,0 +1,31 @@ +autosave_interval 1800 +connection_messages true +log_dest stderr +log_dest topic +log_type error +log_type warning +log_type notice +log_type information +log_type all +log_type debug +log_timestamp true + +max_inflight_messages 20 +max_queued_messages 9000 +message_size_limit 10240 + +#password_file /owntracks/mosquitto/mosquitto.passwd +acl_file /owntracks/mosquitto/mosquitto.acl + +persistence true +persistence_location /owntracks/mosquitto/ +persistence_file mosquitto.db +persistent_client_expiration 1m + +listener 1883 + +listener 8883 +cafile /owntracks/certs//ca.crt +certfile /owntracks/certs/mosquitto.crt +keyfile /owntracks/certs/mosquitto.key +require_certificate false diff --git a/docker/run.sh b/docker/run.sh new file mode 100755 index 0000000..532d6dc --- /dev/null +++ b/docker/run.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +cthostname="owntracks.example.org" + +docker run -v /tmp/o2:/owntracks -p 11883:1883 -p 18883:8883 -p 8083:8083 \ + --hostname "${cthostname}" \ + -e MQTTHOSTNAME="${cthostname}" \ + -e IPLIST="192.168.1.1 127.0.0.83 192.168.1.82" \ + -e HOSTLIST="foo.example.com bar.org.example.com ${cthostname}" \ + owntracks/backend:v01 diff --git a/docker/supervisord.conf b/docker/supervisord.conf new file mode 100644 index 0000000..1941f69 --- /dev/null +++ b/docker/supervisord.conf @@ -0,0 +1,28 @@ +[supervisord] +logfile=/dev/null +pidfile=/var/run/supervisord.pid +nodaemon=true + +#[program:sshd] +#command=/usr/sbin/sshd -D +#redirect_stderr=true +#stdout_logfile=/dev/stdout +#stdout_logfile_maxbytes=0 +#auto_start=true +#autorestart=true + +[program:mosquitto] +command=/usr/sbin/mosquitto -c /owntracks/mosquitto/mosquitto.conf +redirect_stderr=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +auto_start=true +autorestart=true + +[program:recorder] +command=/usr/local/sbin/ot-recorder --http-host 0.0.0.0 'owntracks/#' +redirect_stderr=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +auto_start=true +autorestart=true