RSS
 

Posts Tagged ‘https’

Migrating from Bare Metal to Containers

14 Aug 2024

Running applications on bare metal

I rented my first VPS back around 2010, when I was only hosting a barebones PHP WordPress website and containerisation technology was still in its infancy. Today, I run several different websites, scripts, cron jobs and other applications from my VPS. Unfortunately, every single application runs on the bare metal (albeit as different users with appropriate permissions). This means that every app is running in the same environment as the host OS.

Here is a simple system architecture overview of my VPS:

atom.mobeigi.com Before Container Migration

This has some advantages:

  • Performance: No overheads.
  • Complexity: As simple as it gets. Even your grandma can set up a LAMP stack on a Ubuntu box.

But also some serious disadvantages:

  • Security risks: A compromised app gives the malicious actor direct access to the host OS.
  • Isolation: Applications aren’t isolated. Your hello world test123 application written in Zig can see and interact with your credit card provider service. That’s bad.
  • Portability: Moving to another VPS host is a PITA! You have to copy all the data and config, reinstall the same packages and make sure you haven’t missed that one config file you tweaked in some obscure directory 12 years ago as you watched Agüero sink in a late title winner.
  • Maintenance: Upgrading applications can be manual and break your app without warning. OS updates can break your app. Updating OS software or configs can break another app if they share dependencies.
  • Dependencies: Apps on the same host may depend on different versions of popular software (i.e. Python, Node, Apache) and you therefore must maintain multiple environments to support your applications.

I’ve put off migration for a long time because my setup was still very secure and it would take some effort to containerise all 19 applications I run. However, the time has come to migrate.

Docker: the greatest invention since sliced arrays

The goal is to use Docker as our containerisation technology. We will ensure every application is containerised inside of its own container.

This provides several fantastic advantages that eliminate the previous disadvantages:

  • Security risks: A compromised app is now self-contained within the container, limiting the blast radius.
  • Isolation: Apps can only see and talk to apps that you explicitly allow them to.
  • Portability: Back up your config in a git repo, back up your volume data and you can switch to a new VPS provider in under 30 minutes (or your Pizza’s free!).
  • Maintenance: It’s easy to upgrade your containers and spin them up / down at will.
  • Dependencies: Every container has exactly what it needs in its own environment.

The only two notable downsides are:

  • Performance: A container is not as performant as running on bare metal but Docker is extremely performant today. It is highly optimised, extra disk space is a delta compared to the image, CPU/RAM are low at rest. So the minor performance hit is very acceptable. Did I mention my VPS has a 1% load average? So, this is a moot point in my case anyway.
  • Complexity: Setting up Docker configuration is more complicated than running things on bare metal. But learning new things is fun, so is this really a con?

Containerising EVERYTHING

Okay, not everything. Some services, like Docker itself, sshd , and system auto-update cron jobs, obviously still remain on the host. I also decided to keep Lets Encrypt’s certbot on bare metal as it was highly recommended by their development team.

We want every application to have its own container. Each container should represent some service that can spin up and serve its purpose.

Consider a typical full stack CRUD application:

  • Frontend (i.e. React app)
  • Backend (i.e. Kotlin Spring Boot)
  • Data (i.e. PostgreSQL)

We have three distinct services here that each serve their own goal but work as a collective to power an application.

Reverse proxy it all with Traefik

Since we’re using containers for everything, we’d ideally like to constrain all container traffic as much as possible. We also don’t want to complicate things by having SSL communications happening in localhost.

Two concepts:

  • Every container is docker network only by default.
    • Then, localhost if host machine needs access.
    • Then, public if internet needs access.
  • Every container should be configured to send non-TLS data.
    • HTTP or non-tls data over a TCP/UDP port only.

We want to control what ports are exposed on our box.

In my case, only four main ports are exposed overall:

  • 22 (sshd)
  • 80 (http)
  • 443 (https)
  • 3306 (mariadb)
    • I do some remote DB management sometimes okay, sue me. On a serious note, a better approach is to setup a SSH tunnel to then talk to the DB instead of user/password auth over an open port but I opt for simplicity here as its my personal box. I do use the SSH tunnel approach in all serious projects and professional environments.

Traefik is a reverse proxy designed for containerised applications. We’re using v3.

It handles the following tasks for us:

  • Talk to each containerised app in Docker.
  • Redirect all incoming HTTP traffic to HTTPs
  • Route traffic coming it for our domains to a service (running in docker) and provide the output response back to the caller.
  • Set up SSL certificates so each HTTPs response is trusted.

It’s important to note Traefik is not a web server so any static files etc need to be served with some webserver running inside docker (i.e. nginx).

Fun fact: Traefik itself runs inside of a Docker container!

Restricting docker containers to localhost

A Dockerfile responsible for building a service that might serve data by exposing some port.

 

In compose.yml files, this exposed port can then be changed / mapped differently.

One common option is:

By using this, or omitting the ports  field, we indicate we don’t want the host to access this Docker container. Only the docker network will be used for communication between this container and other containers.

Another common option is:

In this case we explicitly map port 8123 on the host to port 80 in the container. This lets us access the served nginx web traffic.

Another option is:

In this case, we’re only specifying we want to reach port 80 in the container. Docker will automatically assign an unused port on the host machine. For example, it could choose port 54613.

Now, ideally we’d like to avoid hard coding the port mappings on the host. This is because only 1 host port can be mapped at a time and if you decide to map this yourself, you have to keep track of the ports and make sure you don’t reuse them etc. Instead, let Docker randomly map it for you and use docker CLI commands to find the port at runtime.

In my setup, I use a base compose.yml  to setup each container (this one is barebones and likely looks like the example one from repo documentation). Then I use a secondary compose.override.yml to override various config. I use the overrides to setup the Traefik labels for the container, networks as well as the ports. This has the added benefit of ensuring the base compose.yml can be used for local development and testing without any modifications.

Finally, this is how I might restrict the port to localhost for this nginx app:

Note the use of !override, this is needed to override previous port settings because Docker simply adds / combines multi-valued fields when using Docker Compose with multiple compose files. The use of !override ensures the previously specified port in our base compose.yml is not used.

127.0.0.1::80 is equivalent to 127.0.0.1:0:80. We’re ensuring the port is only accessible on the localhost interface and not 0.0.0.0 (all interfaces). We omit the host port (or use 0) to tell Docker we’d like to map a random host port and finally we specify the port on the container we’d like to access.

Make sure to check which ports are mapped to all interfaces so you don’t accidentally expose an application directly to the world instead of through Traefik:

This is useful because malicious actors use automated port scanning to find attack vectors. If your app is available on an open port on your server IP, it might attract probing, which is undesirable, especially if the app has a vulnerability.

You can use a tool like Censys to scan your servers IP to find any public ports and see what malicious actors can use against you.

censys results for atom.mobeigi.com

Pro tip: You should run Censys against your home network IP address if you host any applications (i.e. security cameras or private web server) on a NAS or other local machines.

Setting up SSL Certificates

This one is very easy (almost too easy…), simply define all your certificate files in the traefik_dynamic.yml.

 

Redirecting HTTP to HTTPS

My websites don’t support HTTP at all. Every website I serve is HTTPs only. Sorry I don’t make the rules, I just enforce them.

For this reason, we use some nice middleware in our traefik_static.yml that lets us redirect HTTP to HTTPS:

Now each compose file can use labels to invoke this middleware:

Redirecting www and non-www

Depending on the website, I usually also like to redirect www to non-www or vice versa.
This is the middleware I use for this in my traefik_dynamic.yml:

Invoked by labels:

 

Letting Traefik route to services on the host

You might find you need Traefik to route to services on the host that aren’t run inside of docker.

For example, my Centos distribution comes with cockpit built in. I would like to expose it at cockpit.mobeigi.com through Traefik but how do I reach it on the host.

In Linux, its best practice to bind host.docker.internal to host-gateway  inside your compose.yml then use that hostname to refer to the host machine from inside your docker containers. Here is an example for Traefik:

Now we can add some config to our traefik_dynamic.yml to route to our service:

And boom, cockpit.mobeigi.com is being routed through Traefik inside Docker to the host and back, served with HTTPs!

cockpit.mobeigi.com

Note: Additional configuration is needed for this specific case for cockpit, please see this post.

Docker Container Management

An optional but very nice tool to utilise is Portainer.

It is a Docker container that provides a web interface to:

  • Manage docker containers
  • Start/Stop/Restart/Spawn docker containers
  • See logs for container
  • Shell into container

I run it at: portainer.mobeigi.com

portainer.mobeigi.com

I’m a big fan (not literally, I’m a human but I enjoy the product nonetheless).

 

Final thoughts

After all is said and done, here is the new system architecture overview of my VPS:
atom.mobeigi.com After Container Migration

Here is what the Traefik dashboard looks like at traefik.mobeigi.com:

Traefik Dashboard mobeigi.com

In terms of resource usage, here is a before snapshot:

After the migration we have:

As you can see, we’re only using an additional 2GB of storage space (for images). CPU and memory usage are the same.

Recap:

  • Everything is containerised.
  • Traefik reverse proxy handles SSL and serves HTTPs.
  • Only the few necessary ports exposed.
  • We now run some cool Docker management tools like Portainer.
  • We moved away from apache to nginx for most websites and yielded small (~5%) performance improvements!
 
No Comments

Posted in Server

 

How to Hide your Servers Origin IP Address

23 Apr 2018

Overview

One project I maintain is frequently targetted by DDoS attacks. The power behind each attack ranges from weak attacks (100 Mbps) to very strong attacks (100+ Gbps). This project is essentially a side project and as a result I cannot afford to set up expensive DDoS mitigation solutions with failover servers. I suspect there are many that also face the same situation. A long time ago I decided to do everything I could to hide my servers origin IP in hopes of preventing my cheap web server from being bombarded with malicious traffic. The results from these changes have been fantastic!

Below is a summary of all the changes I made to hide my servers origin IP address for this project.

CloudFlare

Cloudflare Logo

One of the first changes I made was moving over my website to CloudFlare. CloudFlare provided me with many benefits (and all for free!). Not only did CloudFlare mask my servers origin IP by routing requests from users to my server, they also reduced my overall bandwidth costs and provided fast file caching.

The primary reason I signed up to CloudFlare was to mask my servers origin IP. Instead of pointing my domain to my origin server, I instead point my domain towards CloudFlare’s servers. CloudFlare then forwards user request to my origin server and my origin server replies to CloudFlare with a response. Finally, CloudFlare sends the response back to the user. During the initial connection between the user and CloudFlare, CloudFlare can handle malicious traffic it detects and may present users with a CAPTCHA to verify they are human. If the checks fail the request is denied and the traffic never reaches your origin server.

CloudFlare Overview

Firewall Whitelisting

Now that CloudFlare is set up and working, our web server should only be contacted by CloudFlare (and perhaps other services you use). SSH into your server and configure your firewall to only allow CloudFlare IP’s inbound/outbound access. CloudFlare’s IP ranges are available here and rarely change.

HTTPs

Another change you should definitely make is using HTTPs only for the connection between your server and CloudFlare. Make sure you use Full (strict) mode under the Crypto > SSL section in the CloudFlare dashboard. If you didn’t already know, you can get a SSL certificate for free and automate renewal easily using Let’s Encrypt. So there is no real reason to not make this change.

Cloudflare Crypto SSL Dashboard Very Strict

DNS Record

DNS Records can leak the origin IP if the origin IP appears in any records. In my case, I had a A  record called server pointing to the origin IP which had to be changed.

Mail Server

Hosting mail services on the same box as your web server is another problem. Mail servers will not trust mail that fails to pass certain anti-spam checks. Emails that are missing various headers like  X-Originating-IP  will likely not be trusted. Servers that don’t have the correct mail DNS records pointing to the origin server will not be trusted. Servers that fail a reverse DNS lookup will not be trusted. Typically a mail server will lookup the name of your mail server mail.example.com, retrieve its IP and compare it to the connection IP, then perform a reverse DNS lookup on that IP (using the PTR record) and ensure it points to the mail server name.

One solution here is to not run a mail server.
Another is to send your emails knowing they will be untrusted by mail servers (meaning they will always be sent to the spam folder or might not reach their destination at all).

However, if you need proper trusted mail services you must use an external mail service. Unfortunately, almost every external mail provider will include your origin servers IP address in the  X-Originating-IP header field to deter people from using their services to deliver spam. This means an attacker simply needs to get your web server to send an email (via the external mail provider) to retrieve the origin IP by inspecting the RAW email message.

Raw Email Message Origin IP

After some research, I discovered that Amazon Simple Email Service (or Amazon SES) did not include the  X-Originating-IP header field in outgoing mail. However, to ensure email services were not being used for spam, Amazon required you to verify your domain. Furthermore, Amazon monitor your bounce back rate and your complaint rate. If either metric creeps too high then your account status may be in jeopardy which may lead to service termination.

Outbound Requests

A very common issue is that may websites make outgoing requests to user provided endpoints. Making a request to a server you deem safe is fine (i.e. trusted APIs, another server you own) but user input SHOULD NEVER BE TRUSTED! In my case, users on my website could create an account and upload an avatar. Alternatively, they could enter the URL of an image on the web and my web server would fetch the image, store in locally and then use it as that users avatars. The problem here is that its not CloudFlare making the outgoing request, its your origin server. Therefore, an attacker could set up their own web server, enter a link to an image they are hosting, then look at their web server logs to see which IP just accessed their image. That IP is your origin server IP and is now leaked!

The simplest solution here was to not provide the alternative method.

Leaking Client IPs

One additional consideration to make is protecting the IP of your users. If users on your website can make posts and share content that is retrieved separately by the client over HTTP rather than sent by the origin server in the RAW HTML (i.e. images specified in an <img> tag), then they can steal the IP of any user that views that content. A user will load a page with the content on it, they will receive the RAW HTML payload from CloudFlare, next their browser will fetch any external references (i.e. to images in <img> tags) using the clients connection, thus leaking the clients IP to the external server hosting the content.

One solution to this is to use an image proxy which routes all images through your web server.
I ended up using camo as my image proxy which is open source and very easy to set up.

With an image proxy, all links to images are replaced with link that looks like this:

The image URL is then decoded (using a shared secret key) to retrieve the original external image which is then provided to the client via the origin server.

This protects the clients IP but we’ve just leaked the origins server IP.
To solve this, you will need to use a whitelist of trusted external websites that can be used to source images.
For example, you may only allow images be posted from imgur.com.
This may be a slight inconvenience to users but it protects both their IP and the servers origin IP.

Software Vulnerabilities

This is one is pretty obvious but stay up to date with security patches for any software you use and for the box itself.

Changing your Origin IP One Final Time

After making all the above changes, it will likely be necessary to change your origin IP by requesting a new IP from your server provider. This is because there are many websites out there that store historical DNS record information. Some in particular target CloudFlare protected websites. This change is especially required if you were already being attacked as the attackers will already have the servers origin IP and thus your new leakage prevention solution won’t matter.

Keep in mind that clever attacks may still find your new origin server IP using your old origin server IP. If your webserver displays your website when accessed directly by IP, an attacker could assume a newly allocated IP might be on the same /24 or /16 subnet. It would take no time at all to write a web scraper in Python to iterate over all IP’s in that range to search for a server that is responding to HTTP/HTTPS requests and has your logo (or any other identifying information) in the HTML source code. One way to help stop this is by disabling direct IP access to your website or by displaying a simple forbidden page for all direct IP accesses.

Impact

After making all of the above changed, I noticed a massive improvement. Prior to making the changes I received one medium size attack every 3 months. These attacks would take down my website and leave it down for the majority of the day until the attack stopped. After making these changes, I have received almost no significant attacks over the past 3 years. I did receive 1 very large attack that caused some disturbance but the web server still remained online throughout the attack. The best part is I’m only paying $20 for my server a month which is amazing given its overall traffic and the frequency of attacks. On the other hand, a proper DDoS mitigation solution with failover servers would cost hundreds a month and is only really viable for business solutions rather than side projects.

Overall, I’m very happy with the results.

 
1 Comment

Posted in Server

 

Fixing Mixed Content warnings using cronjobs

02 Dec 2015

So if you, like myself, have a HTTPs only website you may have noticed that your green bar, green label or security shield (image below) disappears if your webpage fetches an image from another website over HTTP and not HTTPS.

Chrome Secure Green Lock

HTTPS Secure Chrome Lock

Now this isn’t an issue in itself which is why all modern browsers only produce a small warning in the console.

Console Warning Message

Mixed Content Message in Console

Unfortunately, most browsers also remove the secure label which is unfortunate as most business websites want to display their secure logo for customer reassurance reasons if nothing else. Personally, I just think it looks cool so I like to keep it green.

Easy (Obvious) Solution

The obvious solution is obviously  to host the image yourself or move the image to a website that supports HTTPS (like imgur.com).

However! The real issue is when the image is being produced by some API and you do not have access to the source code of the script producing the image.

cronjobs!

Okay so in this case, we are using some API which regular updates an image.
We want our CRON job to run daily (or whenever required based on your needs) and to download that image and store it locally, so that your website has access to it (over HTTPs!)
This makes all the mixed content errors disappear.
I came up with the following bash script in my case:

Simply save this as some_name.sh and add a CRONjob to run the script at some interval (like daily at 3AM when your server isn’t being used much).

Check out this post on how to make cronjobs:
http://www.cyberciti.biz/faq/how-do-i-add-jobs-to-cron-under-linux-or-unix-oses/

 
No Comments

Posted in Server