Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The Domain Sharing Plan #60

Open
mwild1 opened this issue Sep 6, 2021 · 16 comments
Open

The Domain Sharing Plan #60

mwild1 opened this issue Sep 6, 2021 · 16 comments

Comments

@mwild1
Copy link
Member

mwild1 commented Sep 6, 2021

People would like to use Snikket with JIDs @example.com while also having an existing website on example.com. This is currently tricky, particularly when Snikket is running on a different machine.

Things that need to happen:

  • Snikket needs to be able to separate the XMPP domain and the web domain.
  • We need to be able to overcome certificate issues (if example.com:80 is a website on a separate server, we can't easily get certificates for the XMPP domain)
    • Option 1) Respond to DNS challenges and instruct people to delegate via an _acme-challenge record
    • Option 2) Serve a self-signed certificate and instruct people to upload a POSH file (requires adding support in snikket-ios and snikket-server)
    • Option 3) Sit back and wait for Let's Encrypt/CABF to get moving on SRV support (BRs: Clarify how SRVName subjectAltNames should behave cabforum/servercert#268) (reality: it may never happen for end-user certs)
    • Option 4) Sit back and wait for LE or another CA to get moving on STAR certificates
@singpolyma
Copy link

Option 1 is a slam dunk IMHO. Works with letsencrypt and all clients and servers today, with the only downside being one extra DNS record on a custom domain and that if the owner of that domain wants to use dns-01 on their apex themselves they have to remember to restore the CNAME when they are done.

@worldofgeese
Copy link

I'm so looking forward to this! I have a user name that relies on my domain (so worldof AT geese) to "make sense".

@Rijul-A
Copy link
Contributor

Rijul-A commented Dec 19, 2021

Continuing our discussion from the Snikket Project Chat, you said you want to do wildcard certificate generation via joohoi/acme-dns. I would like to start work on this.

Do you have an architectural design in mind? I'm thinking that acme-dns becomes part of the snikket-cert-manager container, and prints to stdout the relevant CNAME record that needs to be added (this will be persisted into a Docker volume for reuse later). It then waits for the CNAME record to exist before proceeding to obtain an LE certificate.

@mwild1
Copy link
Member Author

mwild1 commented Dec 20, 2021

Yes, I think this should be built into snikket-cert-manager.

I think it could be enabled by an environment variable, such as SNIKKET_ENABLE_ACME_DNS=1.

The CNAME thing is a little awkward. Here's how I expected it would work:

  1. The admin creates A and/or AAAA records for their Snikket server, e.g. snikket.example.com, pointing to their instance's IP address(es).
  2. The admin creates an NS record for snikket.example.com pointing to snikket.example.com.
  3. The admin will then create three CNAME records:
  • _acme-challenge.example.com CNAME cert.snikket.example.com
  • _acme-challenge.share.example.com CNAME cert-share.snikket.example.com
  • _acme-challenge.groups.example.com CNAME cert-groups.snikket.example.com
  1. The admin sets SNIKKET_ENABLE_ACME_DNS=1 in their config and starts Snikket.
  2. snikket-cert-manager (via certbot + acme-dns-certbot plugin) obtains certificates.

The primary difficulty I see is, in step 5, the targets of the CNAME records. Out of the box, acme-dns expects you to use randomly-generated domains (because it is often used for scalable mass hosting). However it should be technically possible to seed the acme-dns database with some static records instead of the UUIDs it uses by default. However this may only be possible in unreleased versions of acme-dns as it would require joohoi/acme-dns#243 which has been merged but not yet released.

Of course at this point it may be worth a sanity-check: is it still worth using acme-dns vs. a different generic small authoritative DNS server we can integrate with certbot? I haven't researched alternatives, so I can't say at this point.

@singpolyma
Copy link

singpolyma commented Dec 20, 2021 via email

@mwild1
Copy link
Member Author

mwild1 commented Dec 20, 2021

Good point about the wildcard, of course that makes sense.

As for the scripts, yes - the problem is the "whatever other provider". The reason for running a small authoritative DNS server ourselves is because it allows us to be provider-independent. If people prefer a provider-specific setup, they can always set up certbot externally to Snikket and mount in the certificates. We just want something that works for as many as people as possible with minimal configuration.

@poVoq
Copy link

poVoq commented Dec 20, 2021

Something like this could maybe also be used: https://github.com/octodns/octodns

@Rijul-A
Copy link
Contributor

Rijul-A commented Dec 21, 2021

I have been playing around with acme-dns, and noticed one thing that might be worth documenting for those who, like me, use Cloudflare as a DNS server.

1. The admin creates A and/or AAAA records for their Snikket server, e.g. `snikket.example.com`, pointing to their instance's IP address(es).

2. The admin creates an NS record for `snikket.example.com` pointing to `snikket.example.com`.

When I attempted to do the above two steps, I got an error saying, "Non-NS records with that host already exist. (Code: 81055)." I even tried to switch the order around, but that resulted in, "NS records with that host already exist. (Code: 81056)."

Another user has reported this issue at joohoi/acme-dns#258, with the conclusion being that Cloudflare does not support glue records (at least not on the free plan); so this approach is not possible. I tried setting up the glue record at my registrar, but that didn't work either. The workaround, as documented in the issue, is to replace the first two steps with:

  1. The admin creates A and/or AAAA records for their Snikket DNS server, e.g. snikket-ns.example.com, pointing to their instance's IP address(es).
  2. The admin creates an NS record for snikket.example.com pointing to snikket-ns.example.com.

@apfsx
Copy link

apfsx commented May 22, 2022

Chiming in to say I would love for this to be a feature so I could use my domain for both purposes without a subdomain for Snikket.

@link2xt
Copy link

link2xt commented Jan 25, 2024

  1. The admin creates an NS record for snikket.example.com pointing to snikket.example.com.

I have tried it when doing DNS for https://github.com/deltachat/chatmail, see deltachat/chatmail#44
I tried running https://www.nlnetlabs.nl/projects/nsd/about/ DNS server and delegating the domain to it.

Creating both A/AAAA and NS record on the same domain does not work, NS record "delegates" A/AAAA record to this server as well. So if you want to use NS record and run your own DNS server, you need a separate domain for DNS server, e.g. ns.example.org has A record and runs NSD, while snikket.example.org is delegated to it.

The best option I found for using a single domain (say snikket.example.org) is to create a subdomain ns.snikket.example.org, run DNS there, delegate chat.snikket.example.org to it and then individually delegate all snikket.example.org records one-by-one to chat.snikket.example.org via CNAME.

We do not use this solution in the end as it is quite ugly and print DNS records instead to stdout or file so admin can decide how they want to set them up, e.g. by copying to DNS server or uploading via Hetzner or Cloudflare API. Running anything else on the same domain is not supported.

@pepper3k
Copy link

pepper3k commented Sep 5, 2024

This is now solved by some nginx black magic with some help from the nginx master @singpolyma🥇

This solution is based on the following snikket.conf:

# The primary domain of your Snikket instance
SNIKKET_DOMAIN=example.com

# An email address where the admin can be contacted
# (also used to register your Let's Encrypt account to obtain certificates)
SNIKKET_ADMIN_EMAIL=admin@example.com
SNIKKET_TWEAK_HTTP_PORT=5080
SNIKKET_TWEAK_HTTPS_PORT=5443

This solution segregates the main domain from the snikket portal.
The first file is the nginx config for the main domain that includes the necessary tweaks for snikket to work:

#/etc/nginx/sites-enabled/example.com
server {
    listen 80;
    server_name example.com;
    location / {
        return 301 https://example.com/;
    }
    location /.well-known/acme-challenge {
        try_files /var/www/html/$uri @fallback;
    }
###### [START SNIKKET]
    location @fallback {
        proxy_pass http://localhost:5080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # This is the maximum size of uploaded files in Snikket
        client_max_body_size 104857616; # 100MB + 16 bytes
    }
###### [END SNIKKET]
}
server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    
    location / {
        root /var/www/html;
        index index.html index.htm;
    }

###### [START SNIKKET]
    location ~* ^/(invite|static) {
      proxy_pass https://localhost:5443;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;
      # REMOVE THIS IF YOU CHANGE localhost TO ANYTHING ELSE ABOVE
      proxy_ssl_verify      off;
      proxy_ssl_server_name on;
      # This is the maximum size of uploaded files in Snikket
      client_max_body_size 104857616; # 100MB + 16 bytes
      # For BOSH and WebSockets
      proxy_read_timeout 900s;
    }
###### [END SNIKKET]
}

You can use this file as a base or integrate the relevant parts into your existing configuration.

The second file is the main snikket configuration for nginx:

#/etc/nginx/sites-enabled/chat.example.com
server {
    listen 80;
    server_name chat.example.com;

    location /.well-known/acme-challenge {
        proxy_pass http://localhost:5080;
        proxy_set_header Host chat.example.com;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # This is the maximum size of uploaded files in Snikket
        client_max_body_size 104857616; # 100MB + 16 bytes
    }

    location / {
        return 301 https://chat.example.com/;
    }
}
server {
    listen 443 ssl; # managed by Certbot
    server_name chat.example.com;

    ssl_certificate /etc/letsencrypt/live/chat.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem; # managed by Certbot
    ssl_trusted_certificate /etc/letsencrypt/live/chat.example.com/chain.pem; # managed by Certbot
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    add_header Strict-Transport-Security "max-age=31536000" always; # managed by Certbot

    location / {
      proxy_pass https://localhost:5443;
      proxy_set_header Host example.com;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;
      # REMOVE THIS IF YOU CHANGE localhost TO ANYTHING ELSE ABOVE
      proxy_ssl_verify      off;
      proxy_ssl_server_name on;
      # This is the maximum size of uploaded files in Snikket
      client_max_body_size 104857616; # 100MB + 16 bytes
      # For BOSH and WebSockets
      proxy_read_timeout 900s;
    }
}
server {
    listen 80;
    server_name groups.example.com share.example.com;

    location /.well-known/acme-challenge {
        proxy_pass http://localhost:5080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # This is the maximum size of uploaded files in Snikket
        client_max_body_size 104857616; # 100MB + 16 bytes
    }

    location / {
        return 301 https://chat.example.com/;
    }
}
server {
    listen 443 ssl; # managed by Certbot
    server_name groups.example.com share.example.com;
    ssl_certificate /etc/letsencrypt/live/chat.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem; # managed by Certbot
    ssl_trusted_certificate /etc/letsencrypt/live/chat.example.com/chain.pem; # managed by Certbot
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    add_header Strict-Transport-Security "max-age=31536000" always; # managed by Certbot
    
    location / {
      proxy_pass https://localhost:5443;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;
      # REMOVE THIS IF YOU CHANGE localhost TO ANYTHING ELSE ABOVE
      proxy_ssl_verify      off;
      proxy_ssl_server_name on;
      # This is the maximum size of uploaded files in Snikket
      client_max_body_size 104857616; # 100MB + 16 bytes
      # For BOSH and WebSockets
      proxy_read_timeout 900s;
    }
}

You should configure these two files before starting snikket. If snikket starts up correctly and you can also use certbot on the docker host to regenerate certs then everything is working properly.

In my opinion @mwild1 can now close this issue as completed. All you need to do is add another page of documentation to https://snikket.org/service/help/advanced/

@andydvsn
Copy link

andydvsn commented Sep 23, 2024

I can confirm that I now have a Snikket instance running successfully on a system with other web services on ports 80 and 443 using the above instructions. Text, voice and video calling are all working and OMEMO is happy. My nginx configs are based on the above, but tweaked slightly as I couldn't otherwise get certbot to play nicely.

This is copied straight from my server with irrelevant bits removed, so replace dvsn.net with your own TLD and put the necessary CNAME entries into your DNS as described in the official documentation.

/etc/nginx/sites-available/dvsn

server {

  listen [::]:80;
  listen 80;

  server_name dvsn.net www.dvsn.net;
  root /var/www/root;
  index index.html index.htm;

  location /.well-known {
    allow all;
    try_files $uri @snikket;
  }

  location / {
    return 301 https://$host$request_uri;
  }

  include /etc/nginx/snippets/snikket.conf;

}

server {
  
  listen [::]:443 ssl http2;
  listen 443 ssl http2;

  server_name dvsn.net www.dvsn.net;
  root /var/www/root;
  index index.html index.htm;

  location /.well-known {
    allow all;
    try_files $uri @snikket_ssl;
  }

  # Snikket Redirection
  location ~* ^/(http-bind|invite|static|xmpp-websocket) {
    try_files $uri @snikket_ssl;
  }

  include /etc/nginx/snippets/snikket.conf;

  ssl_certificate /etc/letsencrypt/live/dvsn.net/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/dvsn.net/privkey.pem;
  include /etc/nginx/snippets/ssl.conf;

}

/etc/nginx/sites-available/xmpp

server {

  listen [::]:80;
  listen 80;

  server_name groups.dvsn.net share.dvsn.net xmpp.dvsn.net;
  root /var/www/root;
  index index.html index.htm;

  location /.well-known {
    allow all;
    try_files $uri @snikket;
  }

  location / {
    return 301 https://xmpp.dvsn.net/;
  }

  include /etc/nginx/snippets/snikket.conf;

  access_log /var/log/nginx/access_xmpp.log;
  error_log  /var/log/nginx/error_xmpp.log;

}

server {

  listen [::]:443 ssl http2;
  listen 443 ssl http2;

  server_name groups.dvsn.net share.dvsn.net xmpp.dvsn.net;

  location / {
    try_files $uri @snikket_ssl;
  }

  include /etc/nginx/snippets/snikket.conf;

  ssl_certificate     /etc/letsencrypt/live/xmpp.dvsn.net/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/xmpp.dvsn.net/privkey.pem;
  include /etc/nginx/snippets/ssl.conf;

  access_log /var/log/nginx/access_xmpp.log;
  error_log  /var/log/nginx/error_xmpp.log;

}

/etc/nginx/snippets/snikket.conf

location @snikket {
  proxy_pass http://localhost:5080;
  proxy_set_header Host dvsn.net;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  client_max_body_size 104857616; # 100MB + 16 bytes
}

location @snikket_ssl {
  proxy_pass https://localhost:5443;
  proxy_set_header Host dvsn.net;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto https;
  proxy_ssl_verify off;
  proxy_ssl_server_name on;
  proxy_set_header Connection $http_connection;
  proxy_set_header Upgrade $http_upgrade;
  proxy_read_timeout 900s;
  client_max_body_size 104857616; # 100MB + 16 bytes
}

/etc/nginx/snippets/ssl.conf

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

Remember to open the necessary ports on your firewall so you don't sit there staring at empty logs for ten minutes wondering why clients can't connect, like someone may have.

Obviously this is more work than the straightforward installation and detracts a bit from Snikket's ease-of-use philosophy, but I'm very glad it's possible, so thank you @pepper3k and @singpolyma. I'll be mucking about with this more through the week, no doubt.

@andydvsn
Copy link

andydvsn commented Sep 24, 2024

While setting up my Keyoxide proofs with the XMPP Ariadne Proof Utility I discovered that websocket connections were broken. So I've fixed that and simplified the configs above, using another snippet file for the proxy settings to avoid duplication.

The main things were to include http-bind and xmpp-websocket in the redirection and ensure .well-known was also accessible over HTTPS, as it seems some services request it that way.

@pepper3k
Copy link

thanks for that @andydvsn, i'll take a look at this soon, but i am a bit curious about why certbot wasn't working with my config (i tested this thoroughly)

also, thanks for letting me know about keyoxide, it looks really interesting 👏 👏

@andydvsn
Copy link

It's entirely possible the certbot problem was an issue of my own making with my previous configuration. :)

Dry-run testing with the above definitely works for Snikket in its container, and for all the others running "direct" on the server, but don't spend too much time looking in your configs as I likely messed it up the first time!

@pepper3k
Copy link

It's entirely possible the certbot problem was an issue of my own making with my previous configuration. :)
That makes me feel a lot better

@andydvsn are you going to make a pr or should i?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants