HedgeDoc bauen

Für dieses Projekt braucht man yarn, da npm nicht funktioniert. Unter Debian ist yarn in dem Paket yarnpkg und das Programm heißt auch nicht yarn, sondern yarnpkg.

% git clone https://github.com/hedgedoc/hedgedoc
% yarn install

Als erstes Module entfernen, die man nicht nutzt. In der config.json muss man für viele Dienste Anmeldedaten eingeben und wenn man diese nicht hat, braucht man den Code dazu auch nicht. In der package.json sieht man, welche Module alles geladen werden.

% yarn remove aws-sdk azure-storage imgur mattermost minio mysql2 \
  @passport-next/passport-openid passport-bitbucket-oauth2 \
  passport-dropbox-oauth2 passport-facebook passport-github passport-gitlab2 \
  passport-google-oauth20 passport-ldapauth passport-oauth2 passport-saml \
  passport-twitter socket.io-client sqlite3

Jetzt die Anwendung bauen und danach noch einmal alle Module laden, die für den Einsatz notwendig sind, und alles zusammen in ein Archiv packen.

% yarn run build
% rm -fr node_modules && yarn install --link-duplicates --production

% tar cf /tmp/hedgedoc.tar --owner root --group root app.js package.json \
  config.json.example bin/cleanup lib/**/*.js locales/*.json \
  --exclude=public/.eslintrc.js public \
  node_modules/**/*.(js|json) \
  node_modules/bufferutil/prebuilds/linux-x64/node.napi.node \
  node_modules/utf-8-validate/prebuilds/linux-x64/node.napi.node \
  node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs

Am Ende die package.json auf den ursprünglichen Stand zurücksetzen.

% git checkout -- package.json

HedgeDoc einrichten

# mkdir /srv/www/n.jo-so.de
# adduser --system --shell /usr/sbin/nologin --home /srv/www/n.jo-so.de \
  --disabled-password --disabled-login hedgedoc
# tar xf hedgedoc.tar -C ~hedgedoc
# mkdir ~hedgedoc/tmp ~hedgedoc/.cache
# chown hedgedoc ~hedgedoc/public/uploads ~hedgedoc/tmp ~hedgedoc/.cache
# sudo -u postgres sh -c 'createuser hedgedoc \
  && createdb -l C.UTF-8 -O hedgedoc -T template0 notes "hedgedoc @ n.jo-so.de"'

Konfiguration

Konfiguriert wird HedgeDoc über die Datei config.json, für die config.json.example als Vorlage genutzt werden kann. Wie eingangs schon erwähnt, gibt es viele externe Dienste, auf die HedgeDoc zugreifen kann, aber man muss sie nicht nutzen. Die Abschnitte der Dienste, die man nicht nutzt, kann man löschen.

Für den Zugriff auf die Datenbank nutze ich den Unix-Socket, weshalb host ein Pfad ist. Ebenso lasse ich HedgeDoc nicht auf einem Netzwerkanschluss lauschen, sondern nutze einen Unix-Socket, auf den dann Nginx zugreift. Dementsprechend brauche ich kein SSL, aber für Cookies muss die SSL-Option gesetzt werden (protocolUseSSL). Die Header für HSTS und CSP setze ich im Nginx, weshalb ich sie in HedgeDoc deaktiviert habe.

Mit der Einstellung allowFreeURL ist es den Nutzern freigestellt, welche URL sie wählen. Dafür braucht man nur die entsprechende URL aufrufen und wenn sie neu ist, legt HedgeDoc ein neues Dokument an.

{
    "production": {
        "path": "/run/hedgedoc/socket",

        "domain": "n.jo-so.de",
        "allowOrigin": ["n.jo-so.de"],
        "useSSL": false,
        "protocolUseSSL": true,
        "useCDN": false,
        "sessionSecret": "mit `pwgen -s 64 1` erzeugen",
        "linkifyHeaderStyle": "gfm",
        "-": "everyone is allowed to choose the path of the URL",
        "allowFreeURL": true,

        "db": {
            "database": "notes",
            "host": "/run/postgresql",
            "port": 5432,
            "dialect": "postgres"
        }
        "hsts": {
            "enable": false
        },
        "csp": {
            "enable": false,
        },
    }
}

Da HedgeDoc beim Start die Datenbank aktualisiert und dabei unter Umständen den externen Prozess bin/cleanup aufruft, ist es besser, das Programm einmal von Hand und ohne AppArmor zu starten.

# sudo -u hedgedoc NODE_ENV=production node app.js

Systemd

Dass der Prozess sich nur in gewissen Grenzen im System bewegen kann, lässt sich mit einem AppArmor-Profil /etc/apparmor.d/hedgedoc lösen. Viele Einschränkungen habe ich der offiziellen Systemd-Unit entnommen, aber Einschränkungen der Dateizugriffe lassen sich besser über AppArmor regulieren.

Die Nutzung des Unix-Socket hat somit auch den Vorteil, dass der Prozess nun keine Netzwerkverbindung mehr nach außerhalb aufbauen kann. Sollte also eine Einbruch erfolgen, hat der Angreifer keinen Zugriff auf das Internet.

[Unit]
Description=Application service for collaborative markdown notes
Documentation=https://demo.hedgedoc.org/
After=network.target postgresql.service

[Service]
Environment=NODE_ENV=production
# Workaround, because phantomjs crashes with libssl_conf.so not found
# https://stackoverflow.com/questions/53355217/genymotion-throws-libssl-conf-so-cannot-open-shared-object-file-no-such-file-o
Environment=OPENSSL_CONF=/dev/null
ExecStart=/usr/bin/node app.js
SyslogIdentifier=hedgedoc
User=hedgedoc
WorkingDirectory=/srv/www/n.jo-so.de
RuntimeDirectory=hedgedoc

AppArmorProfile=hedgedoc

# Common security settings https://jo-so.de/2021-05/H%C3%A4rtung-Systemd.html
CapabilityBoundingSet=

LockPersonality=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true

PrivateDevices=true
PrivateTmp=true
ProtectClock=true
ProtectControlGroups=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectSystem=strict

# Make sure that the process can only see PIDs and process details of itself,
# and the second option disables seeing details of things like system load and
# I/O etc
ProtectProc=invisible
ProcSubset=pid

RemoveIPC=true
RestrictAddressFamilies=AF_UNIX
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true

SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources @obsolete
# end of common security settings

# https://docs.hedgedoc.org/setup/manual-setup/#systemd-unit-example
MemoryDenyWriteExecute=false

[Install]
WantedBy=multi-user.target

Nginx

# used by the websocket connection
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 http2 ssl;
    listen [2a03:4000:8:213::4]:443 http2 ssl;

    server_name n.jo-so.de;

    ssl_certificate /etc/letsencrypt/live/n.jo-so.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/n.jo-so.de/privkey.pem;

    add_header Strict-Transport-Security "max-age=15552000; includeSubdomains; preload";
    include /etc/nginx/conf.d/sec-headers.conf;

    access_log /var/log/nginx/n.jo-so_access.log;
    error_log /var/log/nginx/n.jo-so_error.log info;

    root /srv/www/$server_name/public;

    if ($host != $server_name) {
        # redirect permanent all requests to HTTPS
        return 301 https://$server_name$request_uri;
    }

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri @codimd;
    }

    location @codimd {
        proxy_pass http://unix:/run/hedgedoc/socket;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location = /socket.io/ {
        # in case of polling this would flood the log file
        access_log off;

        proxy_pass http://unix:/run/hedgedoc/socket;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;

        # support websocket
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

Datenbank

Dies hier war ein Versuch, die Rechte des Nutzers für die Datenbank einzuschränken, damit zum Beispiel kein DROP TABLE funktioniert. Aber dies ist nicht möglich, da der ORM Administratorzugriff auf die Datenbank verlangt.

https://dba.stackexchange.com/questions/33943/granting-access-to-all-tables-for-a-user#33960

\set ON_ERROR_STOP on

CREATE DATABASE notes ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C'
 template=template0;
COMMENT ON DATABASE notes IS 'hedgedoc @ n.jo-so.de';

\c notes

REVOKE CONNECT ON DATABASE notes FROM PUBLIC;
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM PUBLIC;

CREATE ROLE hedgedoc WITH LOGIN;

GRANT CONNECT ON DATABASE notes TO hedgedoc;
ALTER DEFAULT PRIVILEGES FOR USER hedgedoc IN SCHEMA public
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO hedgedoc;

Windows

npm install -g windows-build-tools
npm install
npm run build
node app.js

Impressum

public/docs/impressum.md und edit public/views/hedgedoc/header.ejs

HedgeDoc soll kein Wiki werden

https://github.com/hackmdio/codimd/issues/969