/ aws

Securing an AWS ALB with HTTPS

There are a few little tricks to securing an AWS Load Balanced site so I thought it was worth documenting as I couldn't find a single source that explained this simply enough for my old brain.

My Scenario

I have an ASP.Net Core 2.0 Web API, hosted in an ECS container (docker) on an Amazon Linux instance, with an ALB balancing the huge (not) load. No Cloudfront or other CDN. No nginx, just trusting kestrel for now :/

Im also running IdentityServer4 in a similar setup as above.

Disclaimer

I'm no security expert! I may use the wrong words here and there.

Create a Certificate

SSL requires a certificate! :) You can create this manually using the ACM console. I created a wildcard cert as I have 3 sites under each test & prod domain.

Trick: Wildcard certs only work at a single level so *.barney.fred will work for wilma.barney.fred but not for betty.wilma.barney.fred.

The cert needs to be validated before you can use it. You can do this via DNS (route53 in my case) or email. Clearly, you want to use DNS if you want this process automated. I recommend terraform:

resource "aws_acm_certificate" "cert" {
  domain_name       = "*.${lookup(var.base-domain, var.environment-name)}"
  validation_method = "DNS"
}

The vars above are like:

variable "environment-name" {
  description = "test or prod"
}

variable "base-domain" {
  type = "map"

  default = {
    test = "test.example.com"
    prod = "example.com"
  }
}

Using the Certificate

To use the certificate on your ALB (not sure about old-school ELB's - probably similar) open the EC2 console and find your load balancer. Under listeners, create a new one for HTTPS and select the certificate you just created. You should end up with something like this:
ALBListeners
The HTTPS listener is using port 443. Depending on the site you are protecting, you can probably delete the port 80 listener. For web APIs Microsoft recommend doing this and for simplicity that seems logical.

To automate this with terraform is trivial. Firstly you need the arn for the cert. I create my initial resources separately so I need to lookup the cert:

data "aws_acm_certificate" "cert" {
  domain_name = "*.${lookup(var.base-domain, var.environment-name)}"
  most_recent = true
}

Then use it for your listener:

resource "aws_alb_listener" "https" {
  load_balancer_arn = "${aws_alb.ecs-load-balancer.arn}"
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  
  certificate_arn   = "${data.aws_acm_certificate.cert}"

  default_action {
    target_group_arn = "${aws_alb_target_group.tg.arn}"
    type             = "forward"
  }
}

App changes

If you try to run your app now, it may work just fine. In the case of my API it did, but I'm also running an identity server and the wellknown configuration returned by it still referred to http:// which brokeded my front end SPA.

You really need to tell your app that HTTPS has been offloaded to HTTP. There are some headers that will arrive with requests to tell you this. Load Balancers, Proxies and Web Servers will include these. You just need to deal with them. Thankfully Microsoft did the hard work here and provided some middleware. The following worked for me:


public void Configure(IApplicationBuilder app) 
{
    ...
    // add this BEFORE pretty much everything else I guess :?
    app.UseForwardedHeaders(GetForwardedHeadersOptions());
    ...
}

private ForwardedHeadersOptions GetForwardedHeadersOptions()
    {
      ForwardedHeadersOptions forwardedHeadersOptions = new ForwardedHeadersOptions()
      {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
      };

      forwardedHeadersOptions.KnownNetworks.Clear();
      forwardedHeadersOptions.KnownProxies.Clear();
      return forwardedHeadersOptions;
    }

(acknowledgement: I got this from ... I can't remember... so thank you if it was you).

I don't have SSL working locally and I'm not using the IIS shizzle so I couldn't test it locally. I just added this code and deployed to ECS and it worked first time. I got lucky. I hope you do to :)

References

MSDN covers ASP.Net Core deployment very well, but, unsurprisingly, they don't talk about AWS.
https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/?view=aspnetcore-2.0&tabs=aspnetcore2x

AWS Certificate Manager: https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html