ippo

Owntracks location tracker

Owntracks location tracker

Tracking you own and family/friends location and have a history of locations on map like a google maps history open source alternative

  • Owntracks is a location history tracking app
  • Mosquitto is a mqtt protocol, publish/subscribe message brocker that stores data that recieves from clients
  • Recorder is a lightweight program for storing and accessing location data published via MQTT (or HTTP) and displays them in a web ui on a map as tracks, points etc
  • The android app tracks location and sends the location data to mosquitto, then owntracks recorder get the data from mosquitto and graphically displays them in a webui
  • mTLS is used to authenticate clients, whereas “normal” TLS just authenticates the server. The authentication is now mutual!
  • mTLS is one of the puzzle pieces of building a Zero Trust Network as it strictly controls which clients are allowed to connect to a service regardless of where a user or device is connecting from
  • in the below setup the android app connects with mtls with mosquitto and the browser connects with mtls with the webui through caddy
  • Caddy 2 is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go
  • Recorder also supports tls but I suffered trying to make it work without success as it talks with mosquitto only inside our lan and is basic auth protected I’m still fine with the current setup.

Lets dive in

create directories

mkdir {config,mosquitto,store,store/last,mosquitto/config}

create the certificates for mosquitto and owntracks android app

  • script

nano certs.sh

Paste

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/bash

IP="your_lan_ip"
SUBJECT_CA="/C=SE/ST=Athens/L=Athens/O=ippo/OU=CA/CN=$IP"
SUBJECT_SERVER="/C=SE/ST=Athens/L=Athens/O=ippo/OU=Server/CN=$IP"
SUBJECT_CLIENT="/C=SE/ST=Athens/L=Athens/O=ippo/OU=Client/CN=$IP"

function generate_CA () {
   echo "$SUBJECT_CA"
   openssl req -x509 -nodes -sha256 -newkey rsa:2048 -subj "$SUBJECT_CA"  -days 3650 -keyout ca.key -out ca.crt
}

function generate_server () {
   echo "$SUBJECT_SERVER"
   openssl req -nodes -sha256 -new -subj "$SUBJECT_SERVER" -keyout server.key -out server.csr
   openssl x509 -req -sha256 -in server.csr -CA ca.crt -extfile v3.ext -CAkey ca.key -CAcreateserial -out server.crt -days 3650
}

function generate_client () {
   echo "$SUBJECT_CLIENT"
   openssl req -new -nodes -sha256 -subj "$SUBJECT_CLIENT" -out client.csr -keyout client.key 
   openssl x509 -req -sha256 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 3650
}

generate_CA
generate_server
generate_client
  • Change IP and ST,L,O for ca,server,client crt’s also pick an appropriate -days for them
  • place ext file on the same dir with the script
  • copy ca.crt,server.crt,server.key to mosquitto/config (read next)

ext file for filling S.A.N. field

nano v3.ext

Paste

1
2
3
4
5
6
7
8
9
10
11
12
13
subjectKeyIdentifier   = hash

authorityKeyIdentifier = keyid:always,issuer:always

basicConstraints       = CA:TRUE

keyUsage               = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign

subjectAltName         = DNS:mqtt.example.org

issuerAltName          = issuer:copy


  • Note subjectAltName use your dynamic dns address This is mantatory these will be the allowed domain for this certificate

make the script executable and run it

chmod +x certs.sh

bash certs.sh

generate a pkcs12 bundle out of the client cert

openssl pkcs12 -export -out cert.p12 -inkey client.key -in client.crt -legacy

  • Transfer ca.crt and cert.p12 on android device

cp ca.crt server.crt server.key mosquitto/config

  • Install ca.crt on android>settings>security>encryption>install a certificate>ca certificate
  • Select cert.p12 under owntracks>preferences>connection>security
  • Android can’t handle modern pkcs encryption algorythms (PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256) that is used on openssl v3 . You can omit the -legacy flag if you are creating the pkcs cert with older openssl versions

create the recorder configuration file

nano config/recorder.conf

Paste

1
2
3
4
5
OTR_TOPICS = "owntracks/#"
OTR_HTTPHOST = "0.0.0.0"
OTR_HOST = "your lan ip"
OTR_USER = "user"
OTR_PASS = "pass"

create the mosquitto’s configuration file

nano mosquitto/config/mosquitto.conf

Paste

1
2
3
4
5
6
7
8
9
10
11
12
13
persistence true
persistence_location /mosquitto/data/
listener 1883
password_file /mosquitto/passwd/pass

listener 8883

cafile /mosquitto/config/ca.crt
certfile /mosquitto/config/server.crt
keyfile /mosquitto/config/server.key
require_certificate true
use_identity_as_username true
protocol websockets

create the compose file

nano docker-compose.yml

Paste

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
version: '3'
services:
    mosquitto:
        image: eclipse-mosquitto:openssl
        container_name: mosquitto
        restart: unless-stopped
        ports:
            - "1883:1883"
            - "8883:8883"
        volumes:
            - "./mosquitto/config:/mosquitto/config"
            - "./mosquitto/data:/mosquitto/data"
            - "./mosquitto/config/passwd:/mosquitto/passwd"
        environment:
            - TZ=Europe/Athens
        user: "1000:1000"
    otrecorder:
        image: otr-arm:latest
        ports:
            - 8083:8083
        volumes:
            - ./config:/config
            - ./store:/store
        restart: unless-stopped

create and build the recorder image

The files:

Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
FROM alpine:3.16 AS builder

ARG RECORDER_VERSION=0.9.3
# ARG RECORDER_VERSION=master

RUN apk add --no-cache \
        make \
        gcc \
        git \
        shadow \
        musl-dev \
        curl-dev \
        libconfig-dev \
        mosquitto-dev \
        lmdb-dev \
        libsodium-dev \
        lua5.2-dev \
 util-linux-dev

RUN git clone --branch=${RECORDER_VERSION} https://github.com/owntracks/recorder /src/recorder
WORKDIR /src/recorder

COPY config.mk .
RUN make -j $(nprocs)
RUN make install DESTDIR=/app

FROM alpine:3.16

VOLUME ["/store", "/config"]

RUN apk add --no-cache \
 curl \
 jq \
 libcurl \
 libconfig \
 mosquitto \
 lmdb \
 libsodium \
 lua5.2 \
 util-linux

COPY recorder.conf /config/recorder.conf
COPY JSON.lua /config/JSON.lua
COPY --from=builder /app /

COPY recorder-health.sh /usr/sbin/recorder-health.sh
COPY entrypoint.sh /usr/sbin/entrypoint.sh

RUN chmod +x /usr/sbin/*.sh
RUN chmod +r /config/recorder.conf

# If you absolutely need health-checking, enable the option below.  Keep in
# mind that until https://github.com/systemd/systemd/issues/6432 is resolved,
# using the HEALTHCHECK feature will cause systemd to generate a significant
# amount of spam in the system logs.
# HEALTHCHECK CMD /usr/sbin/recorder-health.sh

EXPOSE 8083

# ENV OTR_CAFILE=/etc/ssl/cert.pem
ENV OTR_STORAGEDIR=/store
ENV OTR_TOPIC="owntracks/#"

ENTRYPOINT ["/usr/sbin/entrypoint.sh"]

config.mk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
CFLAGS += -g -DNS_ENABLE_IPV6

INSTALLDIR = /usr

# Do you want support for MQTT?
WITH_MQTT ?= yes

# Do you want recorder's built-in HTTP REST API?
WITH_HTTP ?= yes

# Do you want recorder support for shared views? Requires WITH_HTTP
WITH_TOURS ?= yes

# Do you want to use reverse-geo caching? (Highly recommended)
WITH_LMDB ?= yes

# Do you have Lua libraries installed and want the Lua hook integration?
WITH_LUA ?= yes

# Do you want support for the `pingping' monitoring feature?
WITH_PING ?= yes

# Do you want support for removing data via the API? (Dangerous)
WITH_KILL ?= no

# Do you want support for payload encryption with libsodium?
# This requires WITH_LMDB to be configured.
WITH_ENCRYPT ?= yes

# Do you want R_only support? (Probably not; this is for Hosted)
# If you set this to `yes', WITH_LMDB will be set to yes
WITH_RONLY ?= no

# Do you require support for OwnTracks Greenwich firmware?
WITH_GREENWICH ?= no

# Where should the recorder store its data? This directory must
# exist and be writeable by recorder (and readable by ocat)
STORAGEDEFAULT = /store

# Where should the recorder find its document root (HTTP)?
DOCROOT = /htdocs

# Define the precision for reverse-geo lookups. The higher
# the number, the more granular reverse-geo will be:
#
# 1	=> 5,009.4km x 4,992.6km
# 2	=> 1,252.3km x 624.1km
# 3	=> 156.5km x 156km
# 4	=> 39.1km x 19.5km
# 5	=> 4.9km x 4.9km
# 6	=> 1.2km x 609.4m
# 7	=> 152.9m x 152.4m
# 8	=> 38.2m x 19m
# 9	=> 4.8m x 4.8m
# 10	=> 1.2m x 59.5cm

GHASHPREC = 7

GEOCODE_TIMEOUT = 4000

# Should the JSON emitted by recorder be indented? (i.e. beautified)
# yes or no
JSON_INDENT ?= no

# Location of optional default configuration file
CONFIGFILE = /config/recorder.conf

# Optionally specify the path to the Mosquitto libs, include here
MOSQUITTO_CFLAGS = `$(PKG_CONFIG) --cflags libmosquitto`
MOSQUITTO_LIBS   = `$(PKG_CONFIG) --libs libmosquitto`

# Debian requires uuid-dev
# RHEL/CentOS needs libuuid-devel
MORELIBS += -luuid # -lssl


# If WITH_LUA is configured, specify compilation and linkage flags
# for Lua either manually or using pkg-config. This may require tweaking,
# and in particular could require you to add the lua+version (e.g lua-5.2)
# to both pkg-config invocations

LUA_CFLAGS = `pkg-config --cflags lua5.2`
LUA_LIBS   = `pkg-config --libs lua5.2`

SODIUM_CFLAGS = `pkg-config --cflags libsodium`
SODIUM_LIBS = `pkg-config --libs libsodium`

# Where will the recorder find the TZ data file?
TZDATADB = /usr/share/owntracks/recorder/timezone16.bin
  • You have to add # Where will the recorder find the TZ data file? TZDATADB = /usr/share/owntracks/recorder/timezone16.bin to config.mk link

Build the Dockerfile (arm only, remember I run my services on a RaspberryPi)

clone the owntracks docker git

git clone https://github.com/owntracks/docker-recorder.git

replace recorder and mosquitto config files with the ones we created:

rm docker-recorder/recorder.conf docker-recorder/mosquitto.conf

and

cp config/recorder.conf docker-recorder/recorder.conf

mosquitto/config/mosquitto.conf docker-recorder/mosquitto.conf

name and build the dockerfile:

docker build -t otr-arm -f docker-recorder/Dockerfile .

  • owntracks recorder publishes x86 images on dockerhub but there are no official ARM images
  • mosquitto publishes images for all aarch’s

run the compose file

docker compose up

  • Comment password_file on mosquitto/config/mosquitto.conf on first run then run mosquitto_passwd command as user root on the mosquitto container and finaly uncomment password_file and re-run docker compose up

create user/pass for recorder in mosquitto

docker exec -it --user root mosquitto mosquitto_passwd -c /mosquitto/passwd/pass username

  • now you can run the container detached

docker compose up -d

android app settings

Connection:

  • mode mqtt

  • host mqtt.example.org

  • Port 8883 (open port on router) Client ID random name

  • Websockets toggle enabled

Identification:

  • Username random name (will be displayed on recorder ui)

  • Password empty

  • Device ID random name

  • Tracker ID random name

Security:

  • TLS enabled

  • select client cert under preferences>connection>security

  • CA cert empty (installed the ca.crt in the device user store)

access the webui

  • Configure caddy with reverse proxy and mutual TLS (mtls) for the proxied service and install the client certificate on android

Create certs

  • Request a new key and crt

openssl req -x509 -newkey rsa:4096 -keyout cert_name.key -out cert_name.crt -days 365

  • Request a new certificate signing request

openssl req -new -key cert_name.key -out cert_name.CSR

  • Request a new certificate authority

openssl x509 -req -days 365 -in cert_name.csr -signkey cert_name.key -out cert_name-CA.crt

  • Create a pem certificate

cat cert_name.crt cert_name.key > cert_name.pem

  • Create a pkcs12 certificate

openssl pkcs12 -export -out cert_name.p12 -inkey cert_name.key -in cert_name.pem -legacy

  • On android you have to install the cert_name.p12 cert as vpn & app user certificate under settings > security > more > credentials > install > VPN & app user cert
  • copy cert_name-CA.crt and cert_name.crt to /var/lib/caddy/cert/

Add the certificate directive to caddyfile and reverse proxy the recorder webui subdomain to the running port it is running on localhost

nano /etc/caddy/Caddyfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#cert directive
(fancy_name) {
  tls {
    client_auth {
      mode require_and_verify
      trusted_ca_cert_file /var/lib/caddy/cert/cert_name-CA.crt
      trusted_leaf_cert_file /var/lib/caddy/cert/cert_name.crt
    }
  }
}

#owntracks
owntracks.example.org {
import fancy_name
reverse_proxy localhost:8083
}
  • You can view your location history visiting owntracks.example.org

domains

  • We used two domains One for publishing mqtt location messages from android to mosquitto (mqtt.example.org) And one for accessing the recorder webui with our browser (owntracks.e.ample.org)
  • You can use any dynamic dns provider to create your free subdomains You’ll also need a cron scrpt to regullary update that domain to point to you actually public ip

certificates

  • We installed two certificates on the android certificate store
  • the certificate authority for the mosquitto client cert ca.crt
  • the caddy client certificate cert_name.p12
  • we selected the mosquitto client cert from within the owntracks app cert.p12
  • we created two certificate authorities. The caddy directive “fancy_name” can be imported for other services that you reverse proxy with caddy also

Folder structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ls -R
.:
config  docker-compose.yml  mosquitto  store

./config:
recorder.conf

./mosquitto:
config  data

./mosquitto/config:
ca.crt  mosquitto.conf  passwd  server.crt  server.key

./mosquitto/config/passwd:
pass

./mosquitto/data:

./store:
ghash  last  monitor  rec

./store/ghash:
data.mdb  lock.mdb

Additional config

  • reverse geocode

reverse geocoding is getting street/places names from coordinates Owntracks supports opencage as provider

To set it up

Add your opencage api key to entrypoint.sh

1
2
3
4
5
6
7
#!/bin/sh

if ! [ -f ${OTR_STORAGEDIR}/ghash/data.mdb ]; then
    ot-recorder --initialize
fi

ot-recorder ${OTR_TOPIC}  --geokey "opencage:xxxxxxxxxx"

In config/recorder.conf add:

OTR_GEOKEY = "opencage:xxxxxxccccccccccc"

And in the recoders service in compose add:

1
2
environment:
            - OTR_GEOKEY = "opencage:xxxxxxxxxxxxxxxxx"

rebuild your Dockerfile and rerun compose file

Owntracks is caching the results of reverse geocoding and is feeding back the last (possibly unsuccessful) cached entry instead of going out a fresh to OpenCage

So

Use a mock lockation provider to feed device false GPS data in order to start seeing reverse geocode in action in the recorder

  • Add an image to a friend/family icon

image2card.sh script:

https://github.com/owntracks/recorder/blob/master/contrib/faces/image2card.sh

Run:

image2card.sh image.jpg username > mosquitto/config/user.json

and

1
docker exec -it mosquitto mosquitto_pub  -d -h mqtt.example.org -p 8883 --cafile /mosquitto/config/ca.crt --cert /mosquitto/config/server.crt --key /mosquitto/config/server.key -t owntracks/username/device_id/info -f user.json -r
  • Restore conflicting Frieds faces
1
docker exec -it mosquitto mosquitto_pub  -d -h mqtt.example.org -p 8883 --cafile /mosquitto/config/ca.crt --cert /mosquitto/config/server.crt --key /mosquitto/config/server.key -t owntracks/user/device_id/info -r -n

and

rm -rf store/cards

then recreate cards

sources

this is just a summ up with countless trial and error’s from varius sources

one two three four five

comments powered by Disqus