2

I'm running multidocker on AWS EB. The setup looks like this:

Dockerrun.aws.json (xxx as placeholder for the domain. The app is not secure)

{
  "AWSEBDockerrunVersion": 2,
  "volumes": [
    {
      "name": "nginx-conf",
      "host": {
        "sourcePath": "/var/app/current/nginx.conf"
      }
    },
    {
      "name": "nginx-cert-conf",
      "host": {
        "sourcePath": "/var/app/current/nginx.cert.conf"
      }
    },
    {
      "name": "cert-bot-lib",
      "host": {
        "sourcePath": "/var/app/current/django_project/certbot"
      }
    },
    {
      "name": "lets-encrypt",
      "host": {
        "sourcePath": "/var/app/current/django_project/certs"
      }
    },
    {
      "name": "cert-challenge",
      "host": {
        "sourcePath": "/var/app/current/django_project/cert_challenge"
      }
    }
  ],
  "containerDefinitions": [{
      "name": "web-app",
      "image": "xxx",
      "command": [
        "…"
      ],
      "essential": true,
      "memory": 1000,
      "mountPoints": [
        …
      ]
    },
    {
      "name": "channels-app",
      "image": "xxx",
      "command": [
        "…"
      ],
      "essential": true,
      "memory": 1000,
      "mountPoints": [
        …
      ]
    },
    {
      "name": "nginx-proxy",
      "image": "nginx",
      "essential": false,
      "memory": 500,
      "portMappings": [{
        "hostPort": 443,
        "containerPort": 443
      }],
      "links": ["web-app", "channels-app"],
      "mountPoints": [{
          "sourceVolume": "nginx-conf",
          "containerPath": "/etc/nginx/conf.d/default.conf",
          "readOnly": true
        },
        {
          "sourceVolume": "lets-encrypt",
          "containerPath": "/etc/ssl/certs",
          "readOnly": true
        }
      ]
    },
    {
      "name": "nginx-cert-proxy",
      "image": "nginx",
      "essential": true,
      "memory": 250,
      "portMappings": [{
        "hostPort": 80,
        "containerPort": 80
      }],
      "mountPoints": [{
          "sourceVolume": "nginx-cert-conf",
          "containerPath": "/etc/nginx/conf.d/default.conf",
          "readOnly": true
        },
        {
          "sourceVolume": "cert-challenge",
          "containerPath": "/cert_challenge",
          "readOnly": true
        }
      ]
    },
    {
      "name": "certbot",
      "image": "yspreen/certbot-bash",
      "command": [
        "-c",
        "(([ -d /etc/letsencrypt/live ] && certbot renew --staging --webroot --register-unsafely-without-email --agree-tos --no-eff-email --webroot-path=/cert_challenge) || certbot certonly --staging --webroot --register-unsafely-without-email --agree-tos --no-eff-email --webroot-path=/cert_challenge -d xxx) && cp -Lr /etc/letsencrypt/live /etc/letsencrypt/live_cp"
      ],
      "essential": false,
      "memory": 250,
      "mountPoints": [{
          "sourceVolume": "lets-encrypt",
          "containerPath": "/etc/letsencrypt",
          "readOnly": false
        },
        {
          "sourceVolume": "cert-challenge",
          "containerPath": "/cert_challenge",
          "readOnly": false
        },
        {
          "sourceVolume": "cert-bot-lib",
          "containerPath": "/var/lib/letsencrypt",
          "readOnly": false
        }
      ]
    }
  ]
}

nginx.conf

upstream djangomain {
    server web-app:8000;
}

upstream djangochannels {
    server channels-app:8001;
}

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

    server_tokens off;
    disable_symlinks off;

    ssl on;

    ssl_buffer_size 8k;
    ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

    ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

    ssl_ecdh_curve secp384r1;
    ssl_session_tickets off;

    ## OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 1.0.0.1;

    ssl_certificate /etc/ssl/certs/live_cp/xxx/fullchain.pem;
    ssl_certificate_key /etc/ssl/certs/live_cp/xxx/privkey.pem;


    location /ws/ {
        proxy_pass         http://djangochannels/;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
        proxy_redirect     off;
    }
    location /static/ {
        alias /static_root/;
        autoindex on;
    }
    location / {
        proxy_pass         http://djangomain/;
        proxy_redirect     off;
        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-Host $server_name;
    }
}

nginx.cert.conf

server {
    listen      80;
    listen [::]:80;
    server_name  localhost;

    location ~ /.well-known/acme-challenge {
        allow all;
        root /cert_challenge;
    }
    location / {
        rewrite ^ https://$host$request_uri? permanent;
    }
}

Explanation:

The keyfiles need to be created by the certbot container. For this, the challenge is placed in the cert-challenge volume and exposed by the nginx-cert-proxy container. This works.

What doesn't work is applying the certs to the nginx-proxy container. It's failing with the following error:

nginx: [warn] the "ssl" directive is deprecated, use the "listen ... ssl" directive instead in /etc/nginx/conf.d/default.conf:17
nginx: [emerg] BIO_new_file("/etc/ssl/certs/live_cp/xxx/fullchain.pem") failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/ssl/certs/live_cp/xxx/fullchain.pem','r') error:2006D080:BIO routines:BIO_new_file:no such file)

Now the easy explanation would be that there's some typo, or nginx is messing up because of the symlinks from certbot.

But I've changed the nginx.conf to the default, started bash inside the container, and ls shows:

root@a58f8e87ca:/# ls /etc/ssl/certs/live_cp/xxx/fullchain.pem -lAh
-rw-r--r-- 1 root root 3.8K Oct 25 21:43 /etc/ssl/certs/live_cp/xxx/fullchain.pem

So the file exists. And regarding symlinks, it's definitely no link but an actual file. The modified certbot image allows to add a copy command, because the entry point is bash.

I'm completely lost. Why is nginx unable to read the file?

yspreen
  • 171
  • 2
  • 12

1 Answers1

1

Turns out it’s a timing issue.

When all containers start at the same time, the certs are not generated before nginx looks for them. When I manually enter the container to check, they have been generated in the meantime and it works. So it’s an issue of adding a

until [ -f $cert ]; do sleep 1; done

Before the nginx run command.

I didn’t think about this, because I forgot that the certs are deleted every time I run the setup.
The way I was testing this, was through EB CLI’s deploy command. This created a new version of the app every time, resulting in the storage to be flushed.

yspreen
  • 171
  • 2
  • 12