TECHSTEP

ITインフラ関連の記事を公開してます。

GitLab IaC Scanning機能を利用する

今回はGitLab IaC Scanning機能を検証しました。

docs.gitlab.com

背景

GitLabのIaC ScanningはIaCファイルに対する静的解析機能を提供します。IaC Scanningの内部ではKICSというIac専用の静的解析ツールを実行しており、セキュリティの脆弱性コンプライアンス的な問題点などを指摘します。

IaC Scanningの対象のIaCツールは複数あり、以下に対応しています。なお一つのリポジトリ上に複数のIaCツールファイルを配置する場合もサポートします。

  • Ansible
  • AWS CloudFormation
  • Azure Resource Manager
  • Dockerfile
  • Google Deployment Manager
  • Kubernetes
  • OpenAPI
  • Terraform

docs.gitlab.com

IaC Scanningを利用するには以下の前提条件があります。

  • AMD64アーキテクチャをサポートしています。Windowsはサポートしていません。
  • 最低4GB以上のRAMが一貫したパフォーマンスのために必要です。
  • 利用するテンプレートでは test stageを呼び出すため、 test stageが有効である必要があります。
  • Self-managed版ではGitLab Runnerの dockerkubernetes executorが必要です。

検証

ここから検証します。検証では iac-scan-test というProjectを使用しました。

IaC Scanningの有効化とスキャン

まずはIaC Scanningを有効にします。IaC Scanningは .gitlab-ci.ymlinclude テンプレートを呼び出す形で利用します。

.gitlab-ci,yml を作成する前に、テスト用のIaCファイルとしてAWS CloudFormationのテンプレートを配置しておきます。

AWSTemplateFormatVersion: "2010-09-09"
Description: Security Group for GitLab IaC scan
Parameters:
  VpcId:
    Type: String
Resources:
  SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      VpcId: !Ref VpcId
      GroupName: "test for GitLab IaC scan"
      GroupDescription: "-"
      Tags:
        - Key: "Name"
          Value: "gitlab-iac-scan"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: "0.0.0.0/0"

        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: "0.0.0.0/0"

上記ファイルを配置後、 .gitlab-ci.yml を作成します。ここではGitLab画面から新規ファイルの作成を選択し、ファイル名に .gitlab-ci.yml と入力します。ファイル名を入力すると テンプレートを適用 という項目が表示され、スクロールすると SAST-IaC という項目があるので選択します。

すると以下のようなIaC Scanning用のテンプレートが表示されるので、コミットします。

# This file is a template, and might need editing before it works on your project.
include:
  - template: Jobs/SAST-IaC.gitlab-ci.yml

.gitlab-ci.yml を作成するとパイプラインが起動し、 kics-iac-sast というJobが実行されます。

完了後はアーティファクトが作成されるのでそちらを確認します。

gl-sast-report.json を確認すると、配置していた sg.yaml に対する指摘事項を確認できます。

gl-sast-report.json

{
    "version": "15.0.7",
    "vulnerabilities": [
        {
            "id": "948cd66067594b5dd625c513edc2154979a4c13f49cf2d40f64cd887913856d3",
            "category": "sast",
            "name": "AWS Security Group Ingress CIDR should not be open to the world",
            "description": "Resources.SecurityGroup.Properties.SecurityGroupIngress[0].CidrIp is open to the world (0.0.0.0/0)",
            "cve": "kics_id:4a1e6b34-1008-4e61-a5f2-1f7c276f8d14:20:0",
            "severity": "Critical",
            "scanner": {
                "id": "kics",
                "name": "kics"
            },
            "location": {
                "file": "sg.yaml",
                "start_line": 20
            },
            "identifiers": [
                {
                    "type": "kics_id",
                    "name": "Unrestricted Security Group Ingress",
                    "value": "4a1e6b34-1008-4e61-a5f2-1f7c276f8d14",
                    "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html"
                }
            ]
        },
        {
            "id": "6ea0a3ae7e0c7c7f794b0f522eaaee3faa51765d3bc2ef4b68bd08d40821da37",
            "category": "sast",
            "name": "AWS Security Group Ingress CIDR should not be open to the world",
            "description": "Resources.SecurityGroup.Properties.SecurityGroupIngress[1].CidrIp is open to the world (0.0.0.0/0)",
            "cve": "kics_id:4a1e6b34-1008-4e61-a5f2-1f7c276f8d14:25:0",
            "severity": "Critical",
            "scanner": {
                "id": "kics",
                "name": "kics"
            },
            "location": {
                "file": "sg.yaml",
                "start_line": 25
            },
            "identifiers": [
                {
                    "type": "kics_id",
                    "name": "Unrestricted Security Group Ingress",
                    "value": "4a1e6b34-1008-4e61-a5f2-1f7c276f8d14",
                    "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html"
                }
            ]
        },
        {
            "id": "59ffa5463acc93c37ca657984290586a7e03fcd044fac20dca8dca40d8892d18",
            "category": "sast",
            "name": "'SSH' (TCP:22) should not be public in AWS Security Group",
            "description": "One of the Resources.SecurityGroup.Properties.SecurityGroupIngress has port 22",
            "cve": "kics_id:6e856af2-62d7-4ba2-adc1-73b62cef9cc1:16:0",
            "severity": "Critical",
            "scanner": {
                "id": "kics",
                "name": "kics"
            },
            "location": {
                "file": "sg.yaml",
                "start_line": 16
            },
            "identifiers": [
                {
                    "type": "kics_id",
                    "name": "Security Group With Unrestricted Access To SSH",
                    "value": "6e856af2-62d7-4ba2-adc1-73b62cef9cc1",
                    "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html"
                }
            ]
        },
        {
            "id": "962dfa5683a7f48853ed4ff266be6efb3f6906274ee0b828ba931eb8d7d40278",
            "category": "sast",
            "name": "Security Groups should not have ports open in (20, 21, 22, 23, 115, 137, 138, 139, 2049, 3389)",
            "description": "One of the Resources.SecurityGroup.Properties.SecurityGroupIngress has a exposed port (20,21,22,23,115,137,138,2049,3389)",
            "cve": "kics_id:cdbb0467-2957-4a77-9992-7b55b29df7b7:16:0",
            "severity": "Critical",
            "scanner": {
                "id": "kics",
                "name": "kics"
            },
            "location": {
                "file": "sg.yaml",
                "start_line": 16
            },
            "identifiers": [
                {
                    "type": "kics_id",
                    "name": "Security Groups With Exposed Admin Ports",
                    "value": "cdbb0467-2957-4a77-9992-7b55b29df7b7",
                    "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html"
                }
            ]
        },
        {
            "id": "47d49b481623f9af2ccebc5a958aeaf3ce71edcc4b490766e9a53824a7a0fa92",
            "category": "sast",
            "name": "The HTTP port is open to the internet in a Security Group",
            "description": "Resources.SecurityGroup.Properties.SecurityGroupIngress[0] opens the HTTP port (80)",
            "cve": "kics_id:ddfc4eaa-af23-409f-b96c-bf5c45dc4daa:18:0",
            "severity": "Critical",
            "scanner": {
                "id": "kics",
                "name": "kics"
            },
            "location": {
                "file": "sg.yaml",
                "start_line": 18
            },
            "identifiers": [
                {
                    "type": "kics_id",
                    "name": "HTTP Port Open To Internet",
                    "value": "ddfc4eaa-af23-409f-b96c-bf5c45dc4daa",
                    "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html"
                }
            ]
        },
        {
            "id": "6265d7960f01c6df2209766e9859f955f431a8b651217a14f7df2bd4262b9956",
            "category": "sast",
            "name": "The HTTP port is open to the internet in a Security Group",
            "description": "Resources.SecurityGroup.Properties.SecurityGroupIngress[1] opens the HTTP port (80)",
            "cve": "kics_id:ddfc4eaa-af23-409f-b96c-bf5c45dc4daa:23:0",
            "severity": "Critical",
            "scanner": {
                "id": "kics",
                "name": "kics"
            },
            "location": {
                "file": "sg.yaml",
                "start_line": 23
            },
            "identifiers": [
                {
                    "type": "kics_id",
                    "name": "HTTP Port Open To Internet",
                    "value": "ddfc4eaa-af23-409f-b96c-bf5c45dc4daa",
                    "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html"
                }
            ]
        },
        {
            "id": "4e84f2406329d58c4ba29f1236203991963de184db34853a1f78099bb9b755c5",
            "category": "sast",
            "name": "The HTTP port is open to the internet in a Security Group",
            "description": "Resources.SecurityGroup.Properties.SecurityGroupIngress[1] opens the HTTP port (80)",
            "cve": "kics_id:ddfc4eaa-af23-409f-b96c-bf5c45dc4daa:24:0",
            "severity": "Critical",
            "scanner": {
                "id": "kics",
                "name": "kics"
            },
            "location": {
                "file": "sg.yaml",
                "start_line": 24
            },
            "identifiers": [
                {
                    "type": "kics_id",
                    "name": "HTTP Port Open To Internet",
                    "value": "ddfc4eaa-af23-409f-b96c-bf5c45dc4daa",
                    "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html"
                }
            ]
        },
        {
            "id": "ab61659737242a6eb3038297343c0700bdd48b57029fbd2a40ec3a2f5dbbfc25",
            "category": "sast",
            "name": "It's considered a best practice for AWS Security Group to have a description",
            "description": "Resources.SecurityGroup.Properties.SecurityGroupIngress[1].Description is undefined",
            "cve": "kics_id:5e6c9c68-8a82-408e-8749-ddad78cbb9c5:16:0",
            "severity": "Info",
            "scanner": {
                "id": "kics",
                "name": "kics"
            },
            "location": {
                "file": "sg.yaml",
                "start_line": 16
            },
            "identifiers": [
                {
                    "type": "kics_id",
                    "name": "Security Group Rule Without Description",
                    "value": "5e6c9c68-8a82-408e-8749-ddad78cbb9c5",
                    "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html"
                }
            ]
        },
        {
            "id": "3591683f901244cadea6d0eb31ea463b66fd6d29ca2a7aa7c12c4abc252a83ab",
            "category": "sast",
            "name": "It's considered a best practice for AWS Security Group to have a description",
            "description": "Resources.SecurityGroup.Properties.SecurityGroupIngress[0].Description is undefined",
            "cve": "kics_id:5e6c9c68-8a82-408e-8749-ddad78cbb9c5:16:0",
            "severity": "Info",
            "scanner": {
                "id": "kics",
                "name": "kics"
            },
            "location": {
                "file": "sg.yaml",
                "start_line": 16
            },
            "identifiers": [
                {
                    "type": "kics_id",
                    "name": "Security Group Rule Without Description",
                    "value": "5e6c9c68-8a82-408e-8749-ddad78cbb9c5",
                    "url": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html"
                }
            ]
        },
        {
            "id": "acfb8e43c1353f533f965ef1d3807b1d1d3c786872f77d5053695bcc9fbff7a3",
            "category": "sast",
            "name": "IAM Access Analyzer should be enabled and configured to continuously monitor resource permissions",
            "description": "'AWS::AccessAnalyzer::Analyzer' is undefined",
            "cve": "kics_id:8d29754a-2a18-460d-a1ba-9509f8d359da:6:0",
            "severity": "Info",
            "scanner": {
                "id": "kics",
                "name": "kics"
            },
            "location": {
                "file": "sg.yaml",
                "start_line": 6
            },
            "identifiers": [
                {
                    "type": "kics_id",
                    "name": "IAM Access Analyzer Not Enabled",
                    "value": "8d29754a-2a18-460d-a1ba-9509f8d359da",
                    "url": "https://docs.amazonaws.cn/en_us/AWSCloudFormation/latest/UserGuide/aws-resource-accessanalyzer-analyzer.html"
                }
            ]
        }
    ],
    "dependency_files": [],
    "scan": {
        "analyzer": {
            "id": "kics",
            "name": "kics",
            "url": "https://gitlab.com/gitlab-org/security-products/analyzers/kics",
            "vendor": {
                "name": "GitLab"
            },
            "version": "4.1.10"
        },
        "scanner": {
            "id": "kics",
            "name": "kics",
            "url": "https://github.com/Checkmarx/kics",
            "vendor": {
                "name": "GitLab"
            },
            "version": "v1.7.12"
        },
        "type": "sast",
        "start_time": "2024-01-18T22:24:24",
        "end_time": "2024-01-18T22:24:43",
        "status": "success"
    }
}

ここでは以下の6つの指摘事項が出力されました。

  • AWS Security Group Ingress CIDR should not be open to the world
  • 'SSH' (TCP:22) should not be public in AWS Security Group
  • Security Groups should not have ports open in (20, 21, 22, 23, 115, 137, 138, 139, 2049, 3389)
  • The HTTP port is open to the internet in a Security Group
  • It's considered a best practice for AWS Security Group to have a description
  • IAM Access Analyzer should be enabled and configured to continuously monitor resource permissions

複数のIaCファイルの配置

次にKubernetesマニフェストファイルを追加し、複数のIaCツールのファイルを配置した場合を見てみます。

ここでは以下のファイルを使用します。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx:latest
    name: nginx
    ports:
    - name: nginx
      containerPort: 80
      protocol: TCP

上記ファイルを追加して再びパイプラインを実行し、アーティファクトを確認します。すると先ほどのCloudFormationだけのものと比べ、pod.yamlに対する23の指摘事項が追加されました。

ここでは指摘事項の一部だけ載せておきます。

  • Containers should not run with allowPrivilegeEscalation in order to prevent them from gaining more privileges than their parent process
  • Check if containers are running with low UID, which might cause conflicts with the host's user table.
  • Memory requests should be defined for each container. This allows the kubelet to reserve the requested amount of system resources and prevents over-provisioning on individual nodes
  • Service Account Tokens are automatically mounted even if not necessary
  • CPU limits should be set because if the system has CPU time free, a container is guaranteed to be allocated as much CPU as it requests
  • A Kubernetes Pod should have a Service Account defined so to restrict Kubernetes API access, which means the attribute 'serviceAccountName' should be defined and not empty.
  • Namespaces like 'default', 'kube-system' or 'kube-public' should not be used
  • Check if Readiness Probe is not configured.
  • Memory limits should be defined for each container. This prevents potential resource exhaustion by ensuring that containers consume not more than the designated amount of memory

一部フォルダのスキップ

IaC Scanningのテンプレートを見ると、 test など一部フォルダをスキップするよう設定されています。そこで試しに test フォルダを作成して pod.yaml をそちらに移動します。

ファイルを移動すると再びパイプラインが実行されますが、アーティファクトを確認すると先ほど出力された pod.yaml に対する指摘事項が無くなっていることを確認できました。

なおIaC Scanningのテンプレートでは SAST_EXCLUDED_PATHS という変数でパスを指定しているので、 .gitlab-ci.yml 上で上書きすることも可能です。

その他

Ultimateプランでは以下の機能も利用できます。

  • Merge Requestでのスキャン結果の確認
  • Vulnerability reportでのレビュー
  • カスタマイズルール: sast-ruleset.toml というファイルに定義し、ルールの無効化や上書きが可能です。