今回はGitLab IaC Scanning機能を検証しました。
背景
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
IaC Scanningを利用するには以下の前提条件があります。
- AMD64アーキテクチャをサポートしています。Windowsはサポートしていません。
- 最低4GB以上のRAMが一貫したパフォーマンスのために必要です。
- 利用するテンプレートでは
test
stageを呼び出すため、test
stageが有効である必要があります。 - Self-managed版ではGitLab Runnerの
docker
かkubernetes
executorが必要です。
検証
ここから検証します。検証では iac-scan-test
というProjectを使用しました。
IaC Scanningの有効化とスキャン
まずはIaC Scanningを有効にします。IaC Scanningは .gitlab-ci.yml
で include
テンプレートを呼び出す形で利用します。
.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
というファイルに定義し、ルールの無効化や上書きが可能です。