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

Thoughts about Nginx, certbot, and processes #17

Open
4censord opened this issue May 26, 2024 · 4 comments
Open

Thoughts about Nginx, certbot, and processes #17

4censord opened this issue May 26, 2024 · 4 comments
Milestone

Comments

@4censord
Copy link

I did some thinking, and thought I'd document that here.

Some definitions to prevent confusion:

  • site --> A essy site, consisting of at least
    • A webserver configuration
    • a webroot managed by essy, this would be where the users files are
    • a certbot configuration, renewal and TLS certs

General stuff

As the webserver I'd use nginx.
For generating TLS certs, i'd use certbot with the webroot plugin.

For reloading the webserver configuration, i'd use sudo.
Sudo allows you to restrict what an account can do, so i'd allow essy
to exclusively issue reload commands to nginx, and not allow anything
else.

Example:

# Allow essy to reload or restart nginx
essy HOST_NAME= NOPASSWD: /usr/bin/systemctl reload nginx,/usr/bin/systemctl restart nginx

For nginx, i'd template configuration with jinja.
The main nginx config defines a folder thats writable by essy.
Essy templates its nginx config snippets into that folder, and triggers a nginx reload.
The Nginx configuration for a site should be secure by default.

For certbot, i'd run it as a subprocess.
Alternative: Look into using acme-python.
Advantage: No external dependency on certbot
Disadvantage: More complicated
Need to handle renewals ourselves

Certbot should be set up to automatically renew all certs on the machine.
Certbot should reload nginx automatically upon renewal.
Certbot is used with the webroot plugin.
Certbot puts its challenge files into a folder outside of the users
webroot, nginx is configured to server .well-known/acme-challenge
from this path.

Adding a new Site

stateDiagram-v2
    state "HTTP only configuration" as http
    state "Run certbot" as cert
    state "Add HTTPS config" as https
    [*] --> http
    http --> cert
    cert --> https
    https --> [*]
Loading

Removing a site

stateDiagram-v2
    state "Remove site config" as remove
    state "Revoke TLS certs" as certs
    state "Create backup of webroot" as backup
    state "Clean up certbot files" as certs2
    state "Clean up files" as clean
    [*] --> remove
    remove --> certs: reload nginx
    certs --> certs2
    certs --> backup
    backup --> clean
    certs2 --> [*]
    clean --> [*]
Loading

Configuration templates

Variable name type Description
server_names array All fqdns this server should listen to
certbot_webroot_path string(path) Where certbot will put its acme files
le_cert_chain string(path) This should point to chain.pem
le_cert_fullchain string(path) This should point to fullchain.pem
le_cert_privkey string(path) This should point to privkey.pem
webroot string(path) Where the files live (the webroot location)
HTTP only nginx config
# generated 2024-05-25, Mozilla Guideline v5.7, nginx 1.17.7, OpenSSL 1.1.1k, modern configuration
# https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=modern&openssl=1.1.1k&guideline=5.7
server {
    listen 80;
    listen [::]:80;

    server_name {% for name in server_names %} {{ name }}{% endfor %};

    location ^~ /.well-known/acme-challenge/ {
        root {{certbot_webroot_path}};
    }

    location / {
        return 301 https://$host$request_uri;
    }
}
HTTPS nginx config
# generated 2024-05-25, Mozilla Guideline v5.7, nginx 1.17.7, OpenSSL 1.1.1k, modern configuration
# https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=modern&openssl=1.1.1k&guideline=5.7
server {
    listen 80;
    listen [::]:80;

    server_name {% for name in server_names %} {{ name }}{% endfor %};

    location ^~ /.well-known/acme-challenge/ {
        root {{certbot_webroot_path}};
    }

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

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

    server_name {% for name in server_names %} {{ name }}{% endfor %};

    ssl_certificate {{ le_cert_fullchain }};
    ssl_certificate_key {{ le_cert_privkey }};
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions
    ssl_session_tickets off;

    # modern configuration
    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers off;

    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate {{ le_cert_chain }};

    # Deny access to any hidden files (starting with .)
    # e.g. .htaccess and .htpasswords
    # or .git folders
    location ~ /\.  { deny all; }

    # explicitly allow .well-known/
    location ~ /\.well-known/ {}
    location ^~ /.well-known/acme-challenge/ {
        root {{certbot_webroot_path}};
    }

    root {{webroot}};
}
@ShadowJonathan
Copy link
Owner

ShadowJonathan commented May 26, 2024

Before this issue I was thinking of a different solution, but scoping the need for sudo only to those commands (which can then be added to the install tutorial as "copy this line into visudo, it will do X Y Z"), and running certbot/acme-python in-process, does make things a lot easier from the user-installation and management side, and allows pulling more statistics/data about certificate expiry, and renewal errors.

I like this, I think i'll take this option for v1, since it makes things relatively self-contained.


One other thought is that I'd have users run essy under www-data, which can then reload nginx via nginx reload itself, since then suddenly its under the same user. Do you have any thoughts about that?


One other thing, the current configuration will basically have every website pull from the same webroot, shouldnt it be this?

root {{webroot}}/$host;

@ShadowJonathan ShadowJonathan added this to the Usable (1.0) milestone May 26, 2024
@4censord
Copy link
Author

One other thing, the current configuration will basically have every website pull from the same webroot, shouldnt it be this?

root {{webroot}}/$host;

My idea was that essy puts in something like /var/lib/essy/$ID as webroot, and creates an nginx config file per site. That would allow essy to alias other domains to the same site with only adding them to the server_name directive.

So site 1 lives in /var/lib/essy/1/, and listens on domains 1.example.com and www.1.example.com.
And has the config file /etc/nginx/essy.d/1.conf

Site 2 then lives in /var/lib/essy/2/, and listens on domains my.domain and my.other.domain.
And has the config file /etc/nginx/essy.d/2.conf

@4censord
Copy link
Author

4censord commented May 26, 2024

One other thought is that I'd have users run essy under www-data, which can then reload nginx via nginx reload itself, since then suddenly its under the same user. Do you have any thoughts about that?

That might not work, though i'm not sure.
If essy runs as a service with limited access, it's probably not able to access the nginx process to send a reload signal.
Also, this would complicate installation, as one needs to make sure that nginx and essy run as the same uid, or one needs to set up groups etc.

Oh, and on systems with selinux/apparmor, it would be pain to configure without turning them off, which should be avoided imho

@ShadowJonathan
Copy link
Owner

I don't want to have essy run as root, its both a security risk and an ask of trust for the user, so the visudo approach is better imo, and relatively simple ^^

Also; do join the Zulip via the link i gave you, so we can talk about this more. I don't know exactly your vision for the webroot, but i guess i'd have to tell you my idea first, since i dont see how it fits with what you're telling me.

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

2 participants