[{"data":1,"prerenderedAt":4144},["ShallowReactive",2],{"/blog/fantastic-infrastructure-as-code-security-attacks-and-how-to-find-them":3,"navigation-en-us":3359,"banner-en-us":3787,"footer-en-us":3797,"blog-post-authors-en-us-Michael Friedrich":4041,"blog-related-posts-en-us-fantastic-infrastructure-as-code-security-attacks-and-how-to-find-them":4056,"blog-promotions-en-us":4081,"next-steps-en-us":4134},{"id":4,"title":5,"authors":6,"body":8,"category":3338,"date":3339,"description":3340,"extension":3341,"externalUrl":3342,"faq":3342,"featured":3343,"heroImage":3344,"meta":3345,"navigation":926,"path":3346,"seo":3347,"slug":3351,"stem":3352,"tags":3353,"template":3357,"updatedDate":3342,"__hash__":3358},"blogPosts/en-us/blog/fantastic-infrastructure-as-code-security-attacks-and-how-to-find-them.md","Fantastic Infrastructure as Code security attacks and how to find them",[7],"Michael Friedrich",{"type":9,"value":10,"toc":3315},"minimark",[11,20,23,26,29,32,108,113,121,124,128,131,134,141,208,227,235,238,247,250,257,260,300,309,312,344,346,372,400,413,495,503,508,538,576,583,585,592,611,661,679,713,719,738,741,749,777,779,793,820,822,831,862,864,871,874,883,891,982,990,993,1007,1010,1023,1062,1093,1096,1105,1114,1120,1130,1149,1155,1158,1161,1182,1191,1200,1560,1568,1629,1635,1642,1648,1656,1695,1711,1742,1745,1754,1778,1784,1787,1794,1809,1990,2004,2171,2180,2187,2193,2204,2282,2295,2315,2332,2520,2532,2553,2560,2690,2710,2716,2720,2737,2750,2881,2888,3028,3039,3058,3225,3239,3242,3276,3288,3297,3311],[12,13,14,19],"p",{},[15,16,18],"a",{"href":17},"/topics/gitops/infrastructure-as-code/","Infrastructure as Code","(IaC) has eaten the world. It helps manage and provision computer resources automatically and avoids manual work or UI form workflows. Lifecycle management with IaC started with declarative and idempotent configuration, package, and tool installation. In the era of cloud providers, IaC tools additionally help abstract cloud provisioning. They can create defined resources automatically (network, storage, databases, etc.) and apply the configuration (DNS entries, firewall rules, etc.).",[12,21,22],{},"Like everything else, it has its flaws. IaC workflows have shifted left in the development lifecycle, making it more efficient. Developers and DevOps engineers need to learn new tools and best practices. Mistakes may result in leaked credentials or supply chain attacks. Existing security assessment tools might not be able to detect these new vulnerabilities.",[12,24,25],{},"In this post, we will dive into these specific risks and focus on IaC management tools such as Terraform, cloud providers, and deployment platforms involving containers and Kubernetes.",[12,27,28],{},"For each scenario, we will look into threats, tools, integrations, and best practices to reduce risk.",[12,30,31],{},"You can read the blog post top-down or navigate into the chapters individually.",[33,34,35,50,56,82,102],"ul",{},[36,37,38,42],"li",{},[15,39,41],{"href":40},"#scan-your-infrastructure---know-what-is-important","Scan your own infrastructure - know what's important",[33,43,44],{},[36,45,46],{},[15,47,49],{"href":48},"#thinking-like-an-attacker","Thinking like an attacker",[36,51,52],{},[15,53,55],{"href":54},"#tools-to-detect-terraform-vulnerabilities","Tools to detect Terraform vulnerabilities",[36,57,58,62],{},[15,59,61],{"href":60},"#develop-more-iac-scenarios","Develop more IaC scenarios",[33,63,64,70,76],{},[36,65,66],{},[15,67,69],{"href":68},"#terraform-module-dependency-scans","Terraform Module Dependency Scans",[36,71,72],{},[15,73,75],{"href":74},"#iac-security-scanning-for-containers","IaC Security Scanning for Containers",[36,77,78],{},[15,79,81],{"href":80},"#iac-security-scanning-with-kubernetes","IaC Security Scanning with Kubernetes",[36,83,84,88],{},[15,85,87],{"href":86},"#integrations-into-cicd-and-merge-requests-for-review","Integrations into CI/CD and Merge Requests for Review",[33,89,90,96],{},[36,91,92],{},[15,93,95],{"href":94},"#reports-in-mrs-as-comment","Reports in MRs as comment",[36,97,98],{},[15,99,101],{"href":100},"#mr-comments-using-gitlab-iac-sast-reports-as-source","MR Comments using GitLab IaC SAST reports as source",[36,103,104],{},[15,105,107],{"href":106},"#what-is-the-best-integration-strategy","What is the best integration strategy?",[109,110,112],"h2",{"id":111},"scan-your-infrastructure-know-what-is-important","Scan your infrastructure - know what is important",[12,114,115,116,120],{},"Start with identifying the project/group responsible for managing the IAC tasks. An inventory search for specific IaC tools, file suffixes (Terraform uses ",[117,118,119],"code",{},".tf",", for example), and languages can be helpful. The security scan tools discussed in this blog post will discover all supported types automatically. Once you have identified the projects, you can use one of the tools to run a scan and identify the detected possible vulnerabilities.",[12,122,123],{},"There might not be any scan results because your infrastructure is secure at this time. Though, your processes may require you to create documentation, runbooks, and action items for eventually discovered vulnerabilities in the future. Creating a forecast on possible scenarios to defend is hard, so let us change roles from the defender to the attacker for a moment. Which security vulnerabilities are out there to exploit as a malicious attacker? Maybe it is possible to create vulnerable scenarios and simulate the attacker role by running a security scan.",[125,126,49],"h3",{"id":127},"thinking-like-an-attacker",[12,129,130],{},"There can be noticeable potential vulnerabilities like plaintext passwords in the configuration. Other scenarios involve cases you would never think of or a chain of items causing a security issue.",[12,132,133],{},"Let us create a scenario for an attacker by provisioning an S3 bucket in AWS with Terraform. We intend to store logs, database dumps, or credential vaults in this S3 bucket.",[12,135,136,137,140],{},"The following example creates the ",[117,138,139],{},"aws_s3_bucket"," resource in Terraform using the AWS provider.",[142,143,148],"pre",{"className":144,"code":145,"language":146,"meta":147,"style":147},"language-hcl shiki shiki-themes github-light","# Create the bucket\nresource \"aws_s3_bucket\" \"demobucket\" {\n  bucket = \"terraformdemobucket\"\n  acl = \"private\"\n}\n","hcl","",[117,149,150,159,177,191,202],{"__ignoreMap":147},[151,152,155],"span",{"class":153,"line":154},"line",1,[151,156,158],{"class":157},"sAwPA","# Create the bucket\n",[151,160,162,166,170,173],{"class":153,"line":161},2,[151,163,165],{"class":164},"s7eDp","resource",[151,167,169],{"class":168},"sYu0t"," \"aws_s3_bucket\"",[151,171,172],{"class":168}," \"demobucket\"",[151,174,176],{"class":175},"sgsFI"," {\n",[151,178,180,183,187],{"class":153,"line":179},3,[151,181,182],{"class":175},"  bucket",[151,184,186],{"class":185},"sD7c4"," =",[151,188,190],{"class":189},"sYBdl"," \"terraformdemobucket\"\n",[151,192,194,197,199],{"class":153,"line":193},4,[151,195,196],{"class":175},"  acl",[151,198,186],{"class":185},[151,200,201],{"class":189}," \"private\"\n",[151,203,205],{"class":153,"line":204},5,[151,206,207],{"class":175},"}\n",[12,209,210,211,214,215,218,219,222,223,226],{},"After provisioning the S3 bucket for the first time, someone decided to make the S3 bucket accessible by default. The example below grants public access to the bucket using ",[117,212,213],{},"aws_s3_bucket_public_access_block",". ",[117,216,217],{},"block_public_acls"," and ",[117,220,221],{},"block_public_policy"," are set to ",[117,224,225],{},"false"," to allow any public access.",[142,228,233],{"className":229,"code":231,"language":232,"meta":147},[230],"language-text","# Grant bucket access: public\nresource \"aws_s3_bucket_public_access_block\" \"publicaccess\" {\n  bucket = aws_s3_bucket.demobucket.id\n  block_public_acls = false\n  block_public_policy = false\n}\n","text",[117,234,231],{"__ignoreMap":147},[12,236,237],{},"The S3 bucket is now publicly readable, and anyone who knows the URL or scans network ranges for open ports may find the S3 bucket and its data. Malicious actors can not only capture credentials but also may learn about your infrastructure, IP addresses, internal server FQDNs, etc. from the logs, backups, and database dumps being stored in the S3 bucket.",[12,239,240,241,246],{},"We need ways to mitigate and detect this security problem. The following sections describe the different tools you can use. The full Terraform code is located in ",[15,242,245],{"href":243,"rel":244},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning/-/tree/main/terraform/aws",[],"this project"," and allows you to test all tools described in this blog post.",[109,248,55],{"id":249},"tools-to-detect-terraform-vulnerabilities",[12,251,252,253,256],{},"In the \"not worst case\" scenario, the Terraform code to manage your infrastructure is persisted at a central Git server and not hidden somewhere on a host or local desktop. Maybe you are using ",[117,254,255],{},"terraform init, plan, apply"," jobs in CI/CD pipelines already. Let us look into methods and tools that help detect the public S3 bucket vulnerability. Later, we will discuss CI/CD integrations and automating IaC security scanning.",[12,258,259],{},"Before we dive into the tools, make sure to clone the demo project locally to follow the examples yourself.",[142,261,265],{"className":262,"code":263,"language":264,"meta":147,"style":147},"language-shell shiki shiki-themes github-light","$ cd /tmp\n$ git clone https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning.git && cd  infrastructure-as-code-scanning/\n","shell",[117,266,267,278],{"__ignoreMap":147},[151,268,269,272,275],{"class":153,"line":154},[151,270,271],{"class":164},"$",[151,273,274],{"class":189}," cd",[151,276,277],{"class":189}," /tmp\n",[151,279,280,282,285,288,291,294,297],{"class":153,"line":161},[151,281,271],{"class":164},[151,283,284],{"class":189}," git",[151,286,287],{"class":189}," clone",[151,289,290],{"class":189}," https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning.git",[151,292,293],{"class":175}," && ",[151,295,296],{"class":168},"cd",[151,298,299],{"class":189},"  infrastructure-as-code-scanning/\n",[12,301,302,303,308],{},"The tool installation steps in this blog post are illustrated with ",[15,304,307],{"href":305,"rel":306},"https://brew.sh/",[],"Homebrew on macOS",". Please refer to the tools documentation for alternative installation methods and supported platforms.",[12,310,311],{},"You can follow the tools for Terraform security scanning by reading top-down, or navigate into the tools sections directly:",[33,313,314,320,326,332,338],{},[36,315,316],{},[15,317,319],{"href":318},"#tfsec","tfsec",[36,321,322],{},[15,323,325],{"href":324},"#kics","kics",[36,327,328],{},[15,329,331],{"href":330},"#terrascan","terrascan",[36,333,334],{},[15,335,337],{"href":336},"#semgrep","semgrep",[36,339,340],{},[15,341,343],{"href":342},"#tflint","tflint",[125,345,319],{"id":319},[12,347,348,352,353,358,359,364,365,367,368,371],{},[15,349,319],{"href":350,"rel":351},"https://github.com/aquasecurity/tfsec",[]," from Aqua Security can help detect Terraform vulnerabilities. There are ",[15,354,357],{"href":355,"rel":356},"https://github.com/aquasecurity/tfsec#use-with-docker",[],"Docker images available"," to quickly test the scanner on the CLI, or binaries to ",[15,360,363],{"href":361,"rel":362},"https://aquasecurity.github.io/tfsec/v1.1.4/getting-started/installation/",[],"install tfsec",". Run ",[117,366,319],{}," on the local project path ",[117,369,370],{},"terraform/aws/"," to get a list of vulnerabilities.",[142,373,375],{"className":262,"code":374,"language":264,"meta":147,"style":147},"$ brew install tfsec\n$ tfsec terraform/aws/\n",[117,376,377,390],{"__ignoreMap":147},[151,378,379,381,384,387],{"class":153,"line":154},[151,380,271],{"class":164},[151,382,383],{"class":189}," brew",[151,385,386],{"class":189}," install",[151,388,389],{"class":189}," tfsec\n",[151,391,392,394,397],{"class":153,"line":161},[151,393,271],{"class":164},[151,395,396],{"class":189}," tfsec",[151,398,399],{"class":189}," terraform/aws/\n",[12,401,402,403,406,407,412],{},"The default scan provides a table overview on the CLI, which may need additional filters. Inspect ",[117,404,405],{},"tfsec –help"," to get a list of all available ",[15,408,411],{"href":409,"rel":410},"https://aquasecurity.github.io/tfsec/v1.1.4/getting-started/usage/",[],"parameters"," and try generating JSON and JUnit output files to process further.",[142,414,416],{"className":262,"code":415,"language":264,"meta":147,"style":147},"$ tfsec terraform/aws --format json --out tfsec-report.json\n1 file(s) written: tfsec-report.json\n$ tfsec terraform/aws --format junit --out tfsec-junit.xml\n1 file(s) written: tfsec-junit.xml\n",[117,417,418,439,461,479],{"__ignoreMap":147},[151,419,420,422,424,427,430,433,436],{"class":153,"line":154},[151,421,271],{"class":164},[151,423,396],{"class":189},[151,425,426],{"class":189}," terraform/aws",[151,428,429],{"class":168}," --format",[151,431,432],{"class":189}," json",[151,434,435],{"class":168}," --out",[151,437,438],{"class":189}," tfsec-report.json\n",[151,440,441,444,447,450,453,456,459],{"class":153,"line":161},[151,442,443],{"class":164},"1",[151,445,446],{"class":189}," file",[151,448,449],{"class":175},"(",[151,451,452],{"class":164},"s",[151,454,455],{"class":175},") ",[151,457,458],{"class":189},"written:",[151,460,438],{"class":189},[151,462,463,465,467,469,471,474,476],{"class":153,"line":179},[151,464,271],{"class":164},[151,466,396],{"class":189},[151,468,426],{"class":189},[151,470,429],{"class":168},[151,472,473],{"class":189}," junit",[151,475,435],{"class":168},[151,477,478],{"class":189}," tfsec-junit.xml\n",[151,480,481,483,485,487,489,491,493],{"class":153,"line":193},[151,482,443],{"class":164},[151,484,446],{"class":189},[151,486,449],{"class":175},[151,488,452],{"class":164},[151,490,455],{"class":175},[151,492,458],{"class":189},[151,494,478],{"class":189},[12,496,497,498,502],{},"The full example is located in the ",[15,499,501],{"href":243,"rel":500},[],"terraform/aws directory in this project",".",[504,505,507],"h4",{"id":506},"parse-tfsec-json-reports-with-jq","Parse tfsec JSON reports with jq",[12,509,510,511,515,516,519,520,523,524,527,528,531,532,218,535,502],{},"In an earlier blog post, we shared ",[15,512,514],{"href":513},"/blog/devops-workflows-json-format-jq-ci-cd-lint/","how to detect the JSON data structures and filter with chained jq commands",". The tfsec report is a good practice: Extract the ",[117,517,518],{},"results"," key, iterate through all array list items and filtered by ",[117,521,522],{},"rule_service"," being ",[117,525,526],{},"s3",", and only print ",[117,529,530],{},"severity",", ",[117,533,534],{},"description",[117,536,537],{},"location.filename",[142,539,541],{"className":262,"code":540,"language":264,"meta":147,"style":147},"$ jq \u003C tfsec-report.json | jq -c '.[\"results\"]' | jq -c '.[] | select (.rule_service == \"s3\") | [.severity, .description, .location.filename]'\n",[117,542,543],{"__ignoreMap":147},[151,544,545,547,550,553,556,559,561,564,567,569,571,573],{"class":153,"line":154},[151,546,271],{"class":164},[151,548,549],{"class":189}," jq",[151,551,552],{"class":185}," \u003C",[151,554,555],{"class":189}," tfsec-report.json",[151,557,558],{"class":185}," |",[151,560,549],{"class":164},[151,562,563],{"class":168}," -c",[151,565,566],{"class":189}," '.[\"results\"]'",[151,568,558],{"class":185},[151,570,549],{"class":164},[151,572,563],{"class":168},[151,574,575],{"class":189}," '.[] | select (.rule_service == \"s3\") | [.severity, .description, .location.filename]'\n",[12,577,578],{},[579,580],"img",{"alt":581,"src":582},"tfsec parser output example","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398550/blog/Content%20Images/iac-security-scanning/tfsec-json-jq-parser.png",[125,584,325],{"id":325},[12,586,587,591],{},[15,588,325],{"href":589,"rel":590},"https://kics.io/",[]," is another IaC scanner, providing support for many different tools (Ansible, Terraform, Kubernetes, Dockerfile, and cloud configuration APIs such as AWS CloudFormation, Azure Resource Manager, and Google Deployment Manager).",[12,593,594,595,600,601,531,604,218,607,610],{},"Let's try it: ",[15,596,599],{"href":597,"rel":598},"https://docs.kics.io/latest/getting-started/",[],"Install kics"," and run it on the vulnerable project. ",[117,602,603],{},"--report-formats",[117,605,606],{},"--output-path",[117,608,609],{},"--output-name"," allow you to create a JSON report which can be automatically parsed with additional tooling.",[142,612,614],{"className":262,"code":613,"language":264,"meta":147,"style":147},"$ kics scan --path .\n$ kics scan --path . --report-formats json --output-path kics --output-name kics-report.json\n",[117,615,616,632],{"__ignoreMap":147},[151,617,618,620,623,626,629],{"class":153,"line":154},[151,619,271],{"class":164},[151,621,622],{"class":189}," kics",[151,624,625],{"class":189}," scan",[151,627,628],{"class":168}," --path",[151,630,631],{"class":189}," .\n",[151,633,634,636,638,640,642,645,648,650,653,655,658],{"class":153,"line":161},[151,635,271],{"class":164},[151,637,622],{"class":189},[151,639,625],{"class":189},[151,641,628],{"class":168},[151,643,644],{"class":189}," .",[151,646,647],{"class":168}," --report-formats",[151,649,432],{"class":189},[151,651,652],{"class":168}," --output-path",[151,654,622],{"class":189},[151,656,657],{"class":168}," --output-name",[151,659,660],{"class":189}," kics-report.json\n",[12,662,663,664,666,667,670,671,674,675,678],{},"Parsing the JSON report from ",[117,665,325],{}," with jq works the same way as the tfsec example above. Inspect the data structure and nested object, and filter by AWS as ",[117,668,669],{},"cloud_provider",". The ",[117,672,673],{},"files"," entry is an array of dictionaries, which turned out to be a little tricky to extract with an additional ",[117,676,677],{},"(.files[] | .file_name )"," to add:",[142,680,682],{"className":262,"code":681,"language":264,"meta":147,"style":147},"$ jq \u003C kics/kics-report.json | jq -c '.[\"queries\"]' | jq -c '.[] | select (.cloud_provider == \"AWS\") | [.severity, .description, (.files[] | .file_name ) ]'\n",[117,683,684],{"__ignoreMap":147},[151,685,686,688,690,692,695,697,699,701,704,706,708,710],{"class":153,"line":154},[151,687,271],{"class":164},[151,689,549],{"class":189},[151,691,552],{"class":185},[151,693,694],{"class":189}," kics/kics-report.json",[151,696,558],{"class":185},[151,698,549],{"class":164},[151,700,563],{"class":168},[151,702,703],{"class":189}," '.[\"queries\"]'",[151,705,558],{"class":185},[151,707,549],{"class":164},[151,709,563],{"class":168},[151,711,712],{"class":189}," '.[] | select (.cloud_provider == \"AWS\") | [.severity, .description, (.files[] | .file_name ) ]'\n",[12,714,715],{},[579,716],{"alt":717,"src":718},"kics json jq parser","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398546/blog/Content%20Images/iac-security-scanning/kics-json-jq-parser.png",[12,720,721,723,724,729,730,733,734,737],{},[117,722,325],{}," returns different ",[15,725,728],{"href":726,"rel":727},"https://docs.kics.io/latest/results/#exit_status_code",[],"exit codes"," based on the number of different severities found. ",[117,731,732],{},"50"," indicates ",[117,735,736],{},"HIGH"," severities and causes your CI/CD pipeline to fail.",[125,739,740],{"id":740},"checkov",[12,742,743,748],{},[15,744,747],{"href":745,"rel":746},"https://checkov.io",[],"Checkov"," supports Terraform (for AWS, GCP, Azure and OCI), CloudFormation, ARM, Severless framework, Helm charts, Kubernetes, and Docker.",[142,750,752],{"className":262,"code":751,"language":264,"meta":147,"style":147},"$ brew install checkov\n$ checkov --directory .\n",[117,753,754,765],{"__ignoreMap":147},[151,755,756,758,760,762],{"class":153,"line":154},[151,757,271],{"class":164},[151,759,383],{"class":189},[151,761,386],{"class":189},[151,763,764],{"class":189}," checkov\n",[151,766,767,769,772,775],{"class":153,"line":161},[151,768,271],{"class":164},[151,770,771],{"class":189}," checkov",[151,773,774],{"class":168}," --directory",[151,776,631],{"class":189},[125,778,331],{"id":331},[12,780,781,786,787,792],{},[15,782,785],{"href":783,"rel":784},"https://runterrascan.io/docs/getting-started/",[],"Terrascan"," supports Terraform, and more ",[15,788,791],{"href":789,"rel":790},"https://runterrascan.io/docs/policies/",[],"policies"," for cloud providers, Docker, and Kubernetes.",[142,794,796],{"className":262,"code":795,"language":264,"meta":147,"style":147},"$ brew install terrascan\n$ terrascan scan .\n",[117,797,798,809],{"__ignoreMap":147},[151,799,800,802,804,806],{"class":153,"line":154},[151,801,271],{"class":164},[151,803,383],{"class":189},[151,805,386],{"class":189},[151,807,808],{"class":189}," terrascan\n",[151,810,811,813,816,818],{"class":153,"line":161},[151,812,271],{"class":164},[151,814,815],{"class":189}," terrascan",[151,817,625],{"class":189},[151,819,631],{"class":189},[125,821,337],{"id":337},[12,823,824,825,830],{},"Semgrep is working on ",[15,826,829],{"href":827,"rel":828},"https://semgrep.dev/docs/language-support/",[],"Terraform support",", currently in Beta. It also detects Dockerfile errors - for example invalid port ranges and multiple ranges, similar to kics.",[142,832,834],{"className":262,"code":833,"language":264,"meta":147,"style":147},"$ brew install semgrep\n$ semgrep --config auto .\n",[117,835,836,847],{"__ignoreMap":147},[151,837,838,840,842,844],{"class":153,"line":154},[151,839,271],{"class":164},[151,841,383],{"class":189},[151,843,386],{"class":189},[151,845,846],{"class":189}," semgrep\n",[151,848,849,851,854,857,860],{"class":153,"line":161},[151,850,271],{"class":164},[151,852,853],{"class":189}," semgrep",[151,855,856],{"class":168}," --config",[151,858,859],{"class":189}," auto",[151,861,631],{"class":189},[125,863,343],{"id":343},[12,865,866,870],{},[15,867,343],{"href":868,"rel":869},"https://github.com/terraform-linters/tflint",[]," also is an alternative scanner.",[109,872,61],{"id":873},"develop-more-iac-scenarios",[12,875,876,877,882],{},"While testing IaC Security Scanners for the first time, I was looking for demo projects and examples. The ",[15,878,881],{"href":879,"rel":880},"https://docs.kics.io/latest/queries/terraform-queries/",[],"kics queries list for Terraform"," provides an exhaustive list of all vulnerabilities and the documentation linked. From there, you can build and create potential attack vectors for demos and showcases without leaking your company code and workflows.",[12,884,885,890],{},[15,886,889],{"href":887,"rel":888},"https://github.com/bridgecrewio/terragoat",[],"Terragoat"," also is a great learning resource to test various scanners and see real-life examples for vulnerabilities.",[142,892,894],{"className":262,"code":893,"language":264,"meta":147,"style":147},"$ cd /tmp && git clone https://github.com/bridgecrewio/terragoat.git && cd terragoat\n\n$ tfsec .\n$ kics scan --path .\n$ checkov --directory .\n$ semgrep --config auto .\n$ terrascan scan .\n",[117,895,896,922,928,936,948,958,971],{"__ignoreMap":147},[151,897,898,900,902,905,907,910,912,915,917,919],{"class":153,"line":154},[151,899,271],{"class":164},[151,901,274],{"class":189},[151,903,904],{"class":189}," /tmp",[151,906,293],{"class":175},[151,908,909],{"class":164},"git",[151,911,287],{"class":189},[151,913,914],{"class":189}," https://github.com/bridgecrewio/terragoat.git",[151,916,293],{"class":175},[151,918,296],{"class":168},[151,920,921],{"class":189}," terragoat\n",[151,923,924],{"class":153,"line":161},[151,925,927],{"emptyLinePlaceholder":926},true,"\n",[151,929,930,932,934],{"class":153,"line":179},[151,931,271],{"class":164},[151,933,396],{"class":189},[151,935,631],{"class":189},[151,937,938,940,942,944,946],{"class":153,"line":193},[151,939,271],{"class":164},[151,941,622],{"class":189},[151,943,625],{"class":189},[151,945,628],{"class":168},[151,947,631],{"class":189},[151,949,950,952,954,956],{"class":153,"line":204},[151,951,271],{"class":164},[151,953,771],{"class":189},[151,955,774],{"class":168},[151,957,631],{"class":189},[151,959,961,963,965,967,969],{"class":153,"line":960},6,[151,962,271],{"class":164},[151,964,853],{"class":189},[151,966,856],{"class":168},[151,968,859],{"class":189},[151,970,631],{"class":189},[151,972,974,976,978,980],{"class":153,"line":973},7,[151,975,271],{"class":164},[151,977,815],{"class":189},[151,979,625],{"class":189},[151,981,631],{"class":189},[12,983,984,985,989],{},"It is also important to verify the reported vulnerabilities and create documentation for required actions for your teams. Not all detected vulnerabilities are necessarily equally critical in your environment. With the rapid development of IaC, [GitOps}(",[15,986,987],{"href":987,"rel":988},"https://about.gitlab.com/topics/gitops/",[],"), and cloud-native environments, it can also be a good idea to use 2+ scanners to see if there are missing vulnerabilities on one or the other.",[12,991,992],{},"The following sections discuss more scenarios in detail.",[33,994,995,999,1003],{},[36,996,997],{},[15,998,69],{"href":68},[36,1000,1001],{},[15,1002,75],{"href":74},[36,1004,1005],{},[15,1006,81],{"href":80},[125,1008,69],{"id":1009},"terraform-module-dependency-scans",[12,1011,1012,1013,1018,1019,1022],{},"Re-usable IaC workflows also can introduce security vulnerabilities you are not aware of. ",[15,1014,1017],{"href":1015,"rel":1016},"https://gitlab.com/gitlab-da/use-cases/iac-tf-vuln-module",[],"This project"," provides the module files and package in the registry, which can be consumed by ",[117,1020,1021],{},"main.tf"," in the demo project.",[142,1024,1026],{"className":144,"code":1025,"language":146,"meta":147,"style":147},"module \"my_module_name\" {\n  source = \"gitlab.com/gitlab-da/iac-tf-vuln-module/aws\"\n  version = \"1.0.0\"\n}\n",[117,1027,1028,1038,1048,1058],{"__ignoreMap":147},[151,1029,1030,1033,1036],{"class":153,"line":154},[151,1031,1032],{"class":164},"module",[151,1034,1035],{"class":168}," \"my_module_name\"",[151,1037,176],{"class":175},[151,1039,1040,1043,1045],{"class":153,"line":161},[151,1041,1042],{"class":175},"  source",[151,1044,186],{"class":185},[151,1046,1047],{"class":189}," \"gitlab.com/gitlab-da/iac-tf-vuln-module/aws\"\n",[151,1049,1050,1053,1055],{"class":153,"line":179},[151,1051,1052],{"class":175},"  version",[151,1054,186],{"class":185},[151,1056,1057],{"class":189}," \"1.0.0\"\n",[151,1059,1060],{"class":153,"line":193},[151,1061,207],{"class":175},[12,1063,1064,1065,531,1070,1072,1073,218,1075,1077,1078,1081,1082,1084,1085,1087,1088,502],{},"kics has ",[15,1066,1069],{"href":1067,"rel":1068},"https://docs.kics.io/latest/platforms/#terraform_modules",[],"limited support for the official Terraform module registry",[117,1071,740],{}," failed to download private modules, ",[117,1074,331],{},[117,1076,319],{}," work when ",[117,1079,1080],{},"terraform init"," is run before the scan. Depending on your requirements, running ",[117,1083,325],{}," for everything and ",[117,1086,319],{}," for module dependency checks can be a solution, suggestion added ",[15,1089,1092],{"href":1090,"rel":1091},"https://gitlab.com/groups/gitlab-org/-/epics/6653#note_840447132",[],"here",[125,1094,75],{"id":1095},"iac-security-scanning-for-containers",[12,1097,1098,1099,1104],{},"Security problems in containers can lead to application deployment vulnerabilities. The ",[15,1100,1103],{"href":1101,"rel":1102},"https://docs.kics.io/latest/queries/dockerfile-queries/",[],"kics query database"," helps to reverse engineer more vulnerable examples: Using the latest tag, privilege escalations with invoking sudo in a container, ports out of range, and multiple entrypoints are just a few bad practices.",[12,1106,1107,1108,1113],{},"The following ",[15,1109,1112],{"href":1110,"rel":1111},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning/-/blob/main/Dockerfile",[],"Dockerfile"," implements example vulnerabilities for the scanners to detect:",[142,1115,1118],{"className":1116,"code":1117,"language":232,"meta":147},[230],"# Create vulnerabilities based on kics queries in https://docs.kics.io/latest/queries/dockerfile-queries/\nFROM debian:latest\n\n# kics: Run Using Sudo\n# kics: Run Using apt\nRUN sudo apt install git\n\n# kics: UNIX Ports Out Of Range\nEXPOSE 99999\n\n# kics: Multiple ENTRYPOINT Instructions Listed\nENTRYPOINT [\"ex1\"]\nENTRYPOINT [\"ex2\"]\n",[117,1119,1117],{"__ignoreMap":147},[12,1121,1122,1123,1125,1126,1129],{},"Kics, tfsec, and terrascan can detect ",[117,1124,1112],{}," vulnerabilities, similar to semgrep and checkov. As an example scanner, terrascan can detect the vulnerabilities using the ",[117,1127,1128],{},"--iac-type docker"," parameter that allows to filter the scan type.",[142,1131,1133],{"className":262,"code":1132,"language":264,"meta":147,"style":147},"$ terrascan scan --iac-type docker\n",[117,1134,1135],{"__ignoreMap":147},[151,1136,1137,1139,1141,1143,1146],{"class":153,"line":154},[151,1138,271],{"class":164},[151,1140,815],{"class":189},[151,1142,625],{"class":189},[151,1144,1145],{"class":168}," --iac-type",[151,1147,1148],{"class":189}," docker\n",[12,1150,1151],{},[579,1152],{"alt":1153,"src":1154},"terrascan Docker IaC type scan result","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398550/blog/Content%20Images/iac-security-scanning/terrascan-docker-iac.png",[12,1156,1157],{},"You can run kics and tfsec as an exercise to verify the results.",[125,1159,81],{"id":1160},"iac-security-scanning-with-kubernetes",[12,1162,1163,1164,1169,1170,1175,1176,1181],{},"Securing a Kubernetes cluster can be a challenging task. Open Policy Agent, Kyverno, RBAC, etc., and many different YAML configuration attributes require reviews and automated checks before the production deployments. ",[15,1165,1168],{"href":1166,"rel":1167},"https://docs.gitlab.com/user/clusters/agent/vulnerabilities/",[],"Cluster image scanning"," is one way to mitigate security threats, next to ",[15,1171,1174],{"href":1172,"rel":1173},"https://docs.gitlab.com/user/application_security/container_scanning/",[],"Container scanning"," for the applications being deployed. A suggested read is the book ",[15,1177,1180],{"href":1178,"rel":1179},"https://www.oreilly.com/library/view/hacking-kubernetes/9781492081722/",[],"“Hacking Kubernetes” book"," by Andrew Martin and Michael Hausenblas if you want to dive deeper into Kubernetes security and attack vectors.",[12,1183,1184,1185,1190],{},"It's possible to make mistakes when, for example, copying YAML example configuration and continue using it. I've created a deployment and service for a ",[15,1186,1189],{"href":1187,"rel":1188},"https://handbook.gitlab.com/handbook/marketing/developer-relations/developer-advocacy/projects/#practical-kubernetes-monitoring-with-prometheus",[],"Kubernetes monitoring workshop",", which provides a practical example to learn but also uses some not so good practices.",[12,1192,1193,1194,1199],{},"The following configuration in ",[15,1195,1198],{"href":1196,"rel":1197},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning/-/blob/main/kubernetes/ecc-demo-service.yml",[],"ecc-demo-service.yml"," introduces vulnerabilities and potential production problems:",[142,1201,1205],{"className":1202,"code":1203,"language":1204,"meta":147,"style":147},"language-yaml shiki shiki-themes github-light","---\n# A deployment for the ECC Prometheus demo service with 3 replicas.\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: ecc-demo-service\n  labels:\n    app: ecc-demo-service\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: ecc-demo-service\n  template:\n    metadata:\n      labels:\n        app: ecc-demo-service\n    spec:\n      containers:\n      - name: ecc-demo-service\n        image: registry.gitlab.com/everyonecancontribute/observability/prometheus_demo_service:latest\n        imagePullPolicy: IfNotPresent\n        args:\n        - -listen-address=:80\n        ports:\n        - containerPort: 80\n---\n# A service that references the demo service deployment.\napiVersion: v1\nkind: Service\nmetadata:\n  name: ecc-demo-service\n  labels:\n    app: ecc-demo-service\nspec:\n  ports:\n  - port: 80\n    name: web\n  selector:\n    app: ecc-demo-service\n\n","yaml",[117,1206,1207,1212,1217,1229,1239,1247,1257,1264,1274,1282,1293,1301,1309,1319,1327,1335,1343,1353,1361,1369,1382,1393,1404,1412,1421,1429,1442,1447,1453,1463,1473,1480,1489,1496,1505,1512,1520,1533,1544,1551],{"__ignoreMap":147},[151,1208,1209],{"class":153,"line":154},[151,1210,1211],{"class":164},"---\n",[151,1213,1214],{"class":153,"line":161},[151,1215,1216],{"class":157},"# A deployment for the ECC Prometheus demo service with 3 replicas.\n",[151,1218,1219,1223,1226],{"class":153,"line":179},[151,1220,1222],{"class":1221},"shJU0","apiVersion",[151,1224,1225],{"class":175},": ",[151,1227,1228],{"class":189},"apps/v1\n",[151,1230,1231,1234,1236],{"class":153,"line":193},[151,1232,1233],{"class":1221},"kind",[151,1235,1225],{"class":175},[151,1237,1238],{"class":189},"Deployment\n",[151,1240,1241,1244],{"class":153,"line":204},[151,1242,1243],{"class":1221},"metadata",[151,1245,1246],{"class":175},":\n",[151,1248,1249,1252,1254],{"class":153,"line":960},[151,1250,1251],{"class":1221},"  name",[151,1253,1225],{"class":175},[151,1255,1256],{"class":189},"ecc-demo-service\n",[151,1258,1259,1262],{"class":153,"line":973},[151,1260,1261],{"class":1221},"  labels",[151,1263,1246],{"class":175},[151,1265,1267,1270,1272],{"class":153,"line":1266},8,[151,1268,1269],{"class":1221},"    app",[151,1271,1225],{"class":175},[151,1273,1256],{"class":189},[151,1275,1277,1280],{"class":153,"line":1276},9,[151,1278,1279],{"class":1221},"spec",[151,1281,1246],{"class":175},[151,1283,1285,1288,1290],{"class":153,"line":1284},10,[151,1286,1287],{"class":1221},"  replicas",[151,1289,1225],{"class":175},[151,1291,1292],{"class":168},"3\n",[151,1294,1296,1299],{"class":153,"line":1295},11,[151,1297,1298],{"class":1221},"  selector",[151,1300,1246],{"class":175},[151,1302,1304,1307],{"class":153,"line":1303},12,[151,1305,1306],{"class":1221},"    matchLabels",[151,1308,1246],{"class":175},[151,1310,1312,1315,1317],{"class":153,"line":1311},13,[151,1313,1314],{"class":1221},"      app",[151,1316,1225],{"class":175},[151,1318,1256],{"class":189},[151,1320,1322,1325],{"class":153,"line":1321},14,[151,1323,1324],{"class":1221},"  template",[151,1326,1246],{"class":175},[151,1328,1330,1333],{"class":153,"line":1329},15,[151,1331,1332],{"class":1221},"    metadata",[151,1334,1246],{"class":175},[151,1336,1338,1341],{"class":153,"line":1337},16,[151,1339,1340],{"class":1221},"      labels",[151,1342,1246],{"class":175},[151,1344,1346,1349,1351],{"class":153,"line":1345},17,[151,1347,1348],{"class":1221},"        app",[151,1350,1225],{"class":175},[151,1352,1256],{"class":189},[151,1354,1356,1359],{"class":153,"line":1355},18,[151,1357,1358],{"class":1221},"    spec",[151,1360,1246],{"class":175},[151,1362,1364,1367],{"class":153,"line":1363},19,[151,1365,1366],{"class":1221},"      containers",[151,1368,1246],{"class":175},[151,1370,1372,1375,1378,1380],{"class":153,"line":1371},20,[151,1373,1374],{"class":175},"      - ",[151,1376,1377],{"class":1221},"name",[151,1379,1225],{"class":175},[151,1381,1256],{"class":189},[151,1383,1385,1388,1390],{"class":153,"line":1384},21,[151,1386,1387],{"class":1221},"        image",[151,1389,1225],{"class":175},[151,1391,1392],{"class":189},"registry.gitlab.com/everyonecancontribute/observability/prometheus_demo_service:latest\n",[151,1394,1396,1399,1401],{"class":153,"line":1395},22,[151,1397,1398],{"class":1221},"        imagePullPolicy",[151,1400,1225],{"class":175},[151,1402,1403],{"class":189},"IfNotPresent\n",[151,1405,1407,1410],{"class":153,"line":1406},23,[151,1408,1409],{"class":1221},"        args",[151,1411,1246],{"class":175},[151,1413,1415,1418],{"class":153,"line":1414},24,[151,1416,1417],{"class":175},"        - ",[151,1419,1420],{"class":189},"-listen-address=:80\n",[151,1422,1424,1427],{"class":153,"line":1423},25,[151,1425,1426],{"class":1221},"        ports",[151,1428,1246],{"class":175},[151,1430,1432,1434,1437,1439],{"class":153,"line":1431},26,[151,1433,1417],{"class":175},[151,1435,1436],{"class":1221},"containerPort",[151,1438,1225],{"class":175},[151,1440,1441],{"class":168},"80\n",[151,1443,1445],{"class":153,"line":1444},27,[151,1446,1211],{"class":164},[151,1448,1450],{"class":153,"line":1449},28,[151,1451,1452],{"class":157},"# A service that references the demo service deployment.\n",[151,1454,1456,1458,1460],{"class":153,"line":1455},29,[151,1457,1222],{"class":1221},[151,1459,1225],{"class":175},[151,1461,1462],{"class":189},"v1\n",[151,1464,1466,1468,1470],{"class":153,"line":1465},30,[151,1467,1233],{"class":1221},[151,1469,1225],{"class":175},[151,1471,1472],{"class":189},"Service\n",[151,1474,1476,1478],{"class":153,"line":1475},31,[151,1477,1243],{"class":1221},[151,1479,1246],{"class":175},[151,1481,1483,1485,1487],{"class":153,"line":1482},32,[151,1484,1251],{"class":1221},[151,1486,1225],{"class":175},[151,1488,1256],{"class":189},[151,1490,1492,1494],{"class":153,"line":1491},33,[151,1493,1261],{"class":1221},[151,1495,1246],{"class":175},[151,1497,1499,1501,1503],{"class":153,"line":1498},34,[151,1500,1269],{"class":1221},[151,1502,1225],{"class":175},[151,1504,1256],{"class":189},[151,1506,1508,1510],{"class":153,"line":1507},35,[151,1509,1279],{"class":1221},[151,1511,1246],{"class":175},[151,1513,1515,1518],{"class":153,"line":1514},36,[151,1516,1517],{"class":1221},"  ports",[151,1519,1246],{"class":175},[151,1521,1523,1526,1529,1531],{"class":153,"line":1522},37,[151,1524,1525],{"class":175},"  - ",[151,1527,1528],{"class":1221},"port",[151,1530,1225],{"class":175},[151,1532,1441],{"class":168},[151,1534,1536,1539,1541],{"class":153,"line":1535},38,[151,1537,1538],{"class":1221},"    name",[151,1540,1225],{"class":175},[151,1542,1543],{"class":189},"web\n",[151,1545,1547,1549],{"class":153,"line":1546},39,[151,1548,1298],{"class":1221},[151,1550,1246],{"class":175},[151,1552,1554,1556,1558],{"class":153,"line":1553},40,[151,1555,1269],{"class":1221},[151,1557,1225],{"class":175},[151,1559,1256],{"class":189},[12,1561,1562,1563,502],{},"Let's scan the Kubernetes manifest with kics and parse the results again with jq. A list of kics queries for Kubernetes can be found in the ",[15,1564,1567],{"href":1565,"rel":1566},"https://docs.kics.io/latest/queries/kubernetes-queries/",[],"kics documentation",[142,1569,1571],{"className":262,"code":1570,"language":264,"meta":147,"style":147},"$ kics scan --path kubernetes --report-formats json --output-path kics --output-name kics-report.json\n\n$ jq \u003C kics/kics-report.json | jq -c '.[\"queries\"]' | jq -c '.[] | select (.platform == \"Kubernetes\") | [.severity, .description, (.files[] | .file_name ) ]'\n",[117,1572,1573,1598,1602],{"__ignoreMap":147},[151,1574,1575,1577,1579,1581,1583,1586,1588,1590,1592,1594,1596],{"class":153,"line":154},[151,1576,271],{"class":164},[151,1578,622],{"class":189},[151,1580,625],{"class":189},[151,1582,628],{"class":168},[151,1584,1585],{"class":189}," kubernetes",[151,1587,647],{"class":168},[151,1589,432],{"class":189},[151,1591,652],{"class":168},[151,1593,622],{"class":189},[151,1595,657],{"class":168},[151,1597,660],{"class":189},[151,1599,1600],{"class":153,"line":161},[151,1601,927],{"emptyLinePlaceholder":926},[151,1603,1604,1606,1608,1610,1612,1614,1616,1618,1620,1622,1624,1626],{"class":153,"line":179},[151,1605,271],{"class":164},[151,1607,549],{"class":189},[151,1609,552],{"class":185},[151,1611,694],{"class":189},[151,1613,558],{"class":185},[151,1615,549],{"class":164},[151,1617,563],{"class":168},[151,1619,703],{"class":189},[151,1621,558],{"class":185},[151,1623,549],{"class":164},[151,1625,563],{"class":168},[151,1627,1628],{"class":189}," '.[] | select (.platform == \"Kubernetes\") | [.severity, .description, (.files[] | .file_name ) ]'\n",[12,1630,1631],{},[579,1632],{"alt":1633,"src":1634},"Kubernetes manifest scans and jq parser results with kics","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398546/blog/Content%20Images/iac-security-scanning/kics-kubernetes-jq-parser.png",[12,1636,1637,1641],{},[15,1638,747],{"href":1639,"rel":1640},"https://www.checkov.io/",[]," detects similar vulnerabilities with Kubernetes.",[142,1643,1646],{"className":1644,"code":1645,"language":232,"meta":147},[230],"$ checkov --directory kubernetes/\n$ checkov --directory kubernetes -o json > checkov-report.json\n",[117,1647,1645],{"__ignoreMap":147},[12,1649,1650,1655],{},[15,1651,1654],{"href":1652,"rel":1653},"https://docs.kubelinter.io/#/?id=installing-kubelinter",[],"kube-linter"," analyzes Kubernetes YAML files and Helm charts for production readiness and security.",[142,1657,1659],{"className":262,"code":1658,"language":264,"meta":147,"style":147},"$ brew install kube-linter\n$ kube-linter lint kubernetes/ecc-demo-service.yml --format json > kube-linter-report.json\n",[117,1660,1661,1672],{"__ignoreMap":147},[151,1662,1663,1665,1667,1669],{"class":153,"line":154},[151,1664,271],{"class":164},[151,1666,383],{"class":189},[151,1668,386],{"class":189},[151,1670,1671],{"class":189}," kube-linter\n",[151,1673,1674,1676,1679,1682,1685,1687,1689,1692],{"class":153,"line":161},[151,1675,271],{"class":164},[151,1677,1678],{"class":189}," kube-linter",[151,1680,1681],{"class":189}," lint",[151,1683,1684],{"class":189}," kubernetes/ecc-demo-service.yml",[151,1686,429],{"class":168},[151,1688,432],{"class":189},[151,1690,1691],{"class":185}," >",[151,1693,1694],{"class":189}," kube-linter-report.json\n",[12,1696,1697,1702,1703,1705,1706,502],{},[15,1698,1701],{"href":1699,"rel":1700},"https://kubesec.io/",[],"kubesec"," provides security risk analysis for Kubernetes resources. ",[117,1704,1701],{}," is also integrated into the ",[15,1707,1710],{"href":1708,"rel":1709},"https://docs.gitlab.com/user/application_security/sast/#enabling-kubesec-analyzer",[],"GitLab SAST scanners",[142,1712,1714],{"className":262,"code":1713,"language":264,"meta":147,"style":147},"$ docker run -i kubesec/kubesec:512c5e0 scan /dev/stdin \u003C kubernetes/ecc-demo-service.yml\n",[117,1715,1716],{"__ignoreMap":147},[151,1717,1718,1720,1723,1726,1729,1732,1734,1737,1739],{"class":153,"line":154},[151,1719,271],{"class":164},[151,1721,1722],{"class":189}," docker",[151,1724,1725],{"class":189}," run",[151,1727,1728],{"class":168}," -i",[151,1730,1731],{"class":189}," kubesec/kubesec:512c5e0",[151,1733,625],{"class":189},[151,1735,1736],{"class":189}," /dev/stdin",[151,1738,552],{"class":185},[151,1740,1741],{"class":189}," kubernetes/ecc-demo-service.yml\n",[109,1743,87],{"id":1744},"integrations-into-cicd-and-merge-requests-for-review",[12,1746,1747,1748,1753],{},"There are many scanners out there, and most of them return the results in JSON which can be parsed and integrated into your CI/CD pipelines. You can learn more about the evaluation of GitLab IaC scanners in ",[15,1749,1752],{"href":1750,"rel":1751},"https://gitlab.com/gitlab-org/gitlab/-/issues/39695",[],"this issue",". The table in the issue includes licenses, languages, outputs, and examples.",[12,1755,1756,218,1758,1760,1761,1766,1767,1772,1773,502],{},[117,1757,740],{},[117,1759,319],{}," provide JUnit XML reports as output format, which can be parsed and integrated into CI/CD. Vulnerability reports will need a different format though to not confuse them with unit test results for example. Integrating a SAST scanner in GitLab requires you to provide ",[15,1762,1765],{"href":1763,"rel":1764},"https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportssast",[],"artifacts:reports:sast"," as a specified output format and API. ",[15,1768,1771],{"href":1769,"rel":1770},"https://docs.gitlab.com/user/application_security/iac_scanning/#reports-json-format",[],"This report"," can then be consumed by GitLab integrations such as MR widgets and vulnerability dashboards, available in the Ultimate tier. The following screenshot shows adding a Kubernetes deployment and service with potential vulnerabilities in ",[15,1774,1777],{"href":1775,"rel":1776},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning/-/merge_requests/3",[],"this MR",[12,1779,1780],{},[579,1781],{"alt":1782,"src":1783},"MR widget showing IaC vulnerabilities with Kubernetes","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398546/blog/Content%20Images/iac-security-scanning/gitlab-iac-mr-widget-kubernetes.png",[125,1785,95],{"id":1786},"reports-in-mrs-as-comment",[12,1788,1789,1790,502],{},"There are different ways to collect the JSON reports in your CI/CD pipelines or scheduled runs. One of the ideas can be creating a merge request comment with a Markdown table. It needs a bit more work with parsing the reports, formatting the comment text, and interacting with the GitLab REST API, shown in the following steps in a Python script. You can follow the implementation steps to re-create them in your preferred language for the scanner type and use ",[15,1791,1793],{"href":1792},"/partners/technology-partners/#api-clients","GitLab API clients",[12,1795,1796,1797,1800,1801,1804,1805,1808],{},"First, read the report in JSON format, and inspect whether ",[117,1798,1799],{},"kics_version"," is set to continue. Then extract the ",[117,1802,1803],{},"queries"," key, and prepare the ",[117,1806,1807],{},"comment_body"," with the markdown table header columns.",[142,1810,1814],{"className":1811,"code":1812,"language":1813,"meta":147,"style":147},"language-python shiki shiki-themes github-light","FILE=\"kics/kics-report.json\"\n\nf = open(FILE)\nreport = json.load(f)\n\n# Parse the report: kics\nif \"kics_version\" in report:\n    print(\"Found kics '%s' in '%s'\" % (report[\"kics_version\"], FILE))\n    queries = report[\"queries\"]\nelse:\n    raise Exception(\"Unsupported report format\")\n\ncomment_body = \"\"\"### kics vulnerabilities report\n\n| Severity | Description | Platform | Filename |\n|----------|-------------|----------|----------|\n\"\"\"\n","python",[117,1815,1816,1827,1831,1848,1858,1862,1867,1881,1919,1935,1942,1957,1961,1971,1975,1980,1985],{"__ignoreMap":147},[151,1817,1818,1821,1824],{"class":153,"line":154},[151,1819,1820],{"class":168},"FILE",[151,1822,1823],{"class":185},"=",[151,1825,1826],{"class":189},"\"kics/kics-report.json\"\n",[151,1828,1829],{"class":153,"line":161},[151,1830,927],{"emptyLinePlaceholder":926},[151,1832,1833,1836,1838,1841,1843,1845],{"class":153,"line":179},[151,1834,1835],{"class":175},"f ",[151,1837,1823],{"class":185},[151,1839,1840],{"class":168}," open",[151,1842,449],{"class":175},[151,1844,1820],{"class":168},[151,1846,1847],{"class":175},")\n",[151,1849,1850,1853,1855],{"class":153,"line":193},[151,1851,1852],{"class":175},"report ",[151,1854,1823],{"class":185},[151,1856,1857],{"class":175}," json.load(f)\n",[151,1859,1860],{"class":153,"line":204},[151,1861,927],{"emptyLinePlaceholder":926},[151,1863,1864],{"class":153,"line":960},[151,1865,1866],{"class":157},"# Parse the report: kics\n",[151,1868,1869,1872,1875,1878],{"class":153,"line":973},[151,1870,1871],{"class":185},"if",[151,1873,1874],{"class":189}," \"kics_version\"",[151,1876,1877],{"class":185}," in",[151,1879,1880],{"class":175}," report:\n",[151,1882,1883,1886,1888,1891,1894,1897,1899,1902,1905,1908,1911,1914,1916],{"class":153,"line":1266},[151,1884,1885],{"class":168},"    print",[151,1887,449],{"class":175},[151,1889,1890],{"class":189},"\"Found kics '",[151,1892,1893],{"class":168},"%s",[151,1895,1896],{"class":189},"' in '",[151,1898,1893],{"class":168},[151,1900,1901],{"class":189},"'\"",[151,1903,1904],{"class":185}," %",[151,1906,1907],{"class":175}," (report[",[151,1909,1910],{"class":189},"\"kics_version\"",[151,1912,1913],{"class":175},"], ",[151,1915,1820],{"class":168},[151,1917,1918],{"class":175},"))\n",[151,1920,1921,1924,1926,1929,1932],{"class":153,"line":1276},[151,1922,1923],{"class":175},"    queries ",[151,1925,1823],{"class":185},[151,1927,1928],{"class":175}," report[",[151,1930,1931],{"class":189},"\"queries\"",[151,1933,1934],{"class":175},"]\n",[151,1936,1937,1940],{"class":153,"line":1284},[151,1938,1939],{"class":185},"else",[151,1941,1246],{"class":175},[151,1943,1944,1947,1950,1952,1955],{"class":153,"line":1295},[151,1945,1946],{"class":185},"    raise",[151,1948,1949],{"class":168}," Exception",[151,1951,449],{"class":175},[151,1953,1954],{"class":189},"\"Unsupported report format\"",[151,1956,1847],{"class":175},[151,1958,1959],{"class":153,"line":1303},[151,1960,927],{"emptyLinePlaceholder":926},[151,1962,1963,1966,1968],{"class":153,"line":1311},[151,1964,1965],{"class":175},"comment_body ",[151,1967,1823],{"class":185},[151,1969,1970],{"class":189}," \"\"\"### kics vulnerabilities report\n",[151,1972,1973],{"class":153,"line":1321},[151,1974,927],{"emptyLinePlaceholder":926},[151,1976,1977],{"class":153,"line":1329},[151,1978,1979],{"class":189},"| Severity | Description | Platform | Filename |\n",[151,1981,1982],{"class":153,"line":1337},[151,1983,1984],{"class":189},"|----------|-------------|----------|----------|\n",[151,1986,1987],{"class":153,"line":1345},[151,1988,1989],{"class":189},"\"\"\"\n",[12,1991,1992,1993,1996,1997,1999,2000,2003],{},"Next, we need to parse all queries in a loop, and collect all column values. They are collected into a new list, which then gets joined with the ",[117,1994,1995],{},"|"," character. The ",[117,1998,673],{}," key needs a nested collection, as this is a list of dictionaries where only the ",[117,2001,2002],{},"file_name"," is of interest for the demo.",[142,2005,2007],{"className":1811,"code":2006,"language":1813,"meta":147,"style":147},"# Example query to parse: {'query_name': 'Service Does Not Target Pod', 'query_id': '3ca03a61-3249-4c16-8427-6f8e47dda729', 'query_url': 'https://kubernetes.io/docs/concepts/services-networking/service/', 'severity': 'LOW', 'platform': 'Kubernetes', 'category': 'Insecure Configurations', 'description': 'Service should Target a Pod', 'description_id': 'e7c26645', 'files': [{'file_name': 'kubernetes/ecc-demo-service.yml', 'similarity_id': '9da6166956ad0fcfb1dd533df17852342dcbcca02ac559becaf51f6efdc015e8', 'line': 38, 'issue_type': 'IncorrectValue', 'search_key': 'metadata.name={{ecc-demo-service}}.spec.ports.name={{web}}.targetPort', 'search_line': 0, 'search_value': '', 'expected_value': 'metadata.name={{ecc-demo-service}}.spec.ports={{web}}.targetPort has a Pod Port', 'actual_value': 'metadata.name={{ecc-demo-service}}.spec.ports={{web}}.targetPort does not have a Pod Port'}]}\n\nfor q in queries:\n    #print(q) # DEBUG\n    l = []\n    l.append(q[\"severity\"])\n    l.append(q[\"description\"])\n    l.append(q[\"platform\"])\n\n    if \"files\" in q:\n        l.append(\",\".join((f[\"file_name\"] for f in q[\"files\"])))\n\n    comment_body += \"| \" + \" | \".join(l) + \" |\\n\"\n\nf.close()\n",[117,2008,2009,2014,2018,2032,2037,2047,2058,2067,2076,2080,2093,2126,2130,2162,2166],{"__ignoreMap":147},[151,2010,2011],{"class":153,"line":154},[151,2012,2013],{"class":157},"# Example query to parse: {'query_name': 'Service Does Not Target Pod', 'query_id': '3ca03a61-3249-4c16-8427-6f8e47dda729', 'query_url': 'https://kubernetes.io/docs/concepts/services-networking/service/', 'severity': 'LOW', 'platform': 'Kubernetes', 'category': 'Insecure Configurations', 'description': 'Service should Target a Pod', 'description_id': 'e7c26645', 'files': [{'file_name': 'kubernetes/ecc-demo-service.yml', 'similarity_id': '9da6166956ad0fcfb1dd533df17852342dcbcca02ac559becaf51f6efdc015e8', 'line': 38, 'issue_type': 'IncorrectValue', 'search_key': 'metadata.name={{ecc-demo-service}}.spec.ports.name={{web}}.targetPort', 'search_line': 0, 'search_value': '', 'expected_value': 'metadata.name={{ecc-demo-service}}.spec.ports={{web}}.targetPort has a Pod Port', 'actual_value': 'metadata.name={{ecc-demo-service}}.spec.ports={{web}}.targetPort does not have a Pod Port'}]}\n",[151,2015,2016],{"class":153,"line":161},[151,2017,927],{"emptyLinePlaceholder":926},[151,2019,2020,2023,2026,2029],{"class":153,"line":179},[151,2021,2022],{"class":185},"for",[151,2024,2025],{"class":175}," q ",[151,2027,2028],{"class":185},"in",[151,2030,2031],{"class":175}," queries:\n",[151,2033,2034],{"class":153,"line":193},[151,2035,2036],{"class":157},"    #print(q) # DEBUG\n",[151,2038,2039,2042,2044],{"class":153,"line":204},[151,2040,2041],{"class":175},"    l ",[151,2043,1823],{"class":185},[151,2045,2046],{"class":175}," []\n",[151,2048,2049,2052,2055],{"class":153,"line":960},[151,2050,2051],{"class":175},"    l.append(q[",[151,2053,2054],{"class":189},"\"severity\"",[151,2056,2057],{"class":175},"])\n",[151,2059,2060,2062,2065],{"class":153,"line":973},[151,2061,2051],{"class":175},[151,2063,2064],{"class":189},"\"description\"",[151,2066,2057],{"class":175},[151,2068,2069,2071,2074],{"class":153,"line":1266},[151,2070,2051],{"class":175},[151,2072,2073],{"class":189},"\"platform\"",[151,2075,2057],{"class":175},[151,2077,2078],{"class":153,"line":1276},[151,2079,927],{"emptyLinePlaceholder":926},[151,2081,2082,2085,2088,2090],{"class":153,"line":1284},[151,2083,2084],{"class":185},"    if",[151,2086,2087],{"class":189}," \"files\"",[151,2089,1877],{"class":185},[151,2091,2092],{"class":175}," q:\n",[151,2094,2095,2098,2101,2104,2107,2110,2112,2115,2117,2120,2123],{"class":153,"line":1295},[151,2096,2097],{"class":175},"        l.append(",[151,2099,2100],{"class":189},"\",\"",[151,2102,2103],{"class":175},".join((f[",[151,2105,2106],{"class":189},"\"file_name\"",[151,2108,2109],{"class":175},"] ",[151,2111,2022],{"class":185},[151,2113,2114],{"class":175}," f ",[151,2116,2028],{"class":185},[151,2118,2119],{"class":175}," q[",[151,2121,2122],{"class":189},"\"files\"",[151,2124,2125],{"class":175},"])))\n",[151,2127,2128],{"class":153,"line":1303},[151,2129,927],{"emptyLinePlaceholder":926},[151,2131,2132,2135,2138,2141,2144,2147,2150,2153,2156,2159],{"class":153,"line":1311},[151,2133,2134],{"class":175},"    comment_body ",[151,2136,2137],{"class":185},"+=",[151,2139,2140],{"class":189}," \"| \"",[151,2142,2143],{"class":185}," +",[151,2145,2146],{"class":189}," \" | \"",[151,2148,2149],{"class":175},".join(l) ",[151,2151,2152],{"class":185},"+",[151,2154,2155],{"class":189}," \" |",[151,2157,2158],{"class":168},"\\n",[151,2160,2161],{"class":189},"\"\n",[151,2163,2164],{"class":153,"line":1321},[151,2165,927],{"emptyLinePlaceholder":926},[151,2167,2168],{"class":153,"line":1329},[151,2169,2170],{"class":175},"f.close()\n",[12,2172,2173,2174,2179],{},"The markdown table has been prepared, so now it is time to communicate with the GitLab API. ",[15,2175,2178],{"href":2176,"rel":2177},"https://python-gitlab.readthedocs.io/en/stable/api-usage.html",[],"python-gitlab"," provides a great abstraction layer with programmatic interfaces.",[12,2181,2182,2183,2186],{},"The GitLab API needs a project/group access token with API permissions. The ",[117,2184,2185],{},"CI_JOB_TOKEN"," is not sufficient.",[12,2188,2189],{},[579,2190],{"alt":2191,"src":2192},"Set the Project Access Token as CI/CD variable, not protected","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398546/blog/Content%20Images/iac-security-scanning/gitlab-cicd-variable-project-access-token.png",[12,2194,2195,2196,2199,2200,2203],{},"Read the ",[117,2197,2198],{},"GITLAB_TOKEN"," from the environment, and instantiate a new ",[117,2201,2202],{},"Gitlab"," object.",[142,2205,2207],{"className":1811,"code":2206,"language":1813,"meta":147,"style":147},"GITLAB_URL='https://gitlab.com'\n\nif 'GITLAB_TOKEN' in os.environ:\n    gl = gitlab.Gitlab(GITLAB_URL, private_token=os.environ['GITLAB_TOKEN'])\nelse:\n    raise Exception('GITLAB_TOKEN variable not set. Please provide an API token to update the MR!')\n\n",[117,2208,2209,2219,2223,2235,2263,2269],{"__ignoreMap":147},[151,2210,2211,2214,2216],{"class":153,"line":154},[151,2212,2213],{"class":168},"GITLAB_URL",[151,2215,1823],{"class":185},[151,2217,2218],{"class":189},"'https://gitlab.com'\n",[151,2220,2221],{"class":153,"line":161},[151,2222,927],{"emptyLinePlaceholder":926},[151,2224,2225,2227,2230,2232],{"class":153,"line":179},[151,2226,1871],{"class":185},[151,2228,2229],{"class":189}," 'GITLAB_TOKEN'",[151,2231,1877],{"class":185},[151,2233,2234],{"class":175}," os.environ:\n",[151,2236,2237,2240,2242,2245,2247,2249,2253,2255,2258,2261],{"class":153,"line":193},[151,2238,2239],{"class":175},"    gl ",[151,2241,1823],{"class":185},[151,2243,2244],{"class":175}," gitlab.Gitlab(",[151,2246,2213],{"class":168},[151,2248,531],{"class":175},[151,2250,2252],{"class":2251},"sqxcx","private_token",[151,2254,1823],{"class":185},[151,2256,2257],{"class":175},"os.environ[",[151,2259,2260],{"class":189},"'GITLAB_TOKEN'",[151,2262,2057],{"class":175},[151,2264,2265,2267],{"class":153,"line":204},[151,2266,1939],{"class":185},[151,2268,1246],{"class":175},[151,2270,2271,2273,2275,2277,2280],{"class":153,"line":960},[151,2272,1946],{"class":185},[151,2274,1949],{"class":168},[151,2276,449],{"class":175},[151,2278,2279],{"class":189},"'GITLAB_TOKEN variable not set. Please provide an API token to update the MR!'",[151,2281,1847],{"class":175},[12,2283,2284,2285,2288,2289,2294],{},"Next, use the ",[117,2286,2287],{},"CI_PROJECT_ID"," CI/CD variable from the environment to select the ",[15,2290,2293],{"href":2291,"rel":2292},"https://python-gitlab.readthedocs.io/en/stable/gl_objects/projects.html",[],"project object"," which contains the merge request we want to target.",[142,2296,2298],{"className":1811,"code":2297,"language":1813,"meta":147,"style":147},"project = gl.projects.get(os.environ['CI_PROJECT_ID'])\n",[117,2299,2300],{"__ignoreMap":147},[151,2301,2302,2305,2307,2310,2313],{"class":153,"line":154},[151,2303,2304],{"class":175},"project ",[151,2306,1823],{"class":185},[151,2308,2309],{"class":175}," gl.projects.get(os.environ[",[151,2311,2312],{"class":189},"'CI_PROJECT_ID'",[151,2314,2057],{"class":175},[12,2316,2317,2318,2323,2324,2327,2328,2331],{},"The tricky part is to fetch the ",[15,2319,2322],{"href":2320,"rel":2321},"https://python-gitlab.readthedocs.io/en/stable/gl_objects/merge_requests.html",[],"merge request"," ID from the CI/CD pipeline, it is not always available. A workaround can be to read the ",[117,2325,2326],{},"CI_COMMIT_REF_NAME"," variable and match it against all MRs in the project, looking if the ",[117,2329,2330],{},"source_branch"," matches.",[142,2333,2335],{"className":1811,"code":2334,"language":1813,"meta":147,"style":147},"real_mr = None\n\nif 'CI_MERGE_REQUEST_ID' in os.environ:\n    mr_id = os.environ['CI_MERGE_REQUEST_ID']\n    real_mr = project.mergerequests.get(mr_id)\n\n# Note: This workaround can be very expensive in projects with many MRs\nif 'CI_COMMIT_REF_NAME' in os.environ:\n    commit_ref_name = os.environ['CI_COMMIT_REF_NAME']\n\n    mrs = project.mergerequests.list()\n\n    for mr in mrs:\n        if mr.source_branch in commit_ref_name:\n            real_mr = mr\n            # found the MR for this source branch\n            # print(mr) # DEBUG\n\nif not real_mr:\n    print(\"Pipeline not run in a merge request, no reports sent\")\n    sys.exit(0)\n\n",[117,2336,2337,2347,2351,2362,2377,2387,2391,2396,2407,2421,2425,2435,2439,2452,2465,2475,2480,2485,2489,2499,2510],{"__ignoreMap":147},[151,2338,2339,2342,2344],{"class":153,"line":154},[151,2340,2341],{"class":175},"real_mr ",[151,2343,1823],{"class":185},[151,2345,2346],{"class":168}," None\n",[151,2348,2349],{"class":153,"line":161},[151,2350,927],{"emptyLinePlaceholder":926},[151,2352,2353,2355,2358,2360],{"class":153,"line":179},[151,2354,1871],{"class":185},[151,2356,2357],{"class":189}," 'CI_MERGE_REQUEST_ID'",[151,2359,1877],{"class":185},[151,2361,2234],{"class":175},[151,2363,2364,2367,2369,2372,2375],{"class":153,"line":193},[151,2365,2366],{"class":175},"    mr_id ",[151,2368,1823],{"class":185},[151,2370,2371],{"class":175}," os.environ[",[151,2373,2374],{"class":189},"'CI_MERGE_REQUEST_ID'",[151,2376,1934],{"class":175},[151,2378,2379,2382,2384],{"class":153,"line":204},[151,2380,2381],{"class":175},"    real_mr ",[151,2383,1823],{"class":185},[151,2385,2386],{"class":175}," project.mergerequests.get(mr_id)\n",[151,2388,2389],{"class":153,"line":960},[151,2390,927],{"emptyLinePlaceholder":926},[151,2392,2393],{"class":153,"line":973},[151,2394,2395],{"class":157},"# Note: This workaround can be very expensive in projects with many MRs\n",[151,2397,2398,2400,2403,2405],{"class":153,"line":1266},[151,2399,1871],{"class":185},[151,2401,2402],{"class":189}," 'CI_COMMIT_REF_NAME'",[151,2404,1877],{"class":185},[151,2406,2234],{"class":175},[151,2408,2409,2412,2414,2416,2419],{"class":153,"line":1276},[151,2410,2411],{"class":175},"    commit_ref_name ",[151,2413,1823],{"class":185},[151,2415,2371],{"class":175},[151,2417,2418],{"class":189},"'CI_COMMIT_REF_NAME'",[151,2420,1934],{"class":175},[151,2422,2423],{"class":153,"line":1284},[151,2424,927],{"emptyLinePlaceholder":926},[151,2426,2427,2430,2432],{"class":153,"line":1295},[151,2428,2429],{"class":175},"    mrs ",[151,2431,1823],{"class":185},[151,2433,2434],{"class":175}," project.mergerequests.list()\n",[151,2436,2437],{"class":153,"line":1303},[151,2438,927],{"emptyLinePlaceholder":926},[151,2440,2441,2444,2447,2449],{"class":153,"line":1311},[151,2442,2443],{"class":185},"    for",[151,2445,2446],{"class":175}," mr ",[151,2448,2028],{"class":185},[151,2450,2451],{"class":175}," mrs:\n",[151,2453,2454,2457,2460,2462],{"class":153,"line":1321},[151,2455,2456],{"class":185},"        if",[151,2458,2459],{"class":175}," mr.source_branch ",[151,2461,2028],{"class":185},[151,2463,2464],{"class":175}," commit_ref_name:\n",[151,2466,2467,2470,2472],{"class":153,"line":1329},[151,2468,2469],{"class":175},"            real_mr ",[151,2471,1823],{"class":185},[151,2473,2474],{"class":175}," mr\n",[151,2476,2477],{"class":153,"line":1337},[151,2478,2479],{"class":157},"            # found the MR for this source branch\n",[151,2481,2482],{"class":153,"line":1345},[151,2483,2484],{"class":157},"            # print(mr) # DEBUG\n",[151,2486,2487],{"class":153,"line":1355},[151,2488,927],{"emptyLinePlaceholder":926},[151,2490,2491,2493,2496],{"class":153,"line":1363},[151,2492,1871],{"class":185},[151,2494,2495],{"class":185}," not",[151,2497,2498],{"class":175}," real_mr:\n",[151,2500,2501,2503,2505,2508],{"class":153,"line":1371},[151,2502,1885],{"class":168},[151,2504,449],{"class":175},[151,2506,2507],{"class":189},"\"Pipeline not run in a merge request, no reports sent\"",[151,2509,1847],{"class":175},[151,2511,2512,2515,2518],{"class":153,"line":1384},[151,2513,2514],{"class":175},"    sys.exit(",[151,2516,2517],{"class":168},"0",[151,2519,1847],{"class":175},[12,2521,2522,2523,2528,2529,2531],{},"Last but not least, use the MR object to ",[15,2524,2527],{"href":2525,"rel":2526},"https://python-gitlab.readthedocs.io/en/stable/gl_objects/notes.html",[],"create a new note"," with the ",[117,2530,1807],{}," including the Markdown table created before.",[142,2533,2535],{"className":1811,"code":2534,"language":1813,"meta":147,"style":147},"mr_note = real_mr.notes.create({'body': comment_body})\n",[117,2536,2537],{"__ignoreMap":147},[151,2538,2539,2542,2544,2547,2550],{"class":153,"line":154},[151,2540,2541],{"class":175},"mr_note ",[151,2543,1823],{"class":185},[151,2545,2546],{"class":175}," real_mr.notes.create({",[151,2548,2549],{"class":189},"'body'",[151,2551,2552],{"class":175},": comment_body})\n",[12,2554,2555,2556,2559],{},"This workflow creates a new MR comment every time a new commit is pushed. Consider evaluating the script and refining the update frequency by yourself. The script can be integrated into CI/CD with running kics before generating the reports shown in the following example configuration for ",[117,2557,2558],{},".gitlab-ci.yml",":",[142,2561,2563],{"className":1202,"code":2562,"language":1204,"meta":147,"style":147},"# Full RAW example for kics reports and scans\nkics-scan:\n  image: python:3.10.2-slim-bullseye\n  variables:\n    # Visit for new releases\n    # https://github.com/Checkmarx/kics/releases\n    KICS_VERSION: \"1.5.1\"\n  script:\n    - echo $CI_PIPELINE_SOURCE\n    - echo $CI_COMMIT_REF_NAME\n    - echo $CI_MERGE_REQUEST_ID\n    - echo $CI_MERGE_REQUEST_IID\n    - apt-get update && apt-get install wget tar --no-install-recommends\n    - set -ex; wget -q -c \"https://github.com/Checkmarx/kics/releases/download/v${KICS_VERSION}/kics_${KICS_VERSION}_linux_x64.tar.gz\" -O - | tar -xz --directory /usr/bin &>/dev/null\n    # local requirements\n    - pip install -r requirements.txt\n    - kics scan --no-progress -q /usr/bin/assets/queries -p $(pwd) -o $(pwd) --report-formats json --output-path kics --output-name kics-report.json || true\n    - python ./integrations/kics-scan-report-mr-update.py\n\n",[117,2564,2565,2570,2577,2587,2594,2599,2604,2614,2621,2629,2636,2643,2650,2657,2664,2669,2676,2683],{"__ignoreMap":147},[151,2566,2567],{"class":153,"line":154},[151,2568,2569],{"class":157},"# Full RAW example for kics reports and scans\n",[151,2571,2572,2575],{"class":153,"line":161},[151,2573,2574],{"class":1221},"kics-scan",[151,2576,1246],{"class":175},[151,2578,2579,2582,2584],{"class":153,"line":179},[151,2580,2581],{"class":1221},"  image",[151,2583,1225],{"class":175},[151,2585,2586],{"class":189},"python:3.10.2-slim-bullseye\n",[151,2588,2589,2592],{"class":153,"line":193},[151,2590,2591],{"class":1221},"  variables",[151,2593,1246],{"class":175},[151,2595,2596],{"class":153,"line":204},[151,2597,2598],{"class":157},"    # Visit for new releases\n",[151,2600,2601],{"class":153,"line":960},[151,2602,2603],{"class":157},"    # https://github.com/Checkmarx/kics/releases\n",[151,2605,2606,2609,2611],{"class":153,"line":973},[151,2607,2608],{"class":1221},"    KICS_VERSION",[151,2610,1225],{"class":175},[151,2612,2613],{"class":189},"\"1.5.1\"\n",[151,2615,2616,2619],{"class":153,"line":1266},[151,2617,2618],{"class":1221},"  script",[151,2620,1246],{"class":175},[151,2622,2623,2626],{"class":153,"line":1276},[151,2624,2625],{"class":175},"    - ",[151,2627,2628],{"class":189},"echo $CI_PIPELINE_SOURCE\n",[151,2630,2631,2633],{"class":153,"line":1284},[151,2632,2625],{"class":175},[151,2634,2635],{"class":189},"echo $CI_COMMIT_REF_NAME\n",[151,2637,2638,2640],{"class":153,"line":1295},[151,2639,2625],{"class":175},[151,2641,2642],{"class":189},"echo $CI_MERGE_REQUEST_ID\n",[151,2644,2645,2647],{"class":153,"line":1303},[151,2646,2625],{"class":175},[151,2648,2649],{"class":189},"echo $CI_MERGE_REQUEST_IID\n",[151,2651,2652,2654],{"class":153,"line":1311},[151,2653,2625],{"class":175},[151,2655,2656],{"class":189},"apt-get update && apt-get install wget tar --no-install-recommends\n",[151,2658,2659,2661],{"class":153,"line":1321},[151,2660,2625],{"class":175},[151,2662,2663],{"class":189},"set -ex; wget -q -c \"https://github.com/Checkmarx/kics/releases/download/v${KICS_VERSION}/kics_${KICS_VERSION}_linux_x64.tar.gz\" -O - | tar -xz --directory /usr/bin &>/dev/null\n",[151,2665,2666],{"class":153,"line":1329},[151,2667,2668],{"class":157},"    # local requirements\n",[151,2670,2671,2673],{"class":153,"line":1337},[151,2672,2625],{"class":175},[151,2674,2675],{"class":189},"pip install -r requirements.txt\n",[151,2677,2678,2680],{"class":153,"line":1345},[151,2679,2625],{"class":175},[151,2681,2682],{"class":189},"kics scan --no-progress -q /usr/bin/assets/queries -p $(pwd) -o $(pwd) --report-formats json --output-path kics --output-name kics-report.json || true\n",[151,2684,2685,2687],{"class":153,"line":1355},[151,2686,2625],{"class":175},[151,2688,2689],{"class":189},"python ./integrations/kics-scan-report-mr-update.py\n",[12,2691,2692,2693,2698,2699,2704,2705,502],{},"You can find the ",[15,2694,2697],{"href":2695,"rel":2696},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning/-/blob/main/.gitlab-ci.yml",[],".gitlab-ci.yml configuration"," and the full script, including more inline comments and debug output ",[15,2700,2703],{"href":2701,"rel":2702},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning",[],"in this project",". You can see the implementation MR testing itself in ",[15,2706,2709],{"href":2707,"rel":2708},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning/-/merge_requests/4#note_840472146",[],"this comment",[12,2711,2712],{},[579,2713],{"alt":2714,"src":2715},"MR comment with the kics report as Markdown table","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398549/blog/Content%20Images/iac-security-scanning/kics-python-gitlab-mr-update-table.png",[125,2717,2719],{"id":2718},"mr-comments-using-gitlab-iac-sast-reports-as-source","MR comments using GitLab IaC SAST reports as source",[12,2721,2722,2723,2725,2726,2731,2732,2736],{},"The steps in the previous section show the raw ",[117,2724,325],{}," command execution, including JSON report parsing that requires you to create your own parsing logic. Alternatively, you can rely on the ",[15,2727,2730],{"href":2728,"rel":2729},"https://docs.gitlab.com/user/application_security/iac_scanning/#making-iac-analyzers-available-to-all-gitlab-tiers",[],"IaC scanner in GitLab"," and parse the SAST JSON report as ",[15,2733,2735],{"href":1769,"rel":2734},[],"a standardized format",". This is available for all GitLab tiers.",[12,2738,2739,2740,2745,2746,2749],{},"Download the ",[15,2741,2744],{"href":2742,"rel":2743},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning/-/blob/main/example-reports/gl-sast-report-kics-iac.json",[],"gl-sast-report.json example",", save it as ",[117,2747,2748],{},"gl-sast-report.json"," in the same directory as the script, and parse the report in a similar way shown above.",[142,2751,2753],{"className":1811,"code":2752,"language":1813,"meta":147,"style":147},"FILE=\"gl-sast-report.json\"\n\nf = open(FILE)\nreport = json.load(f)\n\n# Parse the report: kics\nif \"scan\" in report:\n    print(\"Found scanner '%s' in '%s'\" % (report[\"scan\"][\"scanner\"][\"name\"], FILE))\n    queries = report[\"vulnerabilities\"]\nelse:\n    raise Exception(\"Unsupported report format\")\n\n",[117,2754,2755,2764,2768,2782,2790,2794,2798,2809,2850,2863,2869],{"__ignoreMap":147},[151,2756,2757,2759,2761],{"class":153,"line":154},[151,2758,1820],{"class":168},[151,2760,1823],{"class":185},[151,2762,2763],{"class":189},"\"gl-sast-report.json\"\n",[151,2765,2766],{"class":153,"line":161},[151,2767,927],{"emptyLinePlaceholder":926},[151,2769,2770,2772,2774,2776,2778,2780],{"class":153,"line":179},[151,2771,1835],{"class":175},[151,2773,1823],{"class":185},[151,2775,1840],{"class":168},[151,2777,449],{"class":175},[151,2779,1820],{"class":168},[151,2781,1847],{"class":175},[151,2783,2784,2786,2788],{"class":153,"line":193},[151,2785,1852],{"class":175},[151,2787,1823],{"class":185},[151,2789,1857],{"class":175},[151,2791,2792],{"class":153,"line":204},[151,2793,927],{"emptyLinePlaceholder":926},[151,2795,2796],{"class":153,"line":960},[151,2797,1866],{"class":157},[151,2799,2800,2802,2805,2807],{"class":153,"line":973},[151,2801,1871],{"class":185},[151,2803,2804],{"class":189}," \"scan\"",[151,2806,1877],{"class":185},[151,2808,1880],{"class":175},[151,2810,2811,2813,2815,2818,2820,2822,2824,2826,2828,2830,2833,2836,2839,2841,2844,2846,2848],{"class":153,"line":1266},[151,2812,1885],{"class":168},[151,2814,449],{"class":175},[151,2816,2817],{"class":189},"\"Found scanner '",[151,2819,1893],{"class":168},[151,2821,1896],{"class":189},[151,2823,1893],{"class":168},[151,2825,1901],{"class":189},[151,2827,1904],{"class":185},[151,2829,1907],{"class":175},[151,2831,2832],{"class":189},"\"scan\"",[151,2834,2835],{"class":175},"][",[151,2837,2838],{"class":189},"\"scanner\"",[151,2840,2835],{"class":175},[151,2842,2843],{"class":189},"\"name\"",[151,2845,1913],{"class":175},[151,2847,1820],{"class":168},[151,2849,1918],{"class":175},[151,2851,2852,2854,2856,2858,2861],{"class":153,"line":1276},[151,2853,1923],{"class":175},[151,2855,1823],{"class":185},[151,2857,1928],{"class":175},[151,2859,2860],{"class":189},"\"vulnerabilities\"",[151,2862,1934],{"class":175},[151,2864,2865,2867],{"class":153,"line":1284},[151,2866,1939],{"class":185},[151,2868,1246],{"class":175},[151,2870,2871,2873,2875,2877,2879],{"class":153,"line":1295},[151,2872,1946],{"class":185},[151,2874,1949],{"class":168},[151,2876,449],{"class":175},[151,2878,1954],{"class":189},[151,2880,1847],{"class":175},[12,2882,2883,2884,2887],{},"The parameters in the vulnerability report also include the CVE number. The ",[117,2885,2886],{},"location"," is using a nested dictionary and thus easier to parse.",[142,2889,2891],{"className":1811,"code":2890,"language":1813,"meta":147,"style":147},"comment_body = \"\"\"### IaC SAST vulnerabilities report\n\n| Severity | Description | Category | Location | CVE |\n|----------|-------------|----------|----------|-----|\n\"\"\"\n\nfor q in queries:\n    #print(q) # DEBUG\n    l = []\n    l.append(q[\"severity\"])\n    l.append(q[\"description\"])\n    l.append(q[\"category\"])\n    l.append(q[\"location\"][\"file\"])\n    l.append(q[\"cve\"])\n\n    comment_body += \"| \" + \" | \".join(l) + \" |\\n\"\n\nf.close()\n",[117,2892,2893,2902,2906,2911,2916,2920,2924,2934,2938,2946,2954,2962,2971,2985,2994,2998,3020,3024],{"__ignoreMap":147},[151,2894,2895,2897,2899],{"class":153,"line":154},[151,2896,1965],{"class":175},[151,2898,1823],{"class":185},[151,2900,2901],{"class":189}," \"\"\"### IaC SAST vulnerabilities report\n",[151,2903,2904],{"class":153,"line":161},[151,2905,927],{"emptyLinePlaceholder":926},[151,2907,2908],{"class":153,"line":179},[151,2909,2910],{"class":189},"| Severity | Description | Category | Location | CVE |\n",[151,2912,2913],{"class":153,"line":193},[151,2914,2915],{"class":189},"|----------|-------------|----------|----------|-----|\n",[151,2917,2918],{"class":153,"line":204},[151,2919,1989],{"class":189},[151,2921,2922],{"class":153,"line":960},[151,2923,927],{"emptyLinePlaceholder":926},[151,2925,2926,2928,2930,2932],{"class":153,"line":973},[151,2927,2022],{"class":185},[151,2929,2025],{"class":175},[151,2931,2028],{"class":185},[151,2933,2031],{"class":175},[151,2935,2936],{"class":153,"line":1266},[151,2937,2036],{"class":157},[151,2939,2940,2942,2944],{"class":153,"line":1276},[151,2941,2041],{"class":175},[151,2943,1823],{"class":185},[151,2945,2046],{"class":175},[151,2947,2948,2950,2952],{"class":153,"line":1284},[151,2949,2051],{"class":175},[151,2951,2054],{"class":189},[151,2953,2057],{"class":175},[151,2955,2956,2958,2960],{"class":153,"line":1295},[151,2957,2051],{"class":175},[151,2959,2064],{"class":189},[151,2961,2057],{"class":175},[151,2963,2964,2966,2969],{"class":153,"line":1303},[151,2965,2051],{"class":175},[151,2967,2968],{"class":189},"\"category\"",[151,2970,2057],{"class":175},[151,2972,2973,2975,2978,2980,2983],{"class":153,"line":1311},[151,2974,2051],{"class":175},[151,2976,2977],{"class":189},"\"location\"",[151,2979,2835],{"class":175},[151,2981,2982],{"class":189},"\"file\"",[151,2984,2057],{"class":175},[151,2986,2987,2989,2992],{"class":153,"line":1321},[151,2988,2051],{"class":175},[151,2990,2991],{"class":189},"\"cve\"",[151,2993,2057],{"class":175},[151,2995,2996],{"class":153,"line":1329},[151,2997,927],{"emptyLinePlaceholder":926},[151,2999,3000,3002,3004,3006,3008,3010,3012,3014,3016,3018],{"class":153,"line":1337},[151,3001,2134],{"class":175},[151,3003,2137],{"class":185},[151,3005,2140],{"class":189},[151,3007,2143],{"class":185},[151,3009,2146],{"class":189},[151,3011,2149],{"class":175},[151,3013,2152],{"class":185},[151,3015,2155],{"class":189},[151,3017,2158],{"class":168},[151,3019,2161],{"class":189},[151,3021,3022],{"class":153,"line":1345},[151,3023,927],{"emptyLinePlaceholder":926},[151,3025,3026],{"class":153,"line":1355},[151,3027,2170],{"class":175},[12,3029,3030,3031,3033,3034,502],{},"The ",[117,3032,1807],{}," contains the Markdown table, and can use the same code to update the MR with a comment using the GitLab API Python bindings. An example run is shown in ",[15,3035,3038],{"href":3036,"rel":3037},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning/-/merge_requests/8#note_841940319",[],"this MR comment",[12,3040,3041,3042,3045,3046,3049,3050,3053,3054,3057],{},"You can integrate the script into your CI/CD workflows using the following steps: 1) Override the ",[117,3043,3044],{},"kics-iac-sast"," job ",[117,3047,3048],{},"artifacts"," created by the ",[117,3051,3052],{},"Security/SAST-IaC.latest.gitlab-ci.yml"," template and 2) Add a job ",[117,3055,3056],{},"iac-sast-parse"," which parses the JSON report and calls the script to send a MR comment.",[142,3059,3061],{"className":1202,"code":3060,"language":1204,"meta":147,"style":147},"# GitLab integration with SAST reports spec\ninclude:\n- template: Security/SAST-IaC.latest.gitlab-ci.yml\n\n# Override the SAST report artifacts\nkics-iac-sast:\n  artifacts:\n    name: sast\n    paths:\n      - gl-sast-report.json\n    reports:\n      sast: gl-sast-report.json\n\niac-sast-parse:\n  image: python:3.10.2-slim-bullseye\n  needs: ['kics-iac-sast']\n  script:\n    - echo \"Parsing gl-sast-report.json\"\n    - pip install -r requirements.txt\n    - python ./integrations/sast-iac-report-mr-update.py\n  artifacts:\n      paths:\n      - gl-sast-report.json\n\n",[117,3062,3063,3068,3075,3088,3092,3097,3103,3110,3119,3126,3133,3140,3149,3153,3159,3167,3180,3186,3193,3199,3206,3212,3219],{"__ignoreMap":147},[151,3064,3065],{"class":153,"line":154},[151,3066,3067],{"class":157},"# GitLab integration with SAST reports spec\n",[151,3069,3070,3073],{"class":153,"line":161},[151,3071,3072],{"class":1221},"include",[151,3074,1246],{"class":175},[151,3076,3077,3080,3083,3085],{"class":153,"line":179},[151,3078,3079],{"class":175},"- ",[151,3081,3082],{"class":1221},"template",[151,3084,1225],{"class":175},[151,3086,3087],{"class":189},"Security/SAST-IaC.latest.gitlab-ci.yml\n",[151,3089,3090],{"class":153,"line":193},[151,3091,927],{"emptyLinePlaceholder":926},[151,3093,3094],{"class":153,"line":204},[151,3095,3096],{"class":157},"# Override the SAST report artifacts\n",[151,3098,3099,3101],{"class":153,"line":960},[151,3100,3044],{"class":1221},[151,3102,1246],{"class":175},[151,3104,3105,3108],{"class":153,"line":973},[151,3106,3107],{"class":1221},"  artifacts",[151,3109,1246],{"class":175},[151,3111,3112,3114,3116],{"class":153,"line":1266},[151,3113,1538],{"class":1221},[151,3115,1225],{"class":175},[151,3117,3118],{"class":189},"sast\n",[151,3120,3121,3124],{"class":153,"line":1276},[151,3122,3123],{"class":1221},"    paths",[151,3125,1246],{"class":175},[151,3127,3128,3130],{"class":153,"line":1284},[151,3129,1374],{"class":175},[151,3131,3132],{"class":189},"gl-sast-report.json\n",[151,3134,3135,3138],{"class":153,"line":1295},[151,3136,3137],{"class":1221},"    reports",[151,3139,1246],{"class":175},[151,3141,3142,3145,3147],{"class":153,"line":1303},[151,3143,3144],{"class":1221},"      sast",[151,3146,1225],{"class":175},[151,3148,3132],{"class":189},[151,3150,3151],{"class":153,"line":1311},[151,3152,927],{"emptyLinePlaceholder":926},[151,3154,3155,3157],{"class":153,"line":1321},[151,3156,3056],{"class":1221},[151,3158,1246],{"class":175},[151,3160,3161,3163,3165],{"class":153,"line":1329},[151,3162,2581],{"class":1221},[151,3164,1225],{"class":175},[151,3166,2586],{"class":189},[151,3168,3169,3172,3175,3178],{"class":153,"line":1337},[151,3170,3171],{"class":1221},"  needs",[151,3173,3174],{"class":175},": [",[151,3176,3177],{"class":189},"'kics-iac-sast'",[151,3179,1934],{"class":175},[151,3181,3182,3184],{"class":153,"line":1345},[151,3183,2618],{"class":1221},[151,3185,1246],{"class":175},[151,3187,3188,3190],{"class":153,"line":1355},[151,3189,2625],{"class":175},[151,3191,3192],{"class":189},"echo \"Parsing gl-sast-report.json\"\n",[151,3194,3195,3197],{"class":153,"line":1363},[151,3196,2625],{"class":175},[151,3198,2675],{"class":189},[151,3200,3201,3203],{"class":153,"line":1371},[151,3202,2625],{"class":175},[151,3204,3205],{"class":189},"python ./integrations/sast-iac-report-mr-update.py\n",[151,3207,3208,3210],{"class":153,"line":1384},[151,3209,3107],{"class":1221},[151,3211,1246],{"class":175},[151,3213,3214,3217],{"class":153,"line":1395},[151,3215,3216],{"class":1221},"      paths",[151,3218,1246],{"class":175},[151,3220,3221,3223],{"class":153,"line":1406},[151,3222,1374],{"class":175},[151,3224,3132],{"class":189},[12,3226,3227,3228,3232,3233,3238],{},"The CI/CD pipeline testing itself can be found in ",[15,3229,3038],{"href":3230,"rel":3231},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning/-/merge_requests/9#note_841976761",[],". Please review the ",[15,3234,3237],{"href":3235,"rel":3236},"https://gitlab.com/gitlab-da/use-cases/infrastructure-as-code-scanning/-/blob/main/integrations/sast-iac-report-mr-update.py",[],"sast-iac-report-mr-update.py"," script and evaluate whether it is useful for your workflows.",[109,3240,107],{"id":3241},"what-is-the-best-integration-strategy",[12,3243,3244,3245,3249,3250,531,3252,3256,3257,531,3260,3264,3265,531,3267,3271,3272,3275],{},"One way to evaluate the scanners is to look at their extensibility. For example, ",[15,3246,325],{"href":3247,"rel":3248},"https://docs.kics.io/latest/creating-queries/",[]," calls them ",[117,3251,1803],{},[15,3253,337],{"href":3254,"rel":3255},"https://semgrep.dev/docs/writing-rules/overview/",[]," uses ",[117,3258,3259],{},"rules",[15,3261,740],{"href":3262,"rel":3263},"https://www.checkov.io/3.Custom%20Policies/Custom%20Policies%20Overview.html",[]," says ",[117,3266,791],{},[15,3268,319],{"href":3269,"rel":3270},"https://aquasecurity.github.io/tfsec/v1.1.5/getting-started/configuration/custom-checks/",[]," goes for ",[117,3273,3274],{},"custom checks"," as a name. These specifications allow you to create and contribute your own detection methods with extensive tutorial guides.",[12,3277,3278,3279,3284,3285,3287],{},"Many of the shown scanners provide container images to use, or CI/CD integration documentation. Make sure to include this requirement in your evaluation. For a fully integrated and tested solution, use the ",[15,3280,3283],{"href":3281,"rel":3282},"https://docs.gitlab.com/user/application_security/iac_scanning/",[],"IaC Security Scanning feature in GitLab",", currently based on the ",[117,3286,325],{}," scanner. If you already have experience with other scanners, or prefer your own custom integration, evaluate the alternatives for your solution. All scanners discussed in this blog post provide JSON as output format, which helps with programmatic parsing and automation.",[12,3289,3290,3291,3296],{},"Maybe you'd like to ",[15,3292,3295],{"href":3293,"rel":3294},"https://docs.gitlab.com/user/application_security/iac_scanning/#contribute-your-scanner",[],"contribute a new IaC scanner"," or help improve the detection rules and functionality from the open source scanners :-)",[12,3298,3299,3300,3305,3306],{},"Cover image by ",[15,3301,3304],{"href":3302,"rel":3303},"https://unsplash.com/photos/tnv84LOjes4",[],"Sawyer Bengtson"," on ",[15,3307,3310],{"href":3308,"rel":3309},"https://unsplash.com",[],"Unsplash",[3312,3313,3314],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":147,"searchDepth":161,"depth":161,"links":3316},[3317,3320,3328,3333,3337],{"id":111,"depth":161,"text":112,"children":3318},[3319],{"id":127,"depth":179,"text":49},{"id":249,"depth":161,"text":55,"children":3321},[3322,3323,3324,3325,3326,3327],{"id":319,"depth":179,"text":319},{"id":325,"depth":179,"text":325},{"id":740,"depth":179,"text":740},{"id":331,"depth":179,"text":331},{"id":337,"depth":179,"text":337},{"id":343,"depth":179,"text":343},{"id":873,"depth":161,"text":61,"children":3329},[3330,3331,3332],{"id":1009,"depth":179,"text":69},{"id":1095,"depth":179,"text":75},{"id":1160,"depth":179,"text":81},{"id":1744,"depth":161,"text":87,"children":3334},[3335,3336],{"id":1786,"depth":179,"text":95},{"id":2718,"depth":179,"text":2719},{"id":3241,"depth":161,"text":107},"insights","2022-02-17","Learn about possible attack scenarios in Infrastructure as Code and GitOps environments, evaluate tools and scanners with Terraform, Kubernetes, etc., and more.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667482/Blog/Hero%20Images/cover-image-unsplash.jpg",{},"/en-us/blog/fantastic-infrastructure-as-code-security-attacks-and-how-to-find-them",{"title":5,"description":3340,"ogTitle":5,"ogDescription":3340,"noIndex":3343,"ogImage":3344,"ogUrl":3348,"ogSiteName":3349,"ogType":3350,"canonicalUrls":3348},"https://about.gitlab.com/blog/fantastic-infrastructure-as-code-security-attacks-and-how-to-find-them","https://about.gitlab.com","article","fantastic-infrastructure-as-code-security-attacks-and-how-to-find-them","en-us/blog/fantastic-infrastructure-as-code-security-attacks-and-how-to-find-them",[3354,3355,3356],"security","kubernetes","DevOps","BlogPost","VNPysaQ7WBrr2sthD_xFr-XoC-bZYdoJp1DJjtYHyBM",{"logo":3360,"freeTrial":3365,"sales":3370,"login":3375,"items":3380,"search":3707,"minimal":3738,"duo":3757,"switchNav":3766,"pricingDeployment":3777},{"config":3361},{"href":3362,"dataGaName":3363,"dataGaLocation":3364},"/","gitlab logo","header",{"text":3366,"config":3367},"Get free trial",{"href":3368,"dataGaName":3369,"dataGaLocation":3364},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":3371,"config":3372},"Request a demo",{"href":3373,"dataGaName":3374,"dataGaLocation":3364},"/sales/?contact-topic=request-demo","sales",{"text":3376,"config":3377},"Sign in",{"href":3378,"dataGaName":3379,"dataGaLocation":3364},"https://gitlab.com/users/sign_in/","sign in",[3381,3410,3510,3515,3629,3685],{"text":3382,"config":3383,"menu":3385},"Platform",{"dataNavLevelOne":3384},"platform",{"type":3386,"columns":3387},"cards",[3388,3394,3402],{"title":3382,"description":3389,"link":3390},"The intelligent orchestration platform for DevSecOps",{"text":3391,"config":3392},"Explore our Platform",{"href":3393,"dataGaName":3384,"dataGaLocation":3364},"/platform/",{"title":3395,"description":3396,"link":3397},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":3398,"config":3399},"Meet GitLab Duo",{"href":3400,"dataGaName":3401,"dataGaLocation":3364},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":3403,"description":3404,"link":3405},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":3406,"config":3407},"Learn more",{"href":3408,"dataGaName":3409,"dataGaLocation":3364},"/why-gitlab/","why gitlab",{"text":3411,"left":926,"config":3412,"menu":3414},"Product",{"dataNavLevelOne":3413},"solutions",{"type":3415,"link":3416,"columns":3420,"feature":3489},"lists",{"text":3417,"config":3418},"View all Solutions",{"href":3419,"dataGaName":3413,"dataGaLocation":3364},"/solutions/",[3421,3445,3468],{"title":3422,"description":3423,"link":3424,"items":3429},"Automation","CI/CD and automation to accelerate deployment",{"config":3425},{"icon":3426,"href":3427,"dataGaName":3428,"dataGaLocation":3364},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[3430,3434,3437,3441],{"text":3431,"config":3432},"CI/CD",{"href":3433,"dataGaLocation":3364,"dataGaName":3431},"/solutions/continuous-integration/",{"text":3395,"config":3435},{"href":3400,"dataGaLocation":3364,"dataGaName":3436},"gitlab duo agent platform - product menu",{"text":3438,"config":3439},"Source Code Management",{"href":3440,"dataGaLocation":3364,"dataGaName":3438},"/solutions/source-code-management/",{"text":3442,"config":3443},"Automated Software Delivery",{"href":3427,"dataGaLocation":3364,"dataGaName":3444},"Automated software delivery",{"title":3446,"description":3447,"link":3448,"items":3453},"Security","Deliver code faster without compromising security",{"config":3449},{"href":3450,"dataGaName":3451,"dataGaLocation":3364,"icon":3452},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[3454,3458,3463],{"text":3455,"config":3456},"Application Security Testing",{"href":3450,"dataGaName":3457,"dataGaLocation":3364},"Application security testing",{"text":3459,"config":3460},"Software Supply Chain Security",{"href":3461,"dataGaLocation":3364,"dataGaName":3462},"/solutions/supply-chain/","Software supply chain security",{"text":3464,"config":3465},"Software Compliance",{"href":3466,"dataGaName":3467,"dataGaLocation":3364},"/solutions/software-compliance/","software compliance",{"title":3469,"link":3470,"items":3475},"Measurement",{"config":3471},{"icon":3472,"href":3473,"dataGaName":3474,"dataGaLocation":3364},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[3476,3480,3484],{"text":3477,"config":3478},"Visibility & Measurement",{"href":3473,"dataGaLocation":3364,"dataGaName":3479},"Visibility and Measurement",{"text":3481,"config":3482},"Value Stream Management",{"href":3483,"dataGaLocation":3364,"dataGaName":3481},"/solutions/value-stream-management/",{"text":3485,"config":3486},"Analytics & Insights",{"href":3487,"dataGaLocation":3364,"dataGaName":3488},"/solutions/analytics-and-insights/","Analytics and insights",{"title":3490,"type":3415,"items":3491},"GitLab for",[3492,3498,3504],{"text":3493,"config":3494},"Enterprise",{"icon":3495,"href":3496,"dataGaLocation":3364,"dataGaName":3497},"Building","/enterprise/","enterprise",{"text":3499,"config":3500},"Small Business",{"icon":3501,"href":3502,"dataGaLocation":3364,"dataGaName":3503},"Work","/small-business/","small business",{"text":3505,"config":3506},"Public Sector",{"icon":3507,"href":3508,"dataGaLocation":3364,"dataGaName":3509},"Organization","/solutions/public-sector/","public sector",{"text":3511,"config":3512},"Pricing",{"href":3513,"dataGaName":3514,"dataGaLocation":3364,"dataNavLevelOne":3514},"/pricing/","pricing",{"text":3516,"config":3517,"menu":3519},"Resources",{"dataNavLevelOne":3518},"resources",{"type":3415,"link":3520,"columns":3524,"feature":3618},{"text":3521,"config":3522},"View all resources",{"href":3523,"dataGaName":3518,"dataGaLocation":3364},"/resources/",[3525,3558,3585],{"title":3526,"items":3527},"Getting started",[3528,3533,3538,3543,3548,3553],{"text":3529,"config":3530},"Install",{"href":3531,"dataGaName":3532,"dataGaLocation":3364},"/install/","install",{"text":3534,"config":3535},"Quick start guides",{"href":3536,"dataGaName":3537,"dataGaLocation":3364},"/get-started/","quick setup checklists",{"text":3539,"config":3540},"Learn",{"href":3541,"dataGaLocation":3364,"dataGaName":3542},"https://university.gitlab.com/","learn",{"text":3544,"config":3545},"Product documentation",{"href":3546,"dataGaName":3547,"dataGaLocation":3364},"https://docs.gitlab.com/","product documentation",{"text":3549,"config":3550},"Best practice videos",{"href":3551,"dataGaName":3552,"dataGaLocation":3364},"/getting-started-videos/","best practice videos",{"text":3554,"config":3555},"Integrations",{"href":3556,"dataGaName":3557,"dataGaLocation":3364},"/integrations/","integrations",{"title":3559,"items":3560},"Discover",[3561,3566,3571,3576,3580],{"text":3562,"config":3563},"Customer success stories",{"href":3564,"dataGaName":3565,"dataGaLocation":3364},"/customers/","customer success stories",{"text":3567,"config":3568},"Blog",{"href":3569,"dataGaName":3570,"dataGaLocation":3364},"/blog/","blog",{"text":3572,"config":3573},"Demo Hub",{"href":3574,"dataGaName":3575,"dataGaLocation":3364},"/demo-hub/","demo hub",{"text":3577,"config":3578},"The Source",{"href":3579,"dataGaName":3570,"dataGaLocation":3364},"/the-source/",{"text":3581,"config":3582},"Remote",{"href":3583,"dataGaName":3584,"dataGaLocation":3364},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":3586,"items":3587},"Connect",[3588,3593,3598,3603,3608,3613],{"text":3589,"config":3590},"GitLab Services",{"href":3591,"dataGaName":3592,"dataGaLocation":3364},"/services/","services",{"text":3594,"config":3595},"Contribute",{"href":3596,"dataGaName":3597,"dataGaLocation":3364},"https://contributors.gitlab.com","contribute",{"text":3599,"config":3600},"Community",{"href":3601,"dataGaName":3602,"dataGaLocation":3364},"/community/","community",{"text":3604,"config":3605},"Forum",{"href":3606,"dataGaName":3607,"dataGaLocation":3364},"https://forum.gitlab.com/","forum",{"text":3609,"config":3610},"Events",{"href":3611,"dataGaName":3612,"dataGaLocation":3364},"/events/","events",{"text":3614,"config":3615},"Partners",{"href":3616,"dataGaName":3617,"dataGaLocation":3364},"/partners/","partners",{"config":3619,"title":3622,"text":3623,"link":3624},{"background":3620,"textColor":3621},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":3625,"config":3626},"Read the latest",{"href":3627,"dataGaName":3628,"dataGaLocation":3364},"/whats-new/","whats new",{"text":3630,"config":3631,"menu":3633},"Company",{"dataNavLevelOne":3632},"company",{"type":3415,"columns":3634},[3635],{"items":3636},[3637,3642,3648,3650,3655,3660,3665,3670,3675,3680],{"text":3638,"config":3639},"About",{"href":3640,"dataGaName":3641,"dataGaLocation":3364},"/company/","about",{"text":3643,"config":3644,"footerGa":3647},"Jobs",{"href":3645,"dataGaName":3646,"dataGaLocation":3364},"/jobs/","jobs",{"dataGaName":3646},{"text":3609,"config":3649},{"href":3611,"dataGaName":3612,"dataGaLocation":3364},{"text":3651,"config":3652},"Leadership",{"href":3653,"dataGaName":3654,"dataGaLocation":3364},"/company/team/e-group/","leadership",{"text":3656,"config":3657},"Handbook",{"href":3658,"dataGaName":3659,"dataGaLocation":3364},"https://handbook.gitlab.com/","handbook",{"text":3661,"config":3662},"Investor relations",{"href":3663,"dataGaName":3664,"dataGaLocation":3364},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":3666,"config":3667},"Trust Center",{"href":3668,"dataGaName":3669,"dataGaLocation":3364},"/security/","trust center",{"text":3671,"config":3672},"AI Transparency Center",{"href":3673,"dataGaName":3674,"dataGaLocation":3364},"/ai-transparency-center/","ai transparency center",{"text":3676,"config":3677},"Newsletter",{"href":3678,"dataGaName":3679,"dataGaLocation":3364},"/company/contact/#contact-forms","newsletter",{"text":3681,"config":3682},"Press",{"href":3683,"dataGaName":3684,"dataGaLocation":3364},"/press/","press",{"text":3686,"config":3687,"menu":3688},"Contact us",{"dataNavLevelOne":3632},{"type":3415,"columns":3689},[3690],{"items":3691},[3692,3697,3702],{"text":3693,"config":3694},"Talk to sales",{"href":3695,"dataGaName":3696,"dataGaLocation":3364},"/sales/","talk to sales",{"text":3698,"config":3699},"Support portal",{"href":3700,"dataGaName":3701,"dataGaLocation":3364},"https://support.gitlab.com/hc/en-us","support portal",{"text":3703,"config":3704},"Customer portal",{"href":3705,"dataGaName":3706,"dataGaLocation":3364},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":3708,"login":3709,"suggestions":3716},"Close",{"text":3710,"link":3711},"To search repositories and projects, login to",{"text":3712,"config":3713},"gitlab.com",{"href":3378,"dataGaName":3714,"dataGaLocation":3715},"search login","search",{"text":3717,"default":3718},"Suggestions",[3719,3721,3725,3727,3731,3735],{"text":3395,"config":3720},{"href":3400,"dataGaName":3395,"dataGaLocation":3715},{"text":3722,"config":3723},"Code Suggestions (AI)",{"href":3724,"dataGaName":3722,"dataGaLocation":3715},"/solutions/code-suggestions/",{"text":3431,"config":3726},{"href":3433,"dataGaName":3431,"dataGaLocation":3715},{"text":3728,"config":3729},"GitLab on AWS",{"href":3730,"dataGaName":3728,"dataGaLocation":3715},"/partners/technology-partners/aws/",{"text":3732,"config":3733},"GitLab on Google Cloud",{"href":3734,"dataGaName":3732,"dataGaLocation":3715},"/partners/technology-partners/google-cloud-platform/",{"text":3736,"config":3737},"Why GitLab?",{"href":3408,"dataGaName":3736,"dataGaLocation":3715},{"freeTrial":3739,"mobileIcon":3744,"desktopIcon":3749,"secondaryButton":3752},{"text":3740,"config":3741},"Start free trial",{"href":3742,"dataGaName":3369,"dataGaLocation":3743},"https://gitlab.com/-/trials/new/","nav",{"altText":3745,"config":3746},"Gitlab Icon",{"src":3747,"dataGaName":3748,"dataGaLocation":3743},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":3745,"config":3750},{"src":3751,"dataGaName":3748,"dataGaLocation":3743},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":3753,"config":3754},"Get Started",{"href":3755,"dataGaName":3756,"dataGaLocation":3743},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":3758,"mobileIcon":3762,"desktopIcon":3764},{"text":3759,"config":3760},"Learn more about GitLab Duo",{"href":3400,"dataGaName":3761,"dataGaLocation":3743},"gitlab duo",{"altText":3745,"config":3763},{"src":3747,"dataGaName":3748,"dataGaLocation":3743},{"altText":3745,"config":3765},{"src":3751,"dataGaName":3748,"dataGaLocation":3743},{"button":3767,"mobileIcon":3772,"desktopIcon":3774},{"text":3768,"config":3769},"/switch",{"href":3770,"dataGaName":3771,"dataGaLocation":3743},"#contact","switch",{"altText":3745,"config":3773},{"src":3747,"dataGaName":3748,"dataGaLocation":3743},{"altText":3745,"config":3775},{"src":3776,"dataGaName":3748,"dataGaLocation":3743},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":3778,"mobileIcon":3783,"desktopIcon":3785},{"text":3779,"config":3780},"Back to pricing",{"href":3513,"dataGaName":3781,"dataGaLocation":3743,"icon":3782},"back to pricing","GoBack",{"altText":3745,"config":3784},{"src":3747,"dataGaName":3748,"dataGaLocation":3743},{"altText":3745,"config":3786},{"src":3751,"dataGaName":3748,"dataGaLocation":3743},{"title":3788,"titleMobile":3789,"button":3790,"config":3795},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":3406,"config":3791},{"href":3792,"dataGaName":3793,"dataGaLocation":3794},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":3796,"disabled":3343},"release",{"data":3798},{"text":3799,"source":3800,"edit":3806,"contribute":3811,"config":3816,"items":3821,"minimal":4030},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":3801,"config":3802},"View page source",{"href":3803,"dataGaName":3804,"dataGaLocation":3805},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":3807,"config":3808},"Edit this page",{"href":3809,"dataGaName":3810,"dataGaLocation":3805},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":3812,"config":3813},"Please contribute",{"href":3814,"dataGaName":3815,"dataGaLocation":3805},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":3817,"facebook":3818,"youtube":3819,"linkedin":3820},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[3822,3869,3922,3966,3998],{"title":3511,"links":3823,"subMenu":3838},[3824,3828,3833],{"text":3825,"config":3826},"View plans",{"href":3513,"dataGaName":3827,"dataGaLocation":3805},"view plans",{"text":3829,"config":3830},"Why Premium?",{"href":3831,"dataGaName":3832,"dataGaLocation":3805},"/pricing/premium/","why premium",{"text":3834,"config":3835},"Why Ultimate?",{"href":3836,"dataGaName":3837,"dataGaLocation":3805},"/pricing/ultimate/","why ultimate",[3839],{"title":3840,"links":3841},"Contact Us",[3842,3845,3847,3849,3854,3859,3864],{"text":3843,"config":3844},"Contact sales",{"href":3695,"dataGaName":3374,"dataGaLocation":3805},{"text":3698,"config":3846},{"href":3700,"dataGaName":3701,"dataGaLocation":3805},{"text":3703,"config":3848},{"href":3705,"dataGaName":3706,"dataGaLocation":3805},{"text":3850,"config":3851},"Status",{"href":3852,"dataGaName":3853,"dataGaLocation":3805},"https://status.gitlab.com/","status",{"text":3855,"config":3856},"Terms of use",{"href":3857,"dataGaName":3858,"dataGaLocation":3805},"/terms/","terms of use",{"text":3860,"config":3861},"Privacy statement",{"href":3862,"dataGaName":3863,"dataGaLocation":3805},"/privacy/","privacy statement",{"text":3865,"config":3866},"Cookie preferences",{"dataGaName":3867,"dataGaLocation":3805,"id":3868,"isOneTrustButton":926},"cookie preferences","ot-sdk-btn",{"title":3411,"links":3870,"subMenu":3879},[3871,3875],{"text":3872,"config":3873},"DevSecOps platform",{"href":3393,"dataGaName":3874,"dataGaLocation":3805},"devsecops platform",{"text":3876,"config":3877},"AI-Assisted Development",{"href":3400,"dataGaName":3878,"dataGaLocation":3805},"ai-assisted development",[3880],{"title":3881,"links":3882},"Topics",[3883,3888,3893,3897,3902,3907,3912,3917],{"text":3884,"config":3885},"CICD",{"href":3886,"dataGaName":3887,"dataGaLocation":3805},"/topics/ci-cd/","cicd",{"text":3889,"config":3890},"GitOps",{"href":3891,"dataGaName":3892,"dataGaLocation":3805},"/topics/gitops/","gitops",{"text":3356,"config":3894},{"href":3895,"dataGaName":3896,"dataGaLocation":3805},"/topics/devops/","devops",{"text":3898,"config":3899},"Version Control",{"href":3900,"dataGaName":3901,"dataGaLocation":3805},"/topics/version-control/","version control",{"text":3903,"config":3904},"DevSecOps",{"href":3905,"dataGaName":3906,"dataGaLocation":3805},"/topics/devsecops/","devsecops",{"text":3908,"config":3909},"Cloud Native",{"href":3910,"dataGaName":3911,"dataGaLocation":3805},"/topics/cloud-native/","cloud native",{"text":3913,"config":3914},"AI for Coding",{"href":3915,"dataGaName":3916,"dataGaLocation":3805},"/topics/devops/ai-for-coding/","ai for coding",{"text":3918,"config":3919},"Agentic AI",{"href":3920,"dataGaName":3921,"dataGaLocation":3805},"/topics/agentic-ai/","agentic ai",{"title":3923,"links":3924},"Solutions",[3925,3927,3929,3934,3938,3941,3945,3948,3950,3953,3956,3961],{"text":3455,"config":3926},{"href":3450,"dataGaName":3455,"dataGaLocation":3805},{"text":3444,"config":3928},{"href":3427,"dataGaName":3428,"dataGaLocation":3805},{"text":3930,"config":3931},"Agile development",{"href":3932,"dataGaName":3933,"dataGaLocation":3805},"/solutions/agile-delivery/","agile delivery",{"text":3935,"config":3936},"SCM",{"href":3440,"dataGaName":3937,"dataGaLocation":3805},"source code management",{"text":3884,"config":3939},{"href":3433,"dataGaName":3940,"dataGaLocation":3805},"continuous integration & delivery",{"text":3942,"config":3943},"Value stream management",{"href":3483,"dataGaName":3944,"dataGaLocation":3805},"value stream management",{"text":3889,"config":3946},{"href":3947,"dataGaName":3892,"dataGaLocation":3805},"/solutions/gitops/",{"text":3493,"config":3949},{"href":3496,"dataGaName":3497,"dataGaLocation":3805},{"text":3951,"config":3952},"Small business",{"href":3502,"dataGaName":3503,"dataGaLocation":3805},{"text":3954,"config":3955},"Public sector",{"href":3508,"dataGaName":3509,"dataGaLocation":3805},{"text":3957,"config":3958},"Education",{"href":3959,"dataGaName":3960,"dataGaLocation":3805},"/solutions/education/","education",{"text":3962,"config":3963},"Financial services",{"href":3964,"dataGaName":3965,"dataGaLocation":3805},"/solutions/finance/","financial services",{"title":3516,"links":3967},[3968,3970,3972,3974,3977,3979,3982,3984,3986,3988,3990,3992,3994,3996],{"text":3529,"config":3969},{"href":3531,"dataGaName":3532,"dataGaLocation":3805},{"text":3534,"config":3971},{"href":3536,"dataGaName":3537,"dataGaLocation":3805},{"text":3539,"config":3973},{"href":3541,"dataGaName":3542,"dataGaLocation":3805},{"text":3544,"config":3975},{"href":3546,"dataGaName":3976,"dataGaLocation":3805},"docs",{"text":3567,"config":3978},{"href":3569,"dataGaName":3570,"dataGaLocation":3805},{"text":3980,"config":3981},"What's new",{"href":3627,"dataGaName":3628,"dataGaLocation":3805},{"text":3562,"config":3983},{"href":3564,"dataGaName":3565,"dataGaLocation":3805},{"text":3581,"config":3985},{"href":3583,"dataGaName":3584,"dataGaLocation":3805},{"text":3589,"config":3987},{"href":3591,"dataGaName":3592,"dataGaLocation":3805},{"text":3594,"config":3989},{"href":3596,"dataGaName":3597,"dataGaLocation":3805},{"text":3599,"config":3991},{"href":3601,"dataGaName":3602,"dataGaLocation":3805},{"text":3604,"config":3993},{"href":3606,"dataGaName":3607,"dataGaLocation":3805},{"text":3609,"config":3995},{"href":3611,"dataGaName":3612,"dataGaLocation":3805},{"text":3614,"config":3997},{"href":3616,"dataGaName":3617,"dataGaLocation":3805},{"title":3630,"links":3999},[4000,4002,4004,4006,4008,4010,4014,4019,4021,4023,4025],{"text":3638,"config":4001},{"href":3640,"dataGaName":3632,"dataGaLocation":3805},{"text":3643,"config":4003},{"href":3645,"dataGaName":3646,"dataGaLocation":3805},{"text":3651,"config":4005},{"href":3653,"dataGaName":3654,"dataGaLocation":3805},{"text":3656,"config":4007},{"href":3658,"dataGaName":3659,"dataGaLocation":3805},{"text":3661,"config":4009},{"href":3663,"dataGaName":3664,"dataGaLocation":3805},{"text":4011,"config":4012},"Sustainability",{"href":4013,"dataGaName":4011,"dataGaLocation":3805},"/sustainability/",{"text":4015,"config":4016},"Diversity, inclusion and belonging (DIB)",{"href":4017,"dataGaName":4018,"dataGaLocation":3805},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":3666,"config":4020},{"href":3668,"dataGaName":3669,"dataGaLocation":3805},{"text":3676,"config":4022},{"href":3678,"dataGaName":3679,"dataGaLocation":3805},{"text":3681,"config":4024},{"href":3683,"dataGaName":3684,"dataGaLocation":3805},{"text":4026,"config":4027},"Modern Slavery Transparency Statement",{"href":4028,"dataGaName":4029,"dataGaLocation":3805},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":4031},[4032,4035,4038],{"text":4033,"config":4034},"Terms",{"href":3857,"dataGaName":3858,"dataGaLocation":3805},{"text":4036,"config":4037},"Cookies",{"dataGaName":3867,"dataGaLocation":3805,"id":3868,"isOneTrustButton":926},{"text":4039,"config":4040},"Privacy",{"href":3862,"dataGaName":3863,"dataGaLocation":3805},[4042],{"id":4043,"title":7,"body":3342,"config":4044,"content":4046,"description":3342,"extension":4050,"meta":4051,"navigation":926,"path":4052,"seo":4053,"stem":4054,"__hash__":4055},"blogAuthors/en-us/blog/authors/michael-friedrich.yml",{"template":4045},"BlogAuthor",{"name":7,"config":4047},{"headshot":4048,"ctfId":4049},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi","yml",{},"/en-us/blog/authors/michael-friedrich",{},"en-us/blog/authors/michael-friedrich","lJ-nfRIhdG49Arfrxdn1Vv4UppwD51BB13S3HwIswt4",[4057,4065,4073],{"title":4058,"description":4059,"heroImage":4060,"category":3338,"date":4061,"authors":4062,"slug":4064,"externalUrl":3342},"How we overhauled GitLab navigation","Users weren't getting what they needed from our navigation. Here are the steps we took to turn that experience around.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682884/Blog/Hero%20Images/navigation.jpg","2023-08-15",[4063],"Ashley Knobloch","navigation-research-blog-post",{"title":4066,"description":4067,"heroImage":4068,"category":3338,"date":4069,"authors":4070,"slug":4072,"externalUrl":3342},"Beautifying our UI: Giving GitLab build features a fresh look","Get an inside look at how we are improving the usability of GitLab build features with multiple visual design improvements.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682807/Blog/Hero%20Images/beautify.jpg","2023-07-05",[4071],"Veethika Mishra","beautifying-of-our-ui",{"title":4074,"description":4075,"heroImage":4076,"category":3338,"date":4077,"authors":4078,"slug":4080,"externalUrl":3342},"4 best practices leading orgs to release software faster","GitLab's 2023 Global DevSecOps Survey illuminates the strategies that organizations deploying more frequently have in common.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663908/Blog/Hero%20Images/2023-devsecops-report-blog-banner2.png","2023-06-08",[4079],"Kristina Weis","best-practices-leading-orgs-to-release-software-faster",{"promotions":4082},[4083,4097,4109,4120],{"id":4084,"categories":4085,"header":4087,"text":4088,"button":4089,"image":4094},"ai-modernization",[4086],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":4090,"config":4091},"Get your AI maturity score",{"href":4092,"dataGaName":4093,"dataGaLocation":3570},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":4095},{"src":4096},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":4098,"categories":4099,"header":4101,"text":4088,"button":4102,"image":4106},"devops-modernization",[4100,3906],"product","Are you just managing tools or shipping innovation?",{"text":4103,"config":4104},"Get your DevOps maturity score",{"href":4105,"dataGaName":4093,"dataGaLocation":3570},"/assessments/devops-modernization-assessment/",{"config":4107},{"src":4108},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":4110,"categories":4111,"header":4112,"text":4088,"button":4113,"image":4117},"security-modernization",[3354],"Are you trading speed for security?",{"text":4114,"config":4115},"Get your security maturity score",{"href":4116,"dataGaName":4093,"dataGaLocation":3570},"/assessments/security-modernization-assessment/",{"config":4118},{"src":4119},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":4121,"paths":4122,"header":4125,"text":4126,"button":4127,"image":4132},"github-azure-migration",[4123,4124],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":4128,"config":4129},"See how GitLab compares to GitHub",{"href":4130,"dataGaName":4131,"dataGaLocation":3570},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":4133},{"src":4108},{"header":4135,"blurb":4136,"button":4137,"secondaryButton":4142},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":4138,"config":4139},"Get your free trial",{"href":4140,"dataGaName":3369,"dataGaLocation":4141},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":3843,"config":4143},{"href":3695,"dataGaName":3374,"dataGaLocation":4141},1786803748984]