Shawn Patrick Vause

Software is my business, quality is my passion...

Shawn's Profile Image

AWS Certified
Full Stack Software Engineer

AWS Solutions Architect Associate Badge

Lift and Shift Legacy .Net Applications with Elastic Beanstalk - Part 4

We’ve come a long way in this series, but we have some additional work to do mainly to ensure end-to-end encryption after the application load balancer receives requests. This is necessary to get tighter adherence for HIPAA data transfer security on the AWS VPC network. This ensures even on the VPC, PHI is encrypted in transit. There is a lot of good information on AWS docs about this here, however it is very terse and jumps around a lot. I am going to my best to summarize how I understood all the necessary work to ensure our data security on the network. So let’s jump in.

EbExtensions


What exactly are EbExtensions? These are configuration files that allow us to perform advanced environment configuration steps on our server(s). We can use them to install software or setup end-to-end encryption with our application. At the root of your solution create an .ebextensions directory for these configuration scripts. Tooling on the EC2 instances will look for this directory and run the necessary scripts it finds with the .config file extension.

The first thing we need to do is terminate HTTPS on EC2 instances running .Net. To make this happen, we generate a certificate for use in self-signing requests after the AWS Application Load Balancer. I recommend doing this with the bash shell provided by WSL on Windows. A lot of this information was pulled from this documentation. First step, create a password.txt file with a strong randomly generated password value. Step 2, copy this file to a secure S3 bucket. Next run the following commands in order on the bash shell with the openssl package installed:

openssl genrsa 2048 > privatekey.pem
openssl req -new -key ./privatekey.pem -out csr.pem -config ./mycert.cnf -passin pass:$<password value from your file>
openssl x509 -req -days 365 -in csr.pem -signkey privatekey.pem -out server.crt
openssl pkcs12 -export -out mycert.pfx -inkey ./privatekey.pem -in ./server.crt -password pass:<password value from your file>


The above Windows certificate .pfx should be uploaded to S3 with the password file. We can now author our first ebextension script. In your .ebextensions directory add a new file and call it 1-https-instance-dotnet.config. There is some debate online whether scripts are ran in any particular order, I find it easier to reason about by numerically ordering them. This script will essentially reach out to S3 (so we assume your instance profile has these necessary permissions), download the .pfx and password.txt files we uploaded to the server, clean up existing IIS bindings for the site, install the certificate on the server using the password and certificate files downloaded, create a binding on the IIS site for port 443 using the certificate and finally punch a hole in the firewall on the server for port 443. It does this by using CloudFormation (written in yaml) to write a powershell script to the instances’ volume and then executes it.

files:
  "C:\\certs\\install-cert.ps1":
    content: |
        import-module webadministration
        ## Settings - replace the following values with your own
        $certkey = "mycert.pfx"      ## S3 object key for your PFX certificate
        $pwdkey = "password.txt"     ## S3 object key for a text file containing the certificate's password
        ##

        # Set variables
        $certfile = "C:\cert.pfx"
        $pwdfile = "C:\certs\pwdcontent"
        $bucket = "mybucketname"

        Read-S3Object -BucketName $bucket -Key $pwdkey -File $pwdfile
        $pwd = Get-Content $pwdfile -Raw

        # Clean up existing binding
        if ( Get-WebBinding "Default Web Site" -Port 443 ) {
            Echo "Removing WebBinding"
            Remove-WebBinding -Name "Default Web Site" -BindingInformation *:443:
        }
        if ( Get-Item -path IIS:\SslBindings\0.0.0.0!443 ) {
            Echo "Deregistering WebBinding from IIS"
            Remove-Item -path IIS:\SslBindings\0.0.0.0!443
        }

        # Download certificate from S3
        Read-S3Object -BucketName $bucket -Key $certkey -File $certfile

        # Install certificate
        Echo "Installing cert..."
        $securepwd = ConvertTo-SecureString -String $pwd -Force -AsPlainText
        $cert = Import-PfxCertificate -FilePath $certfile cert:\localMachine\my -Password $securepwd

        # Create site binding
        Echo "Creating and registering WebBinding"
        New-WebBinding -Name "Default Web Site" -IP "*" -Port 443 -Protocol https
        New-Item -path IIS:\SslBindings\0.0.0.0!443 -value $cert -Force

        ## Remove the HTTP binding
        #Remove-WebBinding -Name "Default Web Site" -BindingInformation *:80:

        # Update firewall
        netsh advfirewall firewall add rule name="Open port 443" protocol=TCP localport=443 action=allow dir=OUT

commands:
  00_install_ssl:
    command: powershell -NoProfile -ExecutionPolicy Bypass -file C:\\certs\\install-cert.ps1


From here until completion, our scripts are simple CloudFormation yaml files (unlike the previous powershell generating config file). For our next script, we will create the load balancer security group we referred to in an earlier post. Start by creating a new ebextension as follows:

option_settings:
  # Use the custom security group for the load balancer
  aws:elbv2:loadbalancer:
    SecurityGroups: '`{ "Ref" : "loadbalancersg" }`'
    ManagedSecurityGroup: '`{ "Ref" : "loadbalancersg" }`'

Resources:
  loadbalancersg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: loadbalancer-sg
      GroupDescription: Load Balancer SG
      VpcId: 'your vpc id'
      Tags: 
        -
          Key: "Name"
          Value: "loadbalancer-sg"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0


Now let’s tell Elastic Beanstalk to re-encrypt the traffic with another configuration script:

option_settings:
  aws:elbv2:listener:443:
    DefaultProcess: https
    ListenerEnabled: 'true'
    Protocol: HTTPS
  aws:elasticbeanstalk:environment:process:https:
    Port: '443'
    Protocol: HTTPS


Finally, tell Elastic Beanstalk to use the new security group:

Resources:
  # Add 443-inbound to instance security group (AWSEBSecurityGroup)
  httpsFromLoadBalancerSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
      IpProtocol: tcp
      ToPort: 443
      FromPort: 443
      SourceSecurityGroupId: {"Fn::GetAtt" : ["loadbalancersg", "GroupId"]}
  # Add 443-outbound to load balancer security group (loadbalancersg)
  httpsToBackendInstances:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId: {"Fn::GetAtt" : ["loadbalancersg", "GroupId"]}
      IpProtocol: tcp
      ToPort: 443
      FromPort: 443
      DestinationSecurityGroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
  # Add 80-inbound to instance security group (AWSEBSecurityGroup)
  httpFromLoadBalancerSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
      IpProtocol: tcp
      ToPort: 80
      FromPort: 80
      SourceSecurityGroupId: {"Fn::GetAtt" : ["loadbalancersg", "GroupId"]}
  # Add 80-outbound to load balancer security group (loadbalancersg)
  httpToBackendInstances:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId: {"Fn::GetAtt" : ["loadbalancersg", "GroupId"]}
      IpProtocol: tcp
      ToPort: 80
      FromPort: 80
      DestinationSecurityGroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}


Now your application traffic is completely encrypted from end-to-end. You will notice in IIS the application binding for port 443 is using your self-signed certificate from S3. Pretty awesome right?

Deploying the Source Code


Our infrastructure is complete, but it is time to take our application and put it on our servers. Start by compiling and creating a zip file with the following items:

  • Compiled application output directory (your publish command output)
  • aws-windows-deployment-manifest.json at root of zip
  • .ebextensions directory at root of zip


Wait, what is this manifest file you are probably asking? Well with every deployment on Elastic Beanstalk, we need a manifest that tells IIS how to setup the applications and virtual directory structure. For instance, if you had a child application or multiple applications on the same server you need to specify this setup. We chose to use the manifest format for an ASP.Net Core application even though this is a full-framework solution. The reason being, is it was simply easier to use. It treated the setup like a file system. There is an msdeploy variant, but it didn’t feel worth the added effort as our shop didn’t use this anywhere else. Here is what a manifest looks like for a parent child app:

{
  "manifestVersion": 1,
  "deployments": {
      "aspNetCoreWeb": [
      {
        "name": "root",
        "parameters": {
          "appBundle": "./root",
          "iisPath": "/",
          "iisWebSite": "Default Web Site"
        }
      },
      {
        "name": "child",
        "parameters": {
          "appBundle": "./child",
          "iisPath": "/child",
          "iisWebSite": "Default Web Site"
        }
      }
    ]
  }
}


To upload this zip you can use the UI or script it to be ran from an S3 bucket. For this tutorial we will use the UI. Go to your application and click the upload button on your application’s status page:

upload button

Choose the zip file and give it a label in the dialog below and hit deploy.

upload dialog

With some waiting you will finally have your application return to healthy status and be accessible by the load balancer URL or custom domain if you went for that extra mile!

Summary


With all this work and guidance I hope you successfully lifted and shifted your first full-framework ASP.Net application to AWS. It is not bad after you have done it a couple times, but it is not exactly the most intuitive process either. We did all this work with the console for illustration purposes only. I highly recommend when you nail down your configuration that you invest the time into scripting with an Infrastructure as Code (IaC) solution. We used Terraform and I kind of loved it. CloudFormation is really cryptic by comparison. Anyway, all feedback on this and the series is welcome. Hope it helps out someone else on their journey to the cloud!

Related Posts


comments powered by Disqus