diff --git a/.github/workflows/sbomify.yaml b/.github/workflows/sbomify.yaml index 14c8f32..026ab77 100644 --- a/.github/workflows/sbomify.yaml +++ b/.github/workflows/sbomify.yaml @@ -302,20 +302,20 @@ jobs: # Arbitrary OS packages (curl, busybox, etc.) do NOT get distro lifecycle # because the PURL doesn't reliably indicate distro version. - # Check OS component has CLE properties - OS_CLE=$(jq '.components[] | select(.type == "operating-system") | .properties[]? | select(.name | startswith("cle:"))' alpine-enriched.cdx.json) - + # Check OS component has CLE properties (using cdx:lifecycle taxonomy) + OS_CLE=$(jq '.components[] | select(.type == "operating-system") | .properties[]? | select(.name | startswith("cdx:lifecycle"))' alpine-enriched.cdx.json) + if [ -n "$OS_CLE" ]; then echo "✅ OS component has CLE lifecycle properties" echo "OS component CLE:" - jq '.components[] | select(.type == "operating-system") | {name, version, cle: [.properties[]? | select(.name | startswith("cle:"))]}' alpine-enriched.cdx.json + jq '.components[] | select(.type == "operating-system") | {name, version, cle: [.properties[]? | select(.name | startswith("cdx:lifecycle"))]}' alpine-enriched.cdx.json else echo "❌ Expected CLE properties on OS component" exit 1 fi # Verify specific CLE values for Alpine 3.20 - CLE_EOL=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cle:eol") | .value' alpine-enriched.cdx.json) + CLE_EOL=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cdx:lifecycle:milestone:endOfLife") | .value' alpine-enriched.cdx.json) if [ "$CLE_EOL" = "2026-04-01" ]; then echo "✅ Alpine 3.20 EOL date is correct: $CLE_EOL" else @@ -429,9 +429,9 @@ jobs: jq '.components[] | select(.type == "operating-system") | {name, version, publisher}' debian-enriched.cdx.json # Check for CLE properties - CLE_EOL=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cle:eol") | .value' debian-enriched.cdx.json) - CLE_EOS=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cle:eos") | .value' debian-enriched.cdx.json) - CLE_RELEASE=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cle:releaseDate") | .value' debian-enriched.cdx.json) + CLE_EOL=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cdx:lifecycle:milestone:endOfLife") | .value' debian-enriched.cdx.json) + CLE_EOS=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cdx:lifecycle:milestone:endOfSupport") | .value' debian-enriched.cdx.json) + CLE_RELEASE=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cdx:lifecycle:milestone:generalAvailability") | .value' debian-enriched.cdx.json) echo "CLE Release Date: $CLE_RELEASE" echo "CLE End of Support: $CLE_EOS" @@ -453,7 +453,7 @@ jobs: echo "=== Verifying Debian 12 CLE Values ===" # Debian 12 EOL should be 2028-06-30 (LTS end) - CLE_EOL=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cle:eol") | .value' debian-enriched.cdx.json) + CLE_EOL=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cdx:lifecycle:milestone:endOfLife") | .value' debian-enriched.cdx.json) if [ "$CLE_EOL" = "2028-06-30" ]; then echo "✅ Debian 12 EOL date is correct: $CLE_EOL" @@ -463,7 +463,7 @@ jobs: fi # Debian 12 EOS should be 2026-06-10 (regular support end) - CLE_EOS=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cle:eos") | .value' debian-enriched.cdx.json) + CLE_EOS=$(jq -r '.components[] | select(.type == "operating-system") | .properties[]? | select(.name == "cdx:lifecycle:milestone:endOfSupport") | .value' debian-enriched.cdx.json) if [ "$CLE_EOS" = "2026-06-10" ]; then echo "✅ Debian 12 EOS date is correct: $CLE_EOS" @@ -488,7 +488,7 @@ jobs: # Show all CLE-enriched components summary echo "" echo "=== All CLE-enriched components ===" - jq -r '.components[] | select(.properties[]?.name | startswith("cle:")) | "\(.type // "library"): \(.name) v\(.version)"' debian-enriched.cdx.json | sort -u + jq -r '.components[] | select(.properties[]?.name | startswith("cdx:lifecycle")) | "\(.type // "library"): \(.name) v\(.version)"' debian-enriched.cdx.json | sort -u - name: Upload enriched SBOM artifact uses: actions/upload-artifact@v4 diff --git a/README.md b/README.md index fdc1844..b895f45 100644 --- a/README.md +++ b/README.md @@ -651,7 +651,7 @@ Operating system components (CycloneDX `type: operating-system`) are enriched wi - **OS components**: Detected by CycloneDX `type: operating-system`, matched by name/version - **Runtimes/frameworks**: Matched by name pattern across all package managers - Version cycle extracted from full version (e.g., `3.12.7` → `3.12`, `12.12` → `12`) -- CLE properties added: `cle:releaseDate`, `cle:eos`, `cle:eol` +- Lifecycle properties added: `cdx:lifecycle:milestone:generalAvailability`, `cdx:lifecycle:milestone:endOfSupport`, `cdx:lifecycle:milestone:endOfLife` > **Note**: Arbitrary OS packages (curl, nginx, openssl, etc.) do not receive lifecycle data. Only the operating system itself and explicitly tracked runtimes/frameworks get CLE data. @@ -663,9 +663,9 @@ Operating system components (CycloneDX `type: operating-system`) are enriched wi "name": "debian", "version": "12.12", "properties": [ - {"name": "cle:releaseDate", "value": "2023-06-10"}, - {"name": "cle:eos", "value": "2026-06-10"}, - {"name": "cle:eol", "value": "2028-06-30"} + {"name": "cdx:lifecycle:milestone:generalAvailability", "value": "2023-06-10"}, + {"name": "cdx:lifecycle:milestone:endOfSupport", "value": "2026-06-10"}, + {"name": "cdx:lifecycle:milestone:endOfLife", "value": "2028-06-30"} ] } ``` diff --git a/sbomify_action/_enrichment/sources/lifecycle.py b/sbomify_action/_enrichment/sources/lifecycle.py index 2207eea..a5bbcde 100644 --- a/sbomify_action/_enrichment/sources/lifecycle.py +++ b/sbomify_action/_enrichment/sources/lifecycle.py @@ -60,7 +60,7 @@ class LifecycleSource: @property def name(self) -> str: - return "lifecycle" + return "sbomify-lifecycle-db" @property def priority(self) -> int: diff --git a/sbomify_action/augmentation.py b/sbomify_action/augmentation.py index 9b541c1..f34199e 100644 --- a/sbomify_action/augmentation.py +++ b/sbomify_action/augmentation.py @@ -38,7 +38,7 @@ from cyclonedx.model.bom import Bom, OrganizationalContact, OrganizationalEntity, Tool from cyclonedx.model.component import Component, ComponentType from cyclonedx.model.license import DisjunctiveLicense, LicenseExpression -from cyclonedx.model.lifecycle import LifecyclePhase, NamedLifecycle, PredefinedLifecycle +from cyclonedx.model.lifecycle import LifecyclePhase, PredefinedLifecycle from cyclonedx.model.service import Service from packageurl import PackageURL from spdx_tools.spdx.model import ( @@ -852,61 +852,29 @@ def augment_cyclonedx_sbom( # Add support period end date # This satisfies CRA support period requirements + # Add support period end date using official CycloneDX property taxonomy + # See: https://cyclonedx.github.io/cyclonedx-property-taxonomy/cdx/lifecycle.html if "support_period_end" in augmentation_data and augmentation_data["support_period_end"]: end_date = augmentation_data["support_period_end"] - - # Primary: Use metadata.lifecycles for CDX 1.5+ (native lifecycle support) - if _is_cdx_version_at_least(spec_version, 1, 5): - # Create custom lifecycle to indicate support end - support_lifecycle = NamedLifecycle( - name="support-end", - description=f"Security support ends: {end_date}", - ) - bom.metadata.lifecycles.add(support_lifecycle) - logger.info(f"Added support-end lifecycle to CycloneDX: {end_date}") - - # For all versions: Add as property with standardized name - # Using cdx: namespace which is conventional for CycloneDX extensions - support_prop = Property(name="cdx:support:enddate", value=end_date) + support_prop = Property(name="cdx:lifecycle:milestone:endOfSupport", value=end_date) bom.metadata.properties.add(support_prop) + logger.info(f"Added cdx:lifecycle:milestone:endOfSupport property: {end_date}") audit_trail.record_augmentation("support_period_end", end_date, source="config") - # Add release date - # Records when the component was released + # Add release date using official CycloneDX property taxonomy if "release_date" in augmentation_data and augmentation_data["release_date"]: release_date = augmentation_data["release_date"] - - # Primary: Use metadata.lifecycles for CDX 1.5+ (native lifecycle support) - if _is_cdx_version_at_least(spec_version, 1, 5): - release_lifecycle = NamedLifecycle( - name="release", - description=f"Released: {release_date}", - ) - bom.metadata.lifecycles.add(release_lifecycle) - logger.info(f"Added release lifecycle to CycloneDX: {release_date}") - - # For all versions: Add as property with standardized name - release_prop = Property(name="cdx:release:date", value=release_date) + release_prop = Property(name="cdx:lifecycle:milestone:generalAvailability", value=release_date) bom.metadata.properties.add(release_prop) + logger.info(f"Added cdx:lifecycle:milestone:generalAvailability property: {release_date}") audit_trail.record_augmentation("release_date", release_date, source="config") - # Add end of life date - # Records when all support ends (beyond security-only support) + # Add end of life date using official CycloneDX property taxonomy if "end_of_life" in augmentation_data and augmentation_data["end_of_life"]: eol_date = augmentation_data["end_of_life"] - - # Primary: Use metadata.lifecycles for CDX 1.5+ (native lifecycle support) - if _is_cdx_version_at_least(spec_version, 1, 5): - eol_lifecycle = NamedLifecycle( - name="end-of-life", - description=f"End of life: {eol_date}", - ) - bom.metadata.lifecycles.add(eol_lifecycle) - logger.info(f"Added end-of-life lifecycle to CycloneDX: {eol_date}") - - # For all versions: Add as property with standardized name - eol_prop = Property(name="cdx:eol:date", value=eol_date) + eol_prop = Property(name="cdx:lifecycle:milestone:endOfLife", value=eol_date) bom.metadata.properties.add(eol_prop) + logger.info(f"Added cdx:lifecycle:milestone:endOfLife property: {eol_date}") audit_trail.record_augmentation("end_of_life", eol_date, source="config") return bom diff --git a/sbomify_action/enrichment.py b/sbomify_action/enrichment.py index 4e7dd81..c2bd031 100644 --- a/sbomify_action/enrichment.py +++ b/sbomify_action/enrichment.py @@ -465,16 +465,16 @@ def _add_cle_property(name: str, value: str) -> bool: return True if metadata.cle_eos: - if _add_cle_property("cle:eos", metadata.cle_eos): - added_fields.append("cle:eos") + if _add_cle_property("cdx:lifecycle:milestone:endOfSupport", metadata.cle_eos): + added_fields.append("cdx:lifecycle:milestone:endOfSupport") if metadata.cle_eol: - if _add_cle_property("cle:eol", metadata.cle_eol): - added_fields.append("cle:eol") + if _add_cle_property("cdx:lifecycle:milestone:endOfLife", metadata.cle_eol): + added_fields.append("cdx:lifecycle:milestone:endOfLife") if metadata.cle_release_date: - if _add_cle_property("cle:releaseDate", metadata.cle_release_date): - added_fields.append("cle:releaseDate") + if _add_cle_property("cdx:lifecycle:milestone:generalAvailability", metadata.cle_release_date): + added_fields.append("cdx:lifecycle:milestone:generalAvailability") # Record to audit trail if any fields were added if added_fields: @@ -613,16 +613,16 @@ def _add_external_ref(category: ExternalPackageRefCategory, ref_type: str, locat if _add_external_ref(ExternalPackageRefCategory.OTHER, "vcs", metadata.repository_url): added_fields.append("externalRef (vcs)") - # CLE (Common Lifecycle Enumeration) data - ECMA-428 - # For SPDX, we add CLE info to the package comment - # See: https://sbomify.com/compliance/cle/ + # CycloneDX lifecycle milestone properties + # For SPDX, we add lifecycle info to the package comment + # See: https://cyclonedx.github.io/cyclonedx-property-taxonomy/cdx/lifecycle.html cle_parts = [] if metadata.cle_eos: - cle_parts.append(f"cle:eos={metadata.cle_eos}") + cle_parts.append(f"cdx:lifecycle:milestone:endOfSupport={metadata.cle_eos}") if metadata.cle_eol: - cle_parts.append(f"cle:eol={metadata.cle_eol}") + cle_parts.append(f"cdx:lifecycle:milestone:endOfLife={metadata.cle_eol}") if metadata.cle_release_date: - cle_parts.append(f"cle:releaseDate={metadata.cle_release_date}") + cle_parts.append(f"cdx:lifecycle:milestone:generalAvailability={metadata.cle_release_date}") if cle_parts: cle_comment = f"CLE lifecycle: {', '.join(cle_parts)}" @@ -671,16 +671,16 @@ def _add_cle_property(name: str, value: str) -> bool: return True if lifecycle.get("release_date"): - if _add_cle_property("cle:releaseDate", lifecycle["release_date"]): - added_fields.append(f"cle:releaseDate ({lifecycle['release_date']})") + if _add_cle_property("cdx:lifecycle:milestone:generalAvailability", lifecycle["release_date"]): + added_fields.append(f"cdx:lifecycle:milestone:generalAvailability ({lifecycle['release_date']})") if lifecycle.get("end_of_support"): - if _add_cle_property("cle:eos", lifecycle["end_of_support"]): - added_fields.append(f"cle:eos ({lifecycle['end_of_support']})") + if _add_cle_property("cdx:lifecycle:milestone:endOfSupport", lifecycle["end_of_support"]): + added_fields.append(f"cdx:lifecycle:milestone:endOfSupport ({lifecycle['end_of_support']})") if lifecycle.get("end_of_life"): - if _add_cle_property("cle:eol", lifecycle["end_of_life"]): - added_fields.append(f"cle:eol ({lifecycle['end_of_life']})") + if _add_cle_property("cdx:lifecycle:milestone:endOfLife", lifecycle["end_of_life"]): + added_fields.append(f"cdx:lifecycle:milestone:endOfLife ({lifecycle['end_of_life']})") return added_fields @@ -861,11 +861,11 @@ def _enrich_spdx_os_packages(document: Document) -> Dict[str, int]: # Build lifecycle comment lifecycle_parts = [] if lifecycle.get("release_date"): - lifecycle_parts.append(f"cle:releaseDate={lifecycle['release_date']}") + lifecycle_parts.append(f"cdx:lifecycle:milestone:generalAvailability={lifecycle['release_date']}") if lifecycle.get("end_of_support"): - lifecycle_parts.append(f"cle:eos={lifecycle['end_of_support']}") + lifecycle_parts.append(f"cdx:lifecycle:milestone:endOfSupport={lifecycle['end_of_support']}") if lifecycle.get("end_of_life"): - lifecycle_parts.append(f"cle:eol={lifecycle['end_of_life']}") + lifecycle_parts.append(f"cdx:lifecycle:milestone:endOfLife={lifecycle['end_of_life']}") if lifecycle_parts: lifecycle_comment = COMMENT_DELIMITER.join(lifecycle_parts) diff --git a/tests/cra_checker.py b/tests/cra_checker.py index c5bde34..26c5e04 100644 --- a/tests/cra_checker.py +++ b/tests/cra_checker.py @@ -16,9 +16,9 @@ CycloneDX (1.5+ for full support, 1.3-1.4 partial): - Security Contact: metadata.component.externalReferences[type=security-contact] (1.5+) OR metadata.supplier.contacts[] (1.3-1.4 fallback) -- Release Date: metadata.lifecycles[name=release] OR metadata.properties[name=cdx:release:date] -- Support Period End: metadata.lifecycles[name=support-end] OR metadata.properties[name=cdx:support:enddate] -- End of Life: metadata.lifecycles[name=end-of-life] OR metadata.properties[name=cdx:eol:date] +- Release Date: metadata.properties[name=cdx:lifecycle:milestone:generalAvailability] +- Support Period End: metadata.properties[name=cdx:lifecycle:milestone:endOfSupport] +- End of Life: metadata.properties[name=cdx:lifecycle:milestone:endOfLife] SPDX (2.2 and 2.3): - Security Contact: packages[].externalRefs[referenceType=security-contact] @@ -130,16 +130,12 @@ def check_cyclonedx( missing.append("Security Contact") # 2. Release Date (Recommended) - # Check named lifecycle first - release_lifecycle = cls._find_named_lifecycle(lifecycles, "release") - release_date = None - if release_lifecycle: - # Extract date from description or use the lifecycle - release_date = release_lifecycle.get("description", "") - - # Check property fallback + # Check property first (official CycloneDX taxonomy), then lifecycle for backward compat + release_date = cls._find_property(properties, "cdx:lifecycle:milestone:generalAvailability") if not release_date: - release_date = cls._find_property(properties, "cdx:release:date") + release_lifecycle = cls._find_named_lifecycle(lifecycles, "release") + if release_lifecycle: + release_date = release_lifecycle.get("description", "") if release_date: present.append("Release Date") @@ -148,15 +144,12 @@ def check_cyclonedx( missing.append("Release Date") # 3. Support Period End (Required for CRA) - # Check named lifecycle first - support_lifecycle = cls._find_named_lifecycle(lifecycles, "support-end") - support_end = None - if support_lifecycle: - support_end = support_lifecycle.get("description", "") - - # Check property fallback + # Check property first (official CycloneDX taxonomy), then lifecycle for backward compat + support_end = cls._find_property(properties, "cdx:lifecycle:milestone:endOfSupport") if not support_end: - support_end = cls._find_property(properties, "cdx:support:enddate") + support_lifecycle = cls._find_named_lifecycle(lifecycles, "support-end") + if support_lifecycle: + support_end = support_lifecycle.get("description", "") if support_end: present.append("Support Period End") @@ -165,15 +158,12 @@ def check_cyclonedx( missing.append("Support Period End") # 4. End of Life (Recommended) - # Check named lifecycle first - eol_lifecycle = cls._find_named_lifecycle(lifecycles, "end-of-life") - end_of_life = None - if eol_lifecycle: - end_of_life = eol_lifecycle.get("description", "") - - # Check property fallback + # Check property first (official CycloneDX taxonomy), then lifecycle for backward compat + end_of_life = cls._find_property(properties, "cdx:lifecycle:milestone:endOfLife") if not end_of_life: - end_of_life = cls._find_property(properties, "cdx:eol:date") + eol_lifecycle = cls._find_named_lifecycle(lifecycles, "end-of-life") + if eol_lifecycle: + end_of_life = eol_lifecycle.get("description", "") if end_of_life: present.append("End of Life") diff --git a/tests/test_cra_compliance.py b/tests/test_cra_compliance.py index 5fbf0d3..19d35d5 100644 --- a/tests/test_cra_compliance.py +++ b/tests/test_cra_compliance.py @@ -98,7 +98,7 @@ def test_partial_cra_compliance_not_sufficient(self): "metadata": { "component": {"name": "test-app"}, "properties": [ - {"name": "cdx:support:enddate", "value": "2026-12-31"}, + {"name": "cdx:lifecycle:milestone:endOfSupport", "value": "2026-12-31"}, ], }, } @@ -441,9 +441,9 @@ def test_cyclonedx_with_property_dates(self): "metadata": { "component": {"name": "test"}, "properties": [ - {"name": "cdx:release:date", "value": "2024-06-15"}, - {"name": "cdx:support:enddate", "value": "2026-12-31"}, - {"name": "cdx:eol:date", "value": "2028-12-31"}, + {"name": "cdx:lifecycle:milestone:generalAvailability", "value": "2024-06-15"}, + {"name": "cdx:lifecycle:milestone:endOfSupport", "value": "2026-12-31"}, + {"name": "cdx:lifecycle:milestone:endOfLife", "value": "2028-12-31"}, ], }, } diff --git a/tests/test_enrichment_module.py b/tests/test_enrichment_module.py index 079c20f..b16e306 100644 --- a/tests/test_enrichment_module.py +++ b/tests/test_enrichment_module.py @@ -808,11 +808,11 @@ def test_enrich_debian_os(self): assert component.publisher == "Debian Project" assert "publisher" in " ".join(added_fields) - # Check CLE properties + # Check lifecycle milestone properties props = {p.name: p.value for p in component.properties} - assert props.get("cle:releaseDate") == "2023-06-10" - assert props.get("cle:eos") == "2026-06-10" - assert props.get("cle:eol") == "2028-06-30" + assert props.get("cdx:lifecycle:milestone:generalAvailability") == "2023-06-10" + assert props.get("cdx:lifecycle:milestone:endOfSupport") == "2026-06-10" + assert props.get("cdx:lifecycle:milestone:endOfLife") == "2028-06-30" def test_enrich_debian_os_with_point_release(self): """Test enriching Debian with point release version (12.12 -> 12).""" @@ -822,7 +822,7 @@ def test_enrich_debian_os_with_point_release(self): # Should still get Debian 12 lifecycle data props = {p.name: p.value for p in component.properties} - assert props.get("cle:eol") == "2028-06-30" + assert props.get("cdx:lifecycle:milestone:endOfLife") == "2028-06-30" def test_enrich_ubuntu_os(self): """Test enriching an Ubuntu OS component.""" @@ -832,11 +832,11 @@ def test_enrich_ubuntu_os(self): assert component.publisher == "Canonical Ltd" - # Check CLE properties + # Check lifecycle milestone properties props = {p.name: p.value for p in component.properties} - assert props.get("cle:releaseDate") == "2022-04" - assert props.get("cle:eos") == "2027-06" - assert props.get("cle:eol") == "2032-04" + assert props.get("cdx:lifecycle:milestone:generalAvailability") == "2022-04" + assert props.get("cdx:lifecycle:milestone:endOfSupport") == "2027-06" + assert props.get("cdx:lifecycle:milestone:endOfLife") == "2032-04" def test_enrich_alpine_os(self): """Test enriching an Alpine OS component.""" @@ -846,10 +846,10 @@ def test_enrich_alpine_os(self): assert component.publisher == "Alpine Linux" - # Check CLE properties + # Check lifecycle milestone properties props = {p.name: p.value for p in component.properties} - assert props.get("cle:releaseDate") == "2023-12-07" - assert props.get("cle:eol") == "2025-11-01" + assert props.get("cdx:lifecycle:milestone:generalAvailability") == "2023-12-07" + assert props.get("cdx:lifecycle:milestone:endOfLife") == "2025-11-01" def test_enrich_unknown_os(self): """Test that unknown OS gets no CLE data.""" diff --git a/tests/test_lifecycle_enrichment.py b/tests/test_lifecycle_enrichment.py index aeca49a..e846987 100644 --- a/tests/test_lifecycle_enrichment.py +++ b/tests/test_lifecycle_enrichment.py @@ -562,7 +562,7 @@ class TestLifecycleSource: def test_source_properties(self): """Test source name and priority.""" source = LifecycleSource() - assert source.name == "lifecycle" + assert source.name == "sbomify-lifecycle-db" assert source.priority == 5 # High priority for local data def test_supports_python_pypi(self): @@ -678,7 +678,7 @@ def test_fetch_python_312(self, mock_session): assert metadata.cle_release_date == "2023-10-02" assert metadata.cle_eos == "2025-04-02" assert metadata.cle_eol == "2028-10-31" - assert metadata.source == "lifecycle" + assert metadata.source == "sbomify-lifecycle-db" def test_fetch_django_42(self, mock_session): """Test fetching Django 4.2 lifecycle.""" @@ -754,8 +754,8 @@ def test_fetch_field_sources_tracked(self, mock_session): metadata = source.fetch(purl, mock_session) assert metadata is not None - assert metadata.field_sources.get("cle_eos") == "lifecycle" - assert metadata.field_sources.get("cle_eol") == "lifecycle" + assert metadata.field_sources.get("cle_eos") == "sbomify-lifecycle-db" + assert metadata.field_sources.get("cle_eol") == "sbomify-lifecycle-db" def test_fetch_python_from_deb(self, mock_session): """Test fetching Python lifecycle from deb package.""" @@ -890,7 +890,7 @@ def test_lifecycle_source_in_default_registry(self): sources = registry.list_sources() source_names = [s["name"] for s in sources] - assert "lifecycle" in source_names + assert "sbomify-lifecycle-db" in source_names def test_lifecycle_source_priority(self): """Test LifecycleSource has correct priority.""" @@ -899,7 +899,7 @@ def test_lifecycle_source_priority(self): registry = create_default_registry() sources = registry.list_sources() - lifecycle_source = next((s for s in sources if s["name"] == "lifecycle"), None) + lifecycle_source = next((s for s in sources if s["name"] == "sbomify-lifecycle-db"), None) assert lifecycle_source is not None assert lifecycle_source["priority"] == 5 diff --git a/tests/test_ntia_compliance.py b/tests/test_ntia_compliance.py index 5b20470..0cf6d13 100644 --- a/tests/test_ntia_compliance.py +++ b/tests/test_ntia_compliance.py @@ -900,20 +900,13 @@ def test_augmentation_adds_support_period_end_cyclonedx(self, sample_backend_met # Augment with support period augmented_bom = augment_cyclonedx_sbom(bom, metadata_with_support, spec_version="1.6") - # Check named lifecycle was added - lifecycles = list(augmented_bom.metadata.lifecycles) - support_lifecycles = [lc for lc in lifecycles if hasattr(lc, "name") and lc.name == "support-end"] - assert len(support_lifecycles) == 1, "Should have one support-end lifecycle" - assert "2028-12-31" in support_lifecycles[0].description - - # Check property was also added + # Check property was added (using official CycloneDX property taxonomy) props = list(augmented_bom.metadata.properties) - support_props = [p for p in props if p.name == "cdx:support:enddate"] - assert len(support_props) == 1, "Should have one cdx:support:enddate property" + support_props = [p for p in props if p.name == "cdx:lifecycle:milestone:endOfSupport"] + assert len(support_props) == 1, "Should have one cdx:lifecycle:milestone:endOfSupport property" assert support_props[0].value == "2028-12-31" print("\nSupport Period End Augmentation Results (CycloneDX 1.6):") - print(f" Lifecycle: {support_lifecycles[0].name} - {support_lifecycles[0].description}") print(f" Property: {support_props[0].name}={support_props[0].value}") def test_augmentation_adds_support_period_end_cyclonedx_14(self, sample_backend_metadata): @@ -941,15 +934,10 @@ def test_augmentation_adds_support_period_end_cyclonedx_14(self, sample_backend_ # Augment with support period augmented_bom = augment_cyclonedx_sbom(bom, metadata_with_support, spec_version="1.4") - # Check named lifecycle was NOT added (1.5+ only) - lifecycles = list(augmented_bom.metadata.lifecycles) - support_lifecycles = [lc for lc in lifecycles if hasattr(lc, "name") and lc.name == "support-end"] - assert len(support_lifecycles) == 0, "Named lifecycle should not be added for CycloneDX 1.4" - - # Check property WAS added (works in all versions) + # Check property was added (using official CycloneDX property taxonomy) props = list(augmented_bom.metadata.properties) - support_props = [p for p in props if p.name == "cdx:support:enddate"] - assert len(support_props) == 1, "Should have one cdx:support:enddate property" + support_props = [p for p in props if p.name == "cdx:lifecycle:milestone:endOfSupport"] + assert len(support_props) == 1, "Should have one cdx:lifecycle:milestone:endOfSupport property" assert support_props[0].value == "2028-12-31" print("\nSupport Period End Results (CycloneDX 1.4):") @@ -1029,20 +1017,13 @@ def test_augmentation_adds_release_date_cyclonedx(self, sample_backend_metadata) # Augment with release date augmented_bom = augment_cyclonedx_sbom(bom, metadata_with_release, spec_version="1.6") - # Check named lifecycle was added - lifecycles = list(augmented_bom.metadata.lifecycles) - release_lifecycles = [lc for lc in lifecycles if hasattr(lc, "name") and lc.name == "release"] - assert len(release_lifecycles) == 1, "Should have one release lifecycle" - assert "2024-06-15" in release_lifecycles[0].description - - # Check property was also added + # Check property was added (using official CycloneDX property taxonomy) props = list(augmented_bom.metadata.properties) - release_props = [p for p in props if p.name == "cdx:release:date"] - assert len(release_props) == 1, "Should have one cdx:release:date property" + release_props = [p for p in props if p.name == "cdx:lifecycle:milestone:generalAvailability"] + assert len(release_props) == 1, "Should have one cdx:lifecycle:milestone:generalAvailability property" assert release_props[0].value == "2024-06-15" print("\nRelease Date Augmentation Results (CycloneDX 1.6):") - print(f" Lifecycle: {release_lifecycles[0].name} - {release_lifecycles[0].description}") print(f" Property: {release_props[0].name}={release_props[0].value}") def test_augmentation_adds_end_of_life_cyclonedx(self, sample_backend_metadata): @@ -1073,20 +1054,13 @@ def test_augmentation_adds_end_of_life_cyclonedx(self, sample_backend_metadata): # Augment with end of life augmented_bom = augment_cyclonedx_sbom(bom, metadata_with_eol, spec_version="1.6") - # Check named lifecycle was added - lifecycles = list(augmented_bom.metadata.lifecycles) - eol_lifecycles = [lc for lc in lifecycles if hasattr(lc, "name") and lc.name == "end-of-life"] - assert len(eol_lifecycles) == 1, "Should have one end-of-life lifecycle" - assert "2028-12-31" in eol_lifecycles[0].description - - # Check property was also added + # Check property was added (using official CycloneDX property taxonomy) props = list(augmented_bom.metadata.properties) - eol_props = [p for p in props if p.name == "cdx:eol:date"] - assert len(eol_props) == 1, "Should have one cdx:eol:date property" + eol_props = [p for p in props if p.name == "cdx:lifecycle:milestone:endOfLife"] + assert len(eol_props) == 1, "Should have one cdx:lifecycle:milestone:endOfLife property" assert eol_props[0].value == "2028-12-31" print("\nEnd of Life Augmentation Results (CycloneDX 1.6):") - print(f" Lifecycle: {eol_lifecycles[0].name} - {eol_lifecycles[0].description}") print(f" Property: {eol_props[0].name}={eol_props[0].value}") def test_augmentation_adds_release_date_cyclonedx_14(self, sample_backend_metadata): @@ -1114,15 +1088,10 @@ def test_augmentation_adds_release_date_cyclonedx_14(self, sample_backend_metada # Augment with release date augmented_bom = augment_cyclonedx_sbom(bom, metadata_with_release, spec_version="1.4") - # Check named lifecycle was NOT added (1.5+ only) - lifecycles = list(augmented_bom.metadata.lifecycles) - release_lifecycles = [lc for lc in lifecycles if hasattr(lc, "name") and lc.name == "release"] - assert len(release_lifecycles) == 0, "Named lifecycle should not be added for CycloneDX 1.4" - - # Check property WAS added (works in all versions) + # Check property was added (using official CycloneDX property taxonomy) props = list(augmented_bom.metadata.properties) - release_props = [p for p in props if p.name == "cdx:release:date"] - assert len(release_props) == 1, "Should have one cdx:release:date property" + release_props = [p for p in props if p.name == "cdx:lifecycle:milestone:generalAvailability"] + assert len(release_props) == 1, "Should have one cdx:lifecycle:milestone:generalAvailability property" assert release_props[0].value == "2024-06-15" print("\nRelease Date Results (CycloneDX 1.4):") @@ -1247,22 +1216,20 @@ def test_augmentation_adds_all_lifecycle_dates_cyclonedx(self, sample_backend_me # Augment with all lifecycle dates augmented_bom = augment_cyclonedx_sbom(bom, metadata_with_all, spec_version="1.6") - # Check all named lifecycles were added - lifecycles = list(augmented_bom.metadata.lifecycles) - lifecycle_names = [lc.name for lc in lifecycles if hasattr(lc, "name")] - assert "release" in lifecycle_names, "Should have release lifecycle" - assert "support-end" in lifecycle_names, "Should have support-end lifecycle" - assert "end-of-life" in lifecycle_names, "Should have end-of-life lifecycle" - - # Check all properties were added + # Check all properties were added (using official CycloneDX property taxonomy) props = list(augmented_bom.metadata.properties) prop_names = [p.name for p in props] - assert "cdx:release:date" in prop_names, "Should have cdx:release:date property" - assert "cdx:support:enddate" in prop_names, "Should have cdx:support:enddate property" - assert "cdx:eol:date" in prop_names, "Should have cdx:eol:date property" + assert "cdx:lifecycle:milestone:generalAvailability" in prop_names, ( + "Should have cdx:lifecycle:milestone:generalAvailability property" + ) + assert "cdx:lifecycle:milestone:endOfSupport" in prop_names, ( + "Should have cdx:lifecycle:milestone:endOfSupport property" + ) + assert "cdx:lifecycle:milestone:endOfLife" in prop_names, ( + "Should have cdx:lifecycle:milestone:endOfLife property" + ) print("\nAll Lifecycle Dates Augmentation Results (CycloneDX 1.6):") - print(f" Lifecycles: {lifecycle_names}") print(f" Properties: {prop_names}")