R7000 nginx build for read-only /opt

Post new topic   Reply to topic    DD-WRT Forum Index -> Broadcom SoC based Hardware
Author Message
RNCTX
DD-WRT Novice


Joined: 26 Jun 2018
Posts: 5

PostPosted: Tue Jun 26, 2018 22:20    Post subject: R7000 nginx build for read-only /opt Reply with quote
Hi folks, I figured someone else might find this useful.

I have an R7000 and am planning on using it for a reverse proxy, among other things, to host various virtual machines I use for web development in my house. I want to run /opt as read-only, so that whatever is being run from there doesn't wear out the flash memory.

Lighttpd is included in Kong's build(s), but unfortunately it does not support SSL passthrough like Nginx does. The problem with making the switch from Lighttpd is that Nginx as-compiled by the maintainer for the Entware package points to /opt for the log, pid, and lock files by default.

https://github.com/Entware/entware-packages/blob/master/net/nginx/Makefile ...

Code:

define Build/Configure
   ( cd $(PKG_BUILD_DIR) ; \
      $(if $(CONFIG_NGINX_LUA),LUA_INC=$(STAGING_DIR)/opt/include LUA_LIB=$(STAGING_DIR)/opt/lib) \
      ./configure \
         --crossbuild=Linux::$(ARCH) \
         --prefix=/opt \
         --modules-path=/opt/lib/nginx \
         --conf-path=/opt/etc/nginx/nginx.conf \
         $(ADDITIONAL_MODULES) \
         --error-log-path=/opt/var/log/nginx/error.log \
         --pid-path=/opt/var/run/nginx.pid \
         --lock-path=/opt/var/lock/nginx.lock \
         --http-log-path=/opt/var/log/nginx/access.log \
         --http-client-body-temp-path=/opt/var/lib/nginx/body \
         --http-proxy-temp-path=/opt/var/lib/nginx/proxy \
         --http-fastcgi-temp-path=/opt/var/lib/nginx/fastcgi \
         --http-uwsgi-temp-path=/opt/var/lib/nginx/uwsgi \
         --http-scgi-temp-path=/opt/var/lib/nginx/scgi \
         --with-cc="$(TARGET_CC)" \
         --with-cc-opt="$(TARGET_CPPFLAGS) $(TARGET_CFLAGS)" \
         --with-ld-opt="$(TARGET_LDFLAGS)" \
         --without-http_upstream_zone_module \
   )
endef

define Package/nginx/install
   $(INSTALL_DIR) $(1)/opt/sbin
   $(INSTALL_BIN) $(PKG_INSTALL_DIR)/opt/sbin/nginx $(1)/opt/sbin/
   $(INSTALL_DIR) $(1)/opt/etc/nginx
   $(INSTALL_DATA) $(addprefix $(PKG_INSTALL_DIR)/opt/etc/nginx/,$(config_files)) $(1)/opt/etc/nginx/
   $(INSTALL_DIR) $(1)/opt/etc/init.d
   $(INSTALL_BIN) ./files/S80nginx $(1)/opt/etc/init.d/
   $(INSTALL_DIR) $(1)/opt/share/nginx/html
   $(INSTALL_DATA) $(PKG_INSTALL_DIR)/opt/html/{index,50x}.html $(1)/opt/share/nginx/html
   $(INSTALL_DIR) $(1)/opt/var/{run,lock} $(1)/opt/var/log/nginx $(1)/opt/lib/nginx
   $(INSTALL_DIR) $(1)/opt/var/lib/nginx/{body,proxy,fastcgi}
ifeq ($(CONFIG_NGINX_NAXSI),y)
   $(INSTALL_DIR) $(1)/opt/etc/nginx
   $(INSTALL_BIN) $(PKG_BUILD_DIR)/nginx-naxsi/naxsi_config/naxsi_core.rules $(1)/opt/etc/nginx
   chmod 0640 $(1)/opt/etc/nginx/naxsi_core.rules
endif


This means that before Nginx will write a log anywhere else, it checks for its log folder under /opt to be writable. If you wish to run packages on /opt mounted read-only to avoid wearing out the internal flash, as I do, then that won't work. Nginx never releases that default logfile location. As long as Nginx is running, you can't remount /opt as read-only.

If only Nginx were trying to put its default log under the ramdisk on /tmp, this would be fixed and Nginx would be totally usable with /opt mounted on the internal flash as read-only. You could then, say... redirect Nginx logs to a remote syslog on another machine, which makes way more sense than writing logs to a USB stick (and also eliminates the obvious physical security issue with putting SSL keys on a USB stick, too). Nginx supports remote syslogs by default, so that would be a trivial (just config, no re-compile) change to make.

So this re-build of the Nginx binary does just that: points Nginx logs, lock, and pid to existing folders on the /tmp ramdisk.

How to use, assuming you want opt on the internal flash:


  • Enable jffs2 as /opt from Administration -> Management
  • Install Entware to the jffs /opt
  • Install Nginx from optware like you normally would (opkg install nginx)
  • Replace the binary in /opt/sbin with the one linked here (rm -f /opt/sbin/nginx && scp user@192.168.1.xx:nginx /opt/sbin/nginx)
  • Modify your config file in /opt/etc/nginx to point the logs somewhere else, or disable the logs entirely, or whatever you want to do with them. BIG FAT WARNING: if you do not do this, the logs will fill up all your RAM and (probably) crash your router, you must turn them off or send them somewhere else.
  • Run nginx once so it can chown all of its directories on /opt to itself (/opt/etc/init.d/S80nginx start && /opt/etc/init.d/S80nginx stop)
  • In your Administration -> Commands -> Startup, add commands to mount and read-only the internal flash at boot: (mount --bind /jffs/opt /opt && mount -o remount,ro /opt)
  • Reboot the router


You could of course modify the above to remount your USB as read-only as well, if you prefer.

Here's the Makefile diff if you wish to compile yourself...

Code:

ddwrt@debian:~/Entware/feeds/packages/net/nginx$ diff Makefile Makefile.1
232,235c232,235
<          --error-log-path=/opt/var/log/nginx/error.log \
<          --pid-path=/opt/var/run/nginx.pid \
<          --lock-path=/opt/var/lock/nginx.lock \
<          --http-log-path=/opt/var/log/nginx/access.log \
---
>          --error-log-path=/tmp/var/log/nginx_error.log \
>          --pid-path=/tmp/var/run/nginx.pid \
>          --lock-path=/tmp/var/lock/nginx.lock \
>          --http-log-path=/tmp/var/log/nginx_access.log \
257c257
<    $(INSTALL_DIR) $(1)/opt/var/{run,lock} $(1)/opt/var/log/nginx $(1)/opt/lib/nginx
---
>    $(INSTALL_DIR) $(1)/opt/lib/nginx


As if this writing the current Entware version of Nginx is 1.12.2, compiled against the 2.6 armv7 kernel. Far into the future, you probably want to recompile this yourself using the diff, by following the Entware instructions

The binary is here...

https://github.com/RNCTX/kong-ddwrt-v3-readonly-nginx
Sponsor
JAMESMTL
DD-WRT Guru


Joined: 13 Mar 2014
Posts: 856
Location: Montreal, QC

PostPosted: Wed Jun 27, 2018 3:41    Post subject: Reply with quote
nginx is available from kong's repo. been running it since forever. http/2 fully supported whereas lighty doesn't.

I just add the following to nginx.conf > http to deal with the compile defaults.

Code:

   ##
   # DDWRT Settings
   ##

   client_body_temp_path /tmp/nginx 1 2;
   proxy_temp_path /tmp/nginx 1 2;
   fastcgi_temp_path /tmp/nginx 1 2;
   uwsgi_temp_path /tmp/nginx 1 2;
   scgi_temp_path /tmp/nginx 1 2;


Here's kong's compile version

Code:
nginx -V
nginx version: nginx/1.10.3
built by gcc 5.3.0 (OpenWrt GCC 5.3.0 r49031)
built with OpenSSL 1.0.2h  3 May 2016
TLS SNI support enabled
configure arguments: --crossbuild=Linux::arm --prefix=/usr --conf-path=/etc/nginx/nginx.conf --add-module=/home/bluebat/Build/KWRT/build_dir/target-arm_cortex-a9_musl-1.1.14_eabi/nginx-1.10.3/nginx-naxsi/naxsi_src --with-ipv6 --with-http_ssl_module --with-http_auth_request_module --with-http_v2_module --error-log-path=/var/log/nginx/error.log --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx.lock --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/body --http-proxy-temp-path=/var/lib/nginx/proxy --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --with-cc=arm-openwrt-linux-muslgnueabi-gcc --with-cc-opt='-I/home/bluebat/Build/KWRT/staging_dir/target-arm_cortex-a9_musl-1.1.14_eabi/usr/include -I/home/bluebat/Build/KWRT/staging_dir/target-arm_cortex-a9_musl-1.1.14_eabi/include -I/home/bluebat/Build/KWRT/staging_dir/toolchain-arm_cortex-a9_gcc-5.3.0_musl-1.1.14_eabi/usr/include -I/home/bluebat/Build/KWRT/staging_dir/toolchain-arm_cortex-a9_gcc-5.3.0_musl-1.1.14_eabi/include/fortify -I/home/bluebat/Build/KWRT/staging_dir/toolchain-arm_cortex-a9_gcc-5.3.0_musl-1.1.14_eabi/include -Os -pipe -march=armv7-a -mtune=cortex-a9 -fno-caller-saves -fno-plt -fhonour-copts -Wno-error=unused-but-set-variable -Wno-error=unused-result -mfloat-abi=soft -iremap /home/bluebat/Build/KWRT/build_dir/target-arm_cortex-a9_musl-1.1.14_eabi/nginx-1.10.3:nginx-1.10.3 -Wformat -Werror=format-security -fstack-protector -D_FORTIFY_SOURCE=1 -Wl,-z,now -Wl,-z,relro -fvisibility=hidden -ffunction-sections -fdata-sections -DNGX_LUA_NO_BY_LUA_BLOCK' --with-ld-opt='-L/home/bluebat/Build/KWRT/staging_dir/target-arm_cortex-a9_musl-1.1.14_eabi/opt/usr/lib -L/home/bluebat/Build/KWRT/staging_dir/target-arm_cortex-a9_musl-1.1.14_eabi/opt/lib -L/home/bluebat/Build/KWRT/staging_dir/target-arm_cortex-a9_musl-1.1.14_eabi/usr/lib -L/home/bluebat/Build/KWRT/staging_dir/target-arm_cortex-a9_musl-1.1.14_eabi/lib -Wl,--dynamic-linker=/opt/lib/ld-musl-arm.so.1 -Wl,-rpath,/opt/usr/lib:/opt/lib -L/home/bluebat/Build/KWRT/staging_dir/toolchain-arm_cortex-a9_gcc-5.3.0_musl-1.1.14_eabi/usr/lib -L/home/bluebat/Build/KWRT/staging_dir/toolchain-arm_cortex-a9_gcc-5.3.0_musl-1.1.14_eabi/lib -znow -zrelro -Wl,--gc-sections' --without-http_upstream_zone_module
RNCTX
DD-WRT Novice


Joined: 26 Jun 2018
Posts: 5

PostPosted: Wed Jun 27, 2018 5:24    Post subject: Reply with quote
In my case I don't really care about the cgi directories since I'm just using it as a proxy. Yours works because of the pointing of those initial read+write files to /var

The Entware one doesn't, because they specify that packages must be wholly contained in /opt

So this is just as much a guide on how to Kong-ify an Entware package? I suppose... Wink
kernel-panic69
DD-WRT Guru


Joined: 08 May 2018
Posts: 14208
Location: Texas, USA

PostPosted: Wed Jun 27, 2018 10:36    Post subject: Reply with quote
Stable version is 1.14.0... unless you're all patched up.

https://nginx.org/en/security_advisories.html
RNCTX
DD-WRT Novice


Joined: 26 Jun 2018
Posts: 5

PostPosted: Wed Jun 27, 2018 14:35    Post subject: Reply with quote
Looks like nothing affecting 1.12.2+ (current version on Entware repo).
kernel-panic69
DD-WRT Guru


Joined: 08 May 2018
Posts: 14208
Location: Texas, USA

PostPosted: Wed Jun 27, 2018 15:56    Post subject: Reply with quote
RNCTX wrote:
Looks like nothing affecting 1.12.2+ (current version on Entware repo).


As I noticed, I guess I read that wrong, and/or they were intentionally ambiguous. My fond memories of nginx is it telling me, "No, you wait, you no need internet access, you been here for hour, you go!" Of course, that was about 10 years ago. Maybe things have improved since then, or maybe that was just the router's configuration that was crap... Rolling Eyes
RNCTX
DD-WRT Novice


Joined: 26 Jun 2018
Posts: 5

PostPosted: Thu Jun 28, 2018 3:19    Post subject: Reply with quote
I'm not sure of the history myself. Last time I worked with web servers (early 2000s) there was Apache way up here, and everyone else down there. With the Apache foundation splintering into lots of different projects, I suppose the door was left open for someone to come along and compete on "just a web server."

In this case it's the most lightweight option for an SSL reverse proxy that I know of. Built-in support for remote syslog is a big plus for putting it on a router, too.
kernel-panic69
DD-WRT Guru


Joined: 08 May 2018
Posts: 14208
Location: Texas, USA

PostPosted: Thu Jun 28, 2018 14:05    Post subject: Reply with quote
RNCTX wrote:
I'm not sure of the history myself. Last time I worked with web servers (early 2000s) there was Apache way up here, and everyone else down there. With the Apache foundation splintering into lots of different projects, I suppose the door was left open for someone to come along and compete on "just a web server."

In this case it's the most lightweight option for an SSL reverse proxy that I know of. Built-in support for remote syslog is a big plus for putting it on a router, too.


Definitely a good thing, lightweight. I think the router market definitely benefitted with the development of nginx and others... but it's yet another thing I need to learn eventually. Taking such a long hiatus has proven to be entertaining jumping back into it. Really itching to dig out all my antiques and tinker, but one thing at a time.
RNCTX
DD-WRT Novice


Joined: 26 Jun 2018
Posts: 5

PostPosted: Thu Jun 28, 2018 16:34    Post subject: Reply with quote
Here's my config, including remote syslog...

nginx.conf...

Code:

user  nobody nobody;
worker_processes  2;
error_log syslog:server=192.168.1.80,tag=nginx_proxy;
lock_file /tmp/var/lock/nginx.lock;
pid       /tmp/var/run/nginx.pid;


events {
    worker_connections  64;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    error_log syslog:server=192.168.1.80,tag=nginx_proxy;
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

server {
    listen 80;
    return 301 https://$host$request_uri;
    error_log syslog:server=192.168.1.80,tag=nginx_proxy;
    access_log syslog:server=192.168.1.80,tag=nginx_proxy,severity=notice combined;
}

server {

    listen 443;
    server_name home.mydomain.net;

    ssl_certificate           /opt/etc/ssl/pem/home.mydomain.net/cert.pem;
    ssl_certificate_key       /opt/etc/ssl/pem/home.mydomain.net/key.pem;

    ssl on;
    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_protocols TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
    ssl_prefer_server_ciphers on;

    error_log syslog:server=192.168.1.80,tag=nginx_proxy;
    access_log syslog:server=192.168.1.80,tag=nginx_proxy,severity=notice combined;

    location / {

      proxy_set_header        Host $host;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        X-Forwarded-Proto $scheme;
      proxy_pass          https://192.168.1.94;
      proxy_ssl_certificate           /opt/etc/ssl/pem/home.mydomain.net/cert.pem;
      proxy_ssl_certificate_key       /opt/etc/ssl/pem/home.mydomain.net/key.pem;
      proxy_read_timeout  90;
      proxy_ssl_session_reuse on;
      proxy_redirect      https://192.168.1.94 https://home.mydomain.net;
    }
  }
}


As mentioned in the OP it's still going to touch the lock, pid, and logs under var, but after it's running it will send over the logs to the syslog on the webserver behind the router running on 192.168.1.80. By default the syslog entries will be prepended with hostname (ddwrt) and optionally the tag included in each entry above. If the webserver is running ubuntu it has a network-capable syslog by default. You simply need to open up the listener in /etc/rsyslog.conf

The above makes them look like this when some script-kiddie is scanning your machine Wink...

Code:

Jun 28 10:33:07 dd-wrt nginx_proxy: 185.112.249.28 - - [28/Jun/2018:10:33:07 -0500] "GET / HTTP/1.0" 301 185 "-" "masscan/1.0 (https://github.com/robertdavidgraham/masscan)"


tl;dr: no writing, to ram or otherwise, other than the initial startup log entry, the lock file, and the pid file.

Since the connection will be SSL end-to-end the same keys must exist on both the router and the webserver(s).

After installing socat and ca-certificates from Entware's repo and acme.sh into /opt/bin, here's a shell script that you can run once per month to renew keys...

/opt/bin/ssl_renew.sh...

Code:
#!/bin/sh
export PATH=/opt/bin:/opt/sbin:$PATH
mount -o remount,rw /opt
rm -f /tmp/root/acme.log
acme.sh --renew --ca-path /opt/etc/ssl/certs --cert-home /opt/etc/ssl \
 --home /opt/root/.acme --log /tmp/root/acme.log -d *.home.mydomain.net --dns dns_aws --ecc
sleep 180
acme.sh --renew --ca-path /opt/etc/ssl/certs --cert-home /opt/etc/ssl \
--home /opt/root/.acme --log /tmp/root/acme.log -d home.mydomain.net --dns dns_aws --ecc
acme.sh --install-cert --ca-path /opt/etc/ssl/certs --cert-home /opt/etc/ssl -- \
home /opt/root/.acme --log /tmp/root/acme.log -d *.home.mydomain.net --ecc \
        --cert-file /opt/etc/ssl/pem/*.home.mydomain.net/cert.pem \
        --key-file /opt/etc/ssl/pem/*.home.mydomain.net/key.pem \
        --fullchain-file /opt/etc/ssl/pem/*.home.mydomain.net/fullchain.pem
acme.sh --install-cert --ca-path /opt/etc/ssl/certs --cert-home /opt/etc/ssl --
home /opt/root/.acme --log /tmp/root/acme.log -d home.mydomain.net --ecc \
        --cert-file /opt/etc/ssl/pem/home.mydomain.net/cert.pem \
        --key-file /opt/etc/ssl/pem/home.mydomain.net/key.pem \
        --fullchain-file /opt/etc/ssl/pem/home.mydomain.net/fullchain.pem
mount -o remount,ro /opt
exit 0;


Lets Encrypt supports wildcard certificates but only with dns verification. acme.sh has lots of pre-written scripts for various providers, as you can see in mine my domains are on AWS, so I'm using the dns_aws script. The dns provider script goes into whatever folder you specify as your acme home (in the script I explicitly point it to /opt/root/.acme) The sleep for 180 seconds is unfortunately unavoidable, acme.sh has a two minute sleep built in when it needs to put a verification key on the domain, so waiting for 160 seconds gives it plenty of time before starting any subsequent keys.

You will of course have to set up an API key for acme to use and specify it in the dns provider script. In AWS it's just a simple IAM role with permission to list and modify records in the zone. There are examples on the acme.sh github page for lots of providers.

On each of your servers you'll want a cron job that runs about five minutes after each renewal cron job runs to get the new certs over from the router by sftp or scp or some similar. If five minutes isn't real-time enough, the most obvious solution that comes to mind is having the router renewal script FTP the keys to each of your webserver(s) and using pure-ftpd on each webserver. Pure-FTPD has the option to fire shell scripts as triggers based on upload actions, so you could get really close to real-time that way.

Obviously this can't be completely write-free unless I want my ssl keys sitting on the ram disk waiting for the power to go out Wink. But only writing to the jffs once a month for a few bytes to renew SSL certs should be okay.

todo: Error handling on the ssl_renew script, and it really should mail me every time it runs to make sure that the cert renewal succeeded, lest my keys expire without me remembering to check them. msmtp is in the Entware repo too, that should work using a Gmail account I would think. Since acme.sh saves a log it would be simple to cat the log > body-of-email in another script that runs on the end of the ssl_renew script.
Display posts from previous:    Page 1 of 1
Post new topic   Reply to topic    DD-WRT Forum Index -> Broadcom SoC based Hardware All times are GMT

Navigation

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You can attach files in this forum
You can download files in this forum