Skip to content

Add internal Starboard vulnerability resolution API endpoint

What does this MR do and why?

Adds an internal API endpoint that resolves Starboard vulnerabilities by finding UUIDs.

The agent will collect UUIDs when it creates Starboard vulnerabilities (!79208 (merged)). After having created all vulnerabilities, it concludes the scan by calling this new endpoint with all collected UUIDs. All active Cluster Image Scanning vulnerabilities of the project with excluded UUIDs get resolved.

Screenshots or screen recordings

n/a

How to set up and validate locally

  1. Create a JWT:
JWT.encode({ 'iss' => Gitlab::Kas::JWT_ISSUER }, Gitlab::Kas.secret, 'HS256')
  1. Create a fresh project.

  2. Create an agent and accompanying token:

agent = Project.last.cluster_agents.create(name: "test", created_by_user: User.find(1))
token = agent.agent_tokens.create(name: "test", created_by_user: User.find(1))
token.token
4. Create ./first.json
{
  "vulnerability": {
    "category": "cluster_image_scanning",
    "name": "CVE-2030-9273",
    "message": "CVE-2030-9273 in Flowdesk",
    "description": "You can't navigate the capacitor without calculating the wireless SMTP driver!",
    "cve": "CVE-2030-9273",
    "severity": "Low",
    "confidence": "High",
    "solution": "If we quantify the alarm, we can get to the EXE program through the back-end SCSI microchip!",
    "scanner": {
      "id": "starboard_trivy",
      "name": "Trivy (via Starboard Operator)"
    },
    "location": {
      "dependency": {
        "package": {
          "name": "Bitchip"
        },
        "version": "0.62"
      },
      "image": "http://wisoky.io/image:latest",
      "kubernetes_resource": {
        "namespace": "aut",
        "kind": "pod",
        "name": "mtkfirkstw",
        "container_name": "spfsbzwrrx",
        "agent_id": "jiccusfqbu"
      }
    },
    "identifiers": [
      {
        "type": "cve",
        "name": "CVE-2030-9273",
        "value": "CVE-2030-9273",
        "url": "http://wilkinson-beier.name/elisha.gorczany"
      }
    ],
    "links": [
      "http://bins.com/zona",
      "http://morar.name/juliana_lakin",
      "http://murazik.info/kamilah_haag"
    ]
  },
  "scanner": {
    "id": "starboard-trivy",
    "name": "Trivy (via Starboard Operator)",
    "url": "https://github.com/aquasecurity/trivy",
    "vendor": {
      "name": "GitLab"
    }
  }
}
5. Create ./second.json
{
  "vulnerability": {
    "category": "cluster_image_scanning",
    "name": "CVE-2030-9273",
    "message": "CVE-2030-9273 in Flowdesk",
    "description": "You can't navigate the capacitor without calculating the wireless SMTP driver!",
    "cve": "CVE-2030-9273",
    "severity": "Low",
    "confidence": "High",
    "solution": "If we quantify the alarm, we can get to the EXE program through the back-end SCSI microchip!",
    "scanner": {
      "id": "starboard_trivy",
      "name": "Trivy (via Starboard Operator)"
    },
    "location": {
      "dependency": {
        "package": {
          "name": "Bitchip"
        },
        "version": "0.62"
      },
      "image": "http://wisoky.io/image:latest",
      "kubernetes_resource": {
        "namespace": "aut",
        "kind": "pod",
        "name": "mtkfirkstw",
        "container_name": "spfsbzwrrx",
        "agent_id": "jiccusfqbu"
      }
    },
    "identifiers": [
      {
        "type": "cve",
        "name": "CVE-2030-9273",
        "value": "CVE-2030-9273",
        "url": "http://wilkinson-beier.name/elisha.gorczany"
      }
    ],
    "links": [
      "http://bins.com/zona",
      "http://morar.name/juliana_lakin",
      "http://murazik.info/kamilah_haag"
    ]
  },
  "scanner": {
    "id": "starboard-trivy",
    "name": "Trivy (via Starboard Operator)",
    "url": "https://github.com/aquasecurity/trivy",
    "vendor": {
      "name": "GitLab"
    }
  }
}
  1. Create both vulnerabilities:
curl --request PUT --header "Gitlab-Kas-Api-Request: $KAS_JWT" \
     --header "Authorization: Bearer $AGENT_TOKEN" --header "Content-Type: application/json" \
     --url "http://gdk.test:3000/api/v4/internal/kubernetes/modules/starboard_vulnerability" \
     --data @first.json

curl --request PUT --header "Gitlab-Kas-Api-Request: $KAS_JWT" \
     --header "Authorization: Bearer $AGENT_TOKEN" --header "Content-Type: application/json" \
     --url "http://gdk.test:3000/api/v4/internal/kubernetes/modules/starboard_vulnerability" \
     --data @second.json
  1. Identify both vulnerabilties' UUIDs:
Project.last.vulnerabilities.map(&:finding).map(&:uuid)
# => ["102e8a0a-fe29-59bd-b46c-57c3e9bc6411", "5eb12985-0ed5-51f4-b545-fd8871dc2870"]
  1. Issues a request that resolves none of the vulnerabilities:
curl --request POST --header "Gitlab-Kas-Api-Request: $KAS_JWT" \
     --header "Authorization: Bearer $AGENT_TOKEN" --header "Content-Type: application/json" \
     --url "http://gdk.test:3000/api/v4/internal/kubernetes/modules/starboard_vulnerability/scan_result" \
     --data '{ "uuids": ["102e8a0a-fe29-59bd-b46c-57c3e9bc6411", "5eb12985-0ed5-51f4-b545-fd8871dc2870"] }'
Project.last.vulnerabilities.pluck(:state)
=> ["detected", "detected"]
  1. Issues a request that resolves one of the vulnerabilities:
curl --request POST --header "Gitlab-Kas-Api-Request: $KAS_JWT" \
     --header "Authorization: Bearer $AGENT_TOKEN" --header "Content-Type: application/json" \
     --url "http://gdk.test:3000/api/v4/internal/kubernetes/modules/starboard_vulnerability/scan_result" \
     --data '{ "uuids": ["102e8a0a-fe29-59bd-b46c-57c3e9bc6411"] }'
Project.last.vulnerabilities.map { |vuln| [vuln.finding.uuid, vuln.state] }
=> [["102e8a0a-fe29-59bd-b46c-57c3e9bc6411", "detected"], ["5eb12985-0ed5-51f4-b545-fd8871dc2870", "resolved"]]
  1. Issue a request that resolves all of the vulnerabilities:
curl --request POST --header "Gitlab-Kas-Api-Request: $KAS_JWT" \
     --header "Authorization: Bearer $AGENT_TOKEN" --header "Content-Type: application/json" \
     --url "http://gdk.test:3000/api/v4/internal/kubernetes/modules/starboard_vulnerability/scan_result" \
     --data '{ "uuids": [] }'
Project.last.vulnerabilities.pluck(:state)
=> ["resolved", "resolved"]

Database query

UPDATE 
  "vulnerabilities" 
SET 
  "resolved_on_default_branch" = TRUE, 
  "state" = 3 
WHERE 
  "vulnerabilities"."id" IN (
    SELECT 
      "vulnerabilities"."id" 
    FROM 
      "vulnerabilities" 
      LEFT OUTER JOIN "vulnerability_occurrences" "findings" ON "findings"."vulnerability_id" = "vulnerabilities"."id" 
    WHERE 
      "vulnerabilities"."project_id" = 8844 
      AND "vulnerabilities"."state" IN (1, 4) 
      AND "vulnerabilities"."report_type" = 7 
      AND "findings"."uuid" NOT IN (
        '41d9bd46-36b9-5a43-a9d6-67dbfb1646e3', 
        '40bff4c6-e530-5ae9-9ace-42dec178f60c'
      )
  )
Execution plan (details)
ModifyTable on public.vulnerabilities  (cost=805.37..808.40 rows=1 width=404) (actual time=7.344..7.348 rows=0 loops=1)
   Buffers: shared hit=13 read=4
   I/O Timings: read=7.055 write=0.000
   ->  Nested Loop  (cost=805.37..808.40 rows=1 width=404) (actual time=7.254..7.257 rows=0 loops=1)
         Buffers: shared hit=13 read=4
         I/O Timings: read=7.055 write=0.000
         ->  HashAggregate  (cost=804.81..804.82 rows=1 width=28) (actual time=7.253..7.255 rows=0 loops=1)
               Group Key: vulnerabilities_1.id
               Buffers: shared hit=13 read=4
               I/O Timings: read=7.055 write=0.000
               ->  Nested Loop  (cost=1.12..804.80 rows=1 width=28) (actual time=7.178..7.180 rows=0 loops=1)
                     Buffers: shared hit=13 read=4
                     I/O Timings: read=7.055 write=0.000
                     ->  Index Scan using index_vulnerabilities_on_project_id_and_state_and_severity on public.vulnerabilities vulnerabilities_1  (cost=0.56..801.21 rows=1 width=14) (actual time=7.175..7.175 rows=0 loops=1)
                           Index Cond: ((vulnerabilities_1.project_id = 8844) AND (vulnerabilities_1.state = ANY ('{1,4}'::integer[])))
                           Filter: (vulnerabilities_1.report_type = 7)
                           Rows Removed by Filter: 0
                           Buffers: shared hit=13 read=4
                           I/O Timings: read=7.055 write=0.000
                     ->  Index Scan using index_vulnerability_occurrences_on_vulnerability_id on public.vulnerability_occurrences findings  (cost=0.56..3.58 rows=1 width=14) (actual time=0.000..0.000 rows=0 loops=0)
                           Index Cond: (findings.vulnerability_id = vulnerabilities_1.id)
                           Filter: ((findings.uuid)::text <> ALL ('{41d9bd46-36b9-5a43-a9d6-67dbfb1646e3,40bff4c6-e530-5ae9-9ace-42dec178f60c}'::text[]))
                           Rows Removed by Filter: 0
                           I/O Timings: read=0.000 write=0.000
         ->  Index Scan using vulnerabilities_pkey on public.vulnerabilities  (cost=0.56..3.58 rows=1 width=381) (actual time=0.000..0.000 rows=0 loops=0)
               Index Cond: (vulnerabilities.id = findings.vulnerability_id)
               I/O Timings: read=0.000 write=0.000

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #345905 (closed)

Edited by Dominic Bauer

Merge request reports