2018-02-24

Peanut Butter and Chocolate: Azure Functions CI/CD Pipeline with AWS CodeCommit (Part 3 of 6)

2018-02-22-01

Part 3

In Part 2 we created the Azure Functions Web App and the AWS CodeCommit repository. In Part 3 we will make the initial deployment from AWS CodeCommit to Azure Functions. To do that we need to create an AWS IAM User Account, grant it access to the CodeCommit repository, generate HTTPS Git Credentials for the user, and configure the Azure Functions Web App external git deployment. By the end of this post, we will be able to manually deploy from AWS CodeCommit to Azure Functions on demand. This is a critical step to make automating the process possible.

This part will be short and sweet. I want to keep the relevant pieces together regardless of their length.


Series Table of Contents


Create AWS IAM User for HTTPS Git Credentials

Now that we have an AWS CodeCommit Repository added, we need to create an AWS IAM User to access it. This user basically represents the Azure Functions Web App's permission in AWS. In order for Azure to pull from the AWS CodeCommit for a manual external git deployment, it will use HTTPS Git credentials assigned to the AWS IAM User.

First we create the AWS IAM User:

# Create a new AWS IAM User required for CodeCommit Git Credentials
$Params = @{
    UserName = $Settings.CCGitUser
    Force = $true
}
$AWSAssets['IAMUser'] = New-IAMUser @Params

We need to attach an inline policy to the IAM User to allow it Git Pull access to the AWS CodeCommit repository. HTTPS Git Credentials are somewhat less secure than other means. So we want to limit the possible damage by by not allowing write access or anything else.

# Create the IAM inline policy document and set it for the user
$PolicyDocument = @"
{
  "Version": "2012-10-17",
  "Statement" : [
    {
      "Effect" : "Allow",
      "Action" : [
        "codecommit:GitPull"
      ],
      "Resource" : [
        "$($AWSAssets.CCRepository.Arn)"
      ]
    }
  ]
}
"@
$Params = @{
    PolicyDocument = $PolicyDocument
    PolicyName = $Settings.CCGitUserPolicyName
    userName = $AWSAssets.IAMUser.UserName
    force = $true
    PassThru = $true
}
$AWSAssets['IAMPolicy'] = Write-IAMUserPolicy @Params

Now we need to verify the User was created and the inline policy has been attached with Pester.

# Validate the new user and inline policy were created successfully
Describe "AWS Git User" {
    It "Was successfully created" {
        $user = Get-IAMUser -UserName $Settings.CCGitUser
        $user.UserName | Should -BeExactly $Settings.CCGitUser
    }
    It "Has a policy attached" {
        $policy = Get-IAMUserPolicy -PolicyName $Settings.CCGitUserPolicyName -UserName $Settings.CCGitUser
        $PolicyObj = [uri]::UnescapeDataString($policy.PolicyDocument) | ConvertFrom-Json
       
        $PolicyObj.Statement[0].Effect | Should -BeExactly 'Allow'
        $PolicyObj.Statement[0].Action[0] | Should -BeExactly 'codecommit:GitPull'
        $PolicyObj.Statement[0].Resource[0] | Should -BeExactly $AWSAssets.CCRepository.Arn
    }
}

If it was successful it should look like this:

2018-02-24-01

As a side note, this user is not configured with any other passwords or access keys. It essentially can only perform a git pull operation on the targeted CodeCommit repository. But if you have some kind of mechanisms that grant all users some kind of access, you may want to exclude this user.


Initial Git Commit to AWS CodeCommit

Before continuing, we need to put something in the AWS CodeCommit repository. It is important that before doing this part that you have configured an SSH key for git to use with your AWS Admin account on your local system (More Info).

# Create the Git Directory and change location to it
$Null = New-Item -ItemType Directory -Path $Settings.GitDirectory -Force -ErrorAction 'SilentlyContinue'
Push-Location $Settings.GitDirectory

# Clone the empty CodeCommit repository
git clone $AWSAssets.CCRepository.CloneUrlSsh
Push-Location $Settings.CCRepositoryName

# Add an empty host.json file. This will let us confirm the Azure Web App Deployment settings are working
'{}' | Set-Content 'host.json'
git add -A
git commit -m 'Initial Commit'
git push --force

This creates the Git Directory on your system if it doesn't exist and then changes tot hat directory. Then it clones the CodeCommit repo (which is currently empty), changes location to the repository folder, creates a host.json file with an empty JSON object, commits the file, and pushes the commit to the CodeCommit repository. The empty host.json will help us ensure the manual external git deployment on the Azure Function Web App works when we configure it later.

Now we can validate our initial git commit with Pester.

# Verify the initial commit was successful
Describe "Initial Git Commit" {
    It "Was successful" {
        $diffs = Get-CCDifferenceList -AfterCommitSpecifier "master" -RepositoryName $Settings.CCRepositoryName
       
        $diffs | Should -HaveCount 1
        $diffs[0].ChangeType | Should -BeExactly 'A'
        $diffs[0].AfterBlob.Path | Should -BeExactly 'host.json'
    }
}

If successful, you should see this:

2018-02-24-02


Retrieve AWS IAM User's HTTPS Git Credentials

You may be wondering why I selected HTTPS Git Credentials instead of using an SSH key or STS. The options here are limited by both AWS and Azure. I tried to use an SSH key but there is a bit of a chicken and egg problem there: Azure lets you create an SSH key pair for SSH git deployments but does not let you set a username for it and AWS lets you associate an SSH public key to an IAM user but doesn't let you set the username. I spent a few hours trying to get this to work and was unsuccessful. Perhaps someone with more experienced configuring SSH keys on Kudu could figure this out.

I don't think it would possible to use STS and/or the AWS git credential manager due to limitations in the Azure Web App. I'd have to host the entire AWS CLI in it too, which means it would likely need to live in the CodeCommit repo too. Then I'd also have to keep that updated.

Ultimately, I settled on the HTTPS Git Credentials. For one thing, it is the recommended usage for external access to CodeCommit, which is exactly what we are doing here. It is less secure, yes, but we can make it somewhat secure by creating one IAM User per Azure Function App and granting that use only git pull access. That way if one user's git credentials are compromised, we can discard them and create new ones and only a single function app will be affected.

Unfortunately, this is one part that cannot be automated. Currently, there is no API available in the AWS CLI, AWSPowerShell module, AWS .NET SDK, or AWS REST API to generate the HTTPS Git Credentials. It can only be done in the AWS Management Console.

The following code generates the AWS Management Console URL you need to visit, displays a message on the console, and then prompts you for the HTTPS Git Credentials username and password:

# At this point we need to use the AWS web console to generate HTTPS Git credentials for AWS CodeCommit
# There is currently no public API for doing this outside of the AWS Web Console
$ConsoleUrl = 'https://console.aws.amazon.com/iam/home?region={0}#/users/{1}?section=security_credentials' -f
    $Settings.AwsRegion, $Settings.CCGitUser
Write-Host @"


Before continuing, you must generate HTTPS Git credentials for AWS CodeCommit for the $($Settings.CCGitUser) user.
Currently, this can only be done in the AWS web console.
You will need to use a web browser to login to the AWS console with an user that has IAM edit permissions.
Then navigate to

    $ConsoleUrl

Once you generate the HTTPS Git credentials for AWS CodeCommit, enter the generated username and password.


"@
$AWSAssets['CCGitUserCredentials'] = Get-Credential -Message "HTTPS Git credentials for AWS CodeCommit"

For further guidance see the Setup for HTTPS Users Using Git Credentials documentation.


Configure Azure Functions Web App Manual External Git Deployment

This is the first bit of glue between the Azure Functions Web App and the AWS Code Commit Repository. Azure Web Apps have quite a few options for automated deployments. For Peanut Butter and Chocolate we are using External Git. This allows you to manually trigger deployments from an external git repository. In our case, the External Git Repository is AWS CodeCommit. Git is git, though, so the fact that it's in AWS is not particularly interesting from that perspective. It's only in the context that web app is in Azure and the git repository is in AWS that there is any kind of intrigue.

Essentially what what happens when an external git deployment is triggered is that the Azure Web App does a git pull operation on the defined git URL. If you include credentials in the URL, git will use those to authenticate. After the web app performs the pull, it copies the contents to the wwwroot folder and probably does a service refresh of some kind. The result is the appearance that the functions code mirrors what is in the git repository.

Configuring this in an automated fashion isn't exactly straightforward. It is, however, very easy to configure from the Azure Portal. What I did to figure this out was to configure it in the Azure Portal and then look at the resulting automation template.

Anyway, on to the code. First we build the URL that includes the AWS IAM User's HTTPS Git credentials and the AWS CodeCommit repository HTTPS URL.

# Build the Git URL to include the Git user credentials
$builder = [UriBuilder]::new($AWSAssets.CCRepository.CloneUrlHttp)
$builder.UserName = $AWSAssets.CCGitUserCredentials.UserName
$builder.Password = [uri]::EscapeDataString($AWSAssets.CCGitUserCredentials.GetNetworkCredential().Password)

Now we can update the Azure Web App resource to include the deployment settings:

# Configure the deployment settings on the Azure Web App to use the AWS CodeCommit Repository
$Params = @{
    PropertyObject    = @{
        repoUrl = $builder.ToString()
        branch = "master"
        isManualIntegration = $true
        deploymentRollbackEnabled = $false
        isMercurial = $false
    }
    ResourceGroupName = $Settings.ResourceGroupName
    ResourceType      = 'Microsoft.Web/sites/SourceControls'
    ResourceName      = '{0}/web' -f $Settings.AppName
    ApiVersion        = '2015-08-01'
    force             = $true
}
$AzureAssets['WebAppDeploymentSettingsSet'] = Set-AzureRmResource @Params -ErrorAction 'SilentlyContinue'

In addition to configuring the settings, it also performs an initial deployment from the external git repository. This is why we updated our CodeCommit repository earlier.

We can now validate the deployment settings and the initial deployment with Pester.

# Validate the Deployment settings were successfully applied
Describe "Azure Web App Deployment Settings" {
    It "Was configured for CodeCommit" {
        $Params = @{
            ResourceGroupName = $Settings.ResourceGroupName
            ResourceType      = 'Microsoft.Web/sites/SourceControls'
            ResourceName      = '{0}/web' -f $Settings.AppName
            ApiVersion        = '2015-08-01'
        }
        $AzureAssets['WebAppDeploymentSettingsGet'] = Get-AzureRmResource @Params
        $Result = $AzureAssets.WebAppDeploymentSettingsGet

        $Result.Properties.repoUrl | Should -BeExactly $builder.ToString()
        $Result.Properties.branch | Should -BeExactly 'master'
        $Result.Properties.isManualIntegration | Should -BeTrue
        $Result.Properties.isMercurial | Should -BeFalse
        $Result.Properties.deploymentRollbackEnabled | Should -BeFalse
    }
    It "Successfully provisioned from AWS CodeCommit" {
        $AzureAssets.WebAppDeploymentSettingsGet.Properties.provisioningState | Should -BeExactly 'Succeeded'
    }
}

And if all was successful you should see:

2018-02-24-03

In the Azure Portal you will be able to see your Function App is now Read Only and that deployment options are configured with External Git.

2018-02-24-04

If you go into the deployment settings you will see the Initial Commit was successfully deployed:

2018-02-24-05

Also, by pressing the Sync button you can manually trigger a deployment from the AWS CodeCommit repository tot he Azure Functions Web App.


Part 3 End

if you are happy with simply having a way to manually trigger deployment from AWS CodeCommit to Azure Web Apps (Including Azure Functions) this is as far as you would need to go. The Azure Web App Deployment is successfully glued to the AWS Code Commit repository at this point. But this is only dipping Chocolate in Peanut Butter.

In Part 4 we will fire up an AWS lambda that will managed our automated deployments.