40 Commits

Author SHA1 Message Date
7ca468a1ac Update CHANGELOG with new version number 2021-05-19 16:10:11 +01:00
26e1e6559a docs: Add upgrade guide 2021-05-19 16:02:39 +01:00
1bc336d933 Bump prosody-modules to 8e58a1b78336 2021-05-17 21:22:54 +01:00
a948cc141f Install latest prosody from deb, factor out variables 2021-05-17 21:22:01 +01:00
105c04a2c7 Add unreleased items to changelog 2021-05-17 18:27:24 +01:00
3cab4faaf9 prosody: Allow specifying additional config include path via environment 2021-03-17 15:22:30 +00:00
bd5329c84d prosody: Allow specification of external TURN via environment 2021-03-17 15:16:27 +00:00
088d9cb840 Add bootstrapped admin accounts to the default group/circle (thanks jonas!) 2021-03-16 14:00:10 +00:00
2bee41057b prosody: bump prosody-modules rev to latest (no particular reason) 2021-03-16 07:18:03 +00:00
173ca8b0c9 Add lua-unbound for more robust DNS resolution 2021-03-15 19:55:33 +00:00
7f26c50ba8 prosody: Add mod_http_host_status_check 2021-03-10 14:37:19 +00:00
32a7bc8954 mod_invites_bootstrap: Fix parameters to store:set() 2021-03-10 12:37:41 +00:00
e9e5762936 mod_invites_bootstrap: Reject old index values 2021-03-10 12:13:11 +00:00
90b13aed6e prosody: Fix another typo, sigh 2021-03-10 12:12:15 +00:00
b2431438b2 mod_invites_bootstrap: API and behaviour improvements 2021-03-10 11:49:18 +00:00
dcc02d374b prosody: Fix typo in module name 2021-03-10 10:32:06 +00:00
13228b1fc3 Some whitespace fixes 2021-03-09 16:33:22 +00:00
36ffd5d4a0 mod_invites_bootstrap: Module/API to create initial admin invite at startup 2021-03-09 16:33:12 +00:00
c4a8a88028 docker: Remove obsolete letsencrypt things (moved to cert-manager) 2021-03-09 15:28:09 +00:00
43c244d55d Merge pull request #34 from resoli/patch-2
Updated reverse_proxy.md Apace section.
2021-03-06 08:20:34 +00:00
19dc32e3e8 Update reverse_proxy.md 2021-03-06 09:12:31 +01:00
7b37c629dd Merge pull request #33 from resoli/patch-1
Add Apache configuration snippet
2021-03-01 21:21:53 +00:00
087b02ca5a Add Apache configuration snippet 2021-03-01 21:28:20 +01:00
724335019e prosody, coturn: Add environment variable to disable TURN server 2021-02-24 14:36:06 +00:00
3c3a74f1cc prosody: Bump prosody-modules to 6d595857164a 2021-02-23 21:00:18 +00:00
c6aa7a9732 prosody: Bump prosody-modules to 38bd4d557413 2021-02-23 16:45:36 +00:00
2ad719122d Add docker healthcheck for docker ps output
Mostly to improve the docker ui, ie it will say "unhealthy" if it takes
too long to start e.g. waiting for certs, or if Prosody crashes without
getting restarted.

Probes the http port on the assumption that this means Prosody is up and
running.

Signed-off-by: Matthew Wild <mwild1@gmail.com>
2021-02-23 15:27:12 +00:00
a9ee76b2f1 Bump prosody-modules to ea820de69265 2021-02-23 15:26:00 +00:00
e881c983a6 prosody: Bump to latest prosody-modules 2021-02-22 13:18:42 +00:00
0224f93843 prosody: Allow custom HTTP bind interface 2021-02-17 13:28:30 +00:00
67f4ffa83f Add changelog entry for beta.20210205 2021-02-05 13:50:42 +00:00
5f8f5657b5 Merge pull request #22 from horazont/feature/bookmarks
... load the module, too
2021-02-04 15:57:40 +00:00
f197b9bf6b Also explicitly load mod_groups_internal because why not 2021-02-04 16:57:14 +01:00
04861cc023 ... load the module, too 2021-02-04 16:55:11 +01:00
e963012ac1 Merge pull request #20 from horazont/feature/bookmarks
Import fixes from prosody-modules
2021-02-04 15:22:06 +00:00
5e74fba75d Import fixes from prosody-modules
- Fix room destruction of circle rooms when circle is deleted or
  fails to be created (fixes #17)
- Add circle rooms to bookmarks of (newly added) members
  (fixes #16)
2021-02-04 16:18:47 +01:00
c3144380de Merge pull request #19 from horazont/feature/invite-url-suffix
Add `/` to invite URL
2021-02-03 18:40:47 +00:00
215426c2db Add / to invite URL
The new portal version will support this (see [1]). The rationale
is also explained over there, but to summarize: If the link ends
in `_` or `-`, some user agents will not linkify it correctly,
worsening the UX.

   [1]: https://github.com/snikket-im/snikket-web-portal/issues/48
2021-02-03 19:02:56 +01:00
84f55744f1 CHANGELOG: Add --remove-orphans to docker-compose command 2021-02-02 20:59:01 +00:00
3e62edfbe9 Add docs for update notifications 2021-02-02 14:12:00 +00:00
12 changed files with 355 additions and 28 deletions

View File

@ -1,5 +1,22 @@
# Snikket Server changelog
## beta.20210519
- Allow custom HTTP bind interface
- Add docker health checks
- Fix warnings about obsolete letsencrypt user
- Add bootstrap API to create initiatal invite in an automated way
- Switch to libunbound for DNS resolution (more robust)
- Add environment variables to disable/replace the built-in TURN service
## beta.20210205
- Fix destruction of circle group chats when a circle
is deleted or fails to be created
- Add circle group chats to bookmarks of newly-added members
- Add trailing '/' to invite URLs for compatibility with some
URL parsers
## beta.20210202
- Support for Raspberry Pi and other ARM-based systems
@ -22,7 +39,7 @@ then put the new one in its place. For example:
mv docker-compose.yml docker-compose.old.yml
wget -O docker-compose.yml https://snikket.org/service/resources/docker-compose.beta.yml
docker-compose pull
docker-compose up -d
docker-compose up -d --remove-orphans
```
You may also want to check out our new repository of scripts to help

View File

@ -1,5 +1,10 @@
#!/bin/sh
if [ "$SNIKKET_TWEAK_TURNSERVER" = "0" ]; then
echo "TURN server disabled by environment, not launching.";
exit 0;
fi
CERTFILE="/snikket/letsencrypt/live/$SNIKKET_DOMAIN/fullchain.pem";
KEYFILE="/snikket/letsencrypt/live/$SNIKKET_DOMAIN/privkey.pem";
@ -11,7 +16,6 @@ done
TURN_EXTERNAL_IP="$(snikket-turn-addresses "$SNIKKET_DOMAIN")"
exec /usr/bin/turnserver -c /etc/turnserver.conf --prod \
--static-auth-secret="$(cat /snikket/prosody/turn-auth-secret-v2)" \
--cert="$CERTFILE" --pkey "$KEYFILE" -r "$SNIKKET_DOMAIN" \

View File

@ -61,6 +61,7 @@ modules_enabled = {
-- HTTP modules
"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
"websocket"; -- XMPP over WebSockets
"http_host_status_check"; -- Health checks over HTTP
-- Other specific functionality
"limits"; -- Enable bandwidth limiting for XMPP connections
@ -95,11 +96,14 @@ modules_enabled = {
"invites_register_api";
"invites_tracking";
"invites_default_group";
"invites_bootstrap";
"firewall";
-- Circles
"groups_internal";
"groups_migration";
"groups_muc_bookmarks";
-- For the web portal
"http_oauth2";
@ -113,7 +117,7 @@ registration_notification = "New user registered: $username"
reload_global_modules = { "http" }
http_ports = { ENV_SNIKKET_TWEAK_INTERNAL_HTTP_PORT or 5280 }
http_interfaces = { "127.0.0.1" }
http_interfaces = { ENV_SNIKKET_TWEAK_INTERNAL_HTTP_INTERFACE or "127.0.0.1" }
https_ports = {};
@ -128,9 +132,12 @@ registration_invite_only = true
-- over what happens when a user invites someone.
allow_contact_invites = false
invites_page = ENV_SNIKKET_INVITE_URL or ("https://"..DOMAIN.."/invite/{invite.token}");
invites_page = ENV_SNIKKET_INVITE_URL or ("https://"..DOMAIN.."/invite/{invite.token}/");
invites_page_external = true
invites_bootstrap_index = tonumber(ENV_TWEAK_SNIKKET_BOOTSTRAP_INDEX)
invites_bootstrap_secret = ENV_TWEAK_SNIKKET_BOOTSTRAP_SECRET
c2s_require_encryption = true
s2s_require_encryption = true
s2s_secure_auth = true
@ -173,8 +180,10 @@ http_host = DOMAIN
http_external_url = "https://"..DOMAIN.."/"
http_max_content_size = 1024 * 1024 * 16 -- 16MB
turncredentials_host = DOMAIN
turncredentials_secret = assert(io.open("/snikket/prosody/turn-auth-secret-v2")):read("*l");
if ENV_SNIKKET_TWEAK_TURNSERVER ~= "0" or ENV_SNIKKET_TWEAK_TURNSERVER_DOMAIN then
turncredentials_host = ENV_SNIKKET_TWEAK_TURNSERVER_DOMAIN or DOMAIN
turncredentials_secret = ENV_SNIKKET_TWEAK_TURNSERVER_SECRET or assert(io.open("/snikket/prosody/turn-auth-secret-v2")):read("*l");
end
VirtualHost (DOMAIN)
authentication = "internal_hashed"
@ -232,4 +241,4 @@ Component ("share."..DOMAIN) "http_upload"
http_upload_file_size_limit = 1024 * 1024 * 16 -- 16MB
http_upload_expire_after = 60 * 60 * 24 * RETENTION_DAYS -- N days
Include "/snikket/prosody/*.cfg.lua"
Include (ENV_SNIKKET_TWEAK_EXTRA_CONFIG or "/snikket/prosody/*.cfg.lua")

View File

@ -27,7 +27,8 @@ umask=002
[program:coturn]
command=start-coturn.sh
startsecs=0
autorestart=true
autorestart=unexpected
exitcodes=0
stopwaitsecs=30
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
@ -41,4 +42,3 @@ stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true
umask=002

View File

@ -4,6 +4,12 @@
- hosts: all
become: yes
gather_facts: no
vars:
prosody:
package: "prosody-trunk"
build: "1436"
prosody_modules:
revision: "8e58a1b78336"
tasks:
- import_tasks: tasks/prosody.yml
- import_tasks: tasks/supervisor.yml

View File

@ -11,9 +11,12 @@
- name: "Add Prosody package repo"
apt_repository:
repo: "deb https://packages.prosody.im/debian buster main"
- name: "Detect dpkg architecture name"
shell: dpkg --print-architecture
register: dpkg_arch
- name: "Install Prosody package"
apt:
name: prosody-trunk
deb: "https://packages.prosody.im/debian/pool/main/p/{{ prosody.package }}/{{ prosody.package }}_1nightly{{ prosody.build }}-1~buster_{{ dpkg_arch.stdout }}.deb"
state: present
install_recommends: yes
- name: "Deploy Prosody config"
@ -64,10 +67,10 @@
hg:
repo: https://hg.prosody.im/prosody-modules
dest: /usr/local/lib/prosody-modules
revision: 94805a7e7b30
revision: "{{ prosody_modules.revision }}"
purge: yes
update: yes
- name: Enable wanted modules
- name: Enable wanted modules (prosody-modules)
file:
state: link
src: "/usr/local/lib/prosody-modules/{{item}}"
@ -109,10 +112,12 @@
- mod_invites_register_api
- mod_invites_tracking
- mod_groups_internal
- mod_groups_muc_bookmarks
- mod_muc_defaults
- mod_muc_local_only
- mod_http_host_status_check
- name: Enable wanted modules
- name: Enable wanted modules (snikket-modules)
file:
state: link
src: "/usr/local/lib/snikket-modules/{{item}}"
@ -121,9 +126,51 @@
- mod_update_check
- mod_update_notify
- mod_invites_default_group
- mod_invites_bootstrap
- name: "Install lua-ossl for encrypted push notifications"
apt:
name: lua-luaossl
state: present
install_recommends: no
- name: "Fetch luaunbound source"
get_url:
url: https://code.zash.se/dl/luaunbound/luaunbound-0.5.tar.gz
sha256sum: a6564ac1cca6bb350576eb2a5cfa03adb0aafd4f99d6cd491bd8028d046c62a7
dest: /tmp/luaunbound-0.5.tar.gz
- name: "Extract luaunbound"
unarchive:
src: /tmp/luaunbound-0.5.tar.gz
remote_src: yes
dest: /tmp
- name: "Install libunbound-dev"
apt:
name:
- libunbound8
- libunbound-dev
- liblua5.2-dev
state: present
- name: "Build luaunbound"
make:
chdir: /tmp/luaunbound-0.5
- name: "Install luaunbound"
make:
chdir: /tmp/luaunbound-0.5
target: install
- name: "Remove luaunbound source"
file:
path: /tmp/luaunbound-0.5
state: absent
- name: "Remove libunbound-dev"
apt:
name:
- libunbound-dev
- liblua5.2-dev
state: absent

View File

@ -14,6 +14,8 @@ ADD docker/entrypoint.sh /bin/entrypoint.sh
RUN chmod 770 /bin/entrypoint.sh
ENTRYPOINT ["/bin/entrypoint.sh"]
HEALTHCHECK CMD lua -l socket -e 'assert(socket.connect(os.getenv"SNIKKET_TWEAK_INTERNAL_HTTP_INTERFACE" or "127.0.0.1",os.getenv"SNIKKET_TWEAK_INTERNAL_HTTP_PORT" or "5280"))'
ADD ansible /opt/ansible
ADD snikket-modules /usr/local/lib/snikket-modules
@ -23,7 +25,7 @@ RUN apt-get update \
software-properties-common ca-certificates \
gpg gpg-agent \
ansible python-passlib python3-passlib \
libcap2-bin \
libcap2-bin build-essential\
&& rm -rf /var/lib/apt/lists/* \
&& c_rehash \
&& ansible-playbook -c local -i localhost, --extra-vars "ansible_python_interpreter=/usr/bin/python2" /opt/ansible/snikket.yml \
@ -32,7 +34,7 @@ RUN apt-get update \
software-properties-common \
gpg gpg-agent \
python-passlib python3-passlib \
mercurial libcap2-bin \
mercurial libcap2-bin build-essential \
&& apt-get autoremove -y \
&& rm -rf /var/cache/*

View File

@ -30,9 +30,6 @@ PGID=${PGID:=$(stat -c %g /snikket)}
if [ "$PUID" != 0 ] && [ "$PGID" != 0 ]; then
usermod -o -u "$PUID" prosody
groupmod -o -g "$PGID" prosody
usermod -o -u "$PUID" letsencrypt
groupmod -o -g "$PGID" letsencrypt
fi
if ! test -d /snikket/prosody; then
@ -41,16 +38,6 @@ fi
chown -R prosody:prosody /var/spool/anacron /var/run/prosody /snikket/prosody /etc/prosody
if ! test -d /snikket/letsencrypt; then
install -o letsencrypt -g letsencrypt -m 750 -d /snikket/letsencrypt;
fi
install -o letsencrypt -g letsencrypt -m 750 -d /var/lib/letsencrypt;
install -o letsencrypt -g letsencrypt -m 750 -d /var/log/letsencrypt;
install -o letsencrypt -g letsencrypt -m 755 -d /var/www/.well-known/acme-challenge;
chown -R letsencrypt:letsencrypt /snikket/letsencrypt
## Generate secret for coturn auth if necessary
if ! test -f /snikket/prosody/turn-auth-secret-v2; then
head -c 32 /dev/urandom | base64 > /snikket/prosody/turn-auth-secret-v2;

View File

@ -109,3 +109,65 @@ protocols:
);
```
### apache
**Note**: The following configuration is for reverse proxying from another machine
(other from the one hosting Snikket containers). A prerequisite is a mechanism to sync
Snikket-managed letsencrypt TLS key and cert to `/opt/chat/letsencrypt`. This is required because
Apache 2.4 is not able to revproxying based on SNI, routing encrypted TLS directly to the Snikket machine.
If the containers are on the same machine
of the reverse proxy, you have to tweak HTTP/S ports as indicated before, and you don't need
to proxy over SSL.
```
<VirtualHost *:443>
ServerName chat.example.com
ServerAlias groups.chat.example.com
ServerAlias share.chat.example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/chat
ErrorLog ${APACHE_LOG_DIR}/chat.example.com-ssl_error.log
CustomLog ${APACHE_LOG_DIR}/chat.example.com-ssl_access.log combined
SSLEngine on
#
SSLCertificateFile /opt/chat/letsencrypt/chat.example.com/cert.pem
SSLCertificateKeyFile /opt/chat/letsencrypt/chat.example.com/privkey.pem
SSLCertificateChainFile /opt/chat/letsencrypt/chat.example.com/chain.pem
SSLProxyEngine On
ProxyPreserveHost On
ProxyPass / https://chat.example.com/
ProxyPassReverse / https://chat.example.com/
</VirtualHost>
<VirtualHost *:80>
ServerName chat.example.com
ServerAlias groups.chat.example.com
ServerAlias share.chat.example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/chat
ProxyPreserveHost On
ProxyPass / http://chat.example.com/
ProxyPassReverse / http://chat.example.com/
ErrorLog ${APACHE_LOG_DIR}/chat.example.com_error.log
CustomLog ${APACHE_LOG_DIR}/chat.example.com_access.log combined
</VirtualHost>
```

View File

@ -0,0 +1,101 @@
# Update notifications
This is an informational technical document about the update notification
system in Snikket server.
## Why are update notifications important?
It is now widely known that [outdated software][OWASP-A9] is one of the
biggest risk factors in securing systems on the internet. Therefore the
Snikket server will alert all admins to available updates and important
notices from the Snikket team.
We believe it is up to you to decide when and how to update your service.
But we will provide you with the tools you need to make that easy, fast
and painless.
## How are they implemented?
To preserve your privacy, private Snikket servers do not make requests
directly to our servers. Instead we put the necessary information about
current releases and security updates into our DNS records.
## Why did you choose DNS?
The obvious choice was HTTP, and this is how most traffic on the internet
is conveyed these days. But we opted for DNS due to the following advantages:
- DNS is designed for serving small amounts of data from one place to many
- Due to caching, and its connectionless nature, DNS is more scalable
- Queries will often travel via an intermediate resolver, so we
typically won't have access to your server's IP address
- A DNS query contains very little information, whereas HTTP will always
leak the IP address, and by default will often leak other headers.
But it also has some known downsides. In particular DNS is not secure by
default. Intermediaries may observe or drop the query, or even modify the
response.
The following conclusions were made about the downsides:
- Observability: an intermediary seeing outbound queries to our DNS
records may deduce that your server is running Snikket. This should
not be a problem in itself - there are many ways to detect if a server
is running Snikket (load up its web page for a start!).
- Availability: an intermediary may block queries for our DNS records.
This would prevent a server admin from receiving update notifications,
which is bad (they may be tricked into thinking they are up to date).
However using another protocol such as HTTP(S) would not prevent this
focused attack.
- Integrity: the data returned to the Snikket server may be modified or
spoofed by an intermediary. This would allow them to trigger false
update notifications. We have designed the system so that the risk is
minimized - the update notifications will always include a link to the
real announcement on snikket.org (if any). It is not possible to direct
admins to arbitrary URLs.
It is possible that in the future we will add support for DNSSEC or manually
sign the data provided in our DNS records.
It is also possible that we will move to another mechanism in the future, if
a more suitable one can be found.
## The details
Snikket releases are organized into 'channels', e.g. 'dev', 'alpha', 'beta',
'stable'. Your server will work out the channel it belongs to, and make a DNS
query to:
```
TXT _channel.update.snikket.net
```
The response will look like:
```
"latest=3"
"secure=2"
"msg=0"
```
This response indicates that version '3' is the latest, but version '2' is the
last release with no known security vulnerabilities is '2'. The `msg` field
allows us to send important announcements that may not be included in a release.
A Snikket server will use the returned information to determine whether the
administrators need to be notified, and generate a message if necessary. Since
the server has no further information, the message will include a link to the
relevant announcement on the snikket.org website by calculating the URL to use.
## Disabling update checks
We strongly recommend you leave update notifications enabled so that you are
notified promptly about important releases and announcements. However if you
plan to receive these another way, you may disable them by adding to your
snikket.conf:
```
SNIKKET_UPDATE_CHECK=0
```
[OWASP-A9]: https://owasp.org/www-project-top-ten/2017/A9_2017-Using_Components_with_Known_Vulnerabilities

26
docs/setup/upgrading.md Normal file
View File

@ -0,0 +1,26 @@
---
title: "Upgrading your Snikket server"
date: 2021-05-19T14:32:02Z
---
Upgrading to a new Snikket release is typically very easy.
## snikket-selfhosted
If you installed Snikket using the [snikket-selfhosted][] scripts, simply run:
cd /opt/snikket
git pull
./scripts/update.sh
## Snikket quickstart
If you're using a version installed from the [original quickstart][] guide on
the website, use these commands instead:
cd /etc/snikket
docker-compose pull
docker-compose up -d
[snikket-selfhosted]: https://github.com/snikket-im/snikket-selfhosted
[original quickstart]: https://snikket.org/service/quickstart/

View File

@ -0,0 +1,66 @@
--luacheck: ignore 143/module
local http_formdecode = require "net.http".formdecode;
local secret = module:get_option_string("invites_bootstrap_secret");
if not secret then return; end
local invites_bootstrap_store = module:open_store("invites_bootstrap");
-- This should be a non-negative integer higher than any set for the
-- previous bootstrap event (if any)
local current_index = module:get_option_number("invites_bootstrap_index");
local invites = module:depends("invites");
module:depends("http");
local function handle_request(event)
local query_params = http_formdecode(event.request.url.query);
if not query_params.token or query_params.token ~= secret then
return 403;
end
local bootstrap_records = invites_bootstrap_store:get() or {};
if #bootstrap_records > 0 then
local last_bootstrap = bootstrap_records[#bootstrap_records];
if current_index == last_bootstrap.index then
event.response.headers.Location = last_bootstrap.result;
return 303;
elseif current_index < last_bootstrap.index then
return 410;
end
end
-- Create invite
local invite, invite_err = invites.create_account(nil, {
roles = { ["prosody:admin"] = true };
groups = { "default" };
source = "api/token/bootstrap-"..current_index;
});
if not invite then
module:log("error", "Failed to create bootstrap invite! %s", invite_err);
return 500;
end
-- Record this bootstrap event (to prevent replay)
table.insert(bootstrap_records, {
index = current_index;
timestamp = os.time();
result = invite.landing_page or invite.uri;
});
local record_ok, record_err = invites_bootstrap_store:set(nil, bootstrap_records);
if not record_ok then
module:log("error", "Failed to store bootstrap record: %s", record_err);
return 500;
end
event.response.headers.Location = invite.landing_page or invite.uri;
return 303;
end
module:provides("http", {
route = {
GET = handle_request;
};
});