Opened 6 years ago

Closed 6 years ago

Last modified 6 years ago

#1744 closed defect (invalid)

Cannot use custom variable in ssl_certificate directive.

Reported by: Antiarchitect@… Owned by:
Priority: minor Milestone:
Component: other Version: 1.15.x
Keywords: Cc:
uname -a: Linux a371d0a1c462 4.15.0-43-generic #46-Ubuntu SMP Thu Dec 6 14:45:28 UTC 2018 x86_64 Linux
nginx -V: nginx version: nginx/1.15.9
built by gcc 8.2.0 (Alpine 8.2.0)
built with OpenSSL 1.1.1a 20 Nov 2018
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-threads --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-stream_realip_module --with-stream_geoip_module=dynamic --with-http_slice_module --with-mail --with-mail_ssl_module --with-compat --with-file-aio --with-http_v2_module

Description

Nginx: nginx:1.15.9-alpine

Part of the config:

server {
...
set $cert_name my.example.com;

ssl_certificate "letsencrypt/live/${cert_name}/fullchain.pem";
...
}

In logs:
"2019/03/12 19:51:20 [error] 6#6: *25 cannot load certificate \"/etc/nginx/letsencrypt/livefullchain.pem\": BIO_new_file() failed (SSL: error:0200100D:system library:fopen:Permission denied:fopen('/etc/nginx/letsencrypt/live//fullchain.pem','r') error:2006D002:BIO routines:BIO_new_file:system lib) while SSL handshaking, client: 10.0.1.135, server: 0.0.0.0:443"

  • Permission denied is because I haven't run nginx as root in docker container and letsencrypt files were readable only by 0 uid.

When using built-in $ssl_server_name variable - all is ok and path is set properly.

Change History (7)

comment:1 by Antiarchitect@…, 6 years ago

Cannot edit ticket - don't know why. The path to the cert in logs is

"/etc/nginx/letsencrypt/live//fullchain.pem"
Last edited 6 years ago by Antiarchitect@… (previous) (diff)

comment:2 by Maxim Dounin, 6 years ago

Resolution: invalid
Status: newclosed

Variables set with the set directive of the rewrite module are only available after rewrite instructions were evaluated when processing a request, see rewrite module documentation. As such, these variables won't have any meaningful value during an SSL handshake. When loading certificates you have to use builtin connection-related variables, or custom variables which are always available - such as provided with map, geo, perl_set, or js_set.

comment:3 by JJJ@…, 6 years ago

connection-related variables, or custom variables which are always available

All of the sites I host follow a subdomain.domain.tld pattern (where subdomain is optional.) Each separate domain has its own wildcard certificate provided by LetsEncrypt.

I've tried every variation of the following with no success:

http {
    ...

    map $host $ssl_domain_name {
        volatile;
        hostnames;
        default 'example.org';
        ~^((?<subdomain>.*)\.)(?<domain>[^.]+)\.(?<tld>[^.]+)$ $domain.$tld;
    }

    server {
        ...

        location {
            ...
            ssl_certificate     /etc/letsencrypt/live/$ssl_domain_name/fullchain.pem;
            ssl_certificate_key /etc/letsencrypt/live/$ssl_domain_name/privkey.pem;
        }
    }
}

In my above map example, I have confirmed that the value of $ssl_domain_name correctly maps to only the domain & TLD.
(I've done this by using add_header X-Debug $ssl_domain_name; and confirming the value.)

I've also tried mapping $ssl_server_name instead of $host, or simply using the $ssl_server_name variable directly while symlinking the directories.

I've tried with and without the volatile; and hostnames; flags.


In my experience, even with the guidance you've provided above, it appears like variables do not work as expected.

Can you offer up a practical example, or a bit more guidance? I must be missing something, but I can't find what it is in the documentation or the source itself.

Last edited 6 years ago by JJJ@… (previous) (diff)

comment:4 by Maxim Dounin, 6 years ago

Using the $host variable hardly make sense - before an HTTP request is received it is expected to map to server_name and will be empty by default.

The $ssl_server_name variable is expected to work fine, and you can create derivative variables from it, for example:

map $ssl_server_name $cert {
    default          /tmp/default;
    example.com      /tmp/example.com;
}

server {
    listen 8443 ssl;

    ssl_certificate $cert.crt;
    ssl_certificate_key $cert.key;
}

Testing with

$ openssl s_client -connect 127.0.0.1:8443 -servername example.com

produces the following error (expected, as there are no corresponding certificate available):

2019/04/30 16:24:09 [error] 28941#100101: *1 cannot load certificate "/tmp/example.com.crt": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/tmp/example.com.crt','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: 127.0.0.1, server: 0.0.0.0:8443

Clearly everything works as expected.

comment:5 by JJJ@…, 6 years ago

Using the $host variable hardly make sense

Only trying to follow your guidance. $host is listed in the Nginx documentation's variables listing as always being available, while $ssl_server_name is not explicitly documented as such.

No matter what I do, I do not get the results that you do.

Having any variable in either ssl_certificate or ssl_certificate_key causes this:

openssl s_client -connect 127.0.0.1:443 -servername example.org

CONNECTED(00000003)
1996304384:error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:../ssl/record/rec_layer_s3.c:1407:SSL alert number 80
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 197 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID:
    Session-ID-ctx:
    Master-Key:
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1556668430
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
---

I'm using:

  • Nginx 1.16.0 (compiled from official source)
  • OpenSSL 1.1.0j

I originally tried all of this on Nginx 1.15..

Is there some specific configuration or module necessary? When I hardcode the path, it works perfectly. When any variable is in the path, it does not work.

Last edited 6 years ago by JJJ@… (previous) (diff)

comment:6 by Maxim Dounin, 6 years ago

Only trying to follow your guidance. $host is listed in the Nginx documentation's variables listing as always being available, while $ssl_server_name is not explicitly documented as such.

The $host is explicitly documented as:

in this order of precedence: host name from the request line, or host name from the “Host” request header field, or the server name matching a request

without a request (and hence without the request line and the "Host" header) it is going to be identical to what is used in the server_name directive in the relevant server block. While it certainly can be used, there is no real difference to writing the same string right in the ssl_certificate directive.

No matter what I do, I do not get the results that you do.

Having any variable in either ssl_certificate or ssl_certificate_key causes this:

openssl s_client -connect 127.0.0.1:443 -servername example.org

CONNECTED(00000003)
1996304384:error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:../ssl/record/rec_layer_s3.c:1407:SSL alert number 80
---
no peer certificate available
...

The error shown by openssl indicate that nginx wasn't able to load a certificate. Check nginx error log, most likely it have corresponding details.

Note well that for nginx worker processes to be able to load certificates and keys, they have to be readable by nginx worker processes user. Usually this is not the case, unless you've specifically configured your certificate paths to allow this.

Note well that dynamic loading of certificates is generally to be avoided, unless you know why you need it and understand possible implications, such as security ones.

Note well that Trac is to report bugs. As demonstrated above, what you observe is clearly not a bug. If you need further help with configuring nginx, consider using support options available.

comment:7 by JJJ@…, 6 years ago

The error shown by openssl indicate that nginx wasn't able to load a certificate. Check nginx error log, most likely it have corresponding details.

I'd forgotten to compile using --with-debug, so my logs were pretty useless. Recompiling got me the same Permission denied response, which was helpful.

Note well that for nginx worker processes to be able to load certificates and keys, they have to be readable by nginx worker processes user. Usually this is not the case, unless you've specifically configured your certificate paths to allow this.

This was a good hint, actually. Thank you.

Note well that dynamic loading of certificates is generally to be avoided, unless you know why you need it and understand possible implications, such as security ones.

I appreciate the warning. Thank you.

Note well that Trac is to report bugs.

I'm aware. I legitimately believed I'd encountered the same bug that the original reported had here. Your additional direction in these replies was extremely helpful. Thank you again, and sorry to muck this issue up here.

Note: See TracTickets for help on using tickets.