From d96edd7c2dc9dfe9bb3f5338118f971619799870 Mon Sep 17 00:00:00 2001 From: Piotr Date: Thu, 10 Oct 2024 13:41:04 +0200 Subject: [PATCH 01/14] adding functions for getting more information & debug Signed-off-by: Piotr --- .../k8s-version-policy/k8s_version_policy.py | 60 +++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index a7884db28..26edce2be 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -24,6 +24,7 @@ (c) Hannes Baum , 6/2023 (c) Martin Morgenstern , 2/2024 (c) Matthias Büchse , 3/2024 +(c) Piotr Bigos , 10/2024 SPDX-License-Identifier: CC-BY-SA-4.0 """ @@ -337,19 +338,32 @@ async def collect_cve_versions(session: aiohttp.ClientSession) -> set: # CVE fix versions cfvs = set() + cve_patch_data = dict() + + # # Request latest version + # async with session.get( + # "https://kubernetes.io/docs/reference/issues-security/official-cve-feed/index.json", + # headers={"Accept": "application/json"} + # ) as resp: + # cve_list = await resp.json() - # Request latest version async with session.get( - "https://kubernetes.io/docs/reference/issues-security/official-cve-feed/index.json", - headers={"Accept": "application/json"} + "https://kubernetes.io/docs/reference/issues-security/official-cve-feed/index.json", + headers={"Accept": "application/json"} ) as resp: + if resp.status != 200: + logger.error(f"Failed to fetch CVE data, status code: {resp.status}") + return cve_patch_data + cve_list = await resp.json() tasks = [request_cve_data(session=session, cveid=cve['id']) - for cve in cve_list['items']] + for cve in cve_list.get('items', [])] cve_data_list = await asyncio.gather(*tasks, return_exceptions=True) + cve_data_list = [data for data in cve_data_list if not isinstance(data, Exception)] + for cve_data in cve_data_list: try: cve_cna = cve_data['containers']['cna'] @@ -381,6 +395,44 @@ async def collect_cve_versions(session: aiohttp.ClientSession) -> set: return cfvs +def is_critical_cve(cve_metrics: list) -> bool: + """Checks if the CVE is considered critical based on CVSS score.""" + for metric in cve_metrics: + if metric.get('cvssV3', {}).get('baseScore', 0) >= CVE_SEVERITY: + return True + return False + + +def parse_cve_version_information_new(version_info: dict) -> str: + """Extracts the affected Kubernetes version from CVE data.""" + return version_info.get('version') + + +def parse_patch_release_date(version_info: dict) -> datetime: + """Extracts the release date of the patch from the CVE version info.""" + patch_release_str = version_info.get('patchReleaseDate', None) + if patch_release_str: + return datetime.strptime(patch_release_str, "%Y-%m-%d") + return None + + +async def check_patch_deployment(session: aiohttp.ClientSession, current_version: str, deployed_date: datetime) -> None: + """Check if the latest patch targeting a critical CVE was deployed within the allowed time.""" + cve_patch_data = await collect_cve_versions(session) + + for cve_id, version_data in cve_patch_data.items(): + for version, patch_release_date in version_data: + if version == current_version: + if patch_release_date: + allowed_timeframe = patch_release_date + CVE_VERSION_CADENCE + if deployed_date > allowed_timeframe: + logger.error(f"Patch for {cve_id} affecting version {version} was not deployed in time!") + else: + logger.info(f"Patch for {cve_id} affecting version {version} deployed in time.") + else: + logger.warning(f"Patch release date for {cve_id} affecting version {version} is missing.") + + async def get_k8s_cluster_info(kubeconfig, context=None) -> ClusterInfo: """Get the k8s version of the cluster under test.""" cluster_config = await kubernetes_asyncio.config.load_kube_config(kubeconfig, context) From ebfe9518d80f1a98eae1b835a33ae7aceb3ba3c2 Mon Sep 17 00:00:00 2001 From: Piotr Date: Wed, 16 Oct 2024 12:15:34 +0200 Subject: [PATCH 02/14] solving conflicts Signed-off-by: Piotr --- Standards/scs-0210-v2-k8s-version-policy.md | 27 +++++++++++++++ .../k8s-version-policy/k8s_version_policy.py | 33 +++++++++++++------ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/Standards/scs-0210-v2-k8s-version-policy.md b/Standards/scs-0210-v2-k8s-version-policy.md index 3b086ec3d..89fa62077 100644 --- a/Standards/scs-0210-v2-k8s-version-policy.md +++ b/Standards/scs-0210-v2-k8s-version-policy.md @@ -55,6 +55,7 @@ window period. In order to keep up-to-date with the latest Kubernetes features, bug fixes and security improvements, the provided Kubernetes versions should be kept up-to-date with new upstream releases: +<<<<<<< HEAD - The latest minor version MUST be provided no later than 4 months after release. - The latest patch version MUST be provided no later than 2 weeks after release. - This time period MUST be even shorter for patches that fix critical CVEs. @@ -63,6 +64,32 @@ the provided Kubernetes versions should be kept up-to-date with new upstream rel It is RECOMMENDED to provide a new patch version in a 2-day time period after their release. - New versions MUST be tested before being rolled out on productive infrastructure; at least the [CNCF E2E tests][cncf-conformance] should be passed beforehand. +======= +1. Minor Versions: + - The latest minor version MUST be provided no later than 4 months after release. + +2. Patch Versions: + - The latest patch version MUST be provided no later than 1 week after release. + - This time period MUST be even shorter for patches that fix critical CVEs. + In this context, a critical CVE is a CVE with a CVSS base score >= 8 according + to the CVSS version used in the original CVE record (e.g., CVSSv3.1). + It is RECOMMENDED to provide a new patch version in a 2-day time period after their release. + - New versions MUST be tested before being rolled out on productive infrastructure; + at least the [CNCF E2E tests][cncf-conformance] should be passed beforehand. + +3. CI Integration + * Trivy + - Providers should integrate Trivy into their CI pipeline to automatically scan Kubernetes cluster components, + including kubelet, apiserver, and others. + - The CI job MUST fail if critical vulnerabilities (CVSS >= 8) are detected in the cluster components. + - JSON reports from Trivy scans should be reviewed, and Trivy's experimental status should be monitored for changes + in output formats. + * nvdlib (Fallback): + - If Trivy fails or cannot meet requierements, nvdlib MUST be used as a fallback to query CVE data for Kubernetes + versions, laveraging CPE-based searches to track vunerabilities for specific versions. + - Providers using nvdlib MUST periodically query for critical cunerabilities affecting the Kubernetes version in production. + +4. TBD At the same time, providers must support Kubernetes versions at least as long as the official sources as described in [Kubernetes Support Period][k8s-support-period]: diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index 7ab586d16..2fe23bffd 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -189,7 +189,6 @@ class K8sBranch: def previous(self): if self.minor == 0: - # FIXME: this is ugly return self return K8sBranch(self.major, self.minor - 1) @@ -258,12 +257,6 @@ class VersionRange: upper_version: K8sVersion = None inclusive: bool = False - def __post_init__(self): - if self.lower_version is None: - raise ValueError("lower_version must not be None") - if self.upper_version and self.upper_version < self.lower_version: - raise ValueError("lower_version must be lower than upper_version") - def __contains__(self, version: K8sVersion) -> bool: if self.upper_version is None: return self.lower_version == version @@ -271,6 +264,19 @@ def __contains__(self, version: K8sVersion) -> bool: return self.lower_version <= version <= self.upper_version return self.lower_version <= version < self.upper_version + # def __post_init__(self): + # if self.lower_version is None: + # raise ValueError("lower_version must not be None") + # if self.upper_version and self.upper_version < self.lower_version: + # raise ValueError("lower_version must be lower than upper_version") + # + # def __contains__(self, version: K8sVersion) -> bool: + # if self.upper_version is None: + # return self.lower_version == version + # if self.inclusive: + # return self.lower_version <= version <= self.upper_version + # return self.lower_version <= version < self.upper_version + @dataclass class ClusterInfo: @@ -468,9 +474,16 @@ def check_k8s_version_recency( if my_version.patch >= release.version.patch: continue # at this point `release` has the same major.minor, but higher patch than `my_version` - if release.age > PATCH_VERSION_CADENCE: - # whoops, the cluster should have been updated to this (or a higher version) already! - return False + if my_version == release.version: + if release.age <= MINOR_VERSION_CADENCE: + logger.info(f"Version {my_version} is recent (within cadence).") + return True + else: + logger.error(f"Version {my_version} is too old.") + return False + # if release.age > PATCH_VERSION_CADENCE: + # # whoops, the cluster should have been updated to this (or a higher version) already! + # return False ranges = [_range for _range in cve_affected_ranges if my_version in _range] if ranges and release.age > CVE_VERSION_CADENCE: # -- two FIXMEs: From e6354115d5c17f622e468209b9c45b1fff2f3711 Mon Sep 17 00:00:00 2001 From: Piotr Date: Wed, 16 Oct 2024 12:20:06 +0200 Subject: [PATCH 03/14] fixing git inset Signed-off-by: Piotr --- Standards/scs-0210-v2-k8s-version-policy.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Standards/scs-0210-v2-k8s-version-policy.md b/Standards/scs-0210-v2-k8s-version-policy.md index 89fa62077..79ab9b435 100644 --- a/Standards/scs-0210-v2-k8s-version-policy.md +++ b/Standards/scs-0210-v2-k8s-version-policy.md @@ -55,16 +55,6 @@ window period. In order to keep up-to-date with the latest Kubernetes features, bug fixes and security improvements, the provided Kubernetes versions should be kept up-to-date with new upstream releases: -<<<<<<< HEAD -- The latest minor version MUST be provided no later than 4 months after release. -- The latest patch version MUST be provided no later than 2 weeks after release. -- This time period MUST be even shorter for patches that fix critical CVEs. - In this context, a critical CVE is a CVE with a CVSS base score >= 8 according - to the CVSS version used in the original CVE record (e.g., CVSSv3.1). - It is RECOMMENDED to provide a new patch version in a 2-day time period after their release. -- New versions MUST be tested before being rolled out on productive infrastructure; - at least the [CNCF E2E tests][cncf-conformance] should be passed beforehand. -======= 1. Minor Versions: - The latest minor version MUST be provided no later than 4 months after release. From 54ee69459d24bc79b07bc22b8569ae0720d50177 Mon Sep 17 00:00:00 2001 From: Piotr Date: Fri, 18 Oct 2024 15:03:08 +0200 Subject: [PATCH 04/14] feat: Add Kubernetes pod image scanning and improve error handling - Integrated Trivy for scanning Kubernetes pod images for security vulnerabilities. - Fixed issue with ClusterInfo object being incorrectly passed where kubeconfig path was expected. - Addressed SSL certificate verification error when making external HTTP requests by adding proper handling. - Updated the compliance check logic to ensure correct validation of Kubernetes cluster versions and vulnerability checks. - Added logging improvements to provide clearer insights during version compliance checks. - Refined the code structure to handle K8s image scanning and cluster versioning in an async manner. Signed-off-by: Piotr --- .../k8s-version-policy/k8s_version_policy.py | 176 +++++++++++------- 1 file changed, 104 insertions(+), 72 deletions(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index 2fe23bffd..95d5bcac0 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -24,10 +24,9 @@ (c) Hannes Baum , 6/2023 (c) Martin Morgenstern , 2/2024 (c) Matthias Büchse , 3/2024 -(c) Piotr Bigos , 10/2024 +(c) Piotr Bigos SPDX-License-Identifier: CC-BY-SA-4.0 """ - from collections import Counter from dataclasses import dataclass from datetime import datetime, timedelta @@ -36,11 +35,13 @@ import asyncio import contextlib import getopt +import json import kubernetes_asyncio import logging import logging.config import re import requests +import subprocess import sys import yaml @@ -94,6 +95,10 @@ class HelpException(BaseException): """Exception raised if the help functionality is called""" +class CriticalException(BaseException): + """Exception raised if the critical CVE are found""" + + class Config: kubeconfig = None context = None @@ -189,6 +194,7 @@ class K8sBranch: def previous(self): if self.minor == 0: + # FIXME: this is ugly return self return K8sBranch(self.major, self.minor - 1) @@ -257,6 +263,12 @@ class VersionRange: upper_version: K8sVersion = None inclusive: bool = False + def __post_init__(self): + if self.lower_version is None: + raise ValueError("lower_version must not be None") + if self.upper_version and self.upper_version < self.lower_version: + raise ValueError("lower_version must be lower than upper_version") + def __contains__(self, version: K8sVersion) -> bool: if self.upper_version is None: return self.lower_version == version @@ -264,24 +276,12 @@ def __contains__(self, version: K8sVersion) -> bool: return self.lower_version <= version <= self.upper_version return self.lower_version <= version < self.upper_version - # def __post_init__(self): - # if self.lower_version is None: - # raise ValueError("lower_version must not be None") - # if self.upper_version and self.upper_version < self.lower_version: - # raise ValueError("lower_version must be lower than upper_version") - # - # def __contains__(self, version: K8sVersion) -> bool: - # if self.upper_version is None: - # return self.lower_version == version - # if self.inclusive: - # return self.lower_version <= version <= self.upper_version - # return self.lower_version <= version < self.upper_version - @dataclass class ClusterInfo: version: K8sVersion name: str + kubeconfig: str async def request_cve_data(session: aiohttp.ClientSession, cveid: str) -> dict: @@ -344,32 +344,19 @@ async def collect_cve_versions(session: aiohttp.ClientSession) -> set: # CVE fix versions cfvs = set() - cve_patch_data = dict() - - # # Request latest version - # async with session.get( - # "https://kubernetes.io/docs/reference/issues-security/official-cve-feed/index.json", - # headers={"Accept": "application/json"} - # ) as resp: - # cve_list = await resp.json() + # Request latest version async with session.get( - "https://kubernetes.io/docs/reference/issues-security/official-cve-feed/index.json", - headers={"Accept": "application/json"} + "https://kubernetes.io/docs/reference/issues-security/official-cve-feed/index.json", + headers={"Accept": "application/json"} ) as resp: - if resp.status != 200: - logger.error(f"Failed to fetch CVE data, status code: {resp.status}") - return cve_patch_data - cve_list = await resp.json() tasks = [request_cve_data(session=session, cveid=cve['id']) - for cve in cve_list.get('items', [])] + for cve in cve_list['items']] cve_data_list = await asyncio.gather(*tasks, return_exceptions=True) - cve_data_list = [data for data in cve_data_list if not isinstance(data, Exception)] - for cve_data in cve_data_list: try: cve_cna = cve_data['containers']['cna'] @@ -401,42 +388,82 @@ async def collect_cve_versions(session: aiohttp.ClientSession) -> set: return cfvs -def is_critical_cve(cve_metrics: list) -> bool: - """Checks if the CVE is considered critical based on CVSS score.""" - for metric in cve_metrics: - if metric.get('cvssV3', {}).get('baseScore', 0) >= CVE_SEVERITY: - return True - return False +async def run_trivy_scan(image: str) -> dict: + """ + Run Trivy scan on the specified image and return the results as a dictionary. + + Args: + image (str): The Docker image to scan. + + Returns: + dict: Parsed JSON results from Trivy. + """ + try: + # Run the Trivy scan command + result = await asyncio.create_subprocess_exec( + 'trivy', + 'image', + '--format', 'json', + '--no-progress', + image, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + stdout, stderr = await result.communicate() + + if result.returncode != 0: + logger.error("Trivy scan failed: %s", stderr.decode().strip()) + return {} + + # Parse the JSON output from Trivy + return json.loads(stdout.decode()) + + except Exception as e: + logger.error("Error running Trivy scan: %s", e) + return {} + + +async def get_k8s_pod_images(kubeconfig, context=None) -> list[str]: + """Get the list of container images used by all the pods in the Kubernetes cluster.""" + cluster_config = await kubernetes_asyncio.config.load_kube_config(kubeconfig, context) + + async with kubernetes_asyncio.client.ApiClient() as api: + v1 = kubernetes_asyncio.client.CoreV1Api(api) + pods = await v1.list_pod_for_all_namespaces(watch=False) + images = set() + for pod in pods.items: + # Get images from pod containers + for container in pod.spec.containers: + images.add(container.image) -def parse_cve_version_information_new(version_info: dict) -> str: - """Extracts the affected Kubernetes version from CVE data.""" - return version_info.get('version') + # Get images from init containers (if any) + if pod.spec.init_containers: + for container in pod.spec.init_containers: + images.add(container.image) + return list(images) -def parse_patch_release_date(version_info: dict) -> datetime: - """Extracts the release date of the patch from the CVE version info.""" - patch_release_str = version_info.get('patchReleaseDate', None) - if patch_release_str: - return datetime.strptime(patch_release_str, "%Y-%m-%d") - return None +async def scan_k8s_images(kubeconfig, context=None) -> None: + """Scan the images used in the Kubernetes cluster for vulnerabilities.""" + images_to_scan = await get_k8s_pod_images(kubeconfig, context) -async def check_patch_deployment(session: aiohttp.ClientSession, current_version: str, deployed_date: datetime) -> None: - """Check if the latest patch targeting a critical CVE was deployed within the allowed time.""" - cve_patch_data = await collect_cve_versions(session) + # Scan each image using Trivy + for image in images_to_scan: + logger.info(f"Scanning image: {image}") + scan_results = await run_trivy_scan(image) - for cve_id, version_data in cve_patch_data.items(): - for version, patch_release_date in version_data: - if version == current_version: - if patch_release_date: - allowed_timeframe = patch_release_date + CVE_VERSION_CADENCE - if deployed_date > allowed_timeframe: - logger.error(f"Patch for {cve_id} affecting version {version} was not deployed in time!") - else: - logger.info(f"Patch for {cve_id} affecting version {version} deployed in time.") - else: - logger.warning(f"Patch release date for {cve_id} affecting version {version} is missing.") + if scan_results: + # Process the results, e.g., log vulnerabilities + for result in scan_results.get('Results', []): + for vulnerability in result.get('Vulnerabilities', []): + logger.warning( + f"""Vulnerability found in image {image}: + {vulnerability['VulnerabilityID']} " + (Severity: {vulnerability['Severity']})""" + ) async def get_k8s_cluster_info(kubeconfig, context=None) -> ClusterInfo: @@ -447,7 +474,7 @@ async def get_k8s_cluster_info(kubeconfig, context=None) -> ClusterInfo: version_api = kubernetes_asyncio.client.VersionApi(api) response = await version_api.get_code() version = parse_version(response.git_version) - return ClusterInfo(version, cluster_config.current_context['name']) + return ClusterInfo(version, cluster_config.current_context['name'], kubeconfig=kubeconfig) def check_k8s_version_recency( @@ -474,16 +501,9 @@ def check_k8s_version_recency( if my_version.patch >= release.version.patch: continue # at this point `release` has the same major.minor, but higher patch than `my_version` - if my_version == release.version: - if release.age <= MINOR_VERSION_CADENCE: - logger.info(f"Version {my_version} is recent (within cadence).") - return True - else: - logger.error(f"Version {my_version} is too old.") - return False - # if release.age > PATCH_VERSION_CADENCE: - # # whoops, the cluster should have been updated to this (or a higher version) already! - # return False + if release.age > PATCH_VERSION_CADENCE: + # whoops, the cluster should have been updated to this (or a higher version) already! + return False ranges = [_range for _range in cve_affected_ranges if my_version in _range] if ranges and release.age > CVE_VERSION_CADENCE: # -- two FIXMEs: @@ -539,11 +559,23 @@ async def main(argv): logger.critical("The EOL data in %s is outdated and we cannot reliably run this script.", EOLDATA_FILE) return 1 + kubeconfig_path = config.kubeconfig + connector = aiohttp.TCPConnector(limit=5) async with aiohttp.ClientSession(connector=connector) as session: cve_affected_ranges = await collect_cve_versions(session) releases_data = fetch_k8s_releases_data() + try: + logger.info(f"Checking cluster specified by {kubeconfig_path}") + cluster = await get_k8s_cluster_info(config.kubeconfig, config.context) + await scan_k8s_images(cluster.kubeconfig) + + except CriticalException as e: + logger.critical(e) + logger.debug("Exception info", exc_info=True) + return 1 + try: context_desc = f"context '{config.context}'" if config.context else "default context" logger.info("Checking cluster specified by %s in %s.", context_desc, config.kubeconfig) From 12987e598146bdf6af2d0a10617f00c936e6ccb5 Mon Sep 17 00:00:00 2001 From: Piotr Date: Wed, 23 Oct 2024 11:55:22 +0200 Subject: [PATCH 05/14] removing comments Signed-off-by: Piotr --- Tests/kaas/k8s-version-policy/k8s_version_policy.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index 95d5bcac0..2597c8821 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -434,11 +434,9 @@ async def get_k8s_pod_images(kubeconfig, context=None) -> list[str]: images = set() for pod in pods.items: - # Get images from pod containers for container in pod.spec.containers: images.add(container.image) - # Get images from init containers (if any) if pod.spec.init_containers: for container in pod.spec.init_containers: images.add(container.image) @@ -450,13 +448,11 @@ async def scan_k8s_images(kubeconfig, context=None) -> None: """Scan the images used in the Kubernetes cluster for vulnerabilities.""" images_to_scan = await get_k8s_pod_images(kubeconfig, context) - # Scan each image using Trivy for image in images_to_scan: logger.info(f"Scanning image: {image}") scan_results = await run_trivy_scan(image) if scan_results: - # Process the results, e.g., log vulnerabilities for result in scan_results.get('Results', []): for vulnerability in result.get('Vulnerabilities', []): logger.warning( From 1d332ae46517494be229ee7e1ae4d113b46c1e84 Mon Sep 17 00:00:00 2001 From: Piotr Date: Mon, 4 Nov 2024 15:20:53 +0100 Subject: [PATCH 06/14] reseting standard to it's original form Signed-off-by: Piotr --- Standards/scs-0210-v2-k8s-version-policy.md | 33 +++++---------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/Standards/scs-0210-v2-k8s-version-policy.md b/Standards/scs-0210-v2-k8s-version-policy.md index 79ab9b435..3b086ec3d 100644 --- a/Standards/scs-0210-v2-k8s-version-policy.md +++ b/Standards/scs-0210-v2-k8s-version-policy.md @@ -55,31 +55,14 @@ window period. In order to keep up-to-date with the latest Kubernetes features, bug fixes and security improvements, the provided Kubernetes versions should be kept up-to-date with new upstream releases: -1. Minor Versions: - - The latest minor version MUST be provided no later than 4 months after release. - -2. Patch Versions: - - The latest patch version MUST be provided no later than 1 week after release. - - This time period MUST be even shorter for patches that fix critical CVEs. - In this context, a critical CVE is a CVE with a CVSS base score >= 8 according - to the CVSS version used in the original CVE record (e.g., CVSSv3.1). - It is RECOMMENDED to provide a new patch version in a 2-day time period after their release. - - New versions MUST be tested before being rolled out on productive infrastructure; - at least the [CNCF E2E tests][cncf-conformance] should be passed beforehand. - -3. CI Integration - * Trivy - - Providers should integrate Trivy into their CI pipeline to automatically scan Kubernetes cluster components, - including kubelet, apiserver, and others. - - The CI job MUST fail if critical vulnerabilities (CVSS >= 8) are detected in the cluster components. - - JSON reports from Trivy scans should be reviewed, and Trivy's experimental status should be monitored for changes - in output formats. - * nvdlib (Fallback): - - If Trivy fails or cannot meet requierements, nvdlib MUST be used as a fallback to query CVE data for Kubernetes - versions, laveraging CPE-based searches to track vunerabilities for specific versions. - - Providers using nvdlib MUST periodically query for critical cunerabilities affecting the Kubernetes version in production. - -4. TBD +- The latest minor version MUST be provided no later than 4 months after release. +- The latest patch version MUST be provided no later than 2 weeks after release. +- This time period MUST be even shorter for patches that fix critical CVEs. + In this context, a critical CVE is a CVE with a CVSS base score >= 8 according + to the CVSS version used in the original CVE record (e.g., CVSSv3.1). + It is RECOMMENDED to provide a new patch version in a 2-day time period after their release. +- New versions MUST be tested before being rolled out on productive infrastructure; + at least the [CNCF E2E tests][cncf-conformance] should be passed beforehand. At the same time, providers must support Kubernetes versions at least as long as the official sources as described in [Kubernetes Support Period][k8s-support-period]: From 11daeac350f0fa6b25ec31d11921b7c2884626d3 Mon Sep 17 00:00:00 2001 From: Piotr Date: Mon, 4 Nov 2024 17:04:34 +0100 Subject: [PATCH 07/14] reverting ClusterInfo to its original shape, removing kubeconfig field, providing more specific description for initiating scan on the kubernetes, removing cluster_config variable Signed-off-by: Piotr --- Tests/kaas/k8s-version-policy/k8s_version_policy.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index 2597c8821..c70b695e7 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -281,7 +281,6 @@ def __contains__(self, version: K8sVersion) -> bool: class ClusterInfo: version: K8sVersion name: str - kubeconfig: str async def request_cve_data(session: aiohttp.ClientSession, cveid: str) -> dict: @@ -426,7 +425,6 @@ async def run_trivy_scan(image: str) -> dict: async def get_k8s_pod_images(kubeconfig, context=None) -> list[str]: """Get the list of container images used by all the pods in the Kubernetes cluster.""" - cluster_config = await kubernetes_asyncio.config.load_kube_config(kubeconfig, context) async with kubernetes_asyncio.client.ApiClient() as api: v1 = kubernetes_asyncio.client.CoreV1Api(api) @@ -563,9 +561,12 @@ async def main(argv): releases_data = fetch_k8s_releases_data() try: - logger.info(f"Checking cluster specified by {kubeconfig_path}") + logger.info( + f"""Initiating scan on the Kubernetes cluster specified by kubeconfig at '{kubeconfig_path}' + {' with context ' + config.context if config.context else ''}. + Fetching cluster information and verifying access.""") cluster = await get_k8s_cluster_info(config.kubeconfig, config.context) - await scan_k8s_images(cluster.kubeconfig) + await scan_k8s_images(config.kubeconfig) except CriticalException as e: logger.critical(e) From e62b347520a69c831db779f871d126f14321519c Mon Sep 17 00:00:00 2001 From: Piotr Date: Mon, 4 Nov 2024 17:36:41 +0100 Subject: [PATCH 08/14] removing unused kubeconfig variable Signed-off-by: Piotr --- Tests/kaas/k8s-version-policy/k8s_version_policy.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index c70b695e7..67f2c5dd0 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -468,7 +468,7 @@ async def get_k8s_cluster_info(kubeconfig, context=None) -> ClusterInfo: version_api = kubernetes_asyncio.client.VersionApi(api) response = await version_api.get_code() version = parse_version(response.git_version) - return ClusterInfo(version, cluster_config.current_context['name'], kubeconfig=kubeconfig) + return ClusterInfo(version, cluster_config.current_context['name']) def check_k8s_version_recency( @@ -553,8 +553,6 @@ async def main(argv): logger.critical("The EOL data in %s is outdated and we cannot reliably run this script.", EOLDATA_FILE) return 1 - kubeconfig_path = config.kubeconfig - connector = aiohttp.TCPConnector(limit=5) async with aiohttp.ClientSession(connector=connector) as session: cve_affected_ranges = await collect_cve_versions(session) @@ -562,7 +560,7 @@ async def main(argv): try: logger.info( - f"""Initiating scan on the Kubernetes cluster specified by kubeconfig at '{kubeconfig_path}' + f"""Initiating scan on the Kubernetes cluster specified by kubeconfig at '{config.kubeconfig}' {' with context ' + config.context if config.context else ''}. Fetching cluster information and verifying access.""") cluster = await get_k8s_cluster_info(config.kubeconfig, config.context) From 5ab2bd0bce245755e8f1845f0c29410c6b20c63a Mon Sep 17 00:00:00 2001 From: Piotr Date: Mon, 4 Nov 2024 17:50:51 +0100 Subject: [PATCH 09/14] fixing pylint and docstring formatting Signed-off-by: Piotr --- Tests/kaas/k8s-version-policy/k8s_version_policy.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index 67f2c5dd0..3811c2cb5 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -388,8 +388,7 @@ async def collect_cve_versions(session: aiohttp.ClientSession) -> set: async def run_trivy_scan(image: str) -> dict: - """ - Run Trivy scan on the specified image and return the results as a dictionary. + """Run Trivy scan on the specified image and return the results as a dictionary. Args: image (str): The Docker image to scan. @@ -560,10 +559,10 @@ async def main(argv): try: logger.info( - f"""Initiating scan on the Kubernetes cluster specified by kubeconfig at '{config.kubeconfig}' - {' with context ' + config.context if config.context else ''}. + f"""Initiating scan on the Kubernetes cluster specified by kubeconfig at {config.kubeconfig} + with context {config.context if config.context else ''}. Fetching cluster information and verifying access.""") - cluster = await get_k8s_cluster_info(config.kubeconfig, config.context) + await get_k8s_cluster_info(config.kubeconfig, config.context) await scan_k8s_images(config.kubeconfig) except CriticalException as e: From 5e2b7646499177669adf8bbf20358835ef51532f Mon Sep 17 00:00:00 2001 From: Piotr Date: Mon, 4 Nov 2024 20:25:38 +0100 Subject: [PATCH 10/14] Update Tests/kaas/k8s-version-policy/k8s_version_policy.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Büchse Signed-off-by: Piotr --- Tests/kaas/k8s-version-policy/k8s_version_policy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index 3811c2cb5..d8d6b1568 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -441,9 +441,8 @@ async def get_k8s_pod_images(kubeconfig, context=None) -> list[str]: return list(images) -async def scan_k8s_images(kubeconfig, context=None) -> None: +async def scan_k8s_images(images_to_scan) -> None: """Scan the images used in the Kubernetes cluster for vulnerabilities.""" - images_to_scan = await get_k8s_pod_images(kubeconfig, context) for image in images_to_scan: logger.info(f"Scanning image: {image}") From 3348960bba7fa36f95ba28a6c1604735f0a28c7c Mon Sep 17 00:00:00 2001 From: Piotr Date: Mon, 4 Nov 2024 21:37:46 +0100 Subject: [PATCH 11/14] fixing pylint and resolving conflict which appeard after review Signed-off-by: Piotr --- Tests/kaas/k8s-version-policy/k8s_version_policy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index d8d6b1568..530e86b2f 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -558,8 +558,8 @@ async def main(argv): try: logger.info( - f"""Initiating scan on the Kubernetes cluster specified by kubeconfig at {config.kubeconfig} - with context {config.context if config.context else ''}. + f"""Initiating scan on the Kubernetes cluster specified by kubeconfig at {config.kubeconfig} + with context {config.context if config.context else ''}. Fetching cluster information and verifying access.""") await get_k8s_cluster_info(config.kubeconfig, config.context) await scan_k8s_images(config.kubeconfig) From 4a598784de2781bc2c3dd3565490a6dca0e49b70 Mon Sep 17 00:00:00 2001 From: Piotr Date: Thu, 7 Nov 2024 15:18:57 +0100 Subject: [PATCH 12/14] fixing the script with providing proper images list to check out Signed-off-by: Piotr --- Tests/kaas/k8s-version-policy/k8s_version_policy.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index 530e86b2f..a0f48804f 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -458,6 +458,11 @@ async def scan_k8s_images(images_to_scan) -> None: ) +async def get_images_and_scan(kubeconfig, context=None) -> None: + images_to_scan = await get_k8s_pod_images(kubeconfig, context) + await scan_k8s_images(images_to_scan) + + async def get_k8s_cluster_info(kubeconfig, context=None) -> ClusterInfo: """Get the k8s version of the cluster under test.""" cluster_config = await kubernetes_asyncio.config.load_kube_config(kubeconfig, context) @@ -562,7 +567,9 @@ async def main(argv): with context {config.context if config.context else ''}. Fetching cluster information and verifying access.""") await get_k8s_cluster_info(config.kubeconfig, config.context) - await scan_k8s_images(config.kubeconfig) + await get_images_and_scan(config.kubeconfig, config.context) + # images_to_scan = await get_k8s_pod_images(config.kubeconfig, config.context) + # await scan_k8s_images(images_to_scan=images_to_scan) except CriticalException as e: logger.critical(e) From cea98405d7b52029b13e4480ca6f4de429d77823 Mon Sep 17 00:00:00 2001 From: Piotr Date: Thu, 7 Nov 2024 15:19:57 +0100 Subject: [PATCH 13/14] femoving unused lines Signed-off-by: Piotr --- Tests/kaas/k8s-version-policy/k8s_version_policy.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index a0f48804f..f89b67ecd 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -568,8 +568,6 @@ async def main(argv): Fetching cluster information and verifying access.""") await get_k8s_cluster_info(config.kubeconfig, config.context) await get_images_and_scan(config.kubeconfig, config.context) - # images_to_scan = await get_k8s_pod_images(config.kubeconfig, config.context) - # await scan_k8s_images(images_to_scan=images_to_scan) except CriticalException as e: logger.critical(e) From f1674fe326c157c3e84349457f926b49798688ed Mon Sep 17 00:00:00 2001 From: Piotr Date: Thu, 21 Nov 2024 08:43:41 +0100 Subject: [PATCH 14/14] Update Tests/kaas/k8s-version-policy/k8s_version_policy.py Co-authored-by: Kurt Garloff Signed-off-by: Piotr --- Tests/kaas/k8s-version-policy/k8s_version_policy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/kaas/k8s-version-policy/k8s_version_policy.py b/Tests/kaas/k8s-version-policy/k8s_version_policy.py index f89b67ecd..0de0e78f3 100755 --- a/Tests/kaas/k8s-version-policy/k8s_version_policy.py +++ b/Tests/kaas/k8s-version-policy/k8s_version_policy.py @@ -96,7 +96,7 @@ class HelpException(BaseException): class CriticalException(BaseException): - """Exception raised if the critical CVE are found""" + """Raise an exception if a critical CVE is found""" class Config: