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
username
etpassword
)
"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.