Skip to main content

Ajouter une couche d'authentification simple à un Cloudfront

· 5 min read
Fabien Villemaine

Présentation

Comment ajouter une couche d'authentification simple à un site web hébergé sur un S3/Cloudfront AWS ?

C'est ce que je vais vous montrer ici, afin de reproduire le comportement de ce que pourrais faire un htaccess/htpasswd sur Apache.

Nous verrons plusieurs méthodes, grâce à l'utilisation de lambda@edge ou de fonction Cloudfront, à la main et via terraform.

Via la console

Création rôle/policy

La lambda aura besoin de droits pour s'exécuter, il faut donc en premier lieu créer le rôle qu'elle aura besoin.

Dans IAM, créer un nouveau rôle

create_role

Puis sélectionnez AWS Servce / Lambda

create_role_lambda

Ensuite créez une nouvelle policy

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}

Sélectionnez la policy à attacher

create_role_policy

Ensuite renseignez un nom pour terminer la création

create_role_name

Enfin éditez les Trust relationships du rôle nouvellement créé

create_role_thrust

Et ajoutez le service edgelambda

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com",
"edgelambda.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}

Création de la lambda

warning

⚠️ Attention à bien vous placer sur la région N.Viginia (us-east-1)

Dans Lambda, créez une nouvelle fonction

create_function

Sélectionnez Author from scratch, donnez-lui un nom, et sélectionnez le rôle créé précédemment

create_function_options

Coller le code ce dessous en spécifiant un username et password

"use strict";
exports.handler = function(t, e, a) {
var s = t.Records[0].cf.request,
i = s.headers,
n = new Buffer("".concat("USERNAME", ":").concat("PASSWORD")).toString("base64"),
o = "Basic ".concat(n);
if (void 0 !== i.authorization && i.authorization[0].value == o) a(null, s);
else {
a(null, {
status: "401",
statusDescription: "Unauthorized",
body: "Unauthorized",
headers: {
"www-authenticate": [{
key: "WWW-Authenticate",
value: "Basic"
}]
}
})
}
};

Puis déployez le code via le bouton Deploy

create_function_deploy

Publiez une nouvelle version

create_function_version

Sur votre Cloudfront, éditez le behavior

Renseignez l'ARN de la version de la lambda dans les associations

cloudfront_association

Via Terraform

Créez/éditez les fichiers suivants :

basic_auth.js (en modifiant les valeur username et password)

"use strict";
exports.handler = function(t, e, a) {
var s = t.Records[0].cf.request,
i = s.headers,
n = new Buffer("".concat("USERNAME", ":").concat("PASSWORD")).toString("base64"),
o = "Basic ".concat(n);
if (void 0 !== i.authorization && i.authorization[0].value == o) a(null, s);
else {
a(null, {
status: "401",
statusDescription: "Unauthorized",
body: "Unauthorized",
headers: {
"www-authenticate": [{
key: "WWW-Authenticate",
value: "Basic"
}]
}
})
}
};

lambda.tf

resource "aws_iam_role" "lambda" {
name = "basic_auth"
description = "Protect CloudFront distributions with Basic Authentication"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"edgelambda.amazonaws.com",
"lambda.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
EOF
tags = {
APPLICATION = var.application
ENVIRONNEMENT = var.environment
MANAGEDBY = "Terraform"
}
}

resource "aws_iam_role_policy" "lambda" {
name = "basic_auth"
role = aws_iam_role.lambda.id

policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
EOF
}

data "template_file" "basic_auth_function" {
template = "${file("${path.module}/basic_auth.js")}"
}

data "archive_file" "basic_auth_function" {
type = "zip"
output_path = "${path.module}/basic_auth.zip"

source {
content = "${data.template_file.basic_auth_function.rendered}"
filename = "basic_auth.js"
}
}

resource "aws_lambda_function" "basic_auth" {
provider = aws.cf
filename = "${path.module}/basic_auth.zip"
function_name = "basic_auth"
role = aws_iam_role.lambda.arn
handler = "basic_auth.handler"
source_code_hash = data.archive_file.basic_auth_function.output_base64sha256
runtime = "nodejs14.x"
description = "Protect CloudFront distributions with Basic Authentication"
publish = true
tags = {
APPLICATION = var.application
ENVIRONNEMENT = var.environment
MANAGEDBY = "Terraform"
}
}

cloudfront.tf

A ajouter dans le bloc default_cache_behavior du module aws_cloudfront_distribution

    lambda_function_association {
event_type = "viewer-request"
lambda_arn = aws_lambda_function.basic_auth.qualified_arn
include_body = false
}

Via une Fonction Cloudfront

Console AWS

Créez une nouvelle Functions

create_function_cloudfront

et renseignez le code ci-dessous (en précisant le user et password)

var USERNAME = 'user';
var PASSWORD = 'password';

var response401 = {
statusCode: 401,
statusDescription: 'Unauthorized',
headers: {
'www-authenticate': {value:'Basic'},
},
};

function validateBasicAuth(authHeader) {
var match = authHeader.match(/^Basic (.+)$/);
if (!match) return false;

var credentials = String.bytesFrom(match[1], 'base64').split(':');

return credentials[0] === USERNAME && credentials[1] === PASSWORD;
}

function handler(event) {
var request = event.request;
var headers = request.headers;
var auth = (headers.authorization && headers.authorization.value) || '';

if (!validateBasicAuth(auth)) return response401;

return request;
}

Puis publiez là

create_function_publish

Enfin vous pouvez associer cette Functions à votre Cloudfront

create_function_associate

Terraform

Créez/éditez les fichiers suivants :

basic_auth_function.js

var USERNAME = 'user';
var PASSWORD = 'password';

var response401 = {
statusCode: 401,
statusDescription: 'Unauthorized',
headers: {
'www-authenticate': {value:'Basic'},
},
};

function validateBasicAuth(authHeader) {
var match = authHeader.match(/^Basic (.+)$/);
if (!match) return false;

var credentials = String.bytesFrom(match[1], 'base64').split(':');

return credentials[0] === USERNAME && credentials[1] === PASSWORD;
}

function handler(event) {
var request = event.request;
var headers = request.headers;
var auth = (headers.authorization && headers.authorization.value) || '';

if (!validateBasicAuth(auth)) return response401;

return request;
}

cloudfront.tf

resource "aws_cloudfront_function" "basic_auth" {
name = "basic_auth"
runtime = "cloudfront-js-1.0"
comment = "basic_auth"
publish = true
code = file("${path.module}/basic_auth_function.js")
}

A ajouter dans le bloc default_cache_behavior du module aws_cloudfront_distribution

    function_association {
event_type = "viewer-request"
function_arn = aws_cloudfront_function.basic_auth.arn
}

Conclusion

Et voilà, si vous allez sur votre site, vous devriez avoir une demande d'ouverture de session

conclusion

Bien entendu cela reste une méthode assez basique, qui ne remplacera pas une vrai authentification, mais cela peut dépanner et servir des besoins simples.