Static websites are everywhere nowadays, alongside them on their rise in popularity are the numerous app frameworks like Jekyll, Gatsby and Docusaurus that allow anyone to spin up and deploy documentation, a blog or simple website, often for for little or no cost.
An common pattern for this deployment is to use an object store ( AWS S3, Azure Object Store ) combined with a content distribution network (CDN) such as AWS CloudFront. Most providers of these services charge on usage and often have great free tier offerings for small sites
However, not every static site is intended to be public. Adding authentication is considerably greater in complexity than the site alone. Thankfully, this is where hamlet Modules really shine.
In this article, we will be stepping through the process of using two of the recently published hamlet modules - cfcognito and githubidp. Used alongside a hamlet Solution for a typical spa
deployment into AWS, these modules will include everything necessary to restrict access to our site with Github.
Before we get started, let’s cover off what you need prior to following along.
Prerequisites
You will need to have configured a hamlet Tenant, Account(s) and Product for our site, and have deployed all the “out-of-the-box” deployment-units:
hamlet deploy run-deployments -u baseline
The hamlet Modules used here are going to restrict access to your chosen GitHub team(s) in a specific GitHub organisation. Select an appropriate one to test with, and note them down as you'll need them shortly.
Within your selected Github organisation you will need to register a new OAuth Application. This will require admin access to the Organisation. Enter a placeholder Callback URL for the time being, we’ll update it along the way. Generate a clientSecret that we can use within our Solution.
Finally, you’ll need to build and publish into our Solution’s artefact registry a unique build for the githubidp module’s lambda function. Though already developed, the function is used to sign JWT’s and the private key for this is generated during the build. A Jenkins pipeline template for the build can be found here, and requires the following additions to our product.properties file:
APPLICATION_UNITS=<MODULE_ID>-lambda
# Code Properties
<PRODUCT>_<MODULE_ID>_LAMBDA_CODE_REPO=github-idp
# Where:
# ◦ <MODULE_ID> is the id parameter value in the module
# ◦ <PRODUCT> is the Id of our product in upper case
We've put together a basic Jenkins declarative pipeline template which will handle the build of the code and uploading the lambda zip file to our registry. When using the pipeline you will need to update the environment section to match your configuration. :
environment {
product_cmdb = '<product CMDB repo url>'
properties_file = '<product pipeline properties file>'
GITHUB_CREDENTIALS = credentials('github')
ENVIRONMENT = params['environment']
SEGMENT = '<segment>'
}
With that out of the way, let’s get on with it!
Initial Setup
Update your Solution with the configuration below.
This does the following:
- Adds 2 plugins:
- cfcognito which enables Cognito Integration for CloudFront Distributions
- github-idp which enables github federation with Cognito
- In out mgmt tier adds a cognito
userpool
which will be used for the integration. I’ve simply called pool
{
"Segment": {
"Plugins" : {
"cfcognito" : {
"Enabled" : true,
"Name" : "cfcognito",
"Priority" : 200,
"Required" : true,
"Source" : "git",
"Source:git" : {
"Url" : "https://github.com/hamlet-io/cloudfront-authorization-at-edge",
"Ref" : "master",
"Path" : "hamlet/cfcognito"
}
},
"githubidp" : {
"Enabled" : true,
"Name" : "githubidp",
"Priority" : 200,
"Required" : true,
"Source" : "git",
"Source:git" : {
"Url" : "https://github.com/gs-gs/github-idp",
"Ref" : "master",
"Path" : "hamlet/githubidp"
}
}
}
},
"Tiers": {
"mgmt" : {
"Components" : {
"pool" : {
"userpool" : {
"deployment:Unit" : "pool",
"Instances" : {
"default" : {}
},
"VerifyEmail" : false,
"DefaultClient" : false,
"Schema" : {
"email" : {
"DataType" : "String",
"Mutable" : true,
"Required" : true
}
}
}
}
}
}
}
}
Working with Plugins
If you haven’t used hamlet Plugins before, Plugins extend hamlet and can provide a wide range of functions, including modules which provide prebuilt solutions that can be added to your own solution. After adding a plugin it needs to be installed in your workspace so that it can be used:
# from the Product's Segment dir
hamlet setup
(Info) Generating outputs:
(Info) - generationcontract
(Info) ~ no change in generationcontract detected
(Info) - plugincontract
(Info) Differences detected:
(Info) - updating loader-generation-contract.json
(Info) - updating loader-plugincontract.json
(Info) Loading plugins from contract...
(Info) [*] id:cfcognito - name:cfcognito
(Info) [*] id:githubidp - name:githubidp
Deploy the updated solution to make sure the userpool is available
hamlet deploy run-deployments
Configuring Modules : githubidp+
Now that the plugins have been installed we can start using them. The first thing we will be using from the Plugins is the cognito_github_api
module from the githubidp
plugin.
This module adds an apigateway
and lambda
components to our Solution. The apigateway
uses the lambda
component to create a Github Authentication service which is supported by Cognito.
Add the following to the Solution to configure the module :
{
"Segment": {
"Plugins" : { /*...*/ },
"Modules" : {
"githubauth" : {
"Provider" : "githubidp",
"Name" : "cognito_github_api",
"Parameters" : {
"id" : {
"Key" : "id",
"Value" : "githubidp"
},
"tier" : {
"Key" : "tier",
"Value" : "api"
},
"githubClientId" : {
"Key" : "githubClientId",
"Value" : "<github-oauth-app-client-id>" /* update */
},
"githubClientSecret" : {
"Key" : "githubClientSecret",
"Value" : "<github-oauth-app-client-secret>" /* update */
},
"githubOrg" : {
"Key" : "githubOrg",
"Value" : "<github-org>" /* update */
},
"githubTeams" : {
"Key" : "githubTeams",
"Value" : [ "<github-team-name>" ] /* update */
},
"cognitoLink" : {
"Key" : "cognitoLink",
"Value" : {
"Tier" : "mgmt",
"Component" : "pool",
"Instance" : "",
"Version" : ""
}
}
}
}
}
},
"Tiers" : { /*...*/ }
}
Let’s briefly review the parameters we’ve provided to the module:
id
- This will become part of the name for components and deployment-units within the module.tier
- which tier the components defined within should be created within.githubClientId
- the Github OAuth Application client ID defined on the Github Org you want to restrict access to.githubClientSecret
- the clientSecret generated on the Github OAuth Application.githubOrg
- the Github organisation that the restricted group belongs to.githubTeams
- the Github Team within the specified organisation that should be authenticated.cognitoLink
- a link to our existinguserpool
component. This links the components in our Solution to those within the module.
Configuring Existing Components
With the cognito_github_api
module configured, we now configure our userpool
to include a userpoolauthprovider
which adds a trust between our Cognito userpool and the Github Authentication service ( the apigateway
we just added).
We configure the AuthProvider to use a new DeploymentProfile
added by the module, the DeploymentProfile
includes all the configuration required on the userpoolauthprovider
. The new profile is named after the id
parameter you’ve specified on our module configuration - in our case the profile name will be githubidp_githubprovider.
{
"Tiers": {
"mgmt" : {
"Components" : {
"pool" : {
"userpool" : {
"deployment:Unit" : "pool",
"Instances" : {
"default" : {}
},
"VerifyEmail" : false,
"DefaultClient" : false,
"Schema" : {
"email" : {
"DataType" : "String",
"Mutable" : true,
"Required" : true
}
},
"AuthProviders" : {
"github" : {
"Profiles" : {
"Deployment" : "githubidp_githubprovider"
}
}
}
}
}
}
}
}
}
With that done, let’s deploy our updated configuration and new deployment-units. We should deploy the lambda
followed by the apigateway
(as the API it deploy uses the functions
as a backend) and then finally the updated userpool
.
hamlet deploy run-deployments -u githubidp-lambda
hamlet deploy run-deployments -u githubidp-apigateway
hamlet deploy run-deployments -u pool
If you've used hamlet before you'll see that we don't have an openapispec for the apigateway
this is included as part of the module.
Configuring Modules: cfcognito
The cdnlambda
module in the cfcognito
plugin enables OIDC compliant authentication at the CloudFront layer. This will allow us to use Cognito to provide course - in or out - level access to all content behind the CloudFront distribution.
When using this authentication approach, your static site won't know which user is accessing the site. Make sure all your users should access the same content
Lets add the module to our solution:
{
"Segment": {
"Plugins" : { /*...*/ },
"Modules" : {
"door" : {
"Provider" : "cfcognito",
"Name" : "cdnlambda",
"Parameters" : {
"id" : {
"Key" : "id",
"Value" : "door"
},
"tier" : {
"Key" : "tier",
"Value" : "web"
},
"origin" : {
"Key" : "originLink",
"Value" : {
"Tier" : "web",
"Component" : "spa",
"Instance" : "",
"Version" : "v1"
}
},
"userpool" : {
"Key" : "userpoolClientLink",
"Value" : {
"Tier" : "mgmt",
"Component" : "pool",
"Client" : "door"
}
}
}
}
}
},
"Tiers" : { /* ... */ }
}
Reviewing each of the parameters here, we have:
id
- the value you provide here will determine the name of the deployment-units that come along with the module, and will be important to remember when it comes to creating links to the module.tier
- the tier that the components within the module should be created within.originLink
- a link configuration to tell the module what we’re going to use as thecdn
origin - in this case that’s ourspa
.userpoolClientLink
- a link to auserpoolclient
( we will add this client in the next couple of steps)
Update Lambda Region
The cdnlambda
module includes a lambda ( deployment unit door-lmb
) that must be specifically deployed into the us-east-1 ( this is required for cloudfront Lambda@Edge ). For this to happen we must override any other Region configuration you may have, but only for this one component. The below example will apply it product-wide for an components which belong to the deployment unit door-lmb
:
{
"Segment" : { /* ... */ },
"Product" : {
"door-lmb" : {
"Region" : "us-east-1"
}
},
"Tiers" : { /* ... */ }
}
Adding the site
As this module adds the cdn
into our Solution we need to add a userpoolclient
sub-component to our userpool
which will be used by CloudFront to access the userpool
.
So that it picks up the Callback URL information for the cdn
, we need to provide a Link on it to the cdn
component configuration inside of the Module.
The configuration for the Link is based off of the parameters you have provided to the Module, so you will know which Tier/Component/Instance combination to use. The Component's Id is a concatenation of the id
Module parameter value you've configured and "auth".
We also want to add in our spa
now, which has a pre-requisite of at least one CDNRoute link. We've had to hold off until the module was configured before we could add it into our Solution.
{
"Segment" : { /* ... */ },
"Product" : { /* ... */ },
"Tiers": {
"mgmt" : {
"Components" : {
"pool" : {
"userpool" : {
"Clients" : {
"door" : {
"AuthProviders" : [ "github" ],
"Links" : {
"cdn" : {
"Tier" : "web",
"Component" : "doorauth"
}
}
}
}
}
}
}
},
"web": {
"Components": {
"spa": {
"spa": {
"Instances": {
"default": {
"Versions": {
"v1": {
"deployment:Unit": "docs-v1"
}
}
}
},
"Links": {
"cdn": {
"Tier": "web",
"Component": "door",
"Instance" : "",
"Version" : "",
"Route": "default",
"Direction": "inbound"
}
}
}
}
}
}
}
}
Deploy
Now we’re all set to go.
First lets deploy all of the lambda functions that will be performing the Authentication process.
hamlet deploy run-deployments -u door-lmb
After that is finished deploying we can ensure the remainder of our changes are picked up by deploying all deployment units.
hamlet deploy run-deployments
Remember you can list the discovered deployment units with hamlet deploy list-deployments
if you would like to review what is available.
Retrieving the Github App Callback
Our Github OAuth Application needs to be updated with the Callback Url of the Cognito userpool’s hosted UI. Once you’re userpool
has been deployed, this can be retrieved with the hamlet CLI:
hamlet component describe-occurrences --name management-pool attributes
The result should be of the format <UI_BASE_URL>/oauth2/idpresponse
. You should now replace the placeholder URL with this value in the Github OAuth Application.
Testing Our Site
Once our Github OAuth App is configured, we can now test it all out! Retrieve the cdn
URL and test out your access.
hamlet component describe-occurrences --name web-door attributes
And with that, your Single Page Application should be secured behind Github OAuth!
When you're done, don't forget to stop any unwanted infrastructure.
hamlet deploy run-deployments -m stop