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

Puis sélectionnez AWS Servce / 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

Ensuite renseignez un nom pour terminer la création

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

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
⚠️ Attention à bien vous placer sur la région N.Viginia (us-east-1)
Dans Lambda, créez une nouvelle fonction

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

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

Publiez une nouvelle version

Sur votre Cloudfront, éditez le behavior
Renseignez l'ARN de la version de la lambda dans les associations

Via Terraform
Créez/éditez les fichiers suivants :
basic_auth.js (en modifiant les valeur
usernameetpassword)
"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

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à

Enfin vous pouvez associer cette Functions à votre Cloudfront

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
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.
