Aliasing a Custom Domain to CodeCommit

by AlphaGeek

Mon, Jul 3, 2017


It’s always bothered me, mainly for aesthetic reasons, that AWS CodeCommit’s URLs are so damned ugly. Especially when writing code in Go, which uses the repository URL as unique package name.

I wish that AWS provided a way to alias a domain name to the code repo so my code urls could look like this: https://code.bluesoftdev.com/repo-name instead of https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo-name. There is no way to make this work with SSH urls. That will need to wait for Amazon to do some work.

To do this I created a CloudFormation .yml file. I will share the whole thing down below but let me explain the parts that were necessary to accomplish our goal. First, here is the initial section of the .yml file:

WSTemplateFormatVersion: "2010-09-09"
Description: Defines an SSL Certificate, CF Distribution, and Route 53 domain name setup to map a custom domain to the CodeCommit domain.
Parameters:
  DomainName:
    Type: String
    Description: The domain of the website being defined.
    AllowedPattern: ^([a-zA-Z]{1}[a-zA-Z0-9\-]{0,63}\.)+([a-zA-Z]{2,6})$
    ConstraintDescription: Must be a valid domain name.
  HostedZone:
    Type: String
    Description: The name of the Route53 hosted zone.
    AllowedPattern: ^([a-zA-Z]{1}[a-zA-Z0-9\-]{0,63}\.)+([a-zA-Z]{2,6})\.$
    ConstraintDescription: Must be a valid hosted zone name.
Resources:
  ...

This defines two parameters that must be passed in so that formation can accomplish it’s job. The DomainName variable specifies the DNS name that will be mapped to the AWS CodeCommit server. The HostedZone parameter specifies the name of the Route53 HostedZone that will have the DNS entry create/updated in.

Next we have the SSL Certificate Resource:

  SSLCertificate:
    Type: "AWS::CertificateManager::Certificate"
    Properties: 
      DomainName: !Ref DomainName

This is very straightforward, a new certificate will be created that will be valid for the domain specified. This may not suit you. For instance, I use my *.bluesoftdev.com SSL cert instead of creating a new one. AWS Certs are free but can only be used with AWS resources, like CloudFront. You will need to decide what the best route here is for your use case. If you want to pass the SSL cert you will still need to load it into IAM before it can be used by CloudFront.

  GitDistribution:
    Type: "AWS::CloudFront::Distribution"  
    Properties: 
      DistributionConfig:
        Enabled: true
        Aliases:
          - !Ref DomainName
        Comment: !Ref DomainName
        DefaultCacheBehavior:
          TargetOriginId: !Sub
            - "CodeCommit-${DomainName}"
            - DomainName: !Ref DomainName
          AllowedMethods: ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
          DefaultTTL: 0
          MaxTTL: 0
          MinTTL: 0
          ViewerProtocolPolicy: redirect-to-https
          ForwardedValues:
            Headers: ["*"]
            QueryString: true
          Compress: true
        Origins:
          - Id: !Sub
              - "CodeCommit-${DomainName}"
              - DomainName: !Ref DomainName
            DomainName: git-codecommit.us-east-1.amazonaws.com
            OriginPath: /v1/repos
            CustomOriginConfig:
              OriginProtocolPolicy: https-only
              OriginSSLProtocols:
                - TLSv1
                - TLSv1.1
                - TLSv1.2
        PriceClass: PriceClass_100
        HttpVersion: http2
        ViewerCertificate:
          AcmCertificateArn: !Ref SSLCertificate
          MinimumProtocolVersion: TLSv1
          SslSupportMethod: sni-only

This is the definition of the CloudFront distribution that does the real work here. A CF Distribution is a combination of some base config, Origins, and Behaviors. The Origin is setup to point at the git-codecommit.us-east-1.amazonaws.com domain and adds an OriginPath that removes to the need to add in the /v1/repos portion of the repo URLs. The Default behavior targets the Origin we created and allows all the methods and headers to be passed through. This actually turns off all caching.

This is one of the downfalls of this strategy. The CF distribution charges bandwidth costs on the bandwidth used between the client and the CF servers and it charges for traffic between CF and your origin. So you get charged twice for all the bandwidth. This will still not amount to much if you are a small team. But a bigger team might have to do somekind of cost analysis to make sure it is cost effective. I leave that as an exercise for the reader.

Lastly we setup the Route53 entries:

  DNSEntries:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      Comment: Map DNS Name to CF resources.
      HostedZoneName: !Ref HostedZone
      RecordSets:
      - Name: !Ref DomainName
        Type: CNAME
        ResourceRecords:
          - !GetAtt GitDistribution.DomainName
        TTL: '3600'

Again this is very straightforward. The DomainName entry is added as a CNAME to the GitDistributions domain name so that the SSL handshaking can work. I found that “Alias” entries did not do this properly, I think because the DNS name of the ultimately backing server was being hidden or something like that. Anyway, this worked.

I have checked in the code along with some supporting scripts here: alias-codecommit. Please check it out. I am open to PRs. I would love to flesh this out some more but it works for what I need right now so I will be moving on to other things.

UPDATE: (7/9/2017)

After doing this the work I did in Automating Static Website Deployments Part 2 stopped working. The problem is that the service role credentials that are provided to the CodeBuild were not getting passed to GIT since the domain name had changed on the submodule urls. To fix this, after many hours of trial and error, I figured out how to pass some CodeCommit HTTPS credentials into the git config. Here is the updated scripts/pre_build.sh script that was developed in the above referenced article.

#!/bin/bash
DIR="$(cd $(dirname "${0}"); pwd)"

. "${DIR}/set_environment.sh"

rawurlencode() {
  local string="${1}"
  local strlen=${#string}
  local encoded=""
  local pos c o

  for (( pos=0 ; pos<strlen ; pos++ )); do
     c=${string:$pos:1}
     case "$c" in
        [-_.~a-zA-Z0-9] ) o="${c}" ;;
        * )               printf -v o '%%%02x' "'$c"
     esac
     encoded+="${o}"
  done
  echo "${encoded}"
}

git config --global credential.helper "store"


URL_GIT_USER_NAME=$(rawurlencode "${GIT_USER_NAME}")
URL_GIT_USER_PSW=$(rawurlencode "${GIT_USER_PSW}")
echo "https://${URL_GIT_USER_NAME}:${URL_GIT_USER_PSW}@code.bluesoftdev.com" >"${HOME}/.git-credentials"
git submodule update --init

The updated script now requires that environment variables be provided containing the GIT username and GIT password. I created a “codebuild-user” user in IAM and gave it readonly permission to all repos in CodeCommit. You can be as specific or general as you want but I would not give this user more than read access since it’s password will be residing in the CodeBuild config. The username and password have to be URL encoded in case they contain characters that are not allowed. I got this bit of bash code from this Stack Overflow answer.