Notre cultureNos solutionsNotre valeur ajoutéeNous rejoindreBlogContact

Ajouter une couche d'authentification simple à un Cloudfront

F
Fabien
··5 min de lecture

Comment ajouter une authentification HTTP Basic Auth à un site web hébergé sur S3/CloudFront AWS, via Lambda@Edge ou CloudFront Functions, à la main et via Terraform.

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
create_role

Puis sélectionnez AWS Servce / Lambda

create_role_lambda
create_role_lambda

Ensuite créez une nouvelle policy

yaml
{
    "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
create_role_policy

Ensuite renseignez un nom pour terminer la création

create_role_name
create_role_name

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

create_role_thrust
create_role_thrust

Et ajoutez le service edgelambda

yaml
{
  "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
create_function

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

create_function_options
create_function_options

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

javascript
"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
create_function_deploy

Publiez une nouvelle version

create_function_version
create_function_version

Sur votre Cloudfront, éditez le behavior

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

cloudfront_association
cloudfront_association

Via Terraform

Créez/éditez les fichiers suivants :

basic_auth.js (en modifiant les valeur username et password)
javascript
"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
yaml
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

yaml
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
create_function_cloudfront

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

javascript
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
create_function_publish

Enfin vous pouvez associer cette Functions à votre Cloudfront

create_function_associate
create_function_associate

Terraform

Créez/éditez les fichiers suivants :

basic_auth_function.js
javascript
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
yaml
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

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