Setting up HTTPS on an AWS Load Balancer

Context

This was my first time setting up HTTPS using a real certificate rather than a self-signed one.  This way you get that nice lock symbol + https in your browser without the user seeing the obnoxious “do you trust this site, continue unsafely” warnings.

Steps

Generate a Key Pair

Note that for common name you should use the DNS name that you will be using for the website (or load balancer in front of the website).  This is important.  E.g. *.appname.yourcompany.com.  This one is in case of a wildcard cert to handle anything in front of the app-name as well.

openssl req -new -newkey rsa:2048 -nodes -keyout appname.key -out appname.csr

Do not lose this key pair! Store it somewhere safe and backed up.

Request a Certificate from DigiCert/etc

Go to DigiCert or whatever service your company uses.  Request a standard SSL certificate using the files you generated above.

For the case of a wild-card cert, you will want to add Subject Alternative Names (SANs) for appname.yourcompany.com and *.appname.yourcompany.com.

If you get odd failure messages, these sites tend to have something to validate your CSR.  In my case, I think it made me go remake the CSR without a password before it would accept it.  This was only obvious from the validator; the initial messages were just confusing.

Note: The server name and location fields don’t seem to matter much.  I just put AWS for location and my app name with some environment suffix for the server name (nothing exists with this name).

Add Your Certificate to the AWS Certificate Manager

Once your certificate comes back from Digicert, you can upload it to the Certificate Manager in AWS.  The body is the certificate returned to you from Digicert/etc.  The private key is the one you generated above.  The certificate chain just needed the “intermediate certificate” that Digicert returned to me along with the new certificate (and only that, don’t put your certificate in there as well).

Create Your Load Balancer

Go to EC2 and create a new network load balancer.  Add a TLS (Secure TCP) listener and on the security settings page, pick “Choose a certificate from ACM (recommended)”.  Then you can select your certificate.

Create Your DNS Name

Contact your system admins to create a DNS name for you with the name you used in your certificate request (e.g. appname.yourcompany.com from above).  Point it at the IP of the load balancer.  You will have to resolve the load balancer name to an IP for this (I’m not sure that is best practice, so you may want to read up more before following this last step).

All Done!

Assuming your load balancer points to an application now, you can use https to talk to talk to the DNS name of the load balancer, and the load balancer will take it, terminate the TLS, and forward to your application.  So, you’re good!

 

Angular 7 Material Modal

Overview

Getting modals working in Angular + Material took me a lot longer than I expected.  But I must confess that the documentation for them here -> https://material.angular.io/components/dialog/overview was spot on.  You just have to actually read all of it.

I’m going to provide a shorter crash course here showing 100% of what you need code-wise.  I suggest you refer to that main link to understand everything fully though.

Requirements Summary

To get a modal working, assuming you already have Angular + Material working, you need to do the following.  This assumes you are just using the root @NgModule in app.module.js, but you can use other modules if you like.

  • import MatDialogModule at the top of your app.module.ts and it to your imports array in the same file.
  • import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} and {Inject} in your current page’s .ts file.
  • Create a new HTML file for your modal at the same level as your current page.
  • Add a dialog component into your typescript file.
  • Write code to trigger your dialog to open.
  • Import your dialog component back in your app.module.js and register it as a declaration *and* as an entry component (you probably have to add entry components as they’re not there by default).
    • This is because dialogs are created on-the-fly and angular needs extra information to deal with ad-hoc components.

Detailed Code Example

app.module.ts

Again, if you have a multi @NgModule angular app, you can still refer to this but you may put the content in other modules.

//... (normal imports left out for brevity)
import { MatDialogModule} from '@angular/material';
import { DialogOverviewExampleDialog } from "./cs-job-monitor/cs-job-monitor.component"

@NgModule({
  declarations: [
    ...,
    DialogOverviewExampleDialog
  ],
  imports: [
    ...
    MatDialogModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  entryComponents: [
    DialogOverviewExampleDialog
  ],
})
export class AppModule { }

cs-job-monitor.component.ts

This is just one of the pages in my angular project as generated by the angular CLI. It just happens to be called cs-job-monitor but that isn’t important to you.

//... (normal imports left out for brevity)
import { Inject } from '@angular/core';
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';

//Your normal page component.
@Component({
  selector: 'app-cs-job-monitor',
  templateUrl: './cs-job-monitor.component.html',
  styleUrls: ['./cs-job-monitor.component.styl']
})
export class CsJobMonitorComponent {

  constructor(private http: HttpClient, public dialog: MatDialog) {
    //Normal work.
  }

  //In my case, I am opening the modal on the "on select row" event
  //of an angular grid (ag-grid).  But this is not important, just look
  /at how it opens.
  onSelectionChanged(event: Object) {
    const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
      data: event["api"].getSelectedRows()
    });
  }
}

//Here's your dialog component.  Mine is still named after the example one from
//angular's documentation page (I'll fix that!).  But it works fine.
@Component({
  selector: 'dialog-overview-example-dialog',
  templateUrl: 'dialog-overview-example-dialog.html'
})
export class DialogOverviewExampleDialog {

  constructor(
    public dialogRef: MatDialogRef,
    @Inject(MAT_DIALOG_DATA) public data: DialogData) {}

  onNoClick(): void {
    this.dialogRef.close();
  }
}

dialog-overview-example-dialog.html

Here is the HTML that appears in your dialog when it pops up. For now, I just have it displaying the object you gave it as data as JSON. In this case, as it will display the selected rows from the ag-grid I was using to call onSelectionChanged(). But I’m not bothering to add that here.

<pre>
  {{data | json}}
</pre>

Apache HTTPD Proxy Add CORS Headers to Use Remote API

I just suffered quite a lot while trying to use an API someone provided me to hack up a dashboard.  This was because it was missing the necessary headers to enable CORS to work from a web-app.

What is CORS?

Suffice to say that CORS is  away to allow your web-app to request resources from a server other than the one it came from.

While we’re not doing Java here, the Spring documentation explains the same origin policy which stops JavaScript from being able to invoke calls for resources from a different origin than it came from.  James Chamber’s blog explains the CORS concepts in terms of a guest list and makes it very comprehensible right here.

Make My Web-App Work!

Chances are, if you’re here, you’re currently writing a web-app (maybe Angular or React?), and you are trying to get data from a URL, and it is failing with the word CORS in your dev-tools console.

This is because the “Access-Control-Allow-Origin” header is not being added by the server.  So, your client app is not allowed to use it.  In terms of the blog referred to earlier, you’re not on the guest list.

If you’re in a company, the correct fix for this is to have the API add the header with the proper origins, or a * if you’re not overly worried about security.  But, often you don’t have time for that, or you’re not in control of the API.  In that case, you can use a proxy!

Assuming you are working on Linux and you’ve already installed Apache (HTTPD), it is pretty easy to fix this with a proxy.  Here is a perfect example. Just add this at the bottom of /etc/httpd/conf/httpd.conf, ensure there is a “Listen 80” in your file, and then restart Apache with “sudo systemctl restart httpd”.

<br /><LocationMatch "/SomePath">
ProxyPass http://:8080/SomePath
Header add "Access-Control-Allow-Origin" "*"


This will make sure all requests to this server, whether they’re from it or another host, which target :8080/SomePath will be proxied to http://:8080/SomePath and will return to the client with the extra header “Access-Control-Allow-Origin” “*”. This will let your web-app correctly talk to URLS under http://:8080/SomePath even though it doesn’t have CORS headers itself.

Note that you can do / instead of SomePath to target the whole server, and of course, you can change the ports/etc.

I hope this helps you!

Oh, and one more thing… If you installed Apache and are having trouble running it in the first place, maybe you’re running SELINUX, in which case you might want to try “/usr/sbin/setsebool -P httpd_can_network_connect 1”. That nailed me before I went on to work on the rest of this.

Also,  if you want to use a different port and it’s not working on Centos/etc, you may need to open the port in SELINUX “semanage port -a -t http_port_t -p tcp 8222”.