Ιστορικό τοποθεσίας με το owntracks με mtls auth

Περί τίνος πρόκειται;

Παρακολούθηση του ιστορικου τοποθεσίας τόσο τις δική σας όσο και της οικογένειας/φίλων σας και εμφάνιση του ιστορικου τοποθεσιών στο χάρτη. Μια εναλλακτική λύση ανοιχτού κώδικα για το Google maps history

  • Το Owntracks είναι μια εφαρμογή παρακολούθησης ιστορικού τοποθεσίας
  • Το Mosquitto είναι ένας brocker μηνυμάτων δημοσίευσης/εγγραφής πρωτόκολλου mqtt, ο οποιος αποθηκεύει δεδομένα που λαμβάνει από πελάτες
  • Το Recorder είναι ένα ελαφρύ πρόγραμμα για την αποθήκευση και πρόσβαση σε δεδομένα τοποθεσίας που δημοσιεύονται μέσω MQTT (ή HTTP) και τα εμφανίζει σε μια διαδικτυακή διεπαφή χρήστη σε έναν χάρτη ως ίχνη, σημεία κ.λπ.
  • Η εφαρμογή Android παρακολουθεί την τοποθεσία και στέλνει τα δεδομένα τοποθεσίας στο mosquitto, στη συνέχεια ο recorderλαμβάνει τα δεδομένα από το mosquitto και τα εμφανίζει γραφικά σε ένα webui
  • Το mTLS χρησιμοποιείται για τον έλεγχο ταυτότητας πελατών, ενώ το “κανονικό” TLS απλώς ελέγχει την ταυτότητα του διακομιστή. Ο έλεγχος ταυτότητας είναι πλέον αμοιβαίος!
  • Το mTLS είναι ένα από τα κομμάτια του παζλ για τη δημιουργία ενός Δικτύου Μηδενικής Εμπιστοσύνης καθώς ελέγχει αυστηρά ποιους πελάτες επιτρέπεται να συνδέονται σε μια υπηρεσία ανεξάρτητα από το από πού συνδέεται ένας χρήστης ή συσκευή
  • στις παρακάτω ρυθμίσεις η εφαρμογή android συνδέεται με mtls με το mosquitto και το πρόγραμμα περιήγησης συνδέεται με mtls με το webui μέσω του caddy
  • Ο Caddy 2 είναι ένας ισχυρός διακομιστής ιστού ανοιχτού κώδικα με αυτόματο HTTPS γραμμένος σε Go
  • Το Recorder υποστηρίζει επίσης tls, αλλά υπέφερα προσπαθώντας να το κάνω να λειτουργήσει χωρίς επιτυχία, καθώς η επικοινωνία με το mosquitto πραγματοποιήται μόνο μέσα στο LAN μας και είναι προστατευμένο με basic auth, είμαι ok με την συγκεκριμένο setup

Δημιουργήστε τα πιστοποιητικά για το mosquitto και την εφαρμογή android

  • script

nano certs.sh

Paste

#!/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
  • Αντικατέστησε τα IP και ST,L,O για τα ca,server,client crt επίσης διάλεξε μια επιθυμητή διαρκεια -days για αυτα
  • το αρχείο ext πρεπει να βρίσκεται στο ίδιο dir με το script
  • Αντέγραψε τα ca.crt,server.crt,server.key στο mosquitto/config

Το αρχείο ext για τη συμπλήρωση του πεδίου S.A.N.

nano v3.ext

Paste

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

  • Στο πεδίο subjectAltName βάλε την dynamic dns διεύθυνση απο την οποία θα σύνδεσε στο mosquitto
  • Αυτο ειναι θεμελιώδες μιας και μονο Οσα domain αναγράφονται σε αυτο το πεδίο θα μπορούν να κανουν χρηση του πιστοποιητικού

Κάνε το script εκτελέσιμο και τρέξε το

chmod +x certs.sh

bash certs.sh

Δημιούργησε ενα pkcs12 bundle απο το client cert

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

  • Μετέφερε τα ca.crt και cert.p12 στην συακευη android
  • Εγκατεστησε το ca.crt απο android>settings>security>encryption>install a certificate>ca certificate
  • Επέλεξε το cert.p12 απο owntracks>preferences>connection>security
  • Το Android δεν μπορεί να διαχειριστεί τους μοντέρνους pkcs encryption αλγορίθμους (PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256) οι οποίοι χρησιμοποιούνται απο το openssl v3 . Μπορεις να παραλήψεις το -legacy flag αν θα δημιουργήσεις τα πιστοποιητικά με παλαιότερη έκδοση openssl

Δημιουργία φακέλων και αρχείων

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

Δημιουργία του confoguration αρχείου του recorder

nano config/recorder.conf

Paste

OTR_TOPICS = "owntracks/#"
OTR_HTTPHOST = "0.0.0.0"
OTR_HOST = "your lan ip"
OTR_USER = "user"
OTR_PASS = "pass"

Δημιουργία του confoguration αρχείου του mosquitto

nano mosquitto/config/mosquitto.conf

Paste

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

Δημιουργία του αρχειου compose

nano docker-compose.yml

Paste

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: ot-arm:latest
        ports:
            - 8083:8083
        volumes:
            - ./config:/config
            - ./store:/store
        restart: unless-stopped

Δημιουργία και build του recorder image

nano Dockerfile

Paste

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"]
  • Το owntracks recorder δημοσιεύει x86 images στο dockerhub ομως δεν υπάρχει official ARM image
  • Το mosquitto δημοσιεύει images γιανολα τα aarch

Δημιουργία και ονομασια του recorder image

docker build -t ot-arm

Τρεχούμενα το compose file

docker compose up

  • Κανουμε Comment το password_file στο mosquitto/config/mosquitto.conf στην πρώτη εκτέλεση και εκτελούμε την εντολή mosquitto_passwd ως χρήστης root στο mosquitto container και έπειτα κάνουμε uncomment το password_file και ξανατρεχουμε docker compose up

Δημιουργία user/pass για τον recorder στο mosquitto

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

  • Τώρα μπορούμε να τρέξουμε το conatainer detached

docker compose up -d

android app settings

Connection:

  • mode mqtt

  • host mqtt.example.org

  • Port 8883 (ανοιχτή πόρτα στο router)

  • Client ID random name

  • Ενεργοποιω το διακόπτη Websockets

Identification:

  • Username random name

  • Password κενό

  • Device ID random name (μαζί με το username εμφανιζεται ως username/deviceID στη λίστα με τα tracks στον recorder)

  • Tracker ID random name (Συνηθος δυο γραμματα. Εμφανιζεται ως εικονίδιο ατο χαρτη)

Security:

  • Διακοπτης TLS ενεργός

  • Επιλέγω το client cert απο preferences>connection>security

  • CA cert κενό (εγκαθιστω το ca.crt στο user store της συακευης)

Πρόσβαση στο webui

  • Ρύθμιση το caddy για reverse proxy and mutual TLS proxied υπηρεσίες και εγκαθιστω το client cert του recorder στο android

Δημιουργία των certs

  • Αίτηση ενος νεου key and crt

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

  • Αίτηση ενος νεου certificate signing request

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

  • Αίτηση ενος νεου certificate authority

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

  • Δημιουργία pem certificate

cat cert_name.crt cert_name.key > cert_name.pem

  • Δημιουργια pkcs12 certificate

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

  • Στο android πρεπει να εγκαταστήσεις το cert_name.p12 cert ως vpn & app user certificate στο settings > security > more > credentials > install > VPN & app user cert
  • Μη ξεχάσεις να αντιγραψεις τα cert_name-CA.crt και cert_name.crt στο /var/lib/caddy/cert/

Προσθήκη του certificate directive στο caddyfile και reverse proxy το subdomain του webui του recorder στην πόρτα που αυτός τρέχει (8083) στη localhost

nano /etc/caddy/Caddyfile

#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
}
  • Μπορείς να δεις το ιστορικό τοποθεσίας επισκεπτοντας την owntracks.example.org

domains

  • Χρησιμοποιήσαμε δυο domains
  • Ενα για τη δημοσίευση mqtt messages τοποθεαιας απο το android στο mosquitto (mqtt.example.org)
  • και ενα για την πρόσβαση στο webui του recorder απο τον browser (owntracks.example.org)

certificates

  • Εγκαταστησαμε δυο πιστοποιητικά στο android certificate store
  • Το certificate authority για το client cert του mosquitto (ca.crt )
  • Το client certificate του caddy (cert_name.p12)
  • Επιλέξαμε το mosquitto client cert μεαα απο την εφαρμογή owntracks (cert.p12)
  • Δημιουργησαμε δυο certificate authorities.
  • Το caddy directive “fancy_name” μπορουμε να το χρησιμοποιείσουμε και σε αλλες υπηρεσίες τις οποίες κάνουμε reverse proxy με το caddy εισάγοντας “import fancy_name” στο reverse proxy directive

Δομή των φακέλων

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

Πηγες

Τα παραπανω ειναι μια συραφή και προσαρμογή απο διάφορες πηγές

one

two

three

four

five

4 «Μου αρέσει»

Πολύ ενδιαφέρον και ειδικά ο τρόπος υλοποίησης και ευχαριστούμε. Αλλά ο τρόπος υλοπίησης παρότι ενδιαφέρον μου φαίνεται κάπως υπερβολικός, οπότε έχω κάτι απορίες. Δεδομένου, για παράδειγμα, οτι μπορούμε να πάρουμε εύκολα τις συντεταγμένες της τοποθεσίας απ’το android μέσω termux με την εντολή termux-location και δεδομένου οτι οι συντεταγμένες είνα δυο αριθμοί που θα μπορούσαν να αποθηκευτούν εύκολα σε πχ ένα csv (και να πάρουν και time stamp αν θέλουμε), δε θα ήταν πιο εύκολο να ακολουθήσουμε ένα τέτοιο μονοπάτι για να κάνουμε την απεικόνηση σε χάρτη; Θέλω να πω, θα ήθελα να μάθω περισσότερα για το σκεπτικό που επιλέχθηκε αυτή η υλοποίηση γιατί έχω την αίσθηση οτι μου διαφεύγει κάτι σημαντικό :smile:

1 «Μου αρέσει»

Το owntracks τρέχει στο παρασκήνιο και περιμένει το λειτουργικό να του πει οτι μετακινήθηκε ,. Μόνο τότε στέλνει στον σερβερ την τοποθεσία (μαζί με την στάθμη της μπαταρίας την ένταση του σήματος κινητής και του wifi και την χιλιομετρική ταχύτητα )

Στο termux πρέπει να τρέξεις την εφαρμογή στο προσκήνιο και να ζητάς περιοδικά πχ κάθε 300 δευτερόλεπτα τοποθεσία
Όλα έχουν να κάνουν πρώτιστα με την κατανάλωση της μπαταρίας

https://owntracks.org/booklet/guide/whathow/

4 «Μου αρέσει»

Εξαιρετικά, ευχαριστώ. Όντως μου διέφευγε κάτι σημαντικό…