LoginSignup
1
1

More than 1 year has passed since last update.

[CDKv2]CloudFrontへWAF適応にパラメータストアを使う。

Posted at

CloudFrontにWAFを当てる場合、WAFv2ではWAFをus-east-1リージョンに作成しないといけない。
CloudFrontが他のリージョンのリソースと一緒に定義されている場合、WAF適応に指定するARNをパラメータストア経由で渡したくなるが、そのままでは別リージョンのパラメータストアにはアクセスできない。

cdk-remote-stackというモジュールで対応できるみたいだが、Lambda@Edgeを噛ませた場合にうまくいかなかったので以下を参考にCDKv2でクロスリージョンでのSSMパラメータストア読み書きを行うクラスを用意した。

$ cdk --version     
2.1.0 (build f4f18b1)
lib/xorigin-ssm-parameter-accessor.ts
import { Construct } from 'constructs';
import { AwsCustomResource, AwsCustomResourcePolicy, AwsSdkCall, PhysicalResourceId } from 'aws-cdk-lib/custom-resources';
interface SSMParameterReaderProps {
  action: "getParameter" | "putParameter" | "deleteParameter";
  parameters: {
    Name: string;
    Value?: string;
    Description?: string;
    Type?: string;
    Overwrite?: boolean;
  }
  region: string;
}

export class XOriginSSMParameterAccessor extends AwsCustomResource {
  constructor(scope: Construct, name: string, props: SSMParameterReaderProps) {
    const {action, parameters, region} = props;

    const ssmAwsSdkCall: AwsSdkCall = {
      service: "SSM",
      action,
      parameters,
      region,
      physicalResourceId: PhysicalResourceId.of(Date.now().toString()),
    };
    super(scope, name, { 
      onUpdate: ssmAwsSdkCall,
      policy: AwsCustomResourcePolicy.fromSdkCalls({
        resources: AwsCustomResourcePolicy.ANY_RESOURCE
      })
    });
  }

  public getParameterValue(): string {
    return this.getResponseField("Parameter.Value").toString();
  }
}

onUpdate(リソースの更新時)で指定のAWS-SDKを実行する。ここでは他のリージョンのパラメータストアから読み出す処理。Lambdaが実行環境となるのでそのポリシーも指定するみたい。

lib/waf-stack.ts
import { Construct } from 'constructs';
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
import * as ssm from 'aws-cdk-lib/aws-ssm';

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

    // == const ==
    const wafName = "YOUR_WAF_NAME";

    // == WAF ==
    const myIpSet = new wafv2.CfnIPSet(this, "WhiteIPList", {
      name: "WhiteIPList",
      addresses: ["***.***.***.***/32"],
      ipAddressVersion: "IPV4",
      scope: "CLOUDFRONT",
    });

    const myWaf = new wafv2.CfnWebACL(this, wafName, {
      defaultAction: { block: {} },
      name: wafName,
      rules: [
        // aws managed rule
        {
          priority: 1,
          overrideAction: { none: {} },
          visibilityConfig: {
            sampledRequestsEnabled: true,
            cloudWatchMetricsEnabled: true,
            metricName: `${wafName}-AWSManagedRulesKnownBadInputsRuleSet`,
          },
          name: `${wafName}-AWSManagedRulesKnownBadInputsRuleSet`,
          statement: {
            managedRuleGroupStatement: {
              vendorName: "AWS",
              name: "AWSManagedRulesKnownBadInputsRuleSet"
            }
          }
        },
        // custom rule
        {
          priority: 2,
          action: { allow: {} },
          name: `${wafName}-CustomAllowIpSetRule`,
          statement: {
            ipSetReferenceStatement: {
              arn: myIpSet.attrArn
            },
          },
          visibilityConfig: {
            sampledRequestsEnabled: true,
            cloudWatchMetricsEnabled: true,
            metricName: `${wafName}-CustomAllowIpSetRule`,
          },
        },
      ],
      scope: "CLOUDFRONT",
      visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        metricName: `${wafName}`,
        sampledRequestsEnabled: true
      }
    });

    // == export ==
    new ssm.StringParameter(this, 'SSMParamWafArn', {
      parameterName: "/cdk-params/wafArn",
      stringValue: myWaf.attrArn,
    });
  }
}
lib/cloudfront-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import { XOriginSSMParameterAccessor } from './xorigin-ssm-parameter-accessor';

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

    // == import ==
    const wafAttrArnReader = new XOriginSSMParameterAccessor(this, `SSMParameterReader`, {
      action: "getParameter",
      parameters: {
        Name: "/cdk-params/wafArn",
      },
      region: 'us-east-1',
    });

    // == CloudFront ==
    new cloudfront.Distribution(this,"Distribution", {
      webAclId: wafAttrArnReader.getParameterValue(),
      // 他略
    });
  }
}
bin/main.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CloudFrontStack } from '../lib/cloudfront-stack';
import { WafStack } from '../lib/waf-stack';

const app = new cdk.App();
new WafStack(app, 'WafStack', {
    env: {
        account: "************",
        region: "us-east-1",
    }
})
new CloudFrontStack(app, 'CloudFrontStack', {
    env: {
        account: "************",
        region: "ap-northeast-1",
    }
});
1
1
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
1
1