For most of my sites that provide an administrative back-end, I try to place them behind a client-side SSL challange. This way, the only way someone can access the administrative web portal, is to present a valid client certificate signed by my personal CA. Most of this is done in nginx.
The goal is to allow HTTPS traffic to the CMS without the need for a client certificate, but restrict the admin portal. Here are both the Ghost and nginx configs for client-side SSL:
- While in the Ghost configuration file, ensure you include the "admin" section:
{ "url": "https://blog.fqdn", "admin": { "url": "https://admin-portal.fqdn" }, "server": { "port": ...
- Restart Ghost. This can be done using the Ghost-cli or by using the custom FreeBSD RC script.
Next we move to nginx. In this example, the Ghost CMS is hosted on a different machine and the nginx host will proxy traffic to it:
upstream ghost_upstream {
server 172.16.1.1:2368;
}
server {
listen 80;
server_name blog.fqdn admin-portal.blog.fqdn;
return 301 https://$server_name$request_uri; # enforce https
}
server {
listen 443 ssl;
ssl_certificate certs/ghost.crt;
ssl_certificate_key certs/ghost.key;
server_name blog.fqdn;
location / {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass ghost_upstream;
proxy_redirect off;
client_max_body_size 10m;
client_body_buffer_size 128k;
}
location ~ ^/(?:ghost|signout) {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass ghost_upstream;
add_header Cache-Control "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0";
proxy_set_header X-Forwarded-Proto https;
}
}
server {
listen 443 ssl;
ssl_certificate certs/ghost.crt;
ssl_certificate_key certs/ghost.key;
ssl_client_certificate certs/rootca.crt;
server_name admin-portal.blog.fqdn;
ssl_verify_client on;
ssl_verify_depth 2;
expires 0;
add_header Cache-Control private;
if ($ssl_client_s_dn !~ "CN=My Full Name") {
return 403;
}
location / {
proxy_set_header X-Forwarded-Host $host;
#proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass ghost_upstream;
proxy_redirect off;
client_max_body_size 10m;
client_body_buffer_size 128k;
}
location ~ ^/(?:ghost|signout) {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass ghost_upstream;
add_header Cache-Control "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0";
proxy_set_header X-Forwarded-Proto https;
}
}
You'll notice that the first and second server
sections are fairly similar except for the following section:
ssl_client_certificate certs/rootca.crt;
ssl_verify_client on;
ssl_verify_depth 2;
expires 0;
add_header Cache-Control private;
if ($ssl_client_s_dn !~ "CN=My Full Name") {
return 403;
}
Caveat: You will need to create your own CA and client certificates. Both CA and client certificate will need to be imported into your browser.