LoginSignup
5
0

目的

Webを作成した時にセキュリティヘッダーを設定したいです。
ここではAWSのCloudFrontを想定して、CloudFront Functionのコードとそれを配置するCDKを書きます。
またCSPで問題が起きた場合にレポートするサイトをAWS Lambdaで作成しました。

コード

レポート

report/main.rb
require 'json'
require 'logger'

def lambda_handler(event:, context:)
  logger = Logger.new($stdout)
  body = JSON.parse(event['body'])
  logger.info(body)
  {
    statusCode: 200,
    body: {}.to_json
  }
end
cdk-report-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as cdk from 'aws-cdk-lib';
import { getProcejctName } from './util';

export class CdkReportStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const procejctName = 'my-project'

    // CSPレポート
    const reportFunction = new lambda.Function(this, 'CspReportHandler', {
      runtime: lambda.Runtime.RUBY_3_2,
      code: lambda.Code.fromAsset('report'),
      handler: 'main.lambda_handler',
      timeout: cdk.Duration.seconds(60),
      functionName: `${procejctName}-report`,
    });
    const api = new apigateway.RestApi(this, 'ReportAPI', {
      restApiName: `${procejctName}-report`,
      deployOptions: {
        stageName: 'production'
      }
    })
    const report = api.root.addMethod('POST', new apigateway.LambdaIntegration(
      reportFunction
    ))
  }
}

CloudFront Functions

security_header.js
function handler(event) {
    var response = event.response;
    var headers = response.headers;

    // HTTPSのみ許可(2年間、サブドメインを含む、HSTS)
    // https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Strict-Transport-Security
    headers['strict-transport-security'] = { value: 'max-age=63072000; includeSubdomains; preload'}; 

    // CSP
    // https://developer.mozilla.org/ja/docs/Web/HTTP/CSP
    // https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy/report-to

    // レポートを受け付けるエンドポイントを指定
    var endpoint = 'https://report.example.com'
    headers['reporting-endpoints'] = { value: `endpoint="${endpoint}"` }

    // ベースとなるサイトを設定
    var base = "'self' my.example.com your.example.com"

    // 良く使うGoogleのサイト
    var google = "https://www.googletagmanager.com https://www.google-analytics.com"

    // 良く使うアマゾンのサイト
    var amazon = "*.amazonaws.com"

    var csps = [
        `connect-src  ${base} ${google} ${amazon}`,
        `default-src ${base}`,
        `font-src 'self' ${base} ${amazon}`,
        `frame-src ${base}`,
        `img-src ${base} ${amazon}`,
        `media-src ${base} ${amazon}`,
        `object-src ${base}`,
        `script-src ${base} ${google}`,
        `style-src ${base}`,
        `worker-src ${base}`,
        'report-to endpoint',
        `report-uri ${endpoint}`,
    ]
    headers['content-security-policy'] = { value: csps.join('; ')}; 

    // MIMEスニッフィングを禁止、コンテンツの内容でContentTypeを無視させない
    // https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Content-Type-Options
    headers['x-content-type-options'] = { value: 'nosniff'}; 

    // 他のサイトのiFrameに埋め込まさせない
    // https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Frame-Options
    headers['x-frame-options'] = {value: 'DENY'}; 

    return response;
}
cdk-cloud-front-function-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';

export class CdkCloudFrontFunctionStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const procejctName = 'my-project'

    // CloudFront Functions
    const addHeaderFunction = new cloudfront.Function(
      this,
      "AddHeaderFunction",
      {
        functionName: `${procejctName}-security-headers`,
        code: cloudfront.FunctionCode.fromFile({
          filePath: "security_headers.js",
        }),
      }
    );
    this.exportValue(addHeaderFunction.functionArn, {name: `${procejctName}-security-headers-arn`})
  }
}
5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0