特定ポートを許可したセキュリティグループがNLB/ALBにアタッチされたことを検知するAWS Config カスタム Lambda ルールを作成してみた

特定ポートを許可したセキュリティグループがNLB/ALBにアタッチされたことを検知するAWS Config カスタム Lambda ルールを作成してみた

ご自由にカスタマイズしてお使いください。
Clock Icon2024.10.25

こんにちは!AWS事業本部のおつまみです。

みなさん、特定ポートを許可したセキュリティグループがNLB/ALBにアタッチされたことを検知したいと思ったことはありますか?私はあります。

例えば、インターネット向けのNLBに対して、SSH/RDPなどのポートを許可したインバウンドルールが設定された場合、NLBを踏み台にしてプライベートサブネットにあるEC2に接続できてしまう場合があります。

そのため、このような潜在的なセキュリティリスクを早期に検知し、対処することが重要です。

検知ですが、残念ながらAWS Configのマネージドルールできません。
そのため、今回はAWS Config カスタム Lambda ルールを作成してみたので、ご紹介します。

CloudFormationテンプレートを準備

以下条件に合致した場合にConfigで「非準拠」となるようにします。

  • NLB/ALBに関連付いているセキュリティグループに対して、RDP/SSHのポートを許可したルールが追加された場合
  • RDP/SSHのポートを許可したセキュリティグループが、NLB/ALBにアタッチされた場合

テンプレートはこちらになります。
前提として、Config,Security Hubが既に有効化されている環境で構築することを想定しています。

テンプレート
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Custom AWS Config rule to monitor NLB/ALB security groups for RDP/SSH access'

Parameters:
  ExcludedLoadBalancers:
    Type: String
    Description: 'Comma-separated list of load balancer names to exclude from the check'
    Default: ''

Resources:
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSConfigRulesExecutionRole
      Policies:
        - PolicyName: LambdaExecutionPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ec2:DescribeSecurityGroups
                  - elasticloadbalancing:DescribeLoadBalancers
                Resource: '*'
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*

  ConfigLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Environment:
        Variables:
          EXCLUDED_LOAD_BALANCERS: !Ref ExcludedLoadBalancers
      Code:
        ZipFile: |
          import os
          import boto3
          import json

          def evaluate_compliance(configuration_item, rule_parameters):
              if configuration_item['resourceType'] not in ['AWS::ElasticLoadBalancingV2::LoadBalancer']:
                  return 'NOT_APPLICABLE'

              # Check that configuration_item['configuration'] is not None
              if not configuration_item.get('configuration'):
                  print("Error: configuration is None or not present in configuration_item")
                  return 'NOT_APPLICABLE'

              # Check the existence of loadBalancerArn
              load_balancer_arn = configuration_item['configuration'].get('loadBalancerArn')
              if not load_balancer_arn:
                  print("Error: loadBalancerArn is not present in configuration")
                  return 'NOT_APPLICABLE'

              elb_client = boto3.client('elbv2')
              ec2_client = boto3.client('ec2')

              try:
                  load_balancer = elb_client.describe_load_balancers(LoadBalancerArns=[load_balancer_arn])['LoadBalancers'][0]
              except Exception as e:
                  print(f"Error describing load balancer: {str(e)}")
                  return 'NOT_APPLICABLE'

              # Check if the load balancer is in the exclusion list
              excluded_load_balancers = os.environ.get('EXCLUDED_LOAD_BALANCERS', '').split(',')
              if load_balancer['LoadBalancerName'] in excluded_load_balancers:
                  return 'COMPLIANT'

              security_groups = load_balancer['SecurityGroups']

              for sg_id in security_groups:
                  sg = ec2_client.describe_security_groups(GroupIds=[sg_id])['SecurityGroups'][0]
                  for rule in sg['IpPermissions']:
                      if rule.get('FromPort') in [22, 3389] or rule.get('ToPort') in [22, 3389]:
                          return 'NON_COMPLIANT'

              return 'COMPLIANT'

          def lambda_handler(event, context):
              try:
                invoking_event = json.loads(event['invokingEvent'])
                rule_parameters = json.loads(event['ruleParameters'])

                configuration_item = invoking_event['configurationItem']
                result_token = event['resultToken']

                compliance_type = evaluate_compliance(configuration_item, rule_parameters)

                config = boto3.client('config')
                config.put_evaluations(
                    Evaluations=[
                        {
                            'ComplianceResourceType': configuration_item['resourceType'],
                            'ComplianceResourceId': configuration_item['resourceId'],
                            'ComplianceType': compliance_type,
                            'Annotation': 'Load Balancer security group allows RDP/SSH access' if compliance_type == 'NON_COMPLIANT' else 'Load Balancer security group is compliant',
                            'OrderingTimestamp': configuration_item['configurationItemCaptureTime']
                        },
                    ],
                    ResultToken=result_token
                )
              except Exception as e:
                  print(f"Error in lambda_handler: {str(e)}")
                  return {
                      'statusCode': 500,
                      'body': json.dumps(f'Error occurred: {str(e)}')
                  }
      Runtime: python3.11
      Timeout: 300

  ConfigRule:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: check-lb-security-groups
      Description: 'Check if NLB/ALB security groups allow RDP/SSH access'
      Source:
        Owner: CUSTOM_LAMBDA
        SourceIdentifier: !GetAtt ConfigLambdaFunction.Arn
        SourceDetails:
          - EventSource: aws.config
            MessageType: ConfigurationItemChangeNotification
      Scope:
        ComplianceResourceTypes:
          - AWS::ElasticLoadBalancingV2::LoadBalancer
      InputParameters:
        ExcludedLoadBalancers: !Ref ExcludedLoadBalancers

  ConfigRulePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt ConfigLambdaFunction.Arn
      Action: lambda:InvokeFunction
      Principal: config.amazonaws.com

Outputs:
  ConfigRuleArn:
    Description: 'ARN of the created Config Rule'
    Value: !GetAtt ConfigRule.Arn
  LambdaFunctionArn:
    Description: 'ARN of the Lambda function'
    Value: !GetAtt ConfigLambdaFunction.Arn

いざ検証

CloudFromationでデプロイ

パラメータは、任意で必要な場合に入力します。除外したいものがない場合は空白でも問題ないです。

  • ExcludedLoadBalancers
    • 除外したいNLB,ALBのロードバランサー名(複数ある場合はカンマ区切り)

1,2分ほどでデプロイが完了し、以下のリソースが作成されます。

CloudFormation_-_スタック_alb

動作確認

以下のパターンに分けて、想定通りの動きになっているか確認します。

No. 操作内容 チェック結果
1 ロードバランサに関連づけられたセキュリティグループに対して、インバウンドルールSSH・RDPを追加 非準拠
2 RDP/SSHのポートを許可したセキュリティグループが、NLB/ALBにアタッチ 非準拠
3 ロードバランサに関連づけられていないセキュリティグループに対して、インバウンドルールSSH・RDPを追加 準拠
4 除外リストに入れたALB・NLBのセキュリティグループに対して、インバウンドルールSSH・RDPを追加 準拠

No.1 ロードバランサに関連づけられたセキュリティグループに対して、インバウンドルールSSH・RDPを追加

ALBに設定されているセキュリティグループに対して、RDPを許可したインバウンドルールを追加してみます。

SecurityGroups___EC2___ap-northeast-1

AWS Configのルール画面で「アクション」→「再評価」を選択します。

ルールの詳細___AWS_Config___ap-northeast-1

このように「非準拠」で検出されることが確認できました。

ルールの詳細___AWS_Config___ap-northeast-1

追加したインバウンドルールを削除して、「再評価」をすると、「準拠」になっていることが確認できます。

ルールの詳細___AWS_Config___ap-northeast-1

No.2 RDP/SSHのポートを許可したセキュリティグループが、NLB/ALBにアタッチ

続いて、セキュリティグループを追加することを想定します。SSHのポートを許可したセキュリティグループをアタッチします。

  • セキュリティグループ

SecurityGroups___EC2___ap-northeast-1

  • ALBにアタッチしたセキュリティグループ

ロードバランサーの詳細___EC2___ap-northeast-1

先ほどと同様にAWS Configのルール画面で「アクション」→「再評価」を選択します。同じように「非準拠」で検出されることが確認できました。

ルールの詳細___AWS_Config___ap-northeast-1

No.3 ロードバランサに関連づけられていないセキュリティグループに対して、インバウンドルールSSH・RDPを追加

続いて、ロードバランサに関連づけられていないセキュリティグループに対して、インバウンドルールSSH・RDPを追加してみます。

  • 関係ないセキュリティグループ

SecurityGroups___EC2___ap-northeast-1

  • ALBにアタッチしたセキュリティグループ

ロードバランサーの詳細___EC2___ap-northeast-1

先ほどと同様にAWS Configのルール画面で「アクション」→「再評価」を選択します。検知条件に合致しないので、「準拠」で検出されることが確認できました。

ルールの詳細___AWS_Config___ap-northeast-1

No.4 除外リストに入れたALB・NLBのセキュリティグループに対して、インバウンドルールSSH・RDPを追加

最後に除外リストに設定し、非準拠にならないことを確認します。
CloudFormationのスタックで「更新」を選択し、既存テンプレートのままパラメータでALB名を指定し、デプロイします。

CloudFormation_-_スタック_config-rule-lb-ssh-rdp-check

ALBに設定されているセキュリティグループに対して、SSHを許可したインバウンドルールを追加してみます。

SecurityGroup___EC2___ap-northeast-1

先ほどと同様にAWS Configのルール画面で「アクション」→「再評価」を選択します。除外しているため、「準拠」で検出されることが確認できました。

ルールの詳細___AWS_Config___ap-northeast-1

さいごに

今回は特定ポートを許可したセキュリティグループがNLB/ALBにアタッチされたことを検知するAWS Config カスタム Lambda ルールを作成する方法をご紹介しました。

はじめてAWS Config カスタム Lambda ルールを作成したのですが、思ったより簡単に作成できました。
ただし、Lambdaの定期的なランタイムのバージョンアップや、実行コストがかかってくるので、可能な限りマネージドルールを使っていきましょう!

最後までお読みいただきありがとうございました! どなたかのお役に立てれば幸いです。

以上、おつまみ(@AWS11077)でした!

関連記事

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.