diff --git a/Cargo.lock b/Cargo.lock index b533110de..9ba538d9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,7 +21,7 @@ dependencies = [ "actix-macros", "actix-rt", "actix_derive", - "bitflags 2.6.0", + "bitflags 2.8.0", "bytes", "crossbeam-channel", "futures-core", @@ -43,7 +43,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "bytes", "futures-core", "futures-sink", @@ -82,7 +82,7 @@ dependencies = [ "actix-utils", "ahash 0.8.11", "base64 0.22.1", - "bitflags 2.6.0", + "bitflags 2.8.0", "brotli", "bytes", "bytestring", @@ -116,7 +116,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -271,7 +271,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "actix-web-opentelemetry" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0925ac14da3466c1430afcc7dd8625d438f7c0f6ffc3f6dc0833ab07d9671a" +checksum = "4f8308557c3ee9606f95df0269c143eb4ea838b58aeeb7272846b0315be1b6ac" dependencies = [ "actix-http", "actix-web", @@ -350,7 +350,7 @@ checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -501,19 +501,20 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arbitrary" @@ -598,9 +599,9 @@ dependencies = [ [[package]] name = "async-graphql" -version = "7.0.13" +version = "7.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fd6bd734afb8b6e4d0f84a3e77305ce0a7ccc60d70f6001cb5e1c3f38d8ff1" +checksum = "e0a9916334e00a14428e03b9b1c73a0baf1c834ebe0ff1be146e1247390b31f1" dependencies = [ "async-graphql-derive", "async-graphql-parser", @@ -615,7 +616,7 @@ dependencies = [ "futures-util", "handlebars", "http 1.2.0", - "indexmap 2.7.0", + "indexmap 2.7.1", "mime", "multer", "num-traits", @@ -633,9 +634,9 @@ dependencies = [ [[package]] name = "async-graphql-actix-web" -version = "7.0.13" +version = "7.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a26490dc60996a7d9c579b25e6879d3a5acba886a96b0931005e71f14a7ef6a" +checksum = "75af23eca6e9be23454eed4a453dab4f173a9800694a74e782872fedaafbd7e0" dependencies = [ "actix", "actix-http", @@ -652,9 +653,9 @@ dependencies = [ [[package]] name = "async-graphql-derive" -version = "7.0.13" +version = "7.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac38b4dd452d529d6c0248b51df23603f0a875770352e26ae8c346ce6c149b3e" +checksum = "88d6f3ad293f7b9974aef6297673ac3e9097824f84264a9548cbb87006c94044" dependencies = [ "Inflector", "async-graphql-parser", @@ -663,15 +664,15 @@ dependencies = [ "proc-macro2", "quote", "strum 0.26.3", - "syn 2.0.90", + "syn 2.0.96", "thiserror 1.0.69", ] [[package]] name = "async-graphql-parser" -version = "7.0.13" +version = "7.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d271ddda2f55b13970928abbcbc3423cfc18187c60e8769b48f21a93b7adaa" +checksum = "7ca5697e57fcad289d26948e2ab2f11b9cfe7d645503a1f37fa86640c061c772" dependencies = [ "async-graphql-value", "pest", @@ -681,12 +682,12 @@ dependencies = [ [[package]] name = "async-graphql-value" -version = "7.0.13" +version = "7.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aefe909173a037eaf3281b046dc22580b59a38b765d7b8d5116f2ffef098048d" +checksum = "6266ea7ab3ce41585e16caa0e1e8d97de37827227950820fdab6b69d9c09a63a" dependencies = [ "bytes", - "indexmap 2.7.0", + "indexmap 2.7.1", "serde", "serde_json", ] @@ -705,7 +706,7 @@ dependencies = [ "eventsource-stream", "futures", "rand", - "reqwest 0.12.9", + "reqwest 0.12.12", "reqwest-eventsource", "secrecy", "serde", @@ -725,7 +726,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -747,18 +748,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -792,14 +793,14 @@ dependencies = [ [[package]] name = "auto_enums" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459b77b7e855f875fd15f101064825cd79eb83185a961d66e6298560126facfb" +checksum = "9c170965892137a3a9aeb000b4524aa3cc022a310e709d848b6e1cdce4ab4781" dependencies = [ "derive_utils", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -996,9 +997,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" dependencies = [ "serde", ] @@ -1043,9 +1044,9 @@ checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" [[package]] name = "borsh" -version = "1.5.3" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" +checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc" dependencies = [ "borsh-derive", "cfg_aliases", @@ -1053,15 +1054,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.3" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" +checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1077,9 +1078,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.1" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1087,9 +1088,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "regex-automata 0.4.9", @@ -1166,7 +1167,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.90", + "syn 2.0.96", "zstd", ] @@ -1292,9 +1293,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "jobserver", "libc", @@ -1346,7 +1347,7 @@ checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" dependencies = [ "chrono", "chrono-tz-build", - "phf 0.11.2", + "phf 0.11.3", ] [[package]] @@ -1356,8 +1357,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" dependencies = [ "parse-zoneinfo", - "phf 0.11.2", - "phf_codegen 0.11.2", + "phf 0.11.3", + "phf_codegen 0.11.3", ] [[package]] @@ -1399,9 +1400,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -1409,9 +1410,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -1421,14 +1422,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1732,7 +1733,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "crossterm_winapi", "mio", "parking_lot 0.12.3", @@ -1754,9 +1755,9 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-bigint" @@ -1819,13 +1820,13 @@ dependencies = [ "lazy_static", "log", "percent-encoding", - "reqwest 0.12.9", + "reqwest 0.12.12", "sectxtlib", "sequoia-openpgp", "serde", "serde_json", "sha2", - "thiserror 2.0.8", + "thiserror 2.0.11", "time", "tokio", "url", @@ -1843,7 +1844,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.11.2", + "phf 0.11.3", "smallvec", ] @@ -1854,7 +1855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1902,7 +1903,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1936,7 +1937,7 @@ dependencies = [ "base64 0.21.7", "cyclonedx-bom-macros", "fluent-uri 0.1.4", - "indexmap 2.7.0", + "indexmap 2.7.1", "once_cell", "ordered-float 4.6.0", "purl", @@ -1959,7 +1960,7 @@ checksum = "c50341f21df64b412b4f917e34b7aa786c092d64f3f905f478cb76950c7e980c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2007,7 +2008,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2029,14 +2030,14 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" [[package]] name = "deflate64" @@ -2073,7 +2074,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2119,7 +2120,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2129,7 +2130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core 0.20.2", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2142,7 +2143,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2162,19 +2163,19 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "unicode-xid", ] [[package]] name = "derive_utils" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65f152f4b8559c4da5d574bafc7af85454d706b4c5fe8b530d508cacbb6807ea" +checksum = "ccfae181bab5ab6c5478b2ccb69e4c68a02f8c3ec72f6616bfec9dbc599d2ee0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2230,7 +2231,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2376,23 +2377,23 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "env_filter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", ] [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", @@ -2429,9 +2430,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -2529,6 +2530,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.0.35" @@ -2584,6 +2591,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "foreign-types" version = "0.3.2" @@ -2691,7 +2704,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2780,9 +2793,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb8bc4c28d15ade99c7e90b219f30da4be5c88e586277e8cbe886beeb868ab2" +checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" dependencies = [ "typenum", ] @@ -2821,7 +2834,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", "libgit2-sys", "log", @@ -2832,9 +2845,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" @@ -2855,7 +2868,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "ignore", "walkdir", ] @@ -2883,7 +2896,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -2902,7 +2915,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.2.0", - "indexmap 2.7.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -2947,24 +2960,25 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash 0.8.11", - "allocator-api2", -] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "hashlink" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -3118,7 +3132,7 @@ dependencies = [ "markup5ever 0.12.1", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -3279,9 +3293,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.4" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6884a48c6826ec44f524c7456b163cebe9e55a18d7b5e307cb4f100371cc767" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http 1.2.0", @@ -3495,7 +3509,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -3553,9 +3567,9 @@ dependencies = [ [[package]] name = "impl-more" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" @@ -3570,9 +3584,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -3611,7 +3625,7 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -3649,9 +3663,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" @@ -3665,13 +3679,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3733,9 +3747,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -3762,15 +3776,15 @@ dependencies = [ [[package]] name = "jsonpath-rust" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69a61b87f6a55cc6c28fed5739dd36b9642321ce63e4a5e4a4715d69106f4a10" +checksum = "0c00ae348f9f8fd2d09f82a98ca381c60df9e0820d8d79fce43e649b4dc3128b" dependencies = [ "pest", "pest_derive", "regex", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.11", ] [[package]] @@ -3784,7 +3798,7 @@ dependencies = [ "ena", "itertools 0.11.0", "lalrpop-util", - "petgraph", + "petgraph 0.6.5", "regex", "regex-syntax 0.8.5", "string_cache", @@ -3822,7 +3836,7 @@ dependencies = [ "ollama-rs", "readability", "regex", - "reqwest 0.12.9", + "reqwest 0.12.12", "reqwest-eventsource", "scraper", "secrecy", @@ -3846,9 +3860,9 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "lazy-regex" -version = "3.3.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" +checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -3857,14 +3871,14 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "3.3.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163" +checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -4030,7 +4044,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", "redox_syscall 0.5.8", ] @@ -4041,7 +4055,6 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ - "cc", "pkg-config", "vcpkg", ] @@ -4072,9 +4085,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.20" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" dependencies = [ "cc", "libc", @@ -4090,9 +4103,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" @@ -4135,9 +4148,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "lru-cache" @@ -4185,8 +4198,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" dependencies = [ "log", - "phf 0.11.2", - "phf_codegen 0.11.2", + "phf 0.11.3", + "phf_codegen 0.11.3", "string_cache", "string_cache_codegen", "tendril", @@ -4227,9 +4240,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "matchit" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0aa4b8ca861b08d68afc8702af3250776898c1508b278e1da9d01e01d4b45c" +checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9" [[package]] name = "maybe-async" @@ -4239,7 +4252,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -4312,9 +4325,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -4514,16 +4527,16 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "oauth2" -version = "5.0.0-rc.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d385da3c602d29036d2f70beed71c36604df7570be17fed4c5b839616785bf" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ "base64 0.22.1", "chrono", "getrandom", "http 1.2.0", "rand", - "reqwest 0.12.9", + "reqwest 0.12.12", "serde", "serde_json", "serde_path_to_error", @@ -4534,23 +4547,23 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "ollama-rs" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46483ac9e1f9e93da045b5875837ca3c9cf014fd6ab89b4d9736580ddefc4759" +checksum = "763afb01db2dced00e656cc2cdcd875659fc3fac4c449e6337a4f04f9e3d9efc" dependencies = [ "async-stream", "async-trait", "log", - "reqwest 0.12.9", + "reqwest 0.12.12", "serde", "serde_json", "tokio", @@ -4581,7 +4594,7 @@ dependencies = [ "chrono", "lazy_static", "mime", - "reqwest 0.12.9", + "reqwest 0.12.12", "serde", "serde_json", "thiserror 1.0.69", @@ -4591,9 +4604,9 @@ dependencies = [ [[package]] name = "openidconnect" -version = "4.0.0-rc.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93a50789d0b649986bfb104cdef97736ca072d579ec88496d5c6f9abed0ea85" +checksum = "6dd50d4a5e7730e754f94d977efe61f611aadd3131f6a2b464f6e3a4167e8ef7" dependencies = [ "base64 0.21.7", "chrono", @@ -4626,7 +4639,7 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "foreign-types", "libc", @@ -4643,14 +4656,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" @@ -4795,9 +4808,9 @@ dependencies = [ [[package]] name = "ouroboros" -version = "0.18.4" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" dependencies = [ "aliasable", "ouroboros_macro", @@ -4806,16 +4819,15 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.18.4" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" dependencies = [ "heck 0.4.1", - "itertools 0.12.1", "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5034,7 +5046,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.8", + "thiserror 2.0.11", "ucd-trie", ] @@ -5058,7 +5070,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5078,8 +5090,18 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset", - "indexmap 2.7.0", + "fixedbitset 0.4.2", + "indexmap 2.7.1", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset 0.5.7", + "indexmap 2.7.1", "serde", "serde_derive", ] @@ -5095,12 +5117,12 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared 0.11.2", + "phf_shared 0.11.3", ] [[package]] @@ -5115,12 +5137,12 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", + "phf_generator 0.11.3", + "phf_shared 0.11.3", ] [[package]] @@ -5135,25 +5157,25 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared 0.11.2", + "phf_shared 0.11.3", "rand", ] [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", + "phf_generator 0.11.3", + "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5162,43 +5184,43 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher", + "siphasher 1.0.1", ] [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -5269,9 +5291,9 @@ checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "postgresql_archive" -version = "0.17.3" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d1a945066f61750f81f49e66e280e9b991caaac421ef99c7f7ce9fffebdb58" +checksum = "1766f3f19cb783067ac13c083a04d791d039355009470eeeebc01fe7e4ae6288" dependencies = [ "anyhow", "async-trait", @@ -5283,7 +5305,7 @@ dependencies = [ "liblzma", "num-format", "regex", - "reqwest 0.12.9", + "reqwest 0.12.12", "reqwest-middleware", "reqwest-retry", "reqwest-tracing", @@ -5294,7 +5316,7 @@ dependencies = [ "tar", "target-triple", "tempfile", - "thiserror 2.0.8", + "thiserror 2.0.11", "tracing", "tracing-indicatif", "url", @@ -5303,21 +5325,21 @@ dependencies = [ [[package]] name = "postgresql_commands" -version = "0.17.3" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b908fb41f27a08adec7bd971fb7c52212e5b0743ef70100d97c6c09d48432a" +checksum = "f46820b2cfb4bb2f40f77fa210ec37aed880f4b1fb652666d01ec11bd910744a" dependencies = [ "anyhow", - "thiserror 2.0.8", + "thiserror 2.0.11", "tokio", "tracing", ] [[package]] name = "postgresql_embedded" -version = "0.17.3" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70bd62b3b53b911b27c84589a6b5fed38a524775935ead23a426c7a7e80d748" +checksum = "3d82620b055062f6dd7e5dcaa822eebff2eb00ac9a0be307106eac9294d19c56" dependencies = [ "anyhow", "home", @@ -5328,7 +5350,7 @@ dependencies = [ "sqlx", "target-triple", "tempfile", - "thiserror 2.0.8", + "thiserror 2.0.11", "tokio", "tracing", "url", @@ -5367,12 +5389,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5436,14 +5458,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -5456,7 +5478,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "version_check", "yansi", ] @@ -5496,7 +5518,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5531,7 +5553,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "memchr", "unicase", ] @@ -5565,9 +5587,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.1" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" dependencies = [ "memchr", ] @@ -5585,7 +5607,7 @@ dependencies = [ "rustc-hash 2.1.0", "rustls", "socket2", - "thiserror 2.0.8", + "thiserror 2.0.11", "tokio", "tracing", ] @@ -5604,7 +5626,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.8", + "thiserror 2.0.11", "tinyvec", "tracing", "web-time", @@ -5626,9 +5648,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -5718,7 +5740,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -5769,7 +5791,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5879,9 +5901,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "base64 0.22.1", "bytes", @@ -5919,6 +5941,7 @@ dependencies = [ "tokio-native-tls", "tokio-rustls", "tokio-util", + "tower 0.5.2", "tower-service", "url", "wasm-bindgen", @@ -5941,7 +5964,7 @@ dependencies = [ "mime", "nom", "pin-project-lite", - "reqwest 0.12.9", + "reqwest 0.12.12", "thiserror 1.0.69", ] @@ -5954,7 +5977,7 @@ dependencies = [ "anyhow", "async-trait", "http 1.2.0", - "reqwest 0.12.9", + "reqwest 0.12.12", "serde", "thiserror 1.0.69", "tower-service", @@ -5973,7 +5996,7 @@ dependencies = [ "http 1.2.0", "hyper 1.5.2", "parking_lot 0.11.2", - "reqwest 0.12.9", + "reqwest 0.12.12", "reqwest-middleware", "retry-policies", "thiserror 1.0.69", @@ -5992,8 +6015,8 @@ dependencies = [ "async-trait", "getrandom", "http 1.2.0", - "matchit 0.8.5", - "reqwest 0.12.9", + "matchit 0.8.6", + "reqwest 0.12.12", "reqwest-middleware", "tracing", ] @@ -6145,7 +6168,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.90", + "syn 2.0.96", "unicode-ident", ] @@ -6180,7 +6203,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.90", + "syn 2.0.96", "walkdir", ] @@ -6287,11 +6310,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.42" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", @@ -6300,9 +6323,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" dependencies = [ "once_cell", "ring", @@ -6321,7 +6344,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.1.0", + "security-framework 3.2.0", ] [[package]] @@ -6364,9 +6387,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rxml" @@ -6420,13 +6443,13 @@ dependencies = [ "humantime", "log", "parking_lot 0.12.3", - "reqwest 0.12.9", + "reqwest 0.12.12", "sequoia-openpgp", "serde", "serde_json", "sha2", "spdx-rs", - "thiserror 2.0.8", + "thiserror 2.0.11", "time", "tokio", "url", @@ -6491,7 +6514,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -6526,14 +6549,14 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "sea-orm" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b24d72a69e89762982c29af249542b06c59fa131f87cc9d5b94be1f692b427a" +checksum = "1a93194430b419da0801f404baf3b986399d6a2a4f43bc79bc96dea83f92ca43" dependencies = [ "async-stream", "async-trait", @@ -6559,9 +6582,9 @@ dependencies = [ [[package]] name = "sea-orm-cli" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653da4aba23cb596bf03d6f5faad274c74852c8ef014171a4fb9518032377105" +checksum = "0e6e0e741bfdf434e6f6aadab156ba4d439e78c9449048698d98fa377871224a" dependencies = [ "chrono", "clap", @@ -6576,23 +6599,23 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0497f4fd82ecb2a222bea5319b9048f8ab58d4e734d095b062987acbcdeecdda" +checksum = "d19e8f22fb474a8a622eb516c46885a080535d8d559386188f525977eaad32b3" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "sea-bae", - "syn 2.0.90", + "syn 2.0.96", "unicode-ident", ] [[package]] name = "sea-orm-migration" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b271c0dd7729623d8debb5f017806f066902e3c287f08dc5ff312c3277dc6f" +checksum = "c0bb76ba314552ce15e3a24778cf9c116fc1225fa406e48b0a36e5a3cdbc1e21" dependencies = [ "async-trait", "clap", @@ -6648,15 +6671,15 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "thiserror 1.0.69", ] [[package]] name = "sea-schema" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab1592d17860a9a8584d9b549aebcd06f7bdc3ff615f71752486ba0b05b1e6e" +checksum = "0ef5dd7848c993f3789d09a2616484c72c9330cae2b048df59d8c9b8c0343e95" dependencies = [ "futures", "sea-query", @@ -6672,7 +6695,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -6715,7 +6738,7 @@ dependencies = [ "iri-string", "nom", "oxilangtag", - "thiserror 2.0.8", + "thiserror 2.0.11", "valuable", ] @@ -6725,7 +6748,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -6734,11 +6757,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d3f8c9bfcc3cbb6b0179eb57042d75b1582bdc65c3cb95f3fa999509c03cbc" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation 0.10.0", "core-foundation-sys", "libc", @@ -6747,9 +6770,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -6761,7 +6784,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cssparser", "derive_more 0.99.18", "fxhash", @@ -6776,9 +6799,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" dependencies = [ "serde", ] @@ -6813,9 +6836,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -6834,7 +6857,7 @@ dependencies = [ "schemafy_lib", "serde", "serde_json", - "syn 2.0.90", + "syn 2.0.96", "thiserror 1.0.69", ] @@ -6850,13 +6873,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -6867,16 +6890,16 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "itoa", "memchr", "ryu", @@ -6916,15 +6939,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.0", + "indexmap 2.7.1", "serde", "serde_derive", "serde_json", @@ -6934,14 +6957,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -6950,7 +6973,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "itoa", "ryu", "serde", @@ -6963,7 +6986,7 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "itoa", "libyml", "memchr", @@ -7005,7 +7028,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f606421e4a6012877e893c399822a4ed4b089164c5969424e1b9d1e66e6964b" dependencies = [ "digest", - "generic-array 1.1.1", + "generic-array 1.2.0", ] [[package]] @@ -7088,9 +7111,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "siphasher" @@ -7098,6 +7121,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -7149,9 +7178,9 @@ dependencies = [ [[package]] name = "spdx" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae30cc7bfe3656d60ee99bf6836f472b0c53dddcbf335e253329abb16e535a2" +checksum = "58b69356da67e2fc1f542c71ea7e654a361a79c938e4424392ecf4fa065d2193" dependencies = [ "smallvec", ] @@ -7204,21 +7233,11 @@ dependencies = [ "der", ] -[[package]] -name = "sqlformat" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" -dependencies = [ - "nom", - "unicode_categories", -] - [[package]] name = "sqlx" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" dependencies = [ "sqlx-core", "sqlx-macros", @@ -7229,33 +7248,28 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" dependencies = [ - "atoi", "bigdecimal", - "byteorder", "bytes", "chrono", "crc", "crossbeam-queue", "either", "event-listener", - "futures-channel", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "hashlink", - "hex", - "indexmap 2.7.0", + "indexmap 2.7.1", "log", "memchr", "native-tls", "once_cell", - "paste", "percent-encoding", "rust_decimal", "rustls", @@ -7264,8 +7278,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "sqlformat", - "thiserror 1.0.69", + "thiserror 2.0.11", "time", "tokio", "tokio-stream", @@ -7277,22 +7290,22 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "sqlx-macros-core" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" dependencies = [ "dotenvy", "either", @@ -7308,7 +7321,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.90", + "syn 2.0.96", "tempfile", "tokio", "url", @@ -7316,14 +7329,14 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags 2.6.0", + "bitflags 2.8.0", "byteorder", "bytes", "chrono", @@ -7354,7 +7367,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.69", + "thiserror 2.0.11", "time", "tracing", "uuid", @@ -7363,14 +7376,14 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags 2.6.0", + "bitflags 2.8.0", "byteorder", "chrono", "crc", @@ -7378,7 +7391,6 @@ dependencies = [ "etcetera", "futures-channel", "futures-core", - "futures-io", "futures-util", "hex", "hkdf", @@ -7398,7 +7410,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.69", + "thiserror 2.0.11", "time", "tracing", "uuid", @@ -7407,9 +7419,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" dependencies = [ "atoi", "chrono", @@ -7511,11 +7523,11 @@ dependencies = [ [[package]] name = "strip-ansi-escapes" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" dependencies = [ - "vte", + "vte 0.14.1", ] [[package]] @@ -7568,7 +7580,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -7590,9 +7602,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -7622,7 +7634,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -7642,7 +7654,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation 0.9.4", "system-configuration-sys 0.6.0", ] @@ -7701,12 +7713,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -7790,14 +7803,14 @@ checksum = "78ea17a2dc368aeca6f554343ced1b1e31f76d63683fa8016e5844bd7a5144a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "test-log" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" +checksum = "e7f46083d221181166e5b6f6b1e5f1d499f3a76888826e6cb1d057554157cd0f" dependencies = [ "env_logger", "test-log-macros", @@ -7806,13 +7819,13 @@ dependencies = [ [[package]] name = "test-log-macros" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" +checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -7845,11 +7858,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.8" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.8", + "thiserror-impl 2.0.11", ] [[package]] @@ -7860,18 +7873,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "thiserror-impl" -version = "2.0.8" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -7969,9 +7982,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -8008,7 +8021,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -8082,7 +8095,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "toml_datetime", "winnow", ] @@ -8147,6 +8160,7 @@ dependencies = [ "futures-util", "pin-project-lite", "sync_wrapper 1.0.2", + "tokio", "tower-layer", "tower-service", ] @@ -8183,7 +8197,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -8198,9 +8212,9 @@ dependencies = [ [[package]] name = "tracing-indicatif" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74ba258e9de86447f75edf6455fded8e5242704c6fccffe7bf8d7fb6daef1180" +checksum = "8201ca430e0cd893ef978226fd3516c06d9c494181c8bf4e5b32e30ed4b40aa1" dependencies = [ "indicatif", "tracing", @@ -8263,7 +8277,7 @@ checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" [[package]] name = "trustify-auth" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-http", "actix-web", @@ -8280,7 +8294,7 @@ dependencies = [ "jsonpath-rust", "log", "openid", - "reqwest 0.12.9", + "reqwest 0.12.12", "schemars", "serde", "serde_json", @@ -8298,7 +8312,7 @@ dependencies = [ [[package]] name = "trustify-common" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-web", "anyhow", @@ -8315,17 +8329,21 @@ dependencies = [ "native-tls", "packageurl", "pem", + "percent-encoding", "postgresql_embedded", "rand", "regex", - "reqwest 0.12.9", + "reqwest 0.12.12", "ring", "rstest", + "sbom-walker", "schemars", "sea-orm", "sea-query", "serde", "serde_json", + "spdx-expression", + "spdx-rs", "sqlx", "strum 0.26.3", "test-context", @@ -8343,7 +8361,7 @@ dependencies = [ [[package]] name = "trustify-cvss" -version = "0.2.0" +version = "0.2.1" dependencies = [ "serde", "test-log", @@ -8353,7 +8371,7 @@ dependencies = [ [[package]] name = "trustify-entity" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "async-graphql", @@ -8377,7 +8395,7 @@ dependencies = [ [[package]] name = "trustify-infrastructure" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-cors", "actix-tls", @@ -8399,7 +8417,7 @@ dependencies = [ "opentelemetry_sdk", "parking_lot 0.12.3", "prometheus", - "reqwest 0.12.9", + "reqwest 0.12.12", "serde", "serde_json", "tokio", @@ -8415,7 +8433,7 @@ dependencies = [ [[package]] name = "trustify-migration" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "sea-orm-migration", @@ -8433,7 +8451,7 @@ dependencies = [ [[package]] name = "trustify-module-analysis" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-http", "actix-web", @@ -8441,20 +8459,23 @@ dependencies = [ "bytes", "bytesize", "chrono", + "cpe", "criterion", "csaf", "hex", "humantime", + "itertools 0.13.0", "jsonpath-rust", "log", "packageurl", "parking_lot 0.12.3", - "petgraph", + "petgraph 0.7.1", "sea-orm", "sea-query", "serde", "serde_json", "sha2", + "spdx-rs", "test-context", "test-log", "thiserror 1.0.69", @@ -8474,7 +8495,7 @@ dependencies = [ [[package]] name = "trustify-module-fundamental" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-http", "actix-web", @@ -8500,7 +8521,7 @@ dependencies = [ "osv", "packageurl", "regex", - "reqwest 0.12.9", + "reqwest 0.12.12", "roxmltree", "sea-orm", "sea-query", @@ -8525,6 +8546,7 @@ dependencies = [ "trustify-common", "trustify-cvss", "trustify-entity", + "trustify-module-analysis", "trustify-module-ingestor", "trustify-module-storage", "trustify-test-context", @@ -8538,7 +8560,7 @@ dependencies = [ [[package]] name = "trustify-module-graphql" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-web", "anyhow", @@ -8561,7 +8583,7 @@ dependencies = [ [[package]] name = "trustify-module-importer" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-web", "anyhow", @@ -8578,7 +8600,7 @@ dependencies = [ "osv", "parking_lot 0.12.3", "regex", - "reqwest 0.12.9", + "reqwest 0.12.12", "sbom-walker", "schemars", "sea-orm", @@ -8597,6 +8619,7 @@ dependencies = [ "trustify-auth", "trustify-common", "trustify-entity", + "trustify-module-analysis", "trustify-module-ingestor", "trustify-module-storage", "trustify-test-context", @@ -8612,7 +8635,7 @@ dependencies = [ [[package]] name = "trustify-module-ingestor" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-http", "actix-web", @@ -8630,7 +8653,7 @@ dependencies = [ "osv", "packageurl", "parking_lot 0.12.3", - "quick-xml 0.37.1", + "quick-xml 0.37.2", "rand", "roxmltree", "rstest", @@ -8667,7 +8690,7 @@ dependencies = [ [[package]] name = "trustify-module-storage" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "async-compression", @@ -8694,7 +8717,7 @@ dependencies = [ [[package]] name = "trustify-module-ui" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-web", "actix-web-static-files", @@ -8705,7 +8728,7 @@ dependencies = [ [[package]] name = "trustify-module-user" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-http", "actix-web", @@ -8727,7 +8750,7 @@ dependencies = [ [[package]] name = "trustify-server" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-web", "anyhow", @@ -8766,7 +8789,7 @@ dependencies = [ [[package]] name = "trustify-test-context" -version = "0.2.0" +version = "0.2.1" dependencies = [ "actix-http", "actix-web", @@ -8797,7 +8820,7 @@ dependencies = [ [[package]] name = "trustify-trustd" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "clap", @@ -8818,7 +8841,7 @@ dependencies = [ [[package]] name = "trustify-ui" version = "0.1.0" -source = "git+https://github.com/trustification/trustify-ui.git?branch=publish%2Fmain#b8c3b1f186b0bd14495425847bc772013b37b24e" +source = "git+https://github.com/trustification/trustify-ui.git?branch=publish%2Fmain#3a64b27e97b1c1a15d37e6386cbc996793008ae3" dependencies = [ "anyhow", "base64 0.22.1", @@ -8898,9 +8921,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" @@ -8910,9 +8933,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" [[package]] name = "unicode-normalization" @@ -8953,12 +8976,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -9031,11 +9048,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utoipa" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e76d357bc95c7d0939c92c04c9269871a8470eea39cb1f0231eeadb0c47d0f" +checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "serde", "serde_json", "serde_yaml", @@ -9055,14 +9072,14 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "564b03f8044ad6806bdc0d635e88be24967e785eef096df6b2636d2cc1e05d4b" +checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.90", + "syn 2.0.96", "url", "uuid", ] @@ -9111,9 +9128,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ "getrandom", "serde", @@ -9147,27 +9164,27 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" dependencies = [ "valuable-derive", ] [[package]] name = "valuable-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d44690c645190cfce32f91a1582281654b2338c6073fa250b0949fd25c55b32" +checksum = "4e3a32a9bcc0f6c6ccfd5b27bcf298c58e753bcc9eeff268157a303393183a6d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.96", ] [[package]] @@ -9191,7 +9208,7 @@ dependencies = [ "itoa", "log", "unicode-width 0.1.14", - "vte", + "vte 0.11.1", ] [[package]] @@ -9205,6 +9222,15 @@ dependencies = [ "vte_generate_state_changes", ] +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + [[package]] name = "vte_generate_state_changes" version = "0.1.2" @@ -9251,12 +9277,12 @@ dependencies = [ "log", "openid", "pem", - "reqwest 0.12.9", + "reqwest 0.12.12", "sequoia-openpgp", "serde", "serde_json", "sha2", - "thiserror 2.0.8", + "thiserror 2.0.11", "thousands", "time", "tokio", @@ -9289,34 +9315,35 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -9327,9 +9354,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9337,22 +9364,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" @@ -9384,9 +9414,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -9647,9 +9677,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] @@ -9687,9 +9717,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", "linux-raw-sys", @@ -9698,9 +9728,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" [[package]] name = "xml5ever" @@ -9715,7 +9745,7 @@ dependencies = [ [[package]] name = "xtask" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "async-trait", @@ -9725,7 +9755,7 @@ dependencies = [ "nu-ansi-term 0.50.1", "postgresql_commands", "reedline", - "reqwest 0.12.9", + "reqwest 0.12.12", "schemars", "serde", "serde_json", @@ -9745,9 +9775,9 @@ dependencies = [ [[package]] name = "xxhash-rust" -version = "0.8.13" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08fd76779ae1883bbf1e46c2c46a75a0c4e37c445e68a24b01479d438f26ae6" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "yansi" @@ -9775,7 +9805,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "synstructure", ] @@ -9797,7 +9827,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -9817,7 +9847,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "synstructure", ] @@ -9838,7 +9868,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -9860,7 +9890,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -9879,13 +9909,13 @@ dependencies = [ "displaydoc", "flate2", "hmac", - "indexmap 2.7.0", + "indexmap 2.7.1", "lzma-rs", "memchr", "pbkdf2", "rand", "sha1", - "thiserror 2.0.8", + "thiserror 2.0.11", "time", "zeroize", "zopfli", diff --git a/Cargo.toml b/Cargo.toml index 9e622c548..923df6c67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "0.2.0" +version = "0.2.1" edition = "2021" publish = false license = "Apache-2.0" @@ -96,7 +96,8 @@ packageurl = "0.3.0" parking_lot = "0.12" peak_alloc = "0.2.0" pem = "3" -petgraph = { version = "0.6.5", features = ["serde-1"] } +percent-encoding = "2.3.1" +petgraph = { version = "0.7.1", features = ["serde-1"] } prometheus = "0.13.3" quick-xml = "0.37.0" rand = "0.8.5" diff --git a/common/Cargo.toml b/common/Cargo.toml index 30fa4d6f3..ce818b675 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -23,15 +23,19 @@ log = { workspace = true } native-tls = { workspace = true } packageurl = { workspace = true } pem = { workspace = true } +percent-encoding = { workspace = true } postgresql_embedded = { workspace = true, features = ["blocking", "tokio"] } regex = { workspace = true } reqwest = { workspace = true, features = ["native-tls"] } ring = { workspace = true } +sbom-walker = { workspace = true } schemars = { workspace = true } sea-orm = { workspace = true, features = ["sea-query-binder", "sqlx-postgres", "runtime-tokio-rustls", "macros"] } sea-query = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +spdx-expression = { workspace = true } +spdx-rs = { workspace = true } sqlx = { workspace = true } strum = { workspace = true, features = ["derive"] } thiserror = { workspace = true } diff --git a/common/infrastructure/Cargo.toml b/common/infrastructure/Cargo.toml index a1fb33c4b..92eaa4b68 100644 --- a/common/infrastructure/Cargo.toml +++ b/common/infrastructure/Cargo.toml @@ -23,7 +23,7 @@ mime = { workspace = true } openssl = { workspace = true } opentelemetry = { workspace = true } opentelemetry-otlp = { workspace = true } -opentelemetry_sdk = { workspace = true, features = ["rt-tokio"] } +opentelemetry_sdk = { workspace = true, features = ["rt-tokio-current-thread"] } parking_lot = { workspace = true } prometheus = { workspace = true } reqwest = { workspace = true } diff --git a/common/infrastructure/src/tracing.rs b/common/infrastructure/src/tracing.rs index 1704d503d..db973bb1f 100644 --- a/common/infrastructure/src/tracing.rs +++ b/common/infrastructure/src/tracing.rs @@ -123,13 +123,13 @@ fn init_otlp(name: &str) { "service.name", name.to_string(), )])) - .with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio) + .with_batch_exporter(exporter, opentelemetry_sdk::runtime::TokioCurrentThread) .with_sampler(opentelemetry_sdk::trace::Sampler::ParentBased(Box::new( sampler(), ))) .build(); - println!("Using Jaeger tracing."); + println!("Using OTEL Collector with Jaeger as the back-end."); println!("{:#?}", provider); let formatting_layer = tracing_subscriber::fmt::Layer::default(); @@ -142,6 +142,7 @@ fn init_otlp(name: &str) { { eprintln!("Error initializing tracing: {:?}", e); } + opentelemetry::global::set_tracer_provider(provider); } fn init_no_tracing() { diff --git a/common/src/cpe.rs b/common/src/cpe.rs index 08a54ce8b..73259e205 100644 --- a/common/src/cpe.rs +++ b/common/src/cpe.rs @@ -2,8 +2,19 @@ use cpe::{ cpe::Cpe as _, uri::{OwnedUri, Uri}, }; -use std::fmt::{Debug, Display, Formatter}; -use std::str::FromStr; +use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{ + borrow::Cow, + fmt::{Debug, Display, Formatter}, + str::FromStr, +}; +use utoipa::{ + openapi::{KnownFormat, ObjectBuilder, RefOr, Schema, SchemaFormat, Type}, + PartialSchema, ToSchema, +}; use uuid::Uuid; #[derive(Clone, Hash, Eq, PartialEq)] @@ -11,12 +22,62 @@ pub struct Cpe { uri: OwnedUri, } +impl ToSchema for Cpe { + fn name() -> Cow<'static, str> { + "Cpe".into() + } +} + +impl PartialSchema for Cpe { + fn schema() -> RefOr { + ObjectBuilder::new() + .schema_type(Type::String) + .format(Some(SchemaFormat::KnownFormat(KnownFormat::Uri))) + .into() + } +} + impl Display for Cpe { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.uri, f) } } +impl Serialize for Cpe { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_str()) + } +} + +impl<'de> Deserialize<'de> for Cpe { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(CpeVisitor) + } +} + +struct CpeVisitor; + +impl Visitor<'_> for CpeVisitor { + type Value = Cpe; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("a CPE") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + v.try_into().map_err(Error::custom) + } +} + const NAMESPACE: Uuid = Uuid::from_bytes([ 0x1b, 0xf1, 0x2a, 0xd5, 0x0d, 0x67, 0x41, 0x18, 0xa1, 0x38, 0xb8, 0x9f, 0x19, 0x35, 0xe0, 0xa7, ]); @@ -172,6 +233,22 @@ impl FromStr for Cpe { } } +impl TryFrom<&str> for Cpe { + type Error = ::Err; + fn try_from(value: &str) -> Result { + Ok(Self { + uri: OwnedUri::from_str(value)?, + }) + } +} + +impl TryFrom for Cpe { + type Error = ::Err; + fn try_from(value: String) -> Result { + value.as_str().try_into() + } +} + pub trait CpeCompare: cpe::cpe::Cpe { fn is_superset(&self, other: &O) -> bool { self.compare(other).superset() diff --git a/common/src/db/query.rs b/common/src/db/query.rs index 4aca1bacb..3c23ad679 100644 --- a/common/src/db/query.rs +++ b/common/src/db/query.rs @@ -162,7 +162,7 @@ impl Query { } } -#[derive(Clone, Default, Debug, Deserialize, Serialize, ToSchema, IntoParams)] +#[derive(Clone, Default, Debug, Eq, PartialEq, Deserialize, Serialize, ToSchema, IntoParams)] #[serde(rename_all = "camelCase")] pub struct Query { #[serde(default)] diff --git a/common/src/purl.rs b/common/src/purl.rs index 01c545628..d5ca52e08 100644 --- a/common/src/purl.rs +++ b/common/src/purl.rs @@ -1,4 +1,5 @@ use packageurl::PackageUrl; +use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; use serde::{ de::{Error, Visitor}, Deserialize, Deserializer, Serialize, Serializer, @@ -115,6 +116,7 @@ impl FromStr for Purl { .map_err(PurlErr::Package) } } + impl<'de> Deserialize<'de> for Purl { fn deserialize(deserializer: D) -> Result where @@ -141,6 +143,35 @@ impl Visitor<'_> for PurlVisitor { } } +const QUERY_ENCODE_SET: &AsciiSet = &CONTROLS + .add(b' ') // Space must be encoded as %20 or +. + .add(b'"') // Double quote + .add(b'#') // Fragment identifier + .add(b'<') // Less than + .add(b'>') // Greater than + .add(b'[') // Left square bracket + .add(b']') // Right square bracket + .add(b'{') // Left curly brace + .add(b'}') // Right curly brace + .add(b'|') // Pipe + .add(b'\\') // Backslash + .add(b'^') // Caret + .add(b'`') // Backtick + .add(b'~') // Tilde + .add(b'@') // At sign + .add(b'!') // Exclamation mark + .add(b'$') // Dollar sign + .add(b'&') // Ampersand + .add(b'\'') // Single quote + .add(b'(') // Left parenthesis + .add(b')') // Right parenthesis + .add(b'*') // Asterisk + .add(b'+') // Plus + .add(b',') // Comma + .add(b';') // Semicolon + .add(b'=') // Equals + .add(b'%'); // Percent itself. + impl Display for Purl { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let ns = if let Some(ns) = &self.namespace { @@ -156,7 +187,7 @@ impl Display for Purl { "?{}", self.qualifiers .iter() - .map(|(k, v)| format!("{}={}", k, v)) + .map(|(k, v)| format!("{}={}", k, utf8_percent_encode(v, QUERY_ENCODE_SET))) .collect::>() .join("&") ) diff --git a/common/src/sbom/mod.rs b/common/src/sbom/mod.rs index 6916d9964..d28c2b31e 100644 --- a/common/src/sbom/mod.rs +++ b/common/src/sbom/mod.rs @@ -1,3 +1,5 @@ +pub mod spdx; + use crate::cpe::Cpe; use crate::purl::Purl; use uuid::Uuid; diff --git a/common/src/sbom/spdx.rs b/common/src/sbom/spdx.rs new file mode 100644 index 000000000..5ca5baf47 --- /dev/null +++ b/common/src/sbom/spdx.rs @@ -0,0 +1,33 @@ +use sbom_walker::report::ReportSink; +use serde_json::Value; +use spdx_rs::models::SPDX; + +/// Parse a SPDX document, possibly replacing invalid license expressions. +/// +/// Returns the parsed document and a flag indicating if license expressions got replaced. +pub fn parse_spdx(report: &dyn ReportSink, json: Value) -> Result<(SPDX, bool), serde_json::Error> { + let (json, changed) = fix_license(report, json); + Ok((serde_json::from_value(json)?, changed)) +} + +/// Check the document for invalid SPDX license expressions and replace them with `NOASSERTION`. +pub fn fix_license(report: &dyn ReportSink, mut json: Value) -> (Value, bool) { + let mut changed = false; + if let Some(packages) = json["packages"].as_array_mut() { + for package in packages { + if let Some(declared) = package["licenseDeclared"].as_str() { + if let Err(err) = spdx_expression::SpdxExpression::parse(declared) { + package["licenseDeclared"] = "NOASSERTION".into(); + changed = true; + + let message = + format!("Replacing faulty SPDX license expression with NOASSERTION: {err}"); + log::debug!("{message}"); + report.error(message); + } + } + } + } + + (json, changed) +} diff --git a/docs/design/log_tracing.md b/docs/design/log_tracing.md index 5b2f80b83..6da5d3707 100644 --- a/docs/design/log_tracing.md +++ b/docs/design/log_tracing.md @@ -135,3 +135,26 @@ However, not all function variants might expose the same behavior. Just because mean it will panic. For example, the `Option::unwrap_or` function: ![Screenshot of rustdoc for Option::unwrap_or](drawings/log_tracing_2.png) + +## Sending traces to OpenTelemetry Collector (devmode) + +Jaeger and OTEL Collector: + +```shell +podman compose -f etc/dev-traces/compose.yaml up +``` + +Database: + +```shell +podman compose -f etc/deploy/compose/compose.yaml up +``` + +Trustify with traces: + +```shell +OTEL_TRACES_SAMPLER_ARG=1 OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317" cargo run --bin trustd api --db-password trustify --auth-disabled --tracing enabled +``` + +Access Trustify at [localhost:8080](http://localhost:8080) and analyze the traces using the [Jaeger UI](http://localhost:16686/) + diff --git a/entity/src/advisory.rs b/entity/src/advisory.rs index a09a454a2..84016a858 100644 --- a/entity/src/advisory.rs +++ b/entity/src/advisory.rs @@ -34,7 +34,7 @@ pub struct Model { #[ComplexObject] impl Model { - async fn organization<'a>(&self, ctx: &Context<'a>) -> Result { + async fn organization(&self, ctx: &Context<'_>) -> Result { let db = ctx.data::>()?; if let Some(found) = self .find_related(organization::Entity) @@ -47,7 +47,7 @@ impl Model { } } - async fn vulnerabilities<'a>(&self, ctx: &Context<'a>) -> Result> { + async fn vulnerabilities(&self, ctx: &Context<'_>) -> Result> { let db = ctx.data::>()?; Ok(self .find_related(vulnerability::Entity) diff --git a/etc/dev-traces/compose.yaml b/etc/dev-traces/compose.yaml new file mode 100644 index 000000000..525de07a9 --- /dev/null +++ b/etc/dev-traces/compose.yaml @@ -0,0 +1,17 @@ +services: + jaeger-all-in-one: + hostname: jaeger-all-in-one + image: jaegertracing/all-in-one:1.53.0 # Using this version to align with trustify-helm-charts + ports: + - "16686:16686" + - "14250:14250" + environment: + - COLLECTOR_OTLP_ENABLED=true + collector: + image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector:0.115.1 # Using this version to align with trustify-helm-charts https://github.com/TylerHelmuth/opentelemetry-helm-charts/commit/86188fea6022a6424ef6a086e928d0056fb5dfe8#diff-55020f2b796ba5770731a3b4913592732431ff180c7f7473e5f469e92ed00e74R48 + command: ["--config=/otel-collector-config.yaml"] + volumes: + - './config.yaml:/otel-collector-config.yaml:z' + ports: + - "4317:4317" + depends_on: [jaeger-all-in-one] diff --git a/etc/dev-traces/config.yaml b/etc/dev-traces/config.yaml new file mode 100644 index 000000000..aa52cda78 --- /dev/null +++ b/etc/dev-traces/config.yaml @@ -0,0 +1,23 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" + +exporters: + otlp: + endpoint: jaeger-all-in-one:4317 + tls: + insecure: true + debug: + verbosity: detailed + +processors: + batch: {} + +service: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [debug, otlp] diff --git a/etc/test-data/cyclonedx/66FF73123BB3489.json b/etc/test-data/cyclonedx/66FF73123BB3489.json new file mode 100644 index 000000000..882845296 --- /dev/null +++ b/etc/test-data/cyclonedx/66FF73123BB3489.json @@ -0,0 +1,77 @@ +{ + "version": 1, + "metadata": { + "tools": { + "components": [ + { + "name": "SBOMer", + "type": "application", + "author": "Red Hat", + "version": "dce92b5d" + } + ] + }, + "component": { + "name": "openshift/ose-console", + "purl": "pkg:oci/openshift-ose-console@sha256%3A94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679", + "type": "container", + "description": "Image index manifest of pkg:oci/openshift-ose-console@sha256%3A94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679" + }, + "timestamp": "2024-12-19T18:04:12Z" + }, + "bomFormat": "CycloneDX", + "components": [ + { + "name": "openshift/ose-console", + "purl": "pkg:oci/openshift-ose-console@sha256%3A94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679", + "type": "container", + "bom-ref": "pkg:oci/openshift-ose-console@sha256%3A94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679", + "version": "sha256:94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679", + "pedigree": { + "variants": [ + { + "name": "openshift/ose-console", + "purl": "pkg:oci/ose-console@sha256%3Ac2d69e860b7457eb42f550ba2559a0452ec3e5c9ff6521d758c186266247678e?arch=s390x&os=linux&tag=v4.14.0-202412110104.p0.g350e1ea.assembly.stream.el8", + "type": "container", + "bom-ref": "pkg:oci/ose-console@sha256%3Ac2d69e860b7457eb42f550ba2559a0452ec3e5c9ff6521d758c186266247678e?arch=s390x&os=linux&tag=v4.14.0-202412110104.p0.g350e1ea.assembly.stream.el8", + "version": "sha256:c2d69e860b7457eb42f550ba2559a0452ec3e5c9ff6521d758c186266247678e", + "supplier": { + "url": [ + "https://www.redhat.com" + ], + "name": "Red Hat" + }, + "publisher": "Red Hat" + } + ] + }, + "supplier": { + "url": [ + "https://www.redhat.com" + ], + "name": "Red Hat" + }, + "publisher": "Red Hat", + "properties": [ + { + "name": "sbomer:image:labels:com.redhat.component", + "value": "openshift-enterprise-console-container" + }, + { + "name": "sbomer:image:labels:name", + "value": "openshift/ose-console" + }, + { + "name": "sbomer:image:labels:release", + "value": "202412110104.p0.g350e1ea.assembly.stream.el8" + }, + { + "name": "sbomer:image:labels:version", + "value": "v4.14.0" + } + ] + } + ], + "specVersion": "1.6", + "serialNumber": "urn:uuid:537c8dc3-6f66-3cac-b504-cc5fb0a09ece" +} diff --git a/etc/test-data/cyclonedx/openssl-3.0.7-18.el9_2.cdx_1.6_aliases.sbom.json b/etc/test-data/cyclonedx/openssl-3.0.7-18.el9_2.cdx_1.6_aliases.sbom.json new file mode 100644 index 000000000..a2768b7ba --- /dev/null +++ b/etc/test-data/cyclonedx/openssl-3.0.7-18.el9_2.cdx_1.6_aliases.sbom.json @@ -0,0 +1,1753 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:a4f16b62-fea9-42c1-8365-d72d3cef37d1", + "version": 1, + "metadata": { + "timestamp": "2024-12-17T05:38:26.655298+00:00", + "tools": { + "components": [ + { + "type": "application", + "manufacturer": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "rpm-manifest-generator", + "version": "0.0.1" + } + ] + }, + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "component": { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl", + "version": "3.0.7-18.el9_2", + "description": "The OpenSSL toolkit provides support for secure communications between\nmachines. OpenSSL includes a certificate management tool and shared\nlibraries which provide various cryptographic algorithms and\nprotocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src" + }, + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src&foo=bar" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "0da276f660b44ab5bd128db8aa3d8ce1" + }, + { + "name": "package:rpm:sha256header", + "value": "31b5079268339cff7ba65a0aee77930560c5adef4b1b3f8f5927a43ee46a56d9" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "9215c64e7289a058248728089e4d98ed1cc392bb5eb9b8fcbe661d57e8145bbd" + } + ] + }, + "licenses": [ + { + "expression": "Apache-2.0" + } + ] + }, + "components": [ + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl", + "version": "3.0.7-18.el9_2", + "description": "The OpenSSL toolkit provides support for secure communications between\nmachines. OpenSSL includes a certificate management tool and shared\nlibraries which provide various cryptographic algorithms and\nprotocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "0da276f660b44ab5bd128db8aa3d8ce1" + }, + { + "name": "package:rpm:sha256header", + "value": "31b5079268339cff7ba65a0aee77930560c5adef4b1b3f8f5927a43ee46a56d9" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src", + "hashes": [ + { + "alg": "SHA-256", + "content": "9215c64e7289a058248728089e4d98ed1cc392bb5eb9b8fcbe661d57e8145bbd" + } + ], + "externalReferences": [ + { + "comment": "brew-build-id", + "type": "build-system", + "url": "https://brewweb.engineering.redhat.com/brew/buildinfo?buildID=2788361" + } + ], + "pedigree": { + "ancestors": [ + { + "type": "library", + "name": "openssl", + "version": "3.0.7", + "hashes": [ + { + "alg": "SHA-512", + "content": "1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e" + } + ], + "purl": "pkg:generic/openssl@3.0.7?download_url=https://pkgs.devel.redhat.com/repo/openssl/openssl-3.0.7-hobbled.tar.gz/sha512/1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e/openssl-3.0.7-hobbled.tar.gz&checksum=SHA-512:1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:generic/openssl@3.0.7?download_url=https://pkgs.devel.redhat.com/repo/openssl/openssl-3.0.7-hobbled.tar.gz/sha512/1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e/openssl-3.0.7-hobbled.tar.gz&checksum=SHA-512:1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e&foo=bar" + } + ] + }, + "bom-ref": "pkg:generic/openssl@3.0.7?download_url=https://pkgs.devel.redhat.com/repo/openssl/openssl-3.0.7-hobbled.tar.gz/sha512/1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e/openssl-3.0.7-hobbled.tar.gz&checksum=SHA-512:1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e" + } + ] + } + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-perl", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-perl\npackage provides Perl scripts for converting certificates and keys\nfrom other formats to the formats used by the OpenSSL toolkit.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=aarch64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=aarch64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "4cc665dd3173c8952184293588f9ee46" + }, + { + "name": "package:rpm:sha256header", + "value": "96e53b2da90ce5ad109ba659ce3ed1b5a819b108c95fc493f84847429898b2ed" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=aarch64", + "hashes": [ + { + "alg": "SHA-256", + "content": "fbdd94bb6ce796441e4d4b9b04a960b41f6f7577e51c764e2befe9226e71da4b" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-debuginfo", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug information for package openssl.\nDebug information is useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=aarch64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=aarch64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "8bf628bbb31e41bcc4139001ffca1fcb" + }, + { + "name": "package:rpm:sha256header", + "value": "8721bc9673ccc43f729485aba48fd75a927305980f48ee9d0b79d06937b68d16" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=aarch64", + "hashes": [ + { + "alg": "SHA-256", + "content": "4cfed027c112894a8742eab76cd1003801d01b0cd2f26be4cf9f7a3a46e62223" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-libs", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-libs\npackage contains the libraries that are used by various applications which\nsupport cryptographic algorithms and protocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=aarch64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=aarch64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "a4815b267d59c43662d6ea94d6b32eba" + }, + { + "name": "package:rpm:sha256header", + "value": "cae5941219fd64e75c2b29509c6fe712bef77181a586702275a46a5e812d4dd4" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=aarch64", + "hashes": [ + { + "alg": "SHA-256", + "content": "832757f9bcb231e954fb390b835fb833b9046594aa3c6387eeaadd9af592f40f" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-libs-debuginfo", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug information for package openssl-libs.\nDebug information is useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=aarch64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=aarch64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "f9e91d29b3e3b10cb1fe6b792d6ec3c6" + }, + { + "name": "package:rpm:sha256header", + "value": "036985b5bd34712963e4a9009dd196e4f583479283d88b6e908a231aa5bddfae" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=aarch64", + "hashes": [ + { + "alg": "SHA-256", + "content": "6daa6877ba8b2951e18ea8e57a5d0267a13ba94ae12eef38223cc3138e7a47bc" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl", + "version": "3.0.7-18.el9_2", + "description": "The OpenSSL toolkit provides support for secure communications between\nmachines. OpenSSL includes a certificate management tool and shared\nlibraries which provide various cryptographic algorithms and\nprotocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=aarch64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=aarch64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "2a8826422579bd43b49774d11a68e383" + }, + { + "name": "package:rpm:sha256header", + "value": "aaafa61c115ec37bb3895e124216ce46774069e49f6178248df085708ecb3878" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=aarch64", + "hashes": [ + { + "alg": "SHA-256", + "content": "9538011195d4997d7be4bba8cd01473abbf50e397dea06b6545d1d5fbd04817d" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-debugsource", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug sources for package openssl.\nDebug sources are useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=aarch64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=aarch64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "b6245825de11c602beff7870c7612542" + }, + { + "name": "package:rpm:sha256header", + "value": "036bd68632078d2b12e87f4541047823b5675d7e1141e56639cbc1e2e42c9f65" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=aarch64", + "hashes": [ + { + "alg": "SHA-256", + "content": "372c4b2996b5d1a539a7e48d65a76e93002a0fe4fd8a3f01717cacc0f513946e" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-devel", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-devel\npackage contains include files needed to develop applications which\nsupport various cryptographic algorithms and protocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=aarch64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=aarch64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "728afe59ed2768caea0634737878e2b9" + }, + { + "name": "package:rpm:sha256header", + "value": "deff41d222f613c3292d1bd0256c793c7fc7d5a4d61a24fa81f23990123bd79d" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=aarch64", + "hashes": [ + { + "alg": "SHA-256", + "content": "af7342cf0b04d24e8055f364d67afd705fbb94c7ec2460999a59cacfdf554920" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-perl", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-perl\npackage provides Perl scripts for converting certificates and keys\nfrom other formats to the formats used by the OpenSSL toolkit.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=ppc64le", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=ppc64le" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "2b8f23243dc5469c31a1d78fe817cc63" + }, + { + "name": "package:rpm:sha256header", + "value": "7ae23594204f2688d5b16be98782d5456080f55e6baf76172d8cb4e100c2507e" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=ppc64le", + "hashes": [ + { + "alg": "SHA-256", + "content": "bf63da42704f9da1e9eeefab808900f521a357b9a07433c0973965a02a2bb915" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-libs", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-libs\npackage contains the libraries that are used by various applications which\nsupport cryptographic algorithms and protocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=ppc64le", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=ppc64le" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "0a8de436e7188fd6a2eae746d0afce1f" + }, + { + "name": "package:rpm:sha256header", + "value": "6c35abfc44cc24048921fd519bbcdba0bc43cf45cdece57df99ede720600a686" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=ppc64le", + "hashes": [ + { + "alg": "SHA-256", + "content": "71cb34e11fa6bda16512f0dbb314ac761348cf907b3e49fa54c7576305ed6afb" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-devel", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-devel\npackage contains include files needed to develop applications which\nsupport various cryptographic algorithms and protocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=ppc64le", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=ppc64le" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "8bc5175c12d9772a5dee1721f40f691e" + }, + { + "name": "package:rpm:sha256header", + "value": "b2e00a6e064e8efd9fac7b4633221ef5b3f49fb16e6eb2752cffae6965007cb7" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=ppc64le", + "hashes": [ + { + "alg": "SHA-256", + "content": "36d11f2f192aef3abf391e4cf6699a24a303f09d68b2477595bb12af82b26e48" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl", + "version": "3.0.7-18.el9_2", + "description": "The OpenSSL toolkit provides support for secure communications between\nmachines. OpenSSL includes a certificate management tool and shared\nlibraries which provide various cryptographic algorithms and\nprotocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=ppc64le", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=ppc64le" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "c8006421182838d0db0559f89121e14d" + }, + { + "name": "package:rpm:sha256header", + "value": "10135a468b85a35b0373609ad48c50e71499d034c6f7e7455691b63f87105f12" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=ppc64le", + "hashes": [ + { + "alg": "SHA-256", + "content": "c96d6029cfa0e6a01c4419c154cbf98c08ab9bb69cc756d81350ab77b4d1f44c" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-debuginfo", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug information for package openssl.\nDebug information is useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=ppc64le", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=ppc64le" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "7d0cccc64dfed49cf9954784fb52d6d8" + }, + { + "name": "package:rpm:sha256header", + "value": "8e14cfaf5ae8bf1858b8d262b6d891dfdcc31378b6805345b8c1e08e4f0ce442" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=ppc64le", + "hashes": [ + { + "alg": "SHA-256", + "content": "a019fb248b731bb774bd559ed9ff1c5f12c2939b8fbe0f56696e9465a4ed1e8f" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-debugsource", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug sources for package openssl.\nDebug sources are useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=ppc64le", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=ppc64le" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "df3c6c904df9135c5dbfb125be69cdb6" + }, + { + "name": "package:rpm:sha256header", + "value": "d7de194a98e577ebaa0344c55002f83c2d22b52bf62cac22920759f2679e8124" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=ppc64le", + "hashes": [ + { + "alg": "SHA-256", + "content": "3eed827b556e88f546fba10840ac33f001550343dc75971077a222ebc2b6e22f" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-libs-debuginfo", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug information for package openssl-libs.\nDebug information is useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=ppc64le", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=ppc64le" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "050d1106c338216f24cf1300b73e7647" + }, + { + "name": "package:rpm:sha256header", + "value": "d6c29685fcd8a62504e223fcd4b520819118456cc65273e43c5fed19dd1d1a11" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=ppc64le", + "hashes": [ + { + "alg": "SHA-256", + "content": "e0d315e1763426f8ce1199e6ed76f116628f3533dc429f6e7980b64c0000b217" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-libs-debuginfo", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug information for package openssl-libs.\nDebug information is useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=i686", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=i686" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "cc789f980e1db3b3b49106f09d94b459" + }, + { + "name": "package:rpm:sha256header", + "value": "8897777ff03ce9308b8e7dd890e3c22c3b9f0da29d641d3844a52635ed926649" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=i686", + "hashes": [ + { + "alg": "SHA-256", + "content": "c6f7722acb3034abfedf21a131e5a1faf93acbed3ff1b4d4150f7c6a158e54d3" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-libs", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-libs\npackage contains the libraries that are used by various applications which\nsupport cryptographic algorithms and protocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=i686", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=i686" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "3ceb671050a402f897728066d844414e" + }, + { + "name": "package:rpm:sha256header", + "value": "ed85dfebb27dadf0d786da2aa15ce0dfbcb442ed38846360a82ab6abf7b71334" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=i686", + "hashes": [ + { + "alg": "SHA-256", + "content": "69e098aceca261b8cdd127b78c90868d40d39c3cb94e37b07703b3f7a85f832f" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-debugsource", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug sources for package openssl.\nDebug sources are useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=i686", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=i686" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "4437626dedb09a7d5fefea71391d9666" + }, + { + "name": "package:rpm:sha256header", + "value": "d13f9f85d90d4d0e4f39bdc743f42dc064a6ca16e2cb85fe1695e8a08b9163b4" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=i686", + "hashes": [ + { + "alg": "SHA-256", + "content": "09a218d7864bfe519ec9815cfcbda1ea68c52947da80f293ed5017cca0e72ebb" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-devel", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-devel\npackage contains include files needed to develop applications which\nsupport various cryptographic algorithms and protocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=i686", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=i686" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "427cd7fdea922cddcfbdd736b6bb8717" + }, + { + "name": "package:rpm:sha256header", + "value": "03d6054aafea54f4496f8c35550eec87f2ceb06566e3fd5eea0446bd91e3242e" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=i686", + "hashes": [ + { + "alg": "SHA-256", + "content": "689777a4cc483b0cbb3757da0e3274b49d6e697f8174405df415e0018cef7646" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-debuginfo", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug information for package openssl.\nDebug information is useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=i686", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=i686" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "497871ca44f5dd4859c73c507f7c40f8" + }, + { + "name": "package:rpm:sha256header", + "value": "02867d6b799b5ebc56f1f945a474f59c6b1a8430da4acf3fa35dae9cf992ea58" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=i686", + "hashes": [ + { + "alg": "SHA-256", + "content": "67f5ec7e09b53771ee0f622b5a18d66028424dd1cdd1316dadeb4a336f9a494c" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl", + "version": "3.0.7-18.el9_2", + "description": "The OpenSSL toolkit provides support for secure communications between\nmachines. OpenSSL includes a certificate management tool and shared\nlibraries which provide various cryptographic algorithms and\nprotocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=i686", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=i686" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "eea71314fcc3f1be5af5d6773f82b6da" + }, + { + "name": "package:rpm:sha256header", + "value": "b7b39d8d7a960d75da6347206927e5a9bf33642148a0d291b0205f70eac8bf42" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=i686", + "hashes": [ + { + "alg": "SHA-256", + "content": "9b529f697702037b712e81778fa4e222d385568e4b3be167e6e42431a3270bfd" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-perl", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-perl\npackage provides Perl scripts for converting certificates and keys\nfrom other formats to the formats used by the OpenSSL toolkit.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=i686", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=i686" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "0c92f2f4cd6bd41361dc25ddd4ffbb21" + }, + { + "name": "package:rpm:sha256header", + "value": "d4732a4e60c831e1e8e4ddb89419a029accf2ee6dc1c2efe62e8bf20e97e2577" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=i686", + "hashes": [ + { + "alg": "SHA-256", + "content": "0bc733036256ec160b8ca5ed6bc1082e4d4c15f84cf6ff141f02d8e45d73b4b8" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-libs-debuginfo", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug information for package openssl-libs.\nDebug information is useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=x86_64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=x86_64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "62bc1369f326b14bfdb5450a59086c05" + }, + { + "name": "package:rpm:sha256header", + "value": "596206f99a50ede734b1f989977b210289debcfdd972666bd0d5ba03e7afbf19" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=x86_64", + "hashes": [ + { + "alg": "SHA-256", + "content": "9b71a8428177e995b301f0821806cf22d64228d384afb624c79399a33e70b676" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-debuginfo", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug information for package openssl.\nDebug information is useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=x86_64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=x86_64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "746bb9be1752b46631e55fc2e8c1d48e" + }, + { + "name": "package:rpm:sha256header", + "value": "7fbdc911663e437e7be7e30f5bd87450f4ff38551ecbb3de27e362bb9f2ac2aa" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=x86_64", + "hashes": [ + { + "alg": "SHA-256", + "content": "f8f2757bf8dd09442c1487b50437e4520d4e80cd132cf834da902eda803a9a2b" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-debugsource", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug sources for package openssl.\nDebug sources are useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=x86_64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=x86_64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "94d964a9a930e8c914e7601b9df395a0" + }, + { + "name": "package:rpm:sha256header", + "value": "96e137f9561e7669417fe5998ec2aa6ba55a67aef0c2c205eede5a5a5941e8ac" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=x86_64", + "hashes": [ + { + "alg": "SHA-256", + "content": "4a5791dd6863d2e6b65c7faae9ce90a603b9c3a4c9be0f01b0103a536e7ecd29" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-perl", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-perl\npackage provides Perl scripts for converting certificates and keys\nfrom other formats to the formats used by the OpenSSL toolkit.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=x86_64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=x86_64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "862b17959c237e619217fabcf9939212" + }, + { + "name": "package:rpm:sha256header", + "value": "403624aed89502e17352b50f00c413104c9be31307477d02c3a7ae78cfcb1ca4" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=x86_64", + "hashes": [ + { + "alg": "SHA-256", + "content": "b2fe5b3a9230446e6e8119cd24eef1bee107b8d5ac37d2c5442d524bf5745a4e" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-libs", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-libs\npackage contains the libraries that are used by various applications which\nsupport cryptographic algorithms and protocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=x86_64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=x86_64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "cceaf391f5defa464df7bb55b6ad7486" + }, + { + "name": "package:rpm:sha256header", + "value": "73e869a62715910bdec02ee3f0275a81ca96f539a07aced314182b6f4dc8c828" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=x86_64", + "hashes": [ + { + "alg": "SHA-256", + "content": "0cb4cd2487dcbc2c0d97ded42f7998ff5f27c6e38ad40b204cce1e22c9966148" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl", + "version": "3.0.7-18.el9_2", + "description": "The OpenSSL toolkit provides support for secure communications between\nmachines. OpenSSL includes a certificate management tool and shared\nlibraries which provide various cryptographic algorithms and\nprotocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "ebe25fb0a0033a8e0248869989e587e8" + }, + { + "name": "package:rpm:sha256header", + "value": "96045880ef4a2167abe9ea14f2b325402996a7671df8f594924dc24b6c2263e4" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64", + "hashes": [ + { + "alg": "SHA-256", + "content": "82b40f3403399fc808bd26f1a91edb7855e8f10087b925651001c17cb17a38ed" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-devel", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-devel\npackage contains include files needed to develop applications which\nsupport various cryptographic algorithms and protocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=x86_64", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=x86_64" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "94df67b3c5c8d3cb192ba79d6559b46f" + }, + { + "name": "package:rpm:sha256header", + "value": "72f42e7c9ade55a24d3c53f4370d5d6d3b2fe99a4735d564825556ccd4cc1df3" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=x86_64", + "hashes": [ + { + "alg": "SHA-256", + "content": "0af316c88f24a4a3df742376b8875c89137acec5df2b40717dcc3d81789f52f0" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-libs-debuginfo", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug information for package openssl-libs.\nDebug information is useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=s390x", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=s390x" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "5ccbf569de20ef8c965993391c024832" + }, + { + "name": "package:rpm:sha256header", + "value": "bc5de92a3830cd99f7d291ccaafb5fd640127c8c1c925231e1e37d060a69563f" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=s390x", + "hashes": [ + { + "alg": "SHA-256", + "content": "1f336ab860b96b066fe6784b4d2674717d879f4902bc405a50dfdda9bf1ad469" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-libs", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-libs\npackage contains the libraries that are used by various applications which\nsupport cryptographic algorithms and protocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=s390x", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=s390x" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "349224e7170e1bcee0cf15c04164fe85" + }, + { + "name": "package:rpm:sha256header", + "value": "0543b42f6491762fab8defbc6ec68a30d8e17e2f55e50b29095c382a6ad5baf1" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=s390x", + "hashes": [ + { + "alg": "SHA-256", + "content": "3a92cded871cffdb2290d078fe4cf300bc9d74fd0af612a607898d9b6c7967fd" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-debugsource", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug sources for package openssl.\nDebug sources are useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=s390x", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=s390x" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "8ea0991914a7a90bccdee1264baaf74f" + }, + { + "name": "package:rpm:sha256header", + "value": "90ca984f844882a5c0678887851cc58be90a187c65ef010b866791c40116f7fc" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=s390x", + "hashes": [ + { + "alg": "SHA-256", + "content": "e8e12b987dbf73a8654a648d82a4c4dcc954a43758bae681c8e7dccc2bcedae4" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-devel", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-devel\npackage contains include files needed to develop applications which\nsupport various cryptographic algorithms and protocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=s390x", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=s390x" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "12e9fefcf812eaf6e727b53b613d7f88" + }, + { + "name": "package:rpm:sha256header", + "value": "4a8588e587337d65cd2da98b0ba813a1e178a9b515c82bbc7fe96f1ba749b8fc" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=s390x", + "hashes": [ + { + "alg": "SHA-256", + "content": "1c4e7f767b47e9dd36f00c1c213328ab84f0c1516d17b17f068eadefd5cc706d" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-debuginfo", + "version": "3.0.7-18.el9_2", + "description": "This package provides debug information for package openssl.\nDebug information is useful when developing applications that use this\npackage or when debugging this package.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=s390x", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=s390x" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "fd189955871f77470c91193b845f755f" + }, + { + "name": "package:rpm:sha256header", + "value": "0a2a6b1409b045894d15c1b05ff5be2c1feba0b2b5f2d3bbac9d2e44c93f0583" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=s390x", + "hashes": [ + { + "alg": "SHA-256", + "content": "9d615e97293c3177eaaffbe9d626467b75a6f97ebbdf85fef5953115eea8a03f" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl", + "version": "3.0.7-18.el9_2", + "description": "The OpenSSL toolkit provides support for secure communications between\nmachines. OpenSSL includes a certificate management tool and shared\nlibraries which provide various cryptographic algorithms and\nprotocols.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=s390x", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=s390x" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "5678f991a6d453ae482989fbe2ef5e51" + }, + { + "name": "package:rpm:sha256header", + "value": "11c42421e6d07bca92122575ac023016471383f58575b2dd478a19e0ba7ce4db" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=s390x", + "hashes": [ + { + "alg": "SHA-256", + "content": "c99bf4a5bf29434baca2e30e40fc9f6c7152ec9c372a48b79dbc9294d08a7774" + } + ] + }, + { + "type": "library", + "supplier": { + "name": "Red Hat", + "url": [ + "https://www.redhat.com" + ] + }, + "name": "openssl-perl", + "version": "3.0.7-18.el9_2", + "description": "OpenSSL is a toolkit for supporting cryptography. The openssl-perl\npackage provides Perl scripts for converting certificates and keys\nfrom other formats to the formats used by the OpenSSL toolkit.", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=s390x", + "evidence": { + "identity": [ + { + "field": "purl", + "concludedValue": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=s390x" + } + ] + }, + "properties": [ + { + "name": "package:rpm:sigmd5", + "value": "879e4c4ba7c890c9fba001534ea552b5" + }, + { + "name": "package:rpm:sha256header", + "value": "6f885dd8acf32d367528f47f0289a04035fddc1eb83b720bc6889293b94892fc" + } + ], + "bom-ref": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=s390x", + "hashes": [ + { + "alg": "SHA-256", + "content": "fe1ef3b6550b1f4153ac2d280f84d46bb36bb63a11ddaec7f76ff7fc0770d2db" + } + ] + }, + { + "bom-ref": "pkg:generic/openssl@3.0.7?package-id=d4557c28c5c2e817", + "type": "application", + "name": "openssl", + "version": "3.0.7", + "purl": "pkg:generic/openssl@3.0.7", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "binary-classifier-cataloger" + }, + { + "name": "syft:location:0:path", + "value": "/usr/bin/openssl" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src", + "provides": [ + "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=aarch64", + "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=aarch64", + "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=aarch64", + "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=aarch64", + "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=aarch64", + "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=aarch64", + "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=aarch64", + "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=ppc64le", + "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=ppc64le", + "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=ppc64le", + "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=ppc64le", + "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=ppc64le", + "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=ppc64le", + "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=ppc64le", + "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=i686", + "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=i686", + "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=i686", + "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=i686", + "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=i686", + "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=i686", + "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=i686", + "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=x86_64", + "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=x86_64", + "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=x86_64", + "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=x86_64", + "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=x86_64", + "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64", + "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=x86_64", + "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=s390x", + "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=s390x", + "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=s390x", + "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=s390x", + "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=s390x", + "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=s390x", + "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=s390x" + ] + }, + { + "ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=aarch64", + "provides": [ + "pkg:generic/openssl@3.0.7?package-id=d4557c28c5c2e817" + ] + }, + { + "ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=ppc64le", + "provides": [ + "pkg:generic/openssl@3.0.7?package-id=d4557c28c5c2e817" + ] + }, + { + "ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=i686", + "provides": [ + "pkg:generic/openssl@3.0.7?package-id=d4557c28c5c2e817" + ] + }, + { + "ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64", + "provides": [ + "pkg:generic/openssl@3.0.7?package-id=d4557c28c5c2e817" + ] + }, + { + "ref": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=s390x", + "provides": [ + "pkg:generic/openssl@3.0.7?package-id=d4557c28c5c2e817" + ] + } + ] +} diff --git a/etc/test-data/indigestable.json b/etc/test-data/indigestable.json new file mode 100644 index 000000000..745fa0439 --- /dev/null +++ b/etc/test-data/indigestable.json @@ -0,0 +1,32 @@ +{ + "SPDXVersion": "SPDX-2.2", + "DataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "DocumentName": "Example-SPDX-Document-using-AncestorOf", + "DocumentNamespace": "http://spdx.org/spdxdocs/example-spdx-document-123456", + "Creator": "Person: Jim Fuller-RedHat", + "Created": "2025-01-21T17:41:32Z", + "packages": [ + { + "PackageName": "PackageUpstreamA", + "SPDXID": "SPDXRef-PackageUpstreamA", + "PackageVersion": "1.0", + "PackageDownloadLocation": "NOASSERTION", + "FilesAnalyzed": false + }, + { + "PackageName": "PackageB", + "SPDXID": "SPDXRef-PackageB", + "PackageVersion": "2.0", + "PackageDownloadLocation": "NOASSERTION", + "FilesAnalyzed": false + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-PackageUpstreamA", + "relationshipType": "ANCESTOR_OF", + "relatedSpdxElement": "SPDXRef-PackageB" + } + ] +} diff --git a/etc/test-data/spdx/1178.json b/etc/test-data/spdx/1178.json new file mode 100644 index 000000000..5a27c0889 --- /dev/null +++ b/etc/test-data/spdx/1178.json @@ -0,0 +1,66 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "1970-01-01T13:30:00Z", + "creators": [ + "Trustify" + ], + "comment": "This is a simple example using an spdx SBOM with AncestorOf relationship.", + "licenseListVersion": "3.8" + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "uri:just-an-example", + "name": "simple", + "packages": [ + { + "SPDXID": "SPDXRef-UpstreamComponent", + "copyrightText": "NOASSERTION", + "downloadLocation": "foo", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:generic/upstream-component@0.0.0?arch=src", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseComments": "Licensing information is automatically generated and may be incomplete or incorrect.", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "name": "A", + "originator": "NOASSERTION", + "packageFileName": "NOASSERTION", + "supplier": "Organization: Red Hat", + "versionInfo": "1" + }, + { + "SPDXID": "SPDXRef-B", + "copyrightText": "NOASSERTION", + "downloadLocation": "foo", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:rpm/redhat/B@0.0.0", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseComments": "Licensing information is automatically generated and may be incomplete or incorrect.", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "name": "B", + "originator": "NOASSERTION", + "packageFileName": "NOASSERTION", + "supplier": "Organization: Red Hat", + "versionInfo": "1" + } + ], + "relationships" : [ + { + "spdxElementId" : "SPDXRef-UpstreamComponent", + "relatedSpdxElement" : "SPDXRef-B", + "relationshipType" : "ANCESTOR_OF" + } + ], + "spdxVersion": "SPDX-2.2" +} diff --git a/etc/test-data/spdx/openssl-3.0.7-18.el9_2.spdx.alias.json b/etc/test-data/spdx/openssl-3.0.7-18.el9_2.spdx.alias.json new file mode 100644 index 000000000..2439ac70e --- /dev/null +++ b/etc/test-data/spdx/openssl-3.0.7-18.el9_2.spdx.alias.json @@ -0,0 +1,1343 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2006-08-14T02:34:56Z", + "creators": [ + "Tool: example SPDX document only" + ] + }, + "name": "openssl-3.0.7-18.el9_2", + "documentNamespace": "https://www.redhat.com/openssl-3.0.7-18.el9_2.spdx.json", + "packages": [ + { + "SPDXID": "SPDXRef-SRPM", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.src.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src&foo=bar" + }, + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:/a:redhat:openssl:3.0.7::el9", + "referenceType": "cpe22Type" + }, + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:/a:redhat:openssl-foo:3.0.7::el9", + "referenceType": "cpe22Type" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "31b5079268339cff7ba65a0aee77930560c5adef4b1b3f8f5927a43ee46a56d9" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 0da276f660b44ab5bd128db8aa3d8ce1" + } + ] + }, + { + "SPDXID": "SPDXRef-Source0-origin", + "name": "openssl", + "versionInfo": "3.0.7", + "downloadLocation": "https://openssl.org/source/openssl-3.0.7.tar.gz", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e" + } + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:generic/openssl@3.0.7?download_url=https://openssl.org/source/openssl-3.0.7.tar.gz&checksum=SHA256:83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e" + } + ], + "packageFileName": "openssl-3.0.7.tar.gz" + }, + { + "SPDXID": "SPDXRef-Source0", + "name": "openssl", + "versionInfo": "3.0.7", + "downloadLocation": "https://github.com/(RH openssl midstream repo)/archive/refs/tags/3.0.7.tar.gz", + "packageFileName": "openssl-3.0.7-hobbled.tar.gz", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "4105046836812ed422922f851a57500118a99cc0f009b7eff2b3436110393377" + } + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:generic/openssl@3.0.7?download_url=https://github.com/(RH openssl midstream repo)/archive/refs/tags/3.0.7.tar.gz" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-perl", + "name": "openssl-perl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-perl-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "96e53b2da90ce5ad109ba659ce3ed1b5a819b108c95fc493f84847429898b2ed" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 4cc665dd3173c8952184293588f9ee46" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-debuginfo", + "name": "openssl-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debuginfo-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "8721bc9673ccc43f729485aba48fd75a927305980f48ee9d0b79d06937b68d16" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 8bf628bbb31e41bcc4139001ffca1fcb" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-libs", + "name": "openssl-libs", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "cae5941219fd64e75c2b29509c6fe712bef77181a586702275a46a5e812d4dd4" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: a4815b267d59c43662d6ea94d6b32eba" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-libs-debuginfo", + "name": "openssl-libs-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-debuginfo-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "036985b5bd34712963e4a9009dd196e4f583479283d88b6e908a231aa5bddfae" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: f9e91d29b3e3b10cb1fe6b792d6ec3c6" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "aaafa61c115ec37bb3895e124216ce46774069e49f6178248df085708ecb3878" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 2a8826422579bd43b49774d11a68e383" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-debugsource", + "name": "openssl-debugsource", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debugsource-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "036bd68632078d2b12e87f4541047823b5675d7e1141e56639cbc1e2e42c9f65" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: b6245825de11c602beff7870c7612542" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-devel", + "name": "openssl-devel", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-devel-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "deff41d222f613c3292d1bd0256c793c7fc7d5a4d61a24fa81f23990123bd79d" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 728afe59ed2768caea0634737878e2b9" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-perl", + "name": "openssl-perl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-perl-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "7ae23594204f2688d5b16be98782d5456080f55e6baf76172d8cb4e100c2507e" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 2b8f23243dc5469c31a1d78fe817cc63" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-libs", + "name": "openssl-libs", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "6c35abfc44cc24048921fd519bbcdba0bc43cf45cdece57df99ede720600a686" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 0a8de436e7188fd6a2eae746d0afce1f" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-devel", + "name": "openssl-devel", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-devel-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "b2e00a6e064e8efd9fac7b4633221ef5b3f49fb16e6eb2752cffae6965007cb7" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 8bc5175c12d9772a5dee1721f40f691e" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "10135a468b85a35b0373609ad48c50e71499d034c6f7e7455691b63f87105f12" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: c8006421182838d0db0559f89121e14d" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-debuginfo", + "name": "openssl-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debuginfo-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "8e14cfaf5ae8bf1858b8d262b6d891dfdcc31378b6805345b8c1e08e4f0ce442" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 7d0cccc64dfed49cf9954784fb52d6d8" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-debugsource", + "name": "openssl-debugsource", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debugsource-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "d7de194a98e577ebaa0344c55002f83c2d22b52bf62cac22920759f2679e8124" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: df3c6c904df9135c5dbfb125be69cdb6" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-libs-debuginfo", + "name": "openssl-libs-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-debuginfo-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "d6c29685fcd8a62504e223fcd4b520819118456cc65273e43c5fed19dd1d1a11" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 050d1106c338216f24cf1300b73e7647" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-libs-debuginfo", + "name": "openssl-libs-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-debuginfo-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "8897777ff03ce9308b8e7dd890e3c22c3b9f0da29d641d3844a52635ed926649" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: cc789f980e1db3b3b49106f09d94b459" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-libs", + "name": "openssl-libs", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "ed85dfebb27dadf0d786da2aa15ce0dfbcb442ed38846360a82ab6abf7b71334" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 3ceb671050a402f897728066d844414e" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-debugsource", + "name": "openssl-debugsource", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debugsource-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "d13f9f85d90d4d0e4f39bdc743f42dc064a6ca16e2cb85fe1695e8a08b9163b4" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 4437626dedb09a7d5fefea71391d9666" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-devel", + "name": "openssl-devel", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-devel-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "03d6054aafea54f4496f8c35550eec87f2ceb06566e3fd5eea0446bd91e3242e" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 427cd7fdea922cddcfbdd736b6bb8717" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-debuginfo", + "name": "openssl-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debuginfo-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "02867d6b799b5ebc56f1f945a474f59c6b1a8430da4acf3fa35dae9cf992ea58" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 497871ca44f5dd4859c73c507f7c40f8" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "b7b39d8d7a960d75da6347206927e5a9bf33642148a0d291b0205f70eac8bf42" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: eea71314fcc3f1be5af5d6773f82b6da" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-perl", + "name": "openssl-perl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-perl-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "d4732a4e60c831e1e8e4ddb89419a029accf2ee6dc1c2efe62e8bf20e97e2577" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 0c92f2f4cd6bd41361dc25ddd4ffbb21" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-libs-debuginfo", + "name": "openssl-libs-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-debuginfo-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "596206f99a50ede734b1f989977b210289debcfdd972666bd0d5ba03e7afbf19" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 62bc1369f326b14bfdb5450a59086c05" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-debuginfo", + "name": "openssl-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debuginfo-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "7fbdc911663e437e7be7e30f5bd87450f4ff38551ecbb3de27e362bb9f2ac2aa" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 746bb9be1752b46631e55fc2e8c1d48e" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-debugsource", + "name": "openssl-debugsource", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debugsource-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "96e137f9561e7669417fe5998ec2aa6ba55a67aef0c2c205eede5a5a5941e8ac" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 94d964a9a930e8c914e7601b9df395a0" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-perl", + "name": "openssl-perl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-perl-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "403624aed89502e17352b50f00c413104c9be31307477d02c3a7ae78cfcb1ca4" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 862b17959c237e619217fabcf9939212" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-libs", + "name": "openssl-libs", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "73e869a62715910bdec02ee3f0275a81ca96f539a07aced314182b6f4dc8c828" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: cceaf391f5defa464df7bb55b6ad7486" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "96045880ef4a2167abe9ea14f2b325402996a7671df8f594924dc24b6c2263e4" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: ebe25fb0a0033a8e0248869989e587e8" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-devel", + "name": "openssl-devel", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-devel-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "72f42e7c9ade55a24d3c53f4370d5d6d3b2fe99a4735d564825556ccd4cc1df3" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 94df67b3c5c8d3cb192ba79d6559b46f" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-libs-debuginfo", + "name": "openssl-libs-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-debuginfo-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "bc5de92a3830cd99f7d291ccaafb5fd640127c8c1c925231e1e37d060a69563f" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 5ccbf569de20ef8c965993391c024832" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-libs", + "name": "openssl-libs", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "0543b42f6491762fab8defbc6ec68a30d8e17e2f55e50b29095c382a6ad5baf1" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 349224e7170e1bcee0cf15c04164fe85" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-debugsource", + "name": "openssl-debugsource", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debugsource-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "90ca984f844882a5c0678887851cc58be90a187c65ef010b866791c40116f7fc" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 8ea0991914a7a90bccdee1264baaf74f" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-devel", + "name": "openssl-devel", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-devel-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "4a8588e587337d65cd2da98b0ba813a1e178a9b515c82bbc7fe96f1ba749b8fc" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 12e9fefcf812eaf6e727b53b613d7f88" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-debuginfo", + "name": "openssl-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debuginfo-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "0a2a6b1409b045894d15c1b05ff5be2c1feba0b2b5f2d3bbac9d2e44c93f0583" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: fd189955871f77470c91193b845f755f" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "11c42421e6d07bca92122575ac023016471383f58575b2dd478a19e0ba7ce4db" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 5678f991a6d453ae482989fbe2ef5e51" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-perl", + "name": "openssl-perl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-perl-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "6f885dd8acf32d367528f47f0289a04035fddc1eb83b720bc6889293b94892fc" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 879e4c4ba7c890c9fba001534ea552b5" + } + ] + } + ], + "files": [], + "relationships": [ + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-Source0", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-Source0-origin" + }, + { + "spdxElementId": "SPDXRef-SRPM", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-Source0" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-perl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-libs", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-libs-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-debugsource", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-devel", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-perl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-libs", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-devel", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-debugsource", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-libs-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-libs-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-libs", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-debugsource", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-devel", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-perl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-libs-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-debugsource", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-perl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-libs", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-devel", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-libs-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-libs", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-debugsource", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-devel", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-perl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + } + ] +} diff --git a/etc/test-data/spdx/openssl-3.0.7-18.el9_2.spdx.json b/etc/test-data/spdx/openssl-3.0.7-18.el9_2.spdx.json new file mode 100644 index 000000000..a9bf2d9ec --- /dev/null +++ b/etc/test-data/spdx/openssl-3.0.7-18.el9_2.spdx.json @@ -0,0 +1,1328 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2006-08-14T02:34:56Z", + "creators": [ + "Tool: example SPDX document only" + ] + }, + "name": "openssl-3.0.7-18.el9_2", + "documentNamespace": "https://www.redhat.com/openssl-3.0.7-18.el9_2.spdx.json", + "packages": [ + { + "SPDXID": "SPDXRef-SRPM", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.src.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "31b5079268339cff7ba65a0aee77930560c5adef4b1b3f8f5927a43ee46a56d9" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 0da276f660b44ab5bd128db8aa3d8ce1" + } + ] + }, + { + "SPDXID": "SPDXRef-Source0-origin", + "name": "openssl", + "versionInfo": "3.0.7", + "downloadLocation": "https://openssl.org/source/openssl-3.0.7.tar.gz", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e" + } + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:generic/openssl@3.0.7?download_url=https://openssl.org/source/openssl-3.0.7.tar.gz&checksum=SHA256:83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e" + } + ], + "packageFileName": "openssl-3.0.7.tar.gz" + }, + { + "SPDXID": "SPDXRef-Source0", + "name": "openssl", + "versionInfo": "3.0.7", + "downloadLocation": "https://github.com/(RH openssl midstream repo)/archive/refs/tags/3.0.7.tar.gz", + "packageFileName": "openssl-3.0.7-hobbled.tar.gz", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "4105046836812ed422922f851a57500118a99cc0f009b7eff2b3436110393377" + } + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:generic/openssl@3.0.7?download_url=https://github.com/(RH openssl midstream repo)/archive/refs/tags/3.0.7.tar.gz" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-perl", + "name": "openssl-perl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-perl-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "96e53b2da90ce5ad109ba659ce3ed1b5a819b108c95fc493f84847429898b2ed" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 4cc665dd3173c8952184293588f9ee46" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-debuginfo", + "name": "openssl-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debuginfo-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "8721bc9673ccc43f729485aba48fd75a927305980f48ee9d0b79d06937b68d16" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 8bf628bbb31e41bcc4139001ffca1fcb" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-libs", + "name": "openssl-libs", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "cae5941219fd64e75c2b29509c6fe712bef77181a586702275a46a5e812d4dd4" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: a4815b267d59c43662d6ea94d6b32eba" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-libs-debuginfo", + "name": "openssl-libs-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-debuginfo-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "036985b5bd34712963e4a9009dd196e4f583479283d88b6e908a231aa5bddfae" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: f9e91d29b3e3b10cb1fe6b792d6ec3c6" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "aaafa61c115ec37bb3895e124216ce46774069e49f6178248df085708ecb3878" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 2a8826422579bd43b49774d11a68e383" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-debugsource", + "name": "openssl-debugsource", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debugsource-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "036bd68632078d2b12e87f4541047823b5675d7e1141e56639cbc1e2e42c9f65" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: b6245825de11c602beff7870c7612542" + } + ] + }, + { + "SPDXID": "SPDXRef-aarch64-openssl-devel", + "name": "openssl-devel", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-devel-3.0.7-18.el9_2.aarch64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=aarch64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "deff41d222f613c3292d1bd0256c793c7fc7d5a4d61a24fa81f23990123bd79d" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 728afe59ed2768caea0634737878e2b9" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-perl", + "name": "openssl-perl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-perl-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "7ae23594204f2688d5b16be98782d5456080f55e6baf76172d8cb4e100c2507e" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 2b8f23243dc5469c31a1d78fe817cc63" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-libs", + "name": "openssl-libs", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "6c35abfc44cc24048921fd519bbcdba0bc43cf45cdece57df99ede720600a686" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 0a8de436e7188fd6a2eae746d0afce1f" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-devel", + "name": "openssl-devel", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-devel-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "b2e00a6e064e8efd9fac7b4633221ef5b3f49fb16e6eb2752cffae6965007cb7" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 8bc5175c12d9772a5dee1721f40f691e" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "10135a468b85a35b0373609ad48c50e71499d034c6f7e7455691b63f87105f12" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: c8006421182838d0db0559f89121e14d" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-debuginfo", + "name": "openssl-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debuginfo-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "8e14cfaf5ae8bf1858b8d262b6d891dfdcc31378b6805345b8c1e08e4f0ce442" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 7d0cccc64dfed49cf9954784fb52d6d8" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-debugsource", + "name": "openssl-debugsource", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debugsource-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "d7de194a98e577ebaa0344c55002f83c2d22b52bf62cac22920759f2679e8124" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: df3c6c904df9135c5dbfb125be69cdb6" + } + ] + }, + { + "SPDXID": "SPDXRef-ppc64le-openssl-libs-debuginfo", + "name": "openssl-libs-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-debuginfo-3.0.7-18.el9_2.ppc64le.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=ppc64le" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "d6c29685fcd8a62504e223fcd4b520819118456cc65273e43c5fed19dd1d1a11" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 050d1106c338216f24cf1300b73e7647" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-libs-debuginfo", + "name": "openssl-libs-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-debuginfo-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "8897777ff03ce9308b8e7dd890e3c22c3b9f0da29d641d3844a52635ed926649" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: cc789f980e1db3b3b49106f09d94b459" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-libs", + "name": "openssl-libs", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "ed85dfebb27dadf0d786da2aa15ce0dfbcb442ed38846360a82ab6abf7b71334" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 3ceb671050a402f897728066d844414e" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-debugsource", + "name": "openssl-debugsource", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debugsource-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "d13f9f85d90d4d0e4f39bdc743f42dc064a6ca16e2cb85fe1695e8a08b9163b4" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 4437626dedb09a7d5fefea71391d9666" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-devel", + "name": "openssl-devel", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-devel-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "03d6054aafea54f4496f8c35550eec87f2ceb06566e3fd5eea0446bd91e3242e" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 427cd7fdea922cddcfbdd736b6bb8717" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-debuginfo", + "name": "openssl-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debuginfo-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "02867d6b799b5ebc56f1f945a474f59c6b1a8430da4acf3fa35dae9cf992ea58" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 497871ca44f5dd4859c73c507f7c40f8" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "b7b39d8d7a960d75da6347206927e5a9bf33642148a0d291b0205f70eac8bf42" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: eea71314fcc3f1be5af5d6773f82b6da" + } + ] + }, + { + "SPDXID": "SPDXRef-i686-openssl-perl", + "name": "openssl-perl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-perl-3.0.7-18.el9_2.i686.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=i686" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "d4732a4e60c831e1e8e4ddb89419a029accf2ee6dc1c2efe62e8bf20e97e2577" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 0c92f2f4cd6bd41361dc25ddd4ffbb21" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-libs-debuginfo", + "name": "openssl-libs-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-debuginfo-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "596206f99a50ede734b1f989977b210289debcfdd972666bd0d5ba03e7afbf19" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 62bc1369f326b14bfdb5450a59086c05" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-debuginfo", + "name": "openssl-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debuginfo-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "7fbdc911663e437e7be7e30f5bd87450f4ff38551ecbb3de27e362bb9f2ac2aa" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 746bb9be1752b46631e55fc2e8c1d48e" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-debugsource", + "name": "openssl-debugsource", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debugsource-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "96e137f9561e7669417fe5998ec2aa6ba55a67aef0c2c205eede5a5a5941e8ac" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 94d964a9a930e8c914e7601b9df395a0" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-perl", + "name": "openssl-perl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-perl-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "403624aed89502e17352b50f00c413104c9be31307477d02c3a7ae78cfcb1ca4" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 862b17959c237e619217fabcf9939212" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-libs", + "name": "openssl-libs", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "73e869a62715910bdec02ee3f0275a81ca96f539a07aced314182b6f4dc8c828" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: cceaf391f5defa464df7bb55b6ad7486" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "96045880ef4a2167abe9ea14f2b325402996a7671df8f594924dc24b6c2263e4" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: ebe25fb0a0033a8e0248869989e587e8" + } + ] + }, + { + "SPDXID": "SPDXRef-x86-64-openssl-devel", + "name": "openssl-devel", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-devel-3.0.7-18.el9_2.x86_64.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=x86_64" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "72f42e7c9ade55a24d3c53f4370d5d6d3b2fe99a4735d564825556ccd4cc1df3" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 94df67b3c5c8d3cb192ba79d6559b46f" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-libs-debuginfo", + "name": "openssl-libs-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-debuginfo-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs-debuginfo@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "bc5de92a3830cd99f7d291ccaafb5fd640127c8c1c925231e1e37d060a69563f" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 5ccbf569de20ef8c965993391c024832" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-libs", + "name": "openssl-libs", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-libs-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-libs@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "0543b42f6491762fab8defbc6ec68a30d8e17e2f55e50b29095c382a6ad5baf1" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 349224e7170e1bcee0cf15c04164fe85" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-debugsource", + "name": "openssl-debugsource", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debugsource-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debugsource@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "90ca984f844882a5c0678887851cc58be90a187c65ef010b866791c40116f7fc" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 8ea0991914a7a90bccdee1264baaf74f" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-devel", + "name": "openssl-devel", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-devel-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-devel@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "4a8588e587337d65cd2da98b0ba813a1e178a9b515c82bbc7fe96f1ba749b8fc" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 12e9fefcf812eaf6e727b53b613d7f88" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-debuginfo", + "name": "openssl-debuginfo", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-debuginfo-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-debuginfo@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "0a2a6b1409b045894d15c1b05ff5be2c1feba0b2b5f2d3bbac9d2e44c93f0583" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: fd189955871f77470c91193b845f755f" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl", + "name": "openssl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "11c42421e6d07bca92122575ac023016471383f58575b2dd478a19e0ba7ce4db" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 5678f991a6d453ae482989fbe2ef5e51" + } + ] + }, + { + "SPDXID": "SPDXRef-s390x-openssl-perl", + "name": "openssl-perl", + "versionInfo": "3.0.7-18.el9_2", + "supplier": "Organization: Red Hat", + "downloadLocation": "NOASSERTION", + "packageFileName": "openssl-perl-3.0.7-18.el9_2.s390x.rpm", + "licenseConcluded": "Apache-2.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:rpm/redhat/openssl-perl@3.0.7-18.el9_2?arch=s390x" + } + ], + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "6f885dd8acf32d367528f47f0289a04035fddc1eb83b720bc6889293b94892fc" + } + ], + "annotations": [ + { + "annotationType": "OTHER", + "annotator": "Tool: example SPDX document only", + "annotationDate": "2006-08-14T02:34:56Z", + "comment": "sigmd5: 879e4c4ba7c890c9fba001534ea552b5" + } + ] + } + ], + "files": [], + "relationships": [ + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-Source0", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-Source0-origin" + }, + { + "spdxElementId": "SPDXRef-SRPM", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-Source0" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-perl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-libs", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-libs-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-debugsource", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-aarch64-openssl-devel", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-perl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-libs", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-devel", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-debugsource", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-ppc64le-openssl-libs-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-libs-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-libs", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-debugsource", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-devel", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-i686-openssl-perl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-libs-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-debugsource", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-perl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-libs", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-x86-64-openssl-devel", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-libs-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-libs", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-debugsource", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-devel", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-debuginfo", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + }, + { + "spdxElementId": "SPDXRef-s390x-openssl-perl", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-SRPM" + } + ] +} diff --git a/etc/test-data/spdx/simple.json b/etc/test-data/spdx/simple.json index 2fc01dec1..ef4f01ced 100644 --- a/etc/test-data/spdx/simple.json +++ b/etc/test-data/spdx/simple.json @@ -12,7 +12,6 @@ "documentNamespace": "uri:just-an-example", "name": "simple", "packages": [ - { "SPDXID": "SPDXRef-A", "copyrightText": "NOASSERTION", @@ -26,7 +25,7 @@ { "referenceCategory": "SECURITY", "referenceLocator": "cpe:/a:redhat:simple:1::el9", - "referenceType": "cpe23Type" + "referenceType": "cpe22Type" } ], "filesAnalyzed": false, diff --git a/migration/src/lib.rs b/migration/src/lib.rs index c18aa0824..2f902e110 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -100,6 +100,7 @@ mod m0000800_alter_product_version_range_scheme; mod m0000810_fix_get_purl; mod m0000820_create_conversation; mod m0000830_perf_indexes; +mod m0000840_add_relationship_14_15; pub struct Migrator; @@ -207,6 +208,7 @@ impl MigratorTrait for Migrator { Box::new(m0000810_fix_get_purl::Migration), Box::new(m0000820_create_conversation::Migration), Box::new(m0000830_perf_indexes::Migration), + Box::new(m0000840_add_relationship_14_15::Migration), ] } } diff --git a/migration/src/m0000720_alter_sbom_fix_null_array.rs b/migration/src/m0000720_alter_sbom_fix_null_array.rs index dc71b1a2c..10c6392be 100644 --- a/migration/src/m0000720_alter_sbom_fix_null_array.rs +++ b/migration/src/m0000720_alter_sbom_fix_null_array.rs @@ -6,7 +6,13 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.alter_table(Self::alter_table()).await?; + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000720_alter_sbom_fix_null_array/migration_up.sql" + )) + .await?; + Ok(()) } @@ -14,39 +20,3 @@ impl MigrationTrait for Migration { Ok(()) } } - -impl Migration { - fn alter_table() -> TableAlterStatement { - Table::alter() - .table(Sbom::Table) - .modify_column( - ColumnDef::new(Sbom::DataLicenses) - .array(ColumnType::Text) - .not_null() - .default(SimpleExpr::Custom("ARRAY[]::text[]".to_string())) - .to_owned(), - ) - .to_owned() - } -} - -#[derive(DeriveIden)] -enum Sbom { - Table, - DataLicenses, -} - -#[cfg(test)] -mod test { - use crate::m0000720_alter_sbom_fix_null_array::Migration; - use crate::PostgresQueryBuilder; - - #[test] - fn test() { - let sql = Migration::alter_table().build(PostgresQueryBuilder); - assert_eq!( - sql, - r#"ALTER TABLE "sbom" ALTER COLUMN "data_licenses" TYPE text[], ALTER COLUMN "data_licenses" SET NOT NULL, ALTER COLUMN "data_licenses" SET DEFAULT ARRAY[]::text[]"# - ); - } -} diff --git a/migration/src/m0000720_alter_sbom_fix_null_array/migration_up.sql b/migration/src/m0000720_alter_sbom_fix_null_array/migration_up.sql new file mode 100644 index 000000000..1d0a7cced --- /dev/null +++ b/migration/src/m0000720_alter_sbom_fix_null_array/migration_up.sql @@ -0,0 +1,4 @@ +ALTER TABLE "sbom" +ALTER COLUMN "data_licenses" TYPE text[] USING COALESCE("data_licenses", ARRAY[]::text[]), +ALTER COLUMN "data_licenses" SET NOT NULL, +ALTER COLUMN "data_licenses" SET DEFAULT ARRAY[]::text[] \ No newline at end of file diff --git a/migration/src/m0000840_add_relationship_14_15.rs b/migration/src/m0000840_add_relationship_14_15.rs new file mode 100644 index 000000000..d8dfaacd2 --- /dev/null +++ b/migration/src/m0000840_add_relationship_14_15.rs @@ -0,0 +1,41 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; +const DATA: [(i32, &str); 2] = [(14, "DescribedBy"), (15, "PackageOf")]; +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + for (id, description) in DATA { + let insert = Query::insert() + .into_table(Relationship::Table) + .columns([Relationship::Id, Relationship::Description]) + .values_panic([id.into(), description.into()]) + .to_owned(); + + manager.exec_stmt(insert).await?; + } + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + for (id, _) in DATA { + let insert = Query::delete() + .from_table(Relationship::Table) + .and_where(Expr::col(Relationship::Id).lt(id)) + .to_owned(); + + manager.exec_stmt(insert).await?; + } + + Ok(()) + } +} + +#[derive(DeriveIden)] +pub enum Relationship { + Table, + Id, + Description, +} diff --git a/modules/analysis/Cargo.toml b/modules/analysis/Cargo.toml index 0b2f1acda..f8309a777 100644 --- a/modules/analysis/Cargo.toml +++ b/modules/analysis/Cargo.toml @@ -13,12 +13,15 @@ trustify-entity = { workspace = true } actix-http = { workspace = true } actix-web = { workspace = true } anyhow = { workspace = true } +cpe = { workspace = true } log = { workspace = true } +parking_lot = { workspace = true } petgraph = { workspace = true } -parking_lot= { workspace = true } sea-orm = { workspace = true } sea-query = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } +spdx-rs = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } utoipa = { workspace = true, features = ["actix_extras", "uuid"] } @@ -32,6 +35,7 @@ bytesize = { workspace = true } chrono = { workspace = true } hex = { workspace = true } humantime = { workspace = true } +itertools = { workspace = true } jsonpath-rust = { workspace = true } log = { workspace = true } petgraph = { workspace = true } @@ -47,7 +51,3 @@ criterion = { workspace = true, features = ["html_reports", "async_tokio"] } csaf = { workspace = true } packageurl = { workspace = true } zip = { workspace = true } - - - - diff --git a/modules/analysis/README.md b/modules/analysis/README.md new file mode 100644 index 000000000..0b57ccff5 --- /dev/null +++ b/modules/analysis/README.md @@ -0,0 +1,21 @@ +# Analysis Graph + +## Get a root component + +```bash +http localhost:8080/api/v2/analysis/root-component/B +``` + +## Get a component + +With the name `B`: + +```bash +http localhost:8080/api/v2/analysis/component/B +``` + +With the PURL ``: + +```bash +http localhost:8080/api/v2/analysis/component/B +``` diff --git a/modules/analysis/src/endpoints.rs b/modules/analysis/src/endpoints.rs deleted file mode 100644 index eea34e800..000000000 --- a/modules/analysis/src/endpoints.rs +++ /dev/null @@ -1,512 +0,0 @@ -use super::service::AnalysisService; -use crate::{ - model::{AnalysisStatus, AncestorSummary, DepSummary}, - Error, -}; -use actix_web::{get, web, HttpResponse, Responder}; -use std::str::FromStr; -use trustify_auth::{ - authenticator::user::UserInformation, - authorizer::{Authorizer, Require}, - Permission, ReadSbom, -}; -use trustify_common::{db::query::Query, db::Database, model::Paginated, purl::Purl}; - -pub fn configure(config: &mut utoipa_actix_web::service_config::ServiceConfig, db: Database) { - let analysis = AnalysisService::new(); - - config - .app_data(web::Data::new(analysis)) - .app_data(web::Data::new(db)) - .service(search_component_root_components) - .service(get_component_root_components) - .service(analysis_status) - .service(search_component_deps) - .service(get_component_deps); -} - -#[utoipa::path( - tag = "analysis", - operation_id = "status", - responses( - (status = 200, description = "Analysis status.", body = AnalysisStatus), - ), -)] -#[get("/v2/analysis/status")] -pub async fn analysis_status( - service: web::Data, - db: web::Data, - user: UserInformation, - authorizer: web::Data, - _: Require, -) -> actix_web::Result { - authorizer.require(&user, Permission::ReadSbom)?; - Ok(HttpResponse::Ok().json(service.status(db.as_ref()).await?)) -} - -#[utoipa::path( - tag = "analysis", - operation_id = "searchComponentRootComponents", - params( - Query, - Paginated, - ), - responses( - (status = 200, description = "Search component(s) and return their root components.", body = AncestorSummary), - ), -)] -#[get("/v2/analysis/root-component")] -pub async fn search_component_root_components( - service: web::Data, - db: web::Data, - web::Query(search): web::Query, - web::Query(paginated): web::Query, - _: Require, -) -> actix_web::Result { - Ok(HttpResponse::Ok().json( - service - .retrieve_root_components(search, paginated, db.as_ref()) - .await?, - )) -} - -#[utoipa::path( - tag = "analysis", - operation_id = "getComponentRootComponents", - params( - ("key" = String, Path, description = "provide component name or URL-encoded pURL itself") - ), - responses( - (status = 200, description = "Retrieve component(s) root components by name or pURL.", body = AncestorSummary), - ), -)] -#[get("/v2/analysis/root-component/{key}")] -pub async fn get_component_root_components( - service: web::Data, - db: web::Data, - key: web::Path, - web::Query(paginated): web::Query, - _: Require, -) -> actix_web::Result { - if key.starts_with("pkg:") { - let purl: Purl = Purl::from_str(&key).map_err(Error::Purl)?; - Ok(HttpResponse::Ok().json( - service - .retrieve_root_components_by_purl(purl, paginated, db.as_ref()) - .await?, - )) - } else { - Ok(HttpResponse::Ok().json( - service - .retrieve_root_components_by_name(key.to_string(), paginated, db.as_ref()) - .await?, - )) - } -} - -#[utoipa::path( - tag = "analysis", - operation_id = "searchComponentDeps", - params( - Query, - Paginated, - ), - responses( - (status = 200, description = "Search component(s) and return their deps.", body = DepSummary), - ), -)] -#[get("/v2/analysis/dep")] -pub async fn search_component_deps( - service: web::Data, - db: web::Data, - web::Query(search): web::Query, - web::Query(paginated): web::Query, - _: Require, -) -> actix_web::Result { - Ok(HttpResponse::Ok().json( - service - .retrieve_deps(search, paginated, db.as_ref()) - .await?, - )) -} - -#[utoipa::path( - tag = "analysis", - operation_id = "getComponentDeps", - params( - ("key" = String, Path, description = "provide component name or URL-encoded pURL itself") - ), - responses( - (status = 200, description = "Retrieve component(s) dep components by name or pURL.", body = DepSummary), - ), -)] -#[get("/v2/analysis/dep/{key}")] -pub async fn get_component_deps( - service: web::Data, - db: web::Data, - key: web::Path, - web::Query(paginated): web::Query, - _: Require, -) -> actix_web::Result { - if key.starts_with("pkg:") { - let purl: Purl = Purl::from_str(&key).map_err(Error::Purl)?; - Ok(HttpResponse::Ok().json( - service - .retrieve_deps_by_purl(purl, paginated, db.as_ref()) - .await?, - )) - } else { - Ok(HttpResponse::Ok().json( - service - .retrieve_deps_by_name(key.to_string(), paginated, db.as_ref()) - .await?, - )) - } -} - -#[cfg(test)] -mod test { - use crate::test::caller; - use actix_http::Request; - use actix_web::test::TestRequest; - use serde_json::Value; - use test_context::test_context; - use test_log::test; - use trustify_test_context::{call::CallService, TrustifyContext}; - - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - async fn test_simple_retrieve_analysis_endpoint( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents(["spdx/simple.json"]).await?; - - //should match multiple components - let uri = "/api/v2/analysis/root-component?q=B"; - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - - if response["items"][0]["purl"] == "pkg:rpm/redhat/BB@0.0.0" - || response["items"][1]["purl"] == "pkg:rpm/redhat/BB@0.0.0" - { - assert_eq!(&response["total"], 2); - } else { - panic!("one of the items component should have matched."); - } - log::info!("{:?}", response); - - //should match a single component - let uri = "/api/v2/analysis/root-component?q=BB"; - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - assert_eq!(response["items"][0]["purl"], "pkg:rpm/redhat/BB@0.0.0"); - assert_eq!( - response["items"][0]["ancestors"][0]["purl"], - "pkg:rpm/redhat/AA@0.0.0?arch=src" - ); - - assert_eq!(&response["total"], 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - async fn test_simple_retrieve_by_name_analysis_endpoint( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let uri = "/api/v2/analysis/root-component/B"; - - let request: Request = TestRequest::get().uri(uri).to_request(); - - let response: Value = app.call_and_read_body_json(request).await; - - assert_eq!(response["items"][0]["purl"], "pkg:rpm/redhat/B@0.0.0"); - assert_eq!( - response["items"][0]["ancestors"][0]["purl"], - "pkg:rpm/redhat/A@0.0.0?arch=src" - ); - assert_eq!(&response["total"], 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - async fn test_simple_retrieve_by_purl_analysis_endpoint( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let uri = "/api/v2/analysis/root-component/pkg%3A%2F%2Frpm%2Fredhat%2FB%400.0.0"; - - let request: Request = TestRequest::get().uri(uri).to_request(); - - let response: Value = app.call_and_read_body_json(request).await; - - assert_eq!(response["items"][0]["purl"], "pkg:rpm/redhat/B@0.0.0"); - assert_eq!( - response["items"][0]["ancestors"][0]["purl"], - "pkg:rpm/redhat/A@0.0.0?arch=src" - ); - assert_eq!(&response["total"], 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - async fn test_quarkus_retrieve_analysis_endpoint( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents([ - "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", - "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", - ]) - .await?; - - let uri = "/api/v2/analysis/root-component?q=spymemcached"; - - let request: Request = TestRequest::get().uri(uri).to_request(); - - let response: Value = app.call_and_read_body_json(request).await; - - assert_eq!( - response["items"][0]["purl"], - "pkg:maven/net.spy/spymemcached@2.12.1?type=jar" - ); - assert_eq!( - response["items"][0]["document_id"], - "https://access.redhat.com/security/data/sbom/spdx/quarkus-bom-3.2.11.Final-redhat-00001" - ); - assert_eq!( - response["items"][0]["ancestors"][0]["purl"], - "pkg:maven/com.redhat.quarkus.platform/quarkus-bom@3.2.11.Final-redhat-00001?type=pom&repository_url=https%3a%2f%2fmaven.repository.redhat.com%2fga%2f" - ); - - assert_eq!(&response["total"], 2); - Ok(()) - } - - // TODO: this test passes when run individually. - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - #[ignore] - async fn test_status_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents(["spdx/simple.json"]).await?; - - //prime the graph hashmap - let uri = "/api/v2/analysis/root-component?q=BB"; - let load1 = TestRequest::get().uri(uri).to_request(); - let _response: Value = app.call_and_read_body_json(load1).await; - - let uri = "/api/v2/analysis/status"; - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - - assert_eq!(response["sbom_count"], 1); - assert_eq!(response["graph_count"], 1); - - // ingest duplicate sbom which has different date - ctx.ingest_documents(["spdx/simple-dup.json"]).await?; - - let uri = "/api/v2/analysis/status"; - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - - assert_eq!(response["sbom_count"], 2); - assert_eq!(response["graph_count"], 1); - - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - async fn test_simple_dep_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let uri = "/api/v2/analysis/dep?q=A"; - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - - assert_eq!( - response["items"][0]["purl"], - "pkg:rpm/redhat/A@0.0.0?arch=src" - ); - assert_eq!( - response["items"][0]["deps"][0]["purl"], - "pkg:rpm/redhat/EE@0.0.0?arch=src" - ); - assert_eq!( - response["items"][0]["deps"][1]["purl"], - "pkg:rpm/redhat/B@0.0.0" - ); - - assert_eq!(&response["total"], 3); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - async fn test_simple_dep_by_name_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let uri = "/api/v2/analysis/dep/A"; - - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - - assert_eq!( - response["items"][0]["purl"], - "pkg:rpm/redhat/A@0.0.0?arch=src" - ); - assert_eq!( - response["items"][0]["deps"][0]["purl"], - "pkg:rpm/redhat/EE@0.0.0?arch=src" - ); - assert_eq!( - response["items"][0]["deps"][1]["purl"], - "pkg:rpm/redhat/B@0.0.0" - ); - - assert_eq!(&response["total"], 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - async fn test_simple_dep_by_purl_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let uri = "/api/v2/analysis/dep/pkg%3A%2F%2Frpm%2Fredhat%2FAA%400.0.0%3Farch%3Dsrc"; - - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - - assert_eq!( - response["items"][0]["purl"], - "pkg:rpm/redhat/AA@0.0.0?arch=src" - ); - assert_eq!( - response["items"][0]["deps"][0]["purl"], - "pkg:rpm/redhat/BB@0.0.0" - ); - assert_eq!(&response["total"], 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - async fn test_quarkus_dep_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents([ - "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", - "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", - ]) - .await?; - - let uri = "/api/v2/analysis/dep?q=spymemcached"; - - let request: Request = TestRequest::get().uri(uri).to_request(); - - let response: Value = app.call_and_read_body_json(request).await; - - assert_eq!( - response["items"][0]["purl"], - "pkg:maven/net.spy/spymemcached@2.12.1?type=jar" - ); - assert_eq!(&response["total"], 2); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - async fn test_retrieve_query_params_endpoint( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents(["spdx/simple.json"]).await?; - - // filter on node_id - let uri = "/api/v2/analysis/dep?q=node_id%3DSPDXRef-A"; - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - assert_eq!(response["items"][0]["name"], "A"); - assert_eq!(&response["total"], 1); - - // filter on node_id - let uri = "/api/v2/analysis/root-component?q=node_id%3DSPDXRef-B"; - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - assert_eq!(response["items"][0]["name"], "B"); - assert_eq!(&response["total"], 1); - - // filter on node_id & name - let uri = "/api/v2/analysis/root-component?q=node_id%3DSPDXRef-B%26name%3DB"; - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - assert_eq!(response["items"][0]["name"], "B"); - assert_eq!(&response["total"], 1); - - // filter on sbom_id (which has urn:uuid: prefix) - let sbom_id = response["items"][0]["sbom_id"].as_str().unwrap(); - let uri = format!("/api/v2/analysis/root-component?q=sbom_id={}", sbom_id); - let request: Request = TestRequest::get().uri(uri.clone().as_str()).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - assert_eq!(&response["total"], 8); - - // negative test - let uri = "/api/v2/analysis/root-component?q=sbom_id=urn:uuid:99999999-9999-9999-9999-999999999999"; - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - assert_eq!(&response["total"], 0); - - // negative test - let uri = "/api/v2/analysis/root-component?q=node_id%3DSPDXRef-B%26name%3DA"; - let request: Request = TestRequest::get().uri(uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - - assert_eq!(&response["total"], 0); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(actix_web::test)] - async fn issue_tc_2050(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents(["cyclonedx/openssl-3.0.7-18.el9_2.cdx_1.6.sbom.json"]) - .await?; - - // Find all deps of src rpm - let src = "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src"; - let uri = format!("/api/v2/analysis/dep/{}", urlencoding::encode(src)); - let request: Request = TestRequest::get().uri(&uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - log::debug!("{response:#?}"); - assert_eq!(35, response["items"][0]["deps"].as_array().unwrap().len()); - - // Ensure binary rpm GeneratedFrom src rpm - let x86 = "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64"; - let uri = format!( - "/api/v2/analysis/root-component/{}", - urlencoding::encode(x86) - ); - let request: Request = TestRequest::get().uri(&uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - log::debug!("{response:#?}"); - assert_eq!( - "GeneratedFrom", - response["items"][0]["ancestors"][0]["relationship"] - ); - assert_eq!(src, response["items"][0]["ancestors"][0]["purl"]); - - Ok(()) - } -} diff --git a/modules/analysis/src/endpoints/mod.rs b/modules/analysis/src/endpoints/mod.rs new file mode 100644 index 000000000..b5db2a773 --- /dev/null +++ b/modules/analysis/src/endpoints/mod.rs @@ -0,0 +1,186 @@ +mod query; + +#[cfg(test)] +mod test; + +use super::service::AnalysisService; +use crate::{ + endpoints::query::OwnedComponentReference, + model::{AnalysisStatus, AncestorSummary, BaseSummary, DepSummary}, +}; +use actix_web::{get, web, HttpResponse, Responder}; +use trustify_auth::{ + authenticator::user::UserInformation, + authorizer::{Authorizer, Require}, + Permission, ReadSbom, +}; +use trustify_common::{ + db::{query::Query, Database}, + model::{Paginated, PaginatedResults}, +}; +use utoipa_actix_web::service_config::ServiceConfig; + +pub fn configure(config: &mut ServiceConfig, db: Database) { + let analysis = AnalysisService::new(); + + config + .app_data(web::Data::new(analysis)) + .app_data(web::Data::new(db)) + .service(search_component_root_components) + .service(get_component_root_components) + .service(get_component) + .service(analysis_status) + .service(search_component_deps) + .service(get_component_deps); +} + +#[utoipa::path( + tag = "analysis", + operation_id = "status", + responses( + (status = 200, description = "Analysis status.", body = AnalysisStatus), + ), +)] +#[get("/v2/analysis/status")] +pub async fn analysis_status( + service: web::Data, + db: web::Data, + user: UserInformation, + authorizer: web::Data, + _: Require, +) -> actix_web::Result { + authorizer.require(&user, Permission::ReadSbom)?; + Ok(HttpResponse::Ok().json(service.status(db.as_ref()).await?)) +} + +#[utoipa::path( + tag = "analysis", + operation_id = "searchComponentRootComponents", + params( + Query, + Paginated, + ), + responses( + (status = 200, description = "Search component(s) and return their root components.", body = PaginatedResults), + ), +)] +#[get("/v2/analysis/root-component")] +pub async fn search_component_root_components( + service: web::Data, + db: web::Data, + web::Query(search): web::Query, + web::Query(paginated): web::Query, + _: Require, +) -> actix_web::Result { + Ok(HttpResponse::Ok().json( + service + .retrieve_root_components(&search, paginated, db.as_ref()) + .await?, + )) +} + +#[utoipa::path( + tag = "analysis", + operation_id = "getComponentRootComponents", + params( + ("key" = String, Path, description = "provide component name, URL-encoded pURL, or CPE itself") + ), + responses( + (status = 200, description = "Retrieve component(s) root components by name, pURL, or CPE.", body = PaginatedResults), + ), +)] +#[get("/v2/analysis/root-component/{key}")] +pub async fn get_component_root_components( + service: web::Data, + db: web::Data, + key: web::Path, + web::Query(paginated): web::Query, + _: Require, +) -> actix_web::Result { + let query = OwnedComponentReference::try_from(key.as_str())?; + + Ok(HttpResponse::Ok().json( + service + .retrieve_root_components(&query, paginated, db.as_ref()) + .await?, + )) +} + +#[utoipa::path( + tag = "analysis", + operation_id = "getComponent", + params( + ("key" = String, Path, description = "provide component name, URL-encoded pURL, or CPE itself") + ), + responses( + (status = 200, description = "Retrieve component(s) root components by name, pURL, or CPE.", body = PaginatedResults), + ), +)] +#[get("/v2/analysis/component/{key}")] +pub async fn get_component( + service: web::Data, + db: web::Data, + key: web::Path, + web::Query(paginated): web::Query, + _: Require, +) -> actix_web::Result { + let query = OwnedComponentReference::try_from(key.as_str())?; + + Ok(HttpResponse::Ok().json( + service + .retrieve_components(&query, paginated, db.as_ref()) + .await?, + )) +} + +#[utoipa::path( + tag = "analysis", + operation_id = "searchComponentDeps", + params( + Query, + Paginated, + ), + responses( + (status = 200, description = "Search component(s) and return their deps.", body = PaginatedResults), + ), +)] +#[get("/v2/analysis/dep")] +pub async fn search_component_deps( + service: web::Data, + db: web::Data, + web::Query(search): web::Query, + web::Query(paginated): web::Query, + _: Require, +) -> actix_web::Result { + Ok(HttpResponse::Ok().json( + service + .retrieve_deps(&search, paginated, db.as_ref()) + .await?, + )) +} + +#[utoipa::path( + tag = "analysis", + operation_id = "getComponentDeps", + params( + ("key" = String, Path, description = "provide component name or URL-encoded pURL itself") + ), + responses( + (status = 200, description = "Retrieve component(s) dep components by name or pURL.", body = PaginatedResults), + ), +)] +#[get("/v2/analysis/dep/{key}")] +pub async fn get_component_deps( + service: web::Data, + db: web::Data, + key: web::Path, + web::Query(paginated): web::Query, + _: Require, +) -> actix_web::Result { + let query = OwnedComponentReference::try_from(key.as_str())?; + Ok(HttpResponse::Ok().json( + service + .retrieve_deps(&query, paginated, db.as_ref()) + .await?, + )) +} diff --git a/modules/analysis/src/endpoints/query.rs b/modules/analysis/src/endpoints/query.rs new file mode 100644 index 000000000..a672cb16b --- /dev/null +++ b/modules/analysis/src/endpoints/query.rs @@ -0,0 +1,46 @@ +use crate::{ + service::{ComponentReference, GraphQuery}, + Error, +}; +use std::str::FromStr; +use trustify_common::{cpe::Cpe, purl::Purl}; + +#[derive(Clone, Debug, Eq, PartialEq)] +#[allow(clippy::large_enum_variant)] +pub enum OwnedComponentReference { + Name(String), + Purl(Purl), + Cpe(Cpe), +} + +impl<'a> From<&'a OwnedComponentReference> for ComponentReference<'a> { + fn from(value: &'a OwnedComponentReference) -> Self { + match value { + OwnedComponentReference::Name(value) => ComponentReference::Name(value), + OwnedComponentReference::Purl(value) => ComponentReference::Purl(value), + OwnedComponentReference::Cpe(value) => ComponentReference::Cpe(value), + } + } +} + +impl<'a> From<&'a OwnedComponentReference> for GraphQuery<'a> { + fn from(value: &'a OwnedComponentReference) -> Self { + GraphQuery::Component(value.into()) + } +} + +impl TryFrom<&str> for OwnedComponentReference { + type Error = Error; + + fn try_from(value: &str) -> Result { + if value.starts_with("pkg:") { + let purl = Purl::from_str(value).map_err(Error::Purl)?; + Ok(OwnedComponentReference::Purl(purl)) + } else if value.starts_with("cpe:") { + let cpe = Cpe::from_str(value).map_err(Error::Cpe)?; + Ok(OwnedComponentReference::Cpe(cpe)) + } else { + Ok(OwnedComponentReference::Name(value.to_string())) + } + } +} diff --git a/modules/analysis/src/endpoints/test.rs b/modules/analysis/src/endpoints/test.rs new file mode 100644 index 000000000..4ce3a1b87 --- /dev/null +++ b/modules/analysis/src/endpoints/test.rs @@ -0,0 +1,737 @@ +use crate::test::caller; +use actix_http::Request; +use actix_web::test::TestRequest; +use itertools::Itertools; +use serde_json::{json, Value}; +use std::collections::HashMap; +use test_context::test_context; +use test_log::test; +use trustify_test_context::{call::CallService, TrustifyContext}; + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_simple_retrieve_analysis_endpoint( + ctx: &TrustifyContext, +) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["spdx/simple.json"]).await?; + + //should match multiple components + let uri = "/api/v2/analysis/root-component?q=B"; + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + + if response["items"][0]["purl"] + .as_array() + .unwrap() + .contains(&Value::from("pkg:rpm/redhat/BB@0.0.0")) + || response["items"][1]["purl"] + .as_array() + .unwrap() + .contains(&Value::from("pkg:rpm/redhat/BB@0.0.0")) + { + assert_eq!(&response["total"], 2); + } else { + panic!("one of the items component should have matched."); + } + log::info!("{:?}", response); + + //should match a single component + let uri = "/api/v2/analysis/root-component?q=BB"; + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + assert_eq!( + response["items"][0]["purl"], + Value::from(["pkg:rpm/redhat/BB@0.0.0"]) + ); + assert_eq!( + response["items"][0]["ancestors"][0]["purl"], + Value::from(["pkg:rpm/redhat/AA@0.0.0?arch=src"]) + ); + + assert_eq!(&response["total"], 1); + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_simple_retrieve_by_name_analysis_endpoint( + ctx: &TrustifyContext, +) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let uri = "/api/v2/analysis/root-component/B"; + + let request: Request = TestRequest::get().uri(uri).to_request(); + + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!( + response["items"][0]["purl"], + Value::from(["pkg:rpm/redhat/B@0.0.0"]) + ); + assert_eq!( + response["items"][0]["ancestors"][0]["purl"], + Value::from(["pkg:rpm/redhat/A@0.0.0?arch=src"]) + ); + assert_eq!(&response["total"], 1); + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_simple_retrieve_by_purl_analysis_endpoint( + ctx: &TrustifyContext, +) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let uri = "/api/v2/analysis/root-component/pkg%3A%2F%2Frpm%2Fredhat%2FB%400.0.0"; + + let request: Request = TestRequest::get().uri(uri).to_request(); + + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!( + response["items"][0]["purl"], + Value::from(["pkg:rpm/redhat/B@0.0.0"]) + ); + assert_eq!( + response["items"][0]["ancestors"][0]["purl"], + Value::from(["pkg:rpm/redhat/A@0.0.0?arch=src"]) + ); + assert_eq!(&response["total"], 1); + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_quarkus_retrieve_analysis_endpoint( + ctx: &TrustifyContext, +) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents([ + "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", + "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", + ]) + .await?; + + let uri = "/api/v2/analysis/root-component?q=spymemcached"; + + let request: Request = TestRequest::get().uri(uri).to_request(); + + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!( + response["items"][0]["purl"], + Value::from(["pkg:maven/net.spy/spymemcached@2.12.1?type=jar"]) + ); + assert_eq!( + response["items"][0]["document_id"], + "https://access.redhat.com/security/data/sbom/spdx/quarkus-bom-3.2.11.Final-redhat-00001" + ); + assert_eq!( + response["items"][0]["ancestors"][0]["purl"], + Value::from(["pkg:maven/com.redhat.quarkus.platform/quarkus-bom@3.2.11.Final-redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=pom"]) + ); + + assert_eq!(&response["total"], 2); + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_status_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["spdx/simple.json"]).await?; + + // prime the graph hashmap + let uri = "/api/v2/analysis/root-component?q=BB"; + let load1 = TestRequest::get().uri(uri).to_request(); + let _response: Value = app.call_and_read_body_json(load1).await; + + let uri = "/api/v2/analysis/status"; + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!(response["sbom_count"], 1); + assert_eq!(response["graph_count"], 1); + + // ingest duplicate sbom which has different date + ctx.ingest_documents(["spdx/simple-dup.json"]).await?; + + let uri = "/api/v2/analysis/status"; + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!(response["sbom_count"], 2); + assert_eq!(response["graph_count"], 1); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_simple_dep_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let uri = "/api/v2/analysis/dep?q=A"; + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!( + response["items"][0]["purl"], + Value::from(["pkg:rpm/redhat/A@0.0.0?arch=src"]), + ); + + let purls = response["items"][0]["deps"] + .as_array() + .iter() + .flat_map(|deps| *deps) + .flat_map(|dep| dep["purl"].as_array()) + .flatten() + .flat_map(|purl| purl.as_str().map(|s| s.to_string())) + .sorted() + .collect::>(); + + assert_eq!( + purls, + &["pkg:rpm/redhat/B@0.0.0", "pkg:rpm/redhat/EE@0.0.0?arch=src"] + ); + + assert_eq!(&response["total"], 2); + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_simple_dep_by_name_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let uri = "/api/v2/analysis/dep/A"; + + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!( + response["items"][0]["purl"], + Value::from(["pkg:rpm/redhat/A@0.0.0?arch=src"]), + ); + assert_eq!( + response["items"][0]["deps"][0]["purl"], + Value::from(["pkg:rpm/redhat/EE@0.0.0?arch=src"]), + ); + assert_eq!( + response["items"][0]["deps"][1]["purl"], + Value::from(["pkg:rpm/redhat/B@0.0.0"]), + ); + + assert_eq!(&response["total"], 1); + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_simple_dep_by_purl_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let uri = "/api/v2/analysis/dep/pkg%3A%2F%2Frpm%2Fredhat%2FAA%400.0.0%3Farch%3Dsrc"; + + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!( + response["items"][0]["purl"], + Value::from(["pkg:rpm/redhat/AA@0.0.0?arch=src"]), + ); + assert_eq!( + response["items"][0]["deps"][0]["purl"], + Value::from(["pkg:rpm/redhat/BB@0.0.0"]), + ); + assert_eq!(&response["total"], 1); + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_quarkus_dep_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents([ + "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", + "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", + ]) + .await?; + + let uri = "/api/v2/analysis/dep?q=spymemcached"; + + let request: Request = TestRequest::get().uri(uri).to_request(); + + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!( + response["items"][0]["purl"], + Value::from(["pkg:maven/net.spy/spymemcached@2.12.1?type=jar"]), + ); + assert_eq!(&response["total"], 2); + Ok(()) +} + +/// find a component by purl +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn quarkus_component_by_purl(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents([ + "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", + "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", + ]) + .await?; + + let uri = format!( + "/api/v2/analysis/component/{}", + urlencoding::encode("pkg:maven/com.redhat.quarkus.platform/quarkus-bom@3.2.11.Final-redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=pom") + ); + + let request: Request = TestRequest::get().uri(&uri).to_request(); + + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!( + response["items"][0]["purl"], + json!(["pkg:maven/com.redhat.quarkus.platform/quarkus-bom@3.2.11.Final-redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=pom"]), + ); + assert_eq!( + response["items"][0]["cpe"], + json!(["cpe:/a:redhat:quarkus:3.2:*:el8:*"]), + ); + assert_eq!(&response["total"], 1); + + Ok(()) +} + +/// find a component by cpe +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn quarkus_component_by_cpe(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents([ + "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", + "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", + ]) + .await?; + + let uri = format!( + "/api/v2/analysis/component/{}", + urlencoding::encode("cpe:/a:redhat:quarkus:3.2::el8") + ); + + let request: Request = TestRequest::get().uri(&uri).to_request(); + + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!( + response["items"][0]["cpe"], + json!(["cpe:/a:redhat:quarkus:3.2:*:el8:*"]), + ); + assert_eq!( + response["items"][0]["purl"], + json!(["pkg:maven/com.redhat.quarkus.platform/quarkus-bom@3.2.11.Final-redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=pom"]), + ); + assert_eq!(&response["total"], 2); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_retrieve_query_params_endpoint(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["spdx/simple.json"]).await?; + + // filter on node_id + let uri = "/api/v2/analysis/dep?q=node_id%3DSPDXRef-A"; + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + assert_eq!(response["items"][0]["name"], "A"); + assert_eq!(&response["total"], 1); + + // filter on node_id + let uri = "/api/v2/analysis/root-component?q=node_id%3DSPDXRef-B"; + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + assert_eq!(response["items"][0]["name"], "B"); + assert_eq!(&response["total"], 1); + + // filter on node_id & name + let uri = "/api/v2/analysis/root-component?q=node_id%3DSPDXRef-B%26name%3DB"; + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + assert_eq!(response["items"][0]["name"], "B"); + assert_eq!(&response["total"], 1); + + // filter on sbom_id (which has urn:uuid: prefix) + let sbom_id = response["items"][0]["sbom_id"].as_str().unwrap(); + let uri = format!("/api/v2/analysis/root-component?q=sbom_id={}", sbom_id); + let request: Request = TestRequest::get().uri(uri.clone().as_str()).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + assert_eq!(&response["total"], 9); + + // negative test + let uri = + "/api/v2/analysis/root-component?q=sbom_id=urn:uuid:99999999-9999-9999-9999-999999999999"; + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + assert_eq!(&response["total"], 0); + + // negative test + let uri = "/api/v2/analysis/root-component?q=node_id%3DSPDXRef-B%26name%3DA"; + let request: Request = TestRequest::get().uri(uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + + assert_eq!(&response["total"], 0); + Ok(()) +} + +fn count_deps(response: &Value, filter: F) -> HashMap<&str, usize> +where + F: Fn(&Value) -> bool, +{ + let mut num = HashMap::new(); + + for item in response["items"].as_array().unwrap() { + num.insert( + item["node_id"].as_str().unwrap(), + item["deps"] + .as_array() + .into_iter() + .flatten() + .filter(|f| filter(f)) + .count(), + ); + } + + num +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn cdx_generated_from(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["cyclonedx/openssl-3.0.7-18.el9_2.cdx_1.6.sbom.json"]) + .await?; + + // Find all deps of src rpm + let src = "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src"; + let uri = format!("/api/v2/analysis/dep/{}", urlencoding::encode(src)); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + + let num = count_deps(&response, |m| m["relationship"] == "GeneratedFrom"); + assert_eq!(num["pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src"], 35); + + // Ensure binary rpm GeneratedFrom src rpm + let x86 = "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64"; + let uri = format!( + "/api/v2/analysis/root-component/{}", + urlencoding::encode(x86) + ); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + assert_eq!( + "GeneratedFrom", + response["items"][0]["ancestors"][0]["relationship"] + ); + assert_eq!( + Value::from(vec![Value::from(src)]), + response["items"][0]["ancestors"][0]["purl"] + ); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn spdx_generated_from(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["spdx/openssl-3.0.7-18.el9_2.spdx.json"]) + .await?; + + // Find all deps of src rpm + let src = "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src"; + let uri = format!("/api/v2/analysis/dep/{}", urlencoding::encode(src)); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + + let num = count_deps(&response, |m| m["relationship"] == "GeneratedFrom"); + assert_eq!(num["SPDXRef-SRPM"], 35); + + // Ensure binary rpm GeneratedFrom src rpm + let x86 = "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64"; + let uri = format!( + "/api/v2/analysis/root-component/{}", + urlencoding::encode(x86) + ); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + assert_eq!( + "GeneratedFrom", + response["items"][0]["ancestors"][0]["relationship"] + ); + assert_eq!( + Value::from(vec![Value::from(src)]), + response["items"][0]["ancestors"][0]["purl"] + ); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn cdx_variant_of(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["cyclonedx/66FF73123BB3489.json"]) + .await?; + + // Find all deps of parent + let parent = "pkg:oci/openshift-ose-console@sha256:94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679"; + let uri = format!("/api/v2/analysis/dep/{}", urlencoding::encode(parent)); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + + let num = count_deps(&response, |_| true); + assert_eq!(num["pkg:oci/openshift-ose-console@sha256%3A94a0d7feec34600a858c8e383ee0e8d5f4a077f6bbc327dcad8762acfcf40679"], 1); + + // Ensure child is variant of src + let child = "pkg:oci/ose-console@sha256:c2d69e860b7457eb42f550ba2559a0452ec3e5c9ff6521d758c186266247678e?arch=s390x&os=linux&tag=v4.14.0-202412110104.p0.g350e1ea.assembly.stream.el8"; + let uri = format!( + "/api/v2/analysis/root-component/{}", + urlencoding::encode(child) + ); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + assert_eq!( + "VariantOf", + response["items"][0]["ancestors"][0]["relationship"] + ); + assert_eq!( + Value::from(vec![Value::from(parent)]), + response["items"][0]["ancestors"][0]["purl"] + ); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn spdx_variant_of(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["ubi9-9.2-755.1697625012.json"]) + .await?; + + // Find all deps of "parent" package + let parent = "pkg:oci/ubi9-container@sha256:2f168398c538b287fd705519b83cd5b604dc277ef3d9f479c28a2adb4d830a49?repository_url=registry.redhat.io/ubi9&tag=9.2-755.1697625012"; + let uri = format!("/api/v2/analysis/dep/{}", urlencoding::encode(parent)); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + assert_eq!( + 4, + response["items"][0]["deps"] + .as_array() + .into_iter() + .flatten() + .filter(|m| m["relationship"] == "VariantOf") + .count() + ); + + // Ensure VariantOf relationships + let purls = [ + "pkg:oci/ubi9-container@sha256:d4c5d9c980678267b81c3c197a4a0dd206382111c912875a6cdffc6ca319b769?arch=aarch64&repository_url=registry.redhat.io/ubi9&tag=9.2-755.1697625012", + "pkg:oci/ubi9-container@sha256:204383c3d96c0e6c7154c91d07764f92035738dd67aa8896679f7feb73f66bfd?arch=x86_64&repository_url=registry.redhat.io/ubi9&tag=9.2-755.1697625012", + "pkg:oci/ubi9-container@sha256:721ca837c80c8b98752010a17ffccbdf17a0d260ddd916b7097f04187f6aa3a8?arch=s390x&repository_url=registry.redhat.io/ubi9&tag=9.2-755.1697625012", + "pkg:oci/ubi9-container@sha256:9a6092cdd8e7f4361ea3f508ae6d6d3d9dbb9458a921ab09e4cc006c0a7f0a61?arch=ppc64le&repository_url=registry.redhat.io/ubi9&tag=9.2-755.1697625012", + ]; + for purl in purls { + let uri = format!( + "/api/v2/analysis/root-component/{}", + urlencoding::encode(purl) + ); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + assert_eq!( + "VariantOf", + response["items"][0]["ancestors"][0]["relationship"] + ); + assert_eq!( + Value::from(vec![Value::from(parent)]), + response["items"][0]["ancestors"][0]["purl"] + ); + } + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn cdx_ancestor_of(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["cyclonedx/openssl-3.0.7-18.el9_2.cdx_1.6.sbom.json"]) + .await?; + + let parent = "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src"; + let child = "pkg:generic/openssl@3.0.7?checksum=SHA-512:1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e&download_url=https://pkgs.devel.redhat.com/repo/openssl/openssl-3.0.7-hobbled.tar.gz/sha512/1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e/openssl-3.0.7-hobbled.tar.gz"; + + // Ensure parent has deps that include the child + let uri = format!("/api/v2/analysis/dep/{}", urlencoding::encode(parent)); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + + // get all PURLs of 'AncestorOf' for all dependencies of the parent + let deps: Vec<_> = response["items"] + .as_array() + .into_iter() + .flatten() + // we're only looking for the parent node + .filter(|m| m["node_id"] == parent) + // flatten all dependencies of that parent node + .flat_map(|m| m["deps"].as_array().into_iter().flatten()) + // filter out all non-AncestorOf dependencies + .filter(|m| m["relationship"] == "AncestorOf") + .collect(); + + // check if there is one dependency of type 'AncestorOf' in the parent package dependencies + assert_eq!(1, deps.len()); + // that dependency must have a single purl + assert_eq!(1, deps[0]["purl"].as_array().unwrap().len()); + // that purl must be the child purl + assert_eq!(child, deps[0]["purl"][0]); + + // Ensure child has ancestors that include the parent + let uri = format!( + "/api/v2/analysis/root-component/{}", + urlencoding::encode(child) + ); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + assert_eq!( + "AncestorOf", + response["items"][0]["ancestors"][0]["relationship"] + ); + assert_eq!( + Value::from(vec![Value::from(parent)]), + response["items"][0]["ancestors"][0]["purl"] + ); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn spdx_package_of(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + // test case for the simple case of "relationshipType": "PACKAGE_OF" spdx relationships: + // https://github.com/trustification/trustify/issues/1140 + + let app = caller(ctx).await?; + ctx.ingest_document("spdx/SATELLITE-6.15-RHEL-8.json") + .await?; + + let purl = "pkg:rpm/redhat/rubygem-google-cloud-compute@0.5.0-1.el8sat?arch=src"; + + // Ensure parent has deps that include the child + let uri = format!("/api/v2/analysis/dep/{}", urlencoding::encode(purl)); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + + let sbom = &response["items"][0]; + let matches: Vec<_> = sbom["deps"] + .as_array() + .into_iter() + .flatten() + .filter(|m| { + m == &&json!({ + "sbom_id": sbom["sbom_id"], + "node_id": "SPDXRef-83c9faa0-ca85-4e48-9165-707b2f9a324b", + "relationship": "PackageOf", + "purl": [], + "cpe": m["cpe"], // long list assume it's correct + "name": "SATELLITE-6.15-RHEL-8", + "version": "6.15", + "deps": [], + }) + }) + .collect(); + + assert_eq!(1, matches.len()); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn spdx_ancestor_of(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + ctx.ingest_documents(["spdx/1178.json"]).await?; + + // This smells a little funny... are they backward? + let parent = "pkg:rpm/redhat/B@0.0.0"; + let child = "pkg:generic/upstream-component@0.0.0?arch=src"; + + // Ensure parent has deps that include the child + let uri = format!("/api/v2/analysis/dep/{}", urlencoding::encode(parent)); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + let item = &response["items"][0]; + let deps = &item["deps"]; + let dep = &deps[0]; + + // assert array lengths + assert!(response["items"].get(1).is_none()); + assert!(item["purl"].get(1).is_none()); + assert!(deps.get(1).is_none()); + assert!(dep["purl"].get(1).is_none()); + + // assert expected values + assert_eq!(parent, item["purl"][0]); + assert_eq!("AncestorOf", dep["relationship"]); + assert_eq!(child, dep["purl"][0]); + + // Ensure child has ancestors that include the parent + let uri = format!( + "/api/v2/analysis/root-component/{}", + urlencoding::encode(child) + ); + let request: Request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + log::debug!("{response:#?}"); + let item = &response["items"][0]; + let ancs = &item["ancestors"]; + let anc = &ancs[0]; + + // assert array lengths + assert!(response["items"].get(1).is_none()); + assert!(item["purl"].get(1).is_none()); + assert!(ancs.get(1).is_none()); + assert!(anc["purl"].get(1).is_none()); + + // assert expected values + assert_eq!(child, item["purl"][0]); + assert_eq!("AncestorOf", anc["relationship"]); + assert_eq!(parent, anc["purl"][0]); + + Ok(()) +} diff --git a/modules/analysis/src/error.rs b/modules/analysis/src/error.rs index 3030f1a28..bd7e431de 100644 --- a/modules/analysis/src/error.rs +++ b/modules/analysis/src/error.rs @@ -1,7 +1,9 @@ use actix_http::StatusCode; use actix_web::body::BoxBody; use actix_web::{HttpResponse, ResponseError}; +use cpe::uri::OwnedUri; use sea_orm::DbErr; +use std::str::FromStr; use trustify_common::error::ErrorInformation; use trustify_common::id::IdError; use trustify_common::purl::PurlErr; @@ -17,6 +19,8 @@ pub enum Error { #[error(transparent)] Purl(#[from] PurlErr), #[error(transparent)] + Cpe(::Err), + #[error(transparent)] Actix(#[from] actix_web::Error), #[error("Invalid request {msg}")] BadRequest { msg: String, status: StatusCode }, @@ -43,6 +47,9 @@ impl From for Error { impl ResponseError for Error { fn error_response(&self) -> HttpResponse { match self { + Self::Cpe(err) => { + HttpResponse::BadRequest().json(ErrorInformation::new("InvalidCpeSyntax", err)) + } Self::Purl(err) => { HttpResponse::BadRequest().json(ErrorInformation::new("InvalidPurlSyntax", err)) } diff --git a/modules/analysis/src/lib.rs b/modules/analysis/src/lib.rs index c77c634a6..45345e0ad 100644 --- a/modules/analysis/src/lib.rs +++ b/modules/analysis/src/lib.rs @@ -1,11 +1,8 @@ pub mod endpoints; - -pub mod service; - pub mod error; - +pub mod service; pub use error::Error; - pub mod model; + #[cfg(test)] pub mod test; diff --git a/modules/analysis/src/model.rs b/modules/analysis/src/model.rs index 4169fe411..fe613b1ed 100644 --- a/modules/analysis/src/model.rs +++ b/modules/analysis/src/model.rs @@ -1,18 +1,20 @@ -use parking_lot::RwLock; use petgraph::Graph; use serde::Serialize; use std::{ collections::HashMap, fmt, - sync::{Arc, OnceLock}, + ops::{Deref, DerefMut}, }; +use trustify_common::{cpe::Cpe, purl::Purl}; use trustify_entity::relationship::Relationship; use utoipa::ToSchema; #[derive(Debug, Clone, PartialEq, Eq, ToSchema, serde::Serialize)] pub struct AnalysisStatus { - pub sbom_count: i32, - pub graph_count: i32, + /// The number of SBOMs found in the database + pub sbom_count: u32, + /// The number of graphs loaded in memory + pub graph_count: u32, } impl fmt::Display for AnalysisStatus { @@ -25,7 +27,8 @@ impl fmt::Display for AnalysisStatus { pub struct PackageNode { pub sbom_id: String, pub node_id: String, - pub purl: String, + pub purl: Vec, + pub cpe: Vec, pub name: String, pub version: String, pub published: String, @@ -35,7 +38,7 @@ pub struct PackageNode { } impl fmt::Display for PackageNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.purl) + write!(f, "{:?}", self.purl) } } @@ -44,67 +47,115 @@ pub struct AncNode { pub sbom_id: String, pub node_id: String, pub relationship: String, - pub purl: String, + pub purl: Vec, + pub cpe: Vec, pub name: String, pub version: String, } impl fmt::Display for AncNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.purl) + write!(f, "{:?}", self.purl) } } #[derive(Debug, Clone, Serialize, ToSchema)] -pub struct AncestorSummary { +pub struct BaseSummary { pub sbom_id: String, pub node_id: String, - pub purl: String, + pub purl: Vec, + pub cpe: Vec, pub name: String, pub version: String, pub published: String, pub document_id: String, pub product_name: String, pub product_version: String, +} + +impl From<&PackageNode> for BaseSummary { + fn from(value: &PackageNode) -> Self { + Self { + sbom_id: value.sbom_id.to_string(), + node_id: value.node_id.to_string(), + purl: value.purl.clone(), + cpe: value.cpe.clone(), + name: value.name.to_string(), + version: value.version.to_string(), + published: value.published.to_string(), + document_id: value.document_id.to_string(), + product_name: value.product_name.to_string(), + product_version: value.product_version.to_string(), + } + } +} + +#[derive(Debug, Clone, Serialize, ToSchema)] +pub struct AncestorSummary { + #[serde(flatten)] + pub base: BaseSummary, pub ancestors: Vec, } +impl Deref for AncestorSummary { + type Target = BaseSummary; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl DerefMut for AncestorSummary { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + #[derive(Debug, Clone, PartialEq, Eq, ToSchema, serde::Serialize)] pub struct DepNode { pub sbom_id: String, pub node_id: String, pub relationship: String, - pub purl: String, + pub purl: Vec, + pub cpe: Vec, pub name: String, pub version: String, #[schema(no_recursion)] pub deps: Vec, } + impl fmt::Display for DepNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.purl) + write!(f, "{:?}", self.purl) } } + #[derive(Debug, Clone, Serialize, ToSchema)] pub struct DepSummary { - pub sbom_id: String, - pub node_id: String, - pub purl: String, - pub name: String, - pub version: String, - pub published: String, - pub document_id: String, - pub product_name: String, - pub product_version: String, + #[serde(flatten)] + pub base: BaseSummary, pub deps: Vec, } + +impl Deref for DepSummary { + type Target = BaseSummary; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl DerefMut for DepSummary { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + #[derive(Debug)] pub struct GraphMap { map: HashMap>, } -static G: OnceLock>> = OnceLock::new(); - impl GraphMap { // Create a new instance of GraphMap pub fn new() -> Self { @@ -147,12 +198,6 @@ impl GraphMap { self.map.keys().cloned().collect() } - // Get the singleton instance of GraphMap - pub fn get_instance() -> Arc> { - G.get_or_init(|| Arc::new(RwLock::new(GraphMap::new()))) - .clone() - } - // Clear all graphs from the map pub fn clear(&mut self) { self.map.clear(); diff --git a/modules/analysis/src/service.rs b/modules/analysis/src/service.rs deleted file mode 100644 index bae2999a1..000000000 --- a/modules/analysis/src/service.rs +++ /dev/null @@ -1,1337 +0,0 @@ -use crate::{ - model::{AnalysisStatus, AncNode, AncestorSummary, DepNode, DepSummary, GraphMap, PackageNode}, - Error, -}; -use petgraph::{ - algo::is_cyclic_directed, - graph::{Graph, NodeIndex}, - visit::{NodeIndexable, VisitMap, Visitable}, - Direction, -}; -use sea_orm::{ - prelude::ConnectionTrait, ColumnTrait, DatabaseBackend, DbErr, EntityOrSelect, EntityTrait, - QueryFilter, QueryOrder, QueryResult, QuerySelect, QueryTrait, Statement, -}; -use sea_query::Order; -use std::{ - collections::{HashMap, HashSet}, - str::FromStr, -}; -use tracing::instrument; -use trustify_common::{ - db::query::{Filtering, Query, Value}, - model::{Paginated, PaginatedResults}, - purl::Purl, -}; -use trustify_entity::{relationship::Relationship, sbom, sbom_node}; -use uuid::Uuid; - -#[derive(Default)] -pub struct AnalysisService {} - -pub fn dep_nodes( - graph: &Graph, - node: NodeIndex, - visited: &mut HashSet, -) -> Vec { - let mut depnodes = Vec::new(); - fn dfs( - graph: &Graph, - node: NodeIndex, - depnodes: &mut Vec, - visited: &mut HashSet, - ) { - if visited.contains(&node) { - return; - } - visited.insert(node); - for neighbor in graph.neighbors_directed(node, Direction::Incoming) { - if let Some(dep_packagenode) = graph.node_weight(neighbor).cloned() { - // Attempt to find the edge and get the relationship in a more elegant way - if let Some(relationship) = graph - .find_edge(neighbor, node) - .and_then(|edge_index| graph.edge_weight(edge_index)) - { - let dep_node = DepNode { - sbom_id: dep_packagenode.sbom_id, - node_id: dep_packagenode.node_id, - relationship: relationship.to_string(), - purl: dep_packagenode.purl.to_string(), - name: dep_packagenode.name.to_string(), - version: dep_packagenode.version.to_string(), - deps: dep_nodes(graph, neighbor, visited), - }; - depnodes.push(dep_node); - dfs(graph, neighbor, depnodes, visited); - } - } else { - log::warn!( - "Processing descendants node weight for neighbor {:?} not found", - neighbor - ); - } - } - } - dfs(graph, node, &mut depnodes, visited); - depnodes -} - -pub fn ancestor_nodes( - graph: &Graph, - node: NodeIndex, -) -> Vec { - let mut discovered = graph.visit_map(); - let mut ancestor_nodes = Vec::new(); - let mut stack = Vec::new(); - - stack.push(graph.from_index(node.index())); - - while let Some(node) = stack.pop() { - if discovered.visit(node) { - for succ in graph.neighbors_directed(node, Direction::Outgoing) { - if !discovered.is_visited(&succ) { - if let Some(anc_packagenode) = graph.node_weight(succ).cloned() { - if let Some(edge) = graph.find_edge(node, succ) { - if let Some(relationship) = graph.edge_weight(edge) { - let anc_node = AncNode { - sbom_id: anc_packagenode.sbom_id, - node_id: anc_packagenode.node_id, - relationship: relationship.to_string(), - purl: anc_packagenode.purl, - name: anc_packagenode.name, - version: anc_packagenode.version, - }; - ancestor_nodes.push(anc_node); - stack.push(succ); - } else { - log::warn!( - "Edge weight not found for edge between {:?} and {:?}", - node, - succ - ); - } - } else { - log::warn!("Edge not found between {:?} and {:?}", node, succ); - } - } else { - log::warn!("Processing ancestors, node value for {:?} not found", succ); - } - } - } - if graph.neighbors_directed(node, Direction::Outgoing).count() == 0 { - continue; // we are at the root - } - } - } - ancestor_nodes -} - -pub async fn get_implicit_relationships( - connection: &C, - distinct_sbom_id: &str, -) -> Result, DbErr> { - let sql = r#" - SELECT - sbom.document_id, - sbom.sbom_id, - sbom.published::text, - get_purl(t1.qualified_purl_id) as purl, - t1_node.node_id, - t1_node.name AS node_name, - t1_version.version AS node_version, - product.name AS product_name, - product_version.version AS product_version - FROM - sbom - LEFT JOIN - product_version ON sbom.sbom_id = product_version.sbom_id - LEFT JOIN - product ON product_version.product_id = product.id - LEFT JOIN - sbom_node t1_node ON sbom.sbom_id = t1_node.sbom_id - LEFT JOIN - package_relates_to_package prtp ON t1_node.node_id = prtp.left_node_id OR t1_node.node_id = prtp.right_node_id - LEFT JOIN - sbom_package_purl_ref t1 ON t1_node.node_id = t1.node_id AND t1.sbom_id = sbom.sbom_id - LEFT JOIN - sbom_package t1_version ON t1_node.node_id = t1_version.node_id AND t1_version.sbom_id = sbom.sbom_id - WHERE - prtp.left_node_id IS NULL AND prtp.right_node_id IS NULL - AND - sbom.sbom_id = $1 - "#; - - let uuid = match Uuid::parse_str(distinct_sbom_id) { - Ok(uuid) => uuid, - Err(_) => return Err(sea_orm::DbErr::Custom("Invalid SBOM ID".to_string())), - }; - let stmt = Statement::from_sql_and_values(DatabaseBackend::Postgres, sql, [uuid.into()]); - let results: Vec = connection.query_all(stmt).await?; - - Ok(results) -} - -pub async fn get_relationships( - connection: &C, - distinct_sbom_id: &str, -) -> Result, DbErr> { - // Retrieve all SBOM components that have defined relationships - let sql = r#" - SELECT - sbom.document_id, - sbom.sbom_id, - sbom.published::text, - t1.node_id AS left_node_id, - get_purl(t1.qualified_purl_id) AS left_qualified_purl, - t1_node.name AS left_node_name, - t1_version.version AS left_node_version, - package_relates_to_package.relationship, - t2.node_id AS right_node_id, - get_purl(t2.qualified_purl_id) AS right_qualified_purl, - t2_node.name AS right_node_name, - t2_version.version AS right_node_version, - product.name AS product_name, - product_version.version AS product_version - FROM - sbom - LEFT JOIN - product_version ON sbom.sbom_id = product_version.sbom_id - LEFT JOIN - product ON product_version.product_id = product.id - LEFT JOIN - package_relates_to_package ON sbom.sbom_id = package_relates_to_package.sbom_id - LEFT JOIN - sbom_package_purl_ref t1 ON sbom.sbom_id = t1.sbom_id AND t1.node_id = package_relates_to_package.left_node_id - LEFT JOIN - sbom_node t1_node ON sbom.sbom_id = t1_node.sbom_id AND t1_node.node_id = package_relates_to_package.left_node_id - LEFT JOIN - sbom_package t1_version ON sbom.sbom_id = t1_version.sbom_id AND t1_version.node_id = package_relates_to_package.left_node_id - LEFT JOIN - sbom_package_purl_ref t2 ON sbom.sbom_id = t2.sbom_id AND t2.node_id = package_relates_to_package.right_node_id - LEFT JOIN - sbom_node t2_node ON sbom.sbom_id = t2_node.sbom_id AND t2_node.node_id = package_relates_to_package.right_node_id - LEFT JOIN - sbom_package t2_version ON sbom.sbom_id = t2_version.sbom_id AND t2_version.node_id = package_relates_to_package.right_node_id - WHERE - package_relates_to_package.relationship IN (0, 1, 8, 13, 14, 15) - AND sbom.sbom_id = $1; - "#; - - let uuid = match Uuid::parse_str(distinct_sbom_id) { - Ok(uuid) => uuid, - Err(_) => return Err(sea_orm::DbErr::Custom("Invalid SBOM ID".to_string())), - }; - let stmt = Statement::from_sql_and_values(DatabaseBackend::Postgres, sql, [uuid.into()]); - let results: Vec = connection.query_all(stmt).await?; - - Ok(results) -} - -pub async fn load_graphs(connection: &C, distinct_sbom_ids: &Vec) { - let graph_map = GraphMap::get_instance(); - { - for distinct_sbom_id in distinct_sbom_ids { - if !graph_map.read().contains_key(distinct_sbom_id) { - // lazy load graphs - let mut g: Graph = Graph::new(); - let mut nodes = HashMap::new(); - - let mut describedby_purl: String = Default::default(); - - // Set relationships explicitly defined in SBOM - match get_relationships(connection, &distinct_sbom_id.to_string()).await { - Ok(results) => { - for row in results { - let ( - sbom_published, - document_id, - product_name, - product_version, - left_node_id, - left_purl_string, - left_node_name, - left_node_version, - right_node_id, - right_purl_string, - right_node_name, - right_node_version, - relationship, - ) = { - let default_value = "NOVALUE".to_string(); // TODO: this eventually will have different defaults. - ( - row.try_get("", "published") - .unwrap_or_else(|_| default_value.clone()), - row.try_get("", "document_id") - .unwrap_or_else(|_| default_value.clone()), - row.try_get("", "product_name") - .unwrap_or_else(|_| default_value.clone()), - row.try_get("", "product_version") - .unwrap_or_else(|_| default_value.clone()), - row.try_get("", "left_node_id") - .unwrap_or(default_value.clone()), - row.try_get("", "left_qualified_purl") - .unwrap_or(default_value.clone()), - row.try_get("", "left_node_name") - .unwrap_or(default_value.clone()), - row.try_get("", "left_node_version") - .unwrap_or(default_value.clone()), - row.try_get("", "right_node_id") - .unwrap_or(default_value.clone()), - row.try_get("", "right_qualified_purl") - .unwrap_or(default_value.clone()), - row.try_get("", "right_node_name") - .unwrap_or(default_value.clone()), - row.try_get("", "right_node_version") - .unwrap_or(default_value.clone()), - row.try_get("", "relationship") - .unwrap_or(Relationship::ContainedBy), - ) - }; - - if relationship == Relationship::DescribedBy { - // Save for implicit relationships performed later - describedby_purl = left_purl_string.clone(); - } else { - let p1 = match nodes.get(&left_purl_string) { - Some(&node_index) => node_index, // already exists - None => { - let new_node = PackageNode { - sbom_id: distinct_sbom_id.clone(), - node_id: left_node_id.clone(), - purl: left_purl_string.clone(), - name: left_node_name.clone(), - version: left_node_version.clone(), - published: sbom_published.clone(), - document_id: document_id.clone(), - product_name: product_name.clone(), - product_version: product_version.clone(), - }; - let i = g.add_node(new_node); - nodes.insert(left_purl_string.clone(), i); - i - } - }; - - let p2 = match nodes.get(&right_purl_string) { - Some(&node_index) => node_index, // already exists - None => { - let new_node = PackageNode { - sbom_id: distinct_sbom_id.clone(), - node_id: right_node_id.clone(), - purl: right_purl_string.clone(), - name: right_node_name.clone(), - version: right_node_version.clone(), - published: sbom_published.clone(), - document_id: document_id.clone(), - product_name: product_name.clone(), - product_version: product_version.clone(), - }; - let i = g.add_node(new_node); - nodes.insert(right_purl_string.clone(), i); - i - } - }; - - g.add_edge(p1, p2, relationship); - } - } - } - Err(err) => { - log::error!("Error fetching graph relationships: {}", err); - } - } - - // Set relationships implicitly defined in SBOM - match get_implicit_relationships(connection, &distinct_sbom_id.to_string()).await { - Ok(results) => { - for row in results { - let ( - sbom_published, - document_id, - product_name, - product_version, - node_id, - purl, - node_name, - node_version, - ) = { - let default_value = "NOVALUE".to_string(); // TODO: this eventually will have different defaults. - ( - row.try_get("", "published") - .unwrap_or_else(|_| default_value.clone()), - row.try_get("", "document_id") - .unwrap_or_else(|_| default_value.clone()), - row.try_get("", "product_name") - .unwrap_or_else(|_| default_value.clone()), - row.try_get("", "product_version") - .unwrap_or_else(|_| default_value.clone()), - row.try_get("", "node_id").unwrap_or(default_value.clone()), - row.try_get("", "purl").unwrap_or(default_value.clone()), - row.try_get("", "node_name") - .unwrap_or(default_value.clone()), - row.try_get("", "node_version") - .unwrap_or(default_value.clone()), - ) - }; - - let p1 = match nodes.get(&purl) { - Some(&node_index) => node_index, // already exists - None => { - let new_node = PackageNode { - sbom_id: distinct_sbom_id.clone(), - node_id: node_id.clone(), - purl: purl.clone(), - name: node_name.clone(), - version: node_version.clone(), - published: sbom_published.clone(), - document_id: document_id.clone(), - product_name: product_name.clone(), - product_version: product_version.clone(), - }; - let i = g.add_node(new_node); - nodes.insert(purl.clone(), i); - i - } - }; - if let Some(describedby_node_index) = nodes.get(&describedby_purl) { - g.add_edge(p1, *describedby_node_index, Relationship::Undefined); - } else { - log::warn!("No 'describes' relationship found in {} SBOM, no implicit relationship set.", distinct_sbom_id); - } - } - } - Err(err) => { - log::error!("Error fetching graph relationships: {}", err); - } - } - - graph_map.write().insert(distinct_sbom_id.to_string(), g); - } - } - } -} - -impl AnalysisService { - pub fn new() -> Self { - let _ = GraphMap::get_instance(); - Self {} - } - - pub async fn load_graphs( - &self, - distinct_sbom_ids: Vec, - connection: &C, - ) -> Result<(), Error> { - load_graphs(connection, &distinct_sbom_ids).await; - - Ok(()) - } - pub async fn load_all_graphs(&self, connection: &C) -> Result<(), Error> { - // retrieve all sboms in trustify - let distinct_sbom_ids = sbom::Entity::find() - .select() - .order_by(sbom::Column::DocumentId, Order::Asc) - .order_by(sbom::Column::Published, Order::Desc) - .all(connection) - .await? - .into_iter() - .map(|record| record.sbom_id.to_string()) // Assuming sbom_id is of type String - .collect(); - - load_graphs(connection, &distinct_sbom_ids).await; - - Ok(()) - } - - pub fn clear_all_graphs(&self) -> Result<(), Error> { - let graph_manager = GraphMap::get_instance(); - let mut manager = graph_manager.write(); - manager.clear(); - Ok(()) - } - - pub async fn status( - &self, - connection: &C, - ) -> Result { - let distinct_sbom_ids = sbom::Entity::find() - .select() - .order_by(sbom::Column::DocumentId, Order::Asc) - .order_by(sbom::Column::Published, Order::Desc) - .all(connection) - .await?; - - let graph_manager = GraphMap::get_instance(); - let manager = graph_manager.read(); - Ok(AnalysisStatus { - sbom_count: distinct_sbom_ids.len() as i32, - graph_count: manager.len() as i32, - }) - } - - pub fn query_ancestor_graph( - component_name: Option, - component_purl: Option, - query: Option, - distinct_sbom_ids: Vec, - ) -> Vec { - let mut components = Vec::new(); - let graph_manager = GraphMap::get_instance(); - { - // RwLock for reading hashmap - let graph_read_guard = graph_manager.read(); - for distinct_sbom_id in &distinct_sbom_ids { - if let Some(graph) = graph_read_guard.get(distinct_sbom_id.to_string().as_str()) { - if is_cyclic_directed(graph) { - log::warn!( - "analysis graph of sbom {} has circular references!", - distinct_sbom_id - ); - } - - let mut visited = HashSet::new(); - - // Iterate over matching node indices and process them directly - graph - .node_indices() - .filter(|&i| { - if let Some(component_name) = &component_name { - graph - .node_weight(i) - .map(|node| node.name.eq(component_name)) - .unwrap_or(false) - } else if let Some(component_purl) = component_purl.clone() { - if let Some(node) = graph.node_weight(i) { - match Purl::from_str(&node.purl).map_err(Error::Purl) { - Ok(purl) => purl == component_purl, - Err(err) => { - log::warn!( - "Error retrieving purl from analysis graph {}", - err - ); - false - } - } - } else { - false // Return false if the node does not exist - } - } else if let Some(query) = &query { - graph - .node_weight(i) - .map(|node| { - query.apply(&HashMap::from([ - ("sbom_id", Value::String(&node.sbom_id)), - ("node_id", Value::String(&node.node_id)), - ("name", Value::String(&node.name)), - ("version", Value::String(&node.version)), - ])) - }) - .unwrap_or(false) - } else { - false - } - }) - .for_each(|node_index| { - if !visited.contains(&node_index) { - visited.insert(node_index); - - if let Some(find_match_package_node) = graph.node_weight(node_index) - { - log::debug!("matched!"); - components.push(AncestorSummary { - sbom_id: find_match_package_node.sbom_id.to_string(), - node_id: find_match_package_node.node_id.to_string(), - purl: find_match_package_node.purl.to_string(), - name: find_match_package_node.name.to_string(), - version: find_match_package_node.version.to_string(), - published: find_match_package_node.published.to_string(), - document_id: find_match_package_node - .document_id - .to_string(), - product_name: find_match_package_node - .product_name - .to_string(), - product_version: find_match_package_node - .product_version - .to_string(), - ancestors: ancestor_nodes(graph, node_index), - }); - } - } - }); - } - } - } - - components - } - - pub async fn query_deps_graph( - component_name: Option, - component_purl: Option, - query: Option, - distinct_sbom_ids: Vec, - ) -> Vec { - let mut components = Vec::new(); - let graph_manager = GraphMap::get_instance(); - { - // RwLock for reading hashmap - let graph_read_guard = graph_manager.read(); - for distinct_sbom_id in &distinct_sbom_ids { - if let Some(graph) = graph_read_guard.get(distinct_sbom_id.to_string().as_str()) { - if is_cyclic_directed(graph) { - log::warn!( - "analysis graph of sbom {} has circular references!", - distinct_sbom_id - ); - } - - let mut visited = HashSet::new(); - - // Iterate over matching node indices and process them directly - graph - .node_indices() - .filter(|&i| { - if let Some(component_name) = &component_name { - graph - .node_weight(i) - .map(|node| node.name.eq(component_name)) - .unwrap_or(false) - } else if let Some(component_purl) = component_purl.clone() { - if let Some(node) = graph.node_weight(i) { - match Purl::from_str(&node.purl).map_err(Error::Purl) { - Ok(purl) => purl == component_purl, - Err(err) => { - log::warn!( - "Error retrieving purl from analysis graph {}", - err - ); - false - } - } - } else { - false // Return false if the node does not exist - } - } else if let Some(query) = &query { - graph - .node_weight(i) - .map(|node| { - query.apply(&HashMap::from([ - ("sbom_id", Value::String(&node.sbom_id)), - ("node_id", Value::String(&node.node_id)), - ("name", Value::String(&node.name)), - ("version", Value::String(&node.version)), - ])) - }) - .unwrap_or(false) - } else { - false - } - }) - .for_each(|node_index| { - if !visited.contains(&node_index) { - visited.insert(node_index); - - if let Some(find_match_package_node) = graph.node_weight(node_index) - { - log::debug!("matched!"); - components.push(DepSummary { - sbom_id: find_match_package_node.sbom_id.to_string(), - node_id: find_match_package_node.node_id.to_string(), - purl: find_match_package_node.purl.to_string(), - name: find_match_package_node.name.to_string(), - version: find_match_package_node.version.to_string(), - published: find_match_package_node.published.to_string(), - document_id: find_match_package_node - .document_id - .to_string(), - product_name: find_match_package_node - .product_name - .to_string(), - product_version: find_match_package_node - .product_version - .to_string(), - deps: dep_nodes(graph, node_index, &mut HashSet::new()), - }); - } - } - }); - } - } - } - - components - } - - #[instrument(skip(self, connection), err)] - pub async fn retrieve_root_components( - &self, - query: Query, - paginated: Paginated, - connection: &C, - ) -> Result, Error> { - let search_sbom_node_name_subquery = sbom_node::Entity::find() - .filtering(query.clone())? - .select_only() - .column(sbom_node::Column::SbomId) - .distinct() - .into_query(); - let distinct_sbom_ids: Vec = sbom::Entity::find() - .filter(sbom::Column::SbomId.in_subquery(search_sbom_node_name_subquery)) - .select() - .order_by(sbom::Column::DocumentId, Order::Asc) - .order_by(sbom::Column::Published, Order::Desc) - .all(connection) - .await? - .into_iter() - .map(|record| record.sbom_id.to_string()) // Assuming sbom_id is of type String - .collect(); - - load_graphs(connection, &distinct_sbom_ids).await; - - let components = AnalysisService::query_ancestor_graph( - None, - None, - Option::from(query), - distinct_sbom_ids, - ); - - Ok(paginated.paginate_array(&components)) - } - - pub async fn retrieve_all_sbom_roots_by_name( - &self, - sbom_id: Uuid, - component_name: String, - connection: &C, - ) -> Result, Error> { - // This function searches for a component(s) by name in a specific sbom, then returns that components - // root components. - - let distinct_sbom_ids = vec![sbom_id.to_string()]; - load_graphs(connection, &distinct_sbom_ids).await; - - let components = AnalysisService::query_ancestor_graph( - Option::from(component_name), - None, - None, - distinct_sbom_ids, - ); - - let mut root_components = Vec::new(); - for component in components { - if let Some(last_ancestor) = component.ancestors.last() { - if !root_components.contains(last_ancestor) { - // we want distinct list - root_components.push(last_ancestor.clone()); - } - } - } - - Ok(root_components) - } - - #[instrument(skip(self, connection), err)] - pub async fn retrieve_root_components_by_name( - &self, - component_name: String, - paginated: Paginated, - connection: &C, - ) -> Result, Error> { - let search_sbom_node_exact_name_subquery = sbom_node::Entity::find() - .filter(sbom_node::Column::Name.eq(component_name.as_str())) - .select_only() - .column(sbom_node::Column::SbomId) - .distinct() - .into_query(); - let distinct_sbom_ids: Vec = sbom::Entity::find() - .filter(sbom::Column::SbomId.in_subquery(search_sbom_node_exact_name_subquery)) - .select() - .order_by(sbom::Column::DocumentId, Order::Asc) - .order_by(sbom::Column::Published, Order::Desc) - .all(connection) - .await? - .into_iter() - .map(|record| record.sbom_id.to_string()) // Assuming sbom_id is of type String - .collect(); - - load_graphs(connection, &distinct_sbom_ids).await; - - let components = AnalysisService::query_ancestor_graph( - Option::from(component_name), - None, - None, - distinct_sbom_ids, - ); - - Ok(paginated.paginate_array(&components)) - } - - #[instrument(skip(self, connection), err)] - pub async fn retrieve_root_components_by_purl( - &self, - component_purl: Purl, - paginated: Paginated, - connection: &C, - ) -> Result, Error> { - let search_sbom_node_exact_name_subquery = sbom_node::Entity::find() - .filter(sbom_node::Column::Name.eq(component_purl.name.as_str())) - .select_only() - .column(sbom_node::Column::SbomId) - .distinct() - .into_query(); - let distinct_sbom_ids: Vec = sbom::Entity::find() - .filter(sbom::Column::SbomId.in_subquery(search_sbom_node_exact_name_subquery)) - .select() - .order_by(sbom::Column::DocumentId, Order::Asc) - .order_by(sbom::Column::Published, Order::Desc) - .all(connection) - .await? - .into_iter() - .map(|record| record.sbom_id.to_string()) // Assuming sbom_id is of type String - .collect(); - - load_graphs(connection, &distinct_sbom_ids).await; - - let components = AnalysisService::query_ancestor_graph( - None, - Option::from(component_purl), - None, - distinct_sbom_ids, - ); - - Ok(paginated.paginate_array(&components)) - } - - #[instrument(skip(self, connection), err)] - pub async fn retrieve_deps( - &self, - query: Query, - paginated: Paginated, - connection: &C, - ) -> Result, Error> { - let search_sbom_node_name_subquery = sbom_node::Entity::find() - .filtering(query.clone())? - .select_only() - .column(sbom_node::Column::SbomId) - .distinct() - .into_query(); - let distinct_sbom_ids: Vec = sbom::Entity::find() - .filter(sbom::Column::SbomId.in_subquery(search_sbom_node_name_subquery)) - .select() - .order_by(sbom::Column::DocumentId, Order::Asc) - .order_by(sbom::Column::Published, Order::Desc) - .all(connection) - .await? - .into_iter() - .map(|record| record.sbom_id.to_string()) // Assuming sbom_id is of type String - .collect(); - - load_graphs(connection, &distinct_sbom_ids).await; - - let components = - AnalysisService::query_deps_graph(None, None, Option::from(query), distinct_sbom_ids) - .await; - - Ok(paginated.paginate_array(&components)) - } - - pub async fn retrieve_deps_by_name( - &self, - component_name: String, - paginated: Paginated, - connection: &C, - ) -> Result, Error> { - let search_sbom_node_exact_name_subquery = sbom_node::Entity::find() - .filter(sbom_node::Column::Name.eq(component_name.as_str())) - .select_only() - .column(sbom_node::Column::SbomId) - .distinct() - .into_query(); - let distinct_sbom_ids: Vec = sbom::Entity::find() - .filter(sbom::Column::SbomId.in_subquery(search_sbom_node_exact_name_subquery)) - .select() - .order_by(sbom::Column::DocumentId, Order::Asc) - .order_by(sbom::Column::Published, Order::Desc) - .all(connection) - .await? - .into_iter() - .map(|record| record.sbom_id.to_string()) // Assuming sbom_id is of type String - .collect(); - - load_graphs(connection, &distinct_sbom_ids).await; - - let components = AnalysisService::query_deps_graph( - Option::from(component_name), - None, - None, - distinct_sbom_ids, - ) - .await; - - Ok(paginated.paginate_array(&components)) - } - - pub async fn retrieve_deps_by_purl( - &self, - component_purl: Purl, - paginated: Paginated, - connection: &C, - ) -> Result, Error> { - let search_sbom_node_exact_name_subquery = sbom_node::Entity::find() - .filter(sbom_node::Column::Name.eq(component_purl.name.as_str())) - .select_only() - .column(sbom_node::Column::SbomId) - .distinct() - .into_query(); - let distinct_sbom_ids: Vec = sbom::Entity::find() - .filter(sbom::Column::SbomId.in_subquery(search_sbom_node_exact_name_subquery)) - .select() - .order_by(sbom::Column::DocumentId, Order::Asc) - .order_by(sbom::Column::Published, Order::Desc) - .all(connection) - .await? - .into_iter() - .map(|record| record.sbom_id.to_string()) // Assuming sbom_id is of type String - .collect(); - - load_graphs(connection, &distinct_sbom_ids).await; - - let components = AnalysisService::query_deps_graph( - None, - Option::from(component_purl), - None, - distinct_sbom_ids, - ) - .await; - - Ok(paginated.paginate_array(&components)) - } -} - -#[cfg(test)] -mod test { - use super::*; - - use test_context::test_context; - use test_log::test; - use trustify_common::model::Paginated; - use trustify_test_context::TrustifyContext; - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_simple_analysis_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["spdx/simple.json", "spdx/simple.json"]) - .await?; //double ingestion intended - - let service = AnalysisService::new(); - - let analysis_graph = service - .retrieve_root_components(Query::q("DD"), Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!( - analysis_graph - .items - .last() - .unwrap() - .ancestors - .last() - .unwrap() - .purl, - "pkg:rpm/redhat/AA@0.0.0?arch=src".to_string() - ); - assert_eq!( - analysis_graph - .items - .last() - .unwrap() - .ancestors - .last() - .unwrap() - .node_id, - "SPDXRef-AA".to_string() - ); - assert_eq!(analysis_graph.total, 1); - - // ensure we set implicit relationship on component with no defined relationships - let analysis_graph = service - .retrieve_root_components(Query::q("EE"), Paginated::default(), &ctx.db) - .await - .unwrap(); - assert_eq!(analysis_graph.total, 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_simple_analysis_cyclonedx_service( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["cyclonedx/simple.json", "cyclonedx/simple.json"]) - .await?; //double ingestion intended - - let service = AnalysisService::new(); - - let analysis_graph = service - .retrieve_root_components(Query::q("DD"), Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!( - analysis_graph - .items - .last() - .unwrap() - .ancestors - .last() - .unwrap() - .purl, - "pkg:rpm/redhat/AA@0.0.0?arch=src".to_string() - ); - let node = analysis_graph - .items - .last() - .unwrap() - .ancestors - .last() - .unwrap(); - assert_eq!(node.node_id, "aa".to_string()); - assert_eq!(node.name, "AA".to_string()); - assert_eq!(analysis_graph.total, 1); - - // ensure we set implicit relationship on component with no defined relationships - let analysis_graph = service - .retrieve_root_components(Query::q("EE"), Paginated::default(), &ctx.db) - .await - .unwrap(); - assert_eq!(analysis_graph.total, 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_simple_by_name_analysis_service( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let service = AnalysisService::new(); - - let analysis_graph = service - .retrieve_root_components_by_name("B".to_string(), Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!( - analysis_graph - .items - .last() - .unwrap() - .ancestors - .last() - .unwrap() - .purl, - "pkg:rpm/redhat/A@0.0.0?arch=src".to_string() - ); - assert_eq!( - analysis_graph - .items - .last() - .unwrap() - .ancestors - .last() - .unwrap() - .node_id, - "SPDXRef-A".to_string() - ); - assert_eq!(analysis_graph.total, 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_simple_by_purl_analysis_service( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let service = AnalysisService::new(); - - let component_purl: Purl = Purl::from_str("pkg:rpm/redhat/B@0.0.0").map_err(Error::Purl)?; - - let analysis_graph = service - .retrieve_root_components_by_purl(component_purl, Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!( - analysis_graph - .items - .last() - .unwrap() - .ancestors - .last() - .unwrap() - .purl, - "pkg:rpm/redhat/A@0.0.0?arch=src".to_string() - ); - assert_eq!( - analysis_graph - .items - .last() - .unwrap() - .ancestors - .last() - .unwrap() - .node_id, - "SPDXRef-A".to_string() - ); - assert_eq!(analysis_graph.total, 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_quarkus_analysis_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - ctx.ingest_documents([ - "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", - "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", - ]) - .await?; - - let service = AnalysisService::new(); - - let analysis_graph = service - .retrieve_root_components(Query::q("spymemcached"), Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!(analysis_graph.items.last().unwrap().ancestors.last().unwrap().purl, - "pkg:maven/com.redhat.quarkus.platform/quarkus-bom@3.2.12.Final-redhat-00002?type=pom&repository_url=https%3a%2f%2fmaven.repository.redhat.com%2fga%2f".to_string() - ); - assert_eq!( - analysis_graph - .items - .last() - .unwrap() - .ancestors - .last() - .unwrap() - .node_id, - "SPDXRef-e24fec28-1001-499c-827f-2e2e5f2671b5".to_string() - ); - - assert_eq!(analysis_graph.total, 2); - Ok(()) - } - - // TODO: this test passes when run individually. - #[test_context(TrustifyContext)] - #[test(tokio::test)] - #[ignore] - async fn test_status_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let service = AnalysisService::new(); - let _load_all_graphs = service.load_all_graphs(&ctx.db).await; - let analysis_status = service.status(&ctx.db).await.unwrap(); - - assert_eq!(analysis_status.sbom_count, 1); - assert_eq!(analysis_status.graph_count, 1); - - let _clear_all_graphs = service.clear_all_graphs(); - - ctx.ingest_documents([ - "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", - "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", - ]) - .await?; - - let analysis_status = service.status(&ctx.db).await.unwrap(); - - assert_eq!(analysis_status.sbom_count, 3); - assert_eq!(analysis_status.graph_count, 0); - - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_simple_deps_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let service = AnalysisService::new(); - - let analysis_graph = service - .retrieve_deps(Query::q("AA"), Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!(analysis_graph.total, 1); - - // ensure we set implicit relationship on component with no defined relationships - let analysis_graph = service - .retrieve_root_components(Query::q("EE"), Paginated::default(), &ctx.db) - .await - .unwrap(); - assert_eq!(analysis_graph.total, 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_simple_deps_cyclonedx_service( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["cyclonedx/simple.json"]).await?; - - let service = AnalysisService::new(); - - let analysis_graph = service - .retrieve_deps(Query::q("AA"), Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!(analysis_graph.total, 1); - - // ensure we set implicit relationship on component with no defined relationships - let analysis_graph = service - .retrieve_root_components(Query::q("EE"), Paginated::default(), &ctx.db) - .await - .unwrap(); - assert_eq!(analysis_graph.total, 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_simple_by_name_deps_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let service = AnalysisService::new(); - - let analysis_graph = service - .retrieve_deps_by_name("A".to_string(), Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!( - analysis_graph.items[0].purl, - "pkg:rpm/redhat/A@0.0.0?arch=src".to_string() - ); - assert_eq!(analysis_graph.total, 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_simple_by_purl_deps_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["spdx/simple.json"]).await?; - - let service = AnalysisService::new(); - - let component_purl: Purl = - Purl::from_str("pkg:rpm/redhat/AA@0.0.0?arch=src").map_err(Error::Purl)?; - - let analysis_graph = service - .retrieve_deps_by_purl(component_purl, Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!( - analysis_graph.items[0].purl, - "pkg:rpm/redhat/AA@0.0.0?arch=src".to_string() - ); - - assert_eq!(analysis_graph.total, 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_quarkus_deps_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - ctx.ingest_documents([ - "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", - "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", - ]) - .await?; - - let service = AnalysisService::new(); - - let analysis_graph = service - .retrieve_deps(Query::q("spymemcached"), Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!(analysis_graph.total, 2); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_circular_deps_cyclonedx_service( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["cyclonedx/cyclonedx-circular.json"]) - .await?; - - let service = AnalysisService::new(); - - let analysis_graph = service - .retrieve_deps_by_name("junit-bom".to_string(), Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!(analysis_graph.total, 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_circular_deps_spdx_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["spdx/loop.json"]).await?; - - let service = AnalysisService::new(); - - let analysis_graph = service - .retrieve_deps_by_name("A".to_string(), Paginated::default(), &ctx.db) - .await - .unwrap(); - - assert_eq!(analysis_graph.total, 1); - Ok(()) - } - - #[test_context(TrustifyContext)] - #[test(tokio::test)] - async fn test_retrieve_all_sbom_roots_by_name1( - ctx: &TrustifyContext, - ) -> Result<(), anyhow::Error> { - ctx.ingest_documents(["spdx/quarkus-bom-3.2.11.Final-redhat-00001.json"]) - .await?; - - let service = AnalysisService::new(); - let component_name = "quarkus-vertx-http".to_string(); - - let analysis_graph = service - .retrieve_root_components(Query::q(&component_name), Paginated::default(), &ctx.db) - .await?; - - let sbom_id = analysis_graph - .items - .last() - .unwrap() - .sbom_id - .parse::()?; - - let roots = service - .retrieve_all_sbom_roots_by_name(sbom_id, component_name, &ctx.db) - .await?; - - assert_eq!(roots.last().unwrap().name, "quarkus-bom"); - - Ok(()) - } -} diff --git a/modules/analysis/src/service/load.rs b/modules/analysis/src/service/load.rs new file mode 100644 index 000000000..5b386c050 --- /dev/null +++ b/modules/analysis/src/service/load.rs @@ -0,0 +1,341 @@ +use crate::{ + model::PackageNode, + service::{AnalysisService, ComponentReference, GraphQuery}, + Error, +}; +use petgraph::{prelude::NodeIndex, Graph}; +use sea_orm::{ + ColumnTrait, ConnectionTrait, DatabaseBackend, DbErr, EntityOrSelect, EntityTrait, + FromQueryResult, QueryFilter, QueryOrder, QuerySelect, QueryTrait, RelationTrait, Statement, +}; +use sea_query::{JoinType, Order, SelectStatement}; +use serde_json::Value; +use std::collections::HashSet; +use std::{collections::hash_map::Entry, collections::HashMap}; +use tracing::{instrument, Level}; +use trustify_common::{cpe::Cpe, db::query::Filtering, purl::Purl}; +use trustify_entity::{ + cpe::CpeDto, package_relates_to_package, relationship::Relationship, sbom, sbom_node, + sbom_package, sbom_package_cpe_ref, sbom_package_purl_ref, +}; +use uuid::Uuid; + +#[derive(Debug, FromQueryResult)] +pub struct Node { + pub document_id: Option, + pub published: String, + pub purls: Option>, + pub cpes: Option>, + pub node_id: String, + pub node_name: String, + pub node_version: Option, + pub product_name: Option, + pub product_version: Option, +} + +#[derive(Debug, FromQueryResult)] +pub struct Edge { + pub left_node_id: String, + pub relationship: Relationship, + pub right_node_id: String, +} + +#[instrument(skip(connection))] +pub async fn get_nodes( + connection: &C, + distinct_sbom_id: Uuid, +) -> Result, DbErr> { + let sql = r#" +WITH +purl_ref AS ( + SELECT + sbom_id, + node_id, + array_agg(get_purl(qualified_purl_id)) AS purls + FROM + sbom_package_purl_ref + GROUP BY + sbom_id, + node_id +), +cpe_ref AS ( + SELECT + sbom_id, + node_id, + array_agg(row_to_json(cpe)) AS cpes + FROM + sbom_package_cpe_ref + LEFT JOIN + cpe ON (sbom_package_cpe_ref.cpe_id = cpe.id) + GROUP BY + sbom_id, + node_id +) +SELECT + sbom.document_id, + sbom.published::text, + purl_ref.purls, + cpe_ref.cpes, + t1_node.node_id AS node_id, + t1_node.name AS node_name, + t1_package.version AS node_version, + product.name AS product_name, + product_version.version AS product_version +FROM + sbom +LEFT JOIN + product_version ON sbom.sbom_id = product_version.sbom_id +LEFT JOIN + product ON product_version.product_id = product.id +LEFT JOIN + sbom_node t1_node ON sbom.sbom_id = t1_node.sbom_id +LEFT JOIN + sbom_package t1_package ON t1_node.sbom_id = t1_package.sbom_id AND t1_node.node_id = t1_package.node_id +LEFT JOIN + purl_ref ON purl_ref.sbom_id = sbom.sbom_id AND purl_ref.node_id = t1_node.node_id +LEFT JOIN + cpe_ref ON cpe_ref.sbom_id = sbom.sbom_id AND cpe_ref.node_id = t1_node.node_id +WHERE + sbom.sbom_id = $1 +"#; + + let stmt = + Statement::from_sql_and_values(DatabaseBackend::Postgres, sql, [distinct_sbom_id.into()]); + + Node::find_by_statement(stmt).all(connection).await +} + +#[instrument(skip(connection))] +pub async fn get_relationships( + connection: &C, + distinct_sbom_id: Uuid, +) -> Result, DbErr> { + Ok(package_relates_to_package::Entity::find() + .filter(package_relates_to_package::Column::SbomId.eq(distinct_sbom_id)) + .all(connection) + .await? + .into_iter() + .map(|prtp| Edge { + left_node_id: prtp.left_node_id, + relationship: prtp.relationship, + right_node_id: prtp.right_node_id, + }) + .collect()) +} + +fn to_purls(purls: Option>) -> Vec { + purls + .into_iter() + .flatten() + .filter_map(|purl| Purl::try_from(purl).ok()) + .collect() +} + +fn to_cpes(cpes: Option>) -> Vec { + cpes.into_iter() + .flatten() + .flat_map(|cpe| { + serde_json::from_value::(cpe) + .ok() + .and_then(|cpe| Cpe::try_from(cpe).ok()) + }) + .collect() +} + +impl AnalysisService { + /// Take a [`GraphQuery`] and load all required SBOMs + #[instrument(skip(self, connection), err(level=Level::INFO))] + pub(crate) async fn load_graphs_query( + &self, + connection: &C, + query: GraphQuery<'_>, + ) -> Result, Error> { + let search_sbom_subquery = match query { + GraphQuery::Component(ComponentReference::Id(name)) => sbom_node::Entity::find() + .filter(sbom_node::Column::NodeId.eq(name)) + .select_only() + .column(sbom_node::Column::SbomId) + .distinct() + .into_query(), + GraphQuery::Component(ComponentReference::Name(name)) => sbom_node::Entity::find() + .filter(sbom_node::Column::Name.eq(name)) + .select_only() + .column(sbom_node::Column::SbomId) + .distinct() + .into_query(), + GraphQuery::Component(ComponentReference::Purl(purl)) => sbom_node::Entity::find() + .join(JoinType::Join, sbom_node::Relation::Package.def()) + .join(JoinType::Join, sbom_package::Relation::Purl.def()) + .filter(sbom_package_purl_ref::Column::QualifiedPurlId.eq(purl.qualifier_uuid())) + .select_only() + .column(sbom_node::Column::SbomId) + .distinct() + .into_query(), + GraphQuery::Component(ComponentReference::Cpe(cpe)) => sbom_node::Entity::find() + .join(JoinType::Join, sbom_node::Relation::Package.def()) + .join(JoinType::Join, sbom_package::Relation::Cpe.def()) + .filter(sbom_package_cpe_ref::Column::CpeId.eq(cpe.uuid())) + .select_only() + .column(sbom_node::Column::SbomId) + .distinct() + .into_query(), + GraphQuery::Query(query) => sbom_node::Entity::find() + .filtering(query.clone())? + .select_only() + .column(sbom_node::Column::SbomId) + .distinct() + .into_query(), + }; + + self.load_graphs_subquery(connection, search_sbom_subquery) + .await + } + + /// Take a select for sboms, and ensure they are loaded and return their IDs. + async fn load_graphs_subquery( + &self, + connection: &C, + subquery: SelectStatement, + ) -> Result, Error> { + let distinct_sbom_ids: Vec = sbom::Entity::find() + .filter(sbom::Column::SbomId.in_subquery(subquery)) + .select() + .order_by(sbom::Column::DocumentId, Order::Asc) + .order_by(sbom::Column::Published, Order::Desc) + .all(connection) + .await? + .into_iter() + .map(|record| record.sbom_id.to_string()) // Assuming sbom_id is of type String + .collect(); + + self.load_graphs(connection, &distinct_sbom_ids).await?; + + Ok(distinct_sbom_ids) + } + + /// Load the SBOM matching the provided ID + #[instrument(skip(self, connection))] + pub async fn load_graph(&self, connection: &C, distinct_sbom_id: &str) { + if self.graph.read().contains_key(distinct_sbom_id) { + // early return if we already loaded it + return; + } + + let distinct_sbom_id = match Uuid::parse_str(distinct_sbom_id) { + Ok(uuid) => uuid, + Err(err) => { + log::warn!("Unable to parse SBOM ID {distinct_sbom_id}: {err}"); + return; + } + }; + + // lazy load graphs + + let mut g: Graph = Graph::new(); + let mut nodes = HashMap::new(); + let mut detected_nodes = HashSet::new(); + + // populate packages/components + + let packages = match get_nodes(connection, distinct_sbom_id).await { + Ok(nodes) => nodes, + Err(err) => { + log::error!("Error fetching graph nodes: {}", err); + return; + } + }; + + for package in packages { + detected_nodes.insert(package.node_id.clone()); + + match nodes.entry(package.node_id.clone()) { + Entry::Vacant(entry) => { + let index = g.add_node(PackageNode { + sbom_id: distinct_sbom_id.to_string(), + node_id: package.node_id, + purl: to_purls(package.purls), + cpe: to_cpes(package.cpes), + name: package.node_name, + version: package.node_version.clone().unwrap_or_default(), + published: package.published.clone(), + document_id: package.document_id.clone().unwrap_or_default(), + product_name: package.product_name.clone().unwrap_or_default(), + product_version: package.product_version.clone().unwrap_or_default(), + }); + + log::debug!("Inserting - id: {}, index: {index:?}", entry.key()); + + entry.insert(index); + } + Entry::Occupied(_) => {} + } + } + + // populate relationships + + let edges = match get_relationships(connection, distinct_sbom_id).await { + Ok(edges) => edges, + Err(err) => { + log::error!("Error fetching graph relationships: {}", err); + return; + } + }; + + // the nodes describing the document + let mut describedby_node_id: Vec = Default::default(); + + for edge in edges { + log::debug!("Adding edge {:?}", edge); + + // insert edge into the graph + if let (Some(left), Some(right)) = ( + nodes.get(&edge.left_node_id), + nodes.get(&edge.right_node_id), + ) { + if edge.relationship == Relationship::DescribedBy { + describedby_node_id.push(*left); + } + + // remove all node IDs we somehow connected + detected_nodes.remove(&edge.left_node_id); + detected_nodes.remove(&edge.right_node_id); + + g.add_edge(*left, *right, edge.relationship); + } + } + + log::debug!("Describing nodes: {describedby_node_id:?}"); + log::debug!("Unconnected nodes: {detected_nodes:?}"); + + if !describedby_node_id.is_empty() { + // search of unconnected nodes and create undefined relationships + // all nodes not removed are unconnected + for id in detected_nodes { + let Some(id) = nodes.get(&id) else { continue }; + // add "undefined" relationship + for from in &describedby_node_id { + log::debug!("Creating undefined relationship - left: {id:?}, right: {from:?}"); + g.add_edge(*id, *from, Relationship::Undefined); + } + } + } + + // Set the result. A parallel call might have done the same. We wasted some time, but the + // state is still correct. + + self.graph.write().insert(distinct_sbom_id.to_string(), g); + } + + /// Load all SBOMs by the provided IDs + pub async fn load_graphs( + &self, + connection: &C, + distinct_sbom_ids: &Vec, + ) -> Result<(), DbErr> { + for distinct_sbom_id in distinct_sbom_ids { + self.load_graph(connection, distinct_sbom_id).await; + } + + Ok(()) + } +} diff --git a/modules/analysis/src/service/mod.rs b/modules/analysis/src/service/mod.rs new file mode 100644 index 000000000..eff7d26ae --- /dev/null +++ b/modules/analysis/src/service/mod.rs @@ -0,0 +1,420 @@ +mod load; +mod query; + +pub use query::*; + +#[cfg(test)] +mod test; + +use crate::{ + model::{ + AnalysisStatus, AncNode, AncestorSummary, BaseSummary, DepNode, DepSummary, GraphMap, + PackageNode, + }, + Error, +}; +use parking_lot::RwLock; +use petgraph::{ + algo::is_cyclic_directed, + graph::{Graph, NodeIndex}, + visit::{NodeIndexable, VisitMap, Visitable}, + Direction, +}; +use sea_orm::{prelude::ConnectionTrait, EntityOrSelect, EntityTrait, QueryOrder}; +use sea_query::Order; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, + sync::Arc, +}; +use tracing::instrument; +use trustify_common::{ + db::query::Value, + model::{Paginated, PaginatedResults}, +}; +use trustify_entity::{relationship::Relationship, sbom}; +use uuid::Uuid; + +#[derive(Clone, Default)] +pub struct AnalysisService { + graph: Arc>, +} + +pub fn dep_nodes( + graph: &Graph, + node: NodeIndex, + visited: &mut HashSet, +) -> Vec { + let mut depnodes = Vec::new(); + fn dfs( + graph: &Graph, + node: NodeIndex, + depnodes: &mut Vec, + visited: &mut HashSet, + ) { + if visited.contains(&node) { + return; + } + visited.insert(node); + for neighbor in graph.neighbors_directed(node, Direction::Incoming) { + if let Some(dep_packagenode) = graph.node_weight(neighbor).cloned() { + // Attempt to find the edge and get the relationship in a more elegant way + if let Some(relationship) = graph + .find_edge(neighbor, node) + .and_then(|edge_index| graph.edge_weight(edge_index)) + { + let dep_node = DepNode { + sbom_id: dep_packagenode.sbom_id, + node_id: dep_packagenode.node_id, + relationship: relationship.to_string(), + purl: dep_packagenode.purl.clone(), + cpe: dep_packagenode.cpe.clone(), + name: dep_packagenode.name.to_string(), + version: dep_packagenode.version.to_string(), + deps: dep_nodes(graph, neighbor, visited), + }; + depnodes.push(dep_node); + dfs(graph, neighbor, depnodes, visited); + } + } else { + log::warn!( + "Processing descendants node weight for neighbor {:?} not found", + neighbor + ); + } + } + } + + dfs(graph, node, &mut depnodes, visited); + + depnodes +} + +pub fn ancestor_nodes( + graph: &Graph, + node: NodeIndex, +) -> Vec { + let mut discovered = graph.visit_map(); + let mut ancestor_nodes = Vec::new(); + let mut stack = Vec::new(); + + stack.push(graph.from_index(node.index())); + + while let Some(node) = stack.pop() { + if discovered.visit(node) { + for succ in graph.neighbors_directed(node, Direction::Outgoing) { + if !discovered.is_visited(&succ) { + if let Some(anc_packagenode) = graph.node_weight(succ).cloned() { + if let Some(edge) = graph.find_edge(node, succ) { + if let Some(relationship) = graph.edge_weight(edge) { + let anc_node = AncNode { + sbom_id: anc_packagenode.sbom_id, + node_id: anc_packagenode.node_id, + relationship: relationship.to_string(), + purl: anc_packagenode.purl, + cpe: anc_packagenode.cpe, + name: anc_packagenode.name, + version: anc_packagenode.version, + }; + ancestor_nodes.push(anc_node); + stack.push(succ); + } else { + log::warn!( + "Edge weight not found for edge between {:?} and {:?}", + node, + succ + ); + } + } else { + log::warn!("Edge not found between {:?} and {:?}", node, succ); + } + } else { + log::warn!("Processing ancestors, node value for {:?} not found", succ); + } + } + } + if graph.neighbors_directed(node, Direction::Outgoing).count() == 0 { + continue; // we are at the root + } + } + } + ancestor_nodes +} + +impl AnalysisService { + pub fn new() -> Self { + Self::default() + } + + #[instrument(skip_all, err)] + pub async fn load_all_graphs(&self, connection: &C) -> Result<(), Error> { + // retrieve all sboms in trustify + + let distinct_sbom_ids = sbom::Entity::find() + .select() + .order_by(sbom::Column::DocumentId, Order::Asc) + .order_by(sbom::Column::Published, Order::Desc) + .all(connection) + .await? + .into_iter() + .map(|record| record.sbom_id.to_string()) // Assuming sbom_id is of type String + .collect(); + + self.load_graphs(connection, &distinct_sbom_ids).await?; + + Ok(()) + } + + pub fn clear_all_graphs(&self) -> Result<(), Error> { + let mut manager = self.graph.write(); + manager.clear(); + Ok(()) + } + + pub async fn status( + &self, + connection: &C, + ) -> Result { + let distinct_sbom_ids = sbom::Entity::find() + .select() + .order_by(sbom::Column::DocumentId, Order::Asc) + .order_by(sbom::Column::Published, Order::Desc) + .all(connection) + .await?; + + let manager = self.graph.read(); + Ok(AnalysisStatus { + sbom_count: distinct_sbom_ids.len() as u32, + graph_count: manager.len() as u32, + }) + } + + /// Collect nodes from the graph + /// + /// Similar to [`Self::query_graph`], but manages the state of collecting. + #[instrument(skip(self, init, collector))] + fn collect_graph<'a, T, I, C>( + &self, + query: impl Into> + Debug, + distinct_sbom_ids: Vec, + init: I, + collector: C, + ) -> T + where + I: FnOnce() -> T, + C: Fn(&mut T, &Graph, NodeIndex, &PackageNode), + { + let mut value = init(); + + self.query_graph(query, distinct_sbom_ids, |graph, index, node| { + collector(&mut value, graph, index, node); + }); + + value + } + + /// Traverse the graph, call the function for every matching node. + #[instrument(skip(self, f))] + fn query_graph<'a, F>( + &self, + query: impl Into> + Debug, + distinct_sbom_ids: Vec, + mut f: F, + ) where + F: FnMut(&Graph, NodeIndex, &PackageNode), + { + let query = query.into(); + + // RwLock for reading hashmap + let graph_read_guard = self.graph.read(); + for distinct_sbom_id in &distinct_sbom_ids { + if let Some(graph) = graph_read_guard.get(distinct_sbom_id.to_string().as_str()) { + if is_cyclic_directed(graph) { + log::warn!( + "analysis graph of sbom {} has circular references!", + distinct_sbom_id + ); + } + + let mut visited = HashSet::new(); + + // Iterate over matching node indices and process them directly + graph + .node_indices() + .filter(|&i| Self::filter(graph, &query, i)) + .for_each(|node_index| { + if !visited.contains(&node_index) { + visited.insert(node_index); + + if let Some(find_match_package_node) = graph.node_weight(node_index) { + log::debug!("matched!"); + f(graph, node_index, find_match_package_node); + } + } + }); + } + } + } + + #[instrument(skip(self))] + pub fn query_ancestor_graph<'a>( + &self, + query: impl Into> + Debug, + distinct_sbom_ids: Vec, + ) -> Vec { + self.collect_graph( + query, + distinct_sbom_ids, + Vec::new, + |components, graph, node_index, node| { + components.push(AncestorSummary { + base: node.into(), + ancestors: ancestor_nodes(graph, node_index), + }); + }, + ) + } + + #[instrument(skip(self))] + pub async fn query_deps_graph( + &self, + query: impl Into> + Debug, + distinct_sbom_ids: Vec, + ) -> Vec { + self.collect_graph( + query, + distinct_sbom_ids, + Vec::new, + |components, graph, node_index, node| { + components.push(DepSummary { + base: node.into(), + deps: dep_nodes(graph, node_index, &mut HashSet::new()), + }); + }, + ) + } + + pub async fn retrieve_all_sbom_roots_by_name( + &self, + sbom_id: Uuid, + component_name: String, + connection: &C, + ) -> Result, Error> { + // This function searches for a component(s) by name in a specific sbom, then returns that components + // root components. + + let distinct_sbom_ids = vec![sbom_id.to_string()]; + self.load_graphs(connection, &distinct_sbom_ids).await?; + + let components = self.query_ancestor_graph( + GraphQuery::Component(ComponentReference::Name(&component_name)), + distinct_sbom_ids, + ); + + let mut root_components = Vec::new(); + for component in components { + if let Some(last_ancestor) = component.ancestors.last() { + if !root_components.contains(last_ancestor) { + // we want a distinct list + root_components.push(last_ancestor.clone()); + } + } + } + + Ok(root_components) + } + + /// locate components, retrieve ancestor information + #[instrument(skip(self, connection), err)] + pub async fn retrieve_root_components( + &self, + query: impl Into> + Debug, + paginated: Paginated, + connection: &C, + ) -> Result, Error> { + let query = query.into(); + + let distinct_sbom_ids = self.load_graphs_query(connection, query).await?; + let components = self.query_ancestor_graph(query, distinct_sbom_ids); + + Ok(paginated.paginate_array(&components)) + } + + /// locate components, retrieve dependency information + #[instrument(skip(self, connection), err)] + pub async fn retrieve_deps( + &self, + query: impl Into> + Debug, + paginated: Paginated, + connection: &C, + ) -> Result, Error> { + let query = query.into(); + + let distinct_sbom_ids = self.load_graphs_query(connection, query).await?; + let components = self.query_deps_graph(query, distinct_sbom_ids).await; + + Ok(paginated.paginate_array(&components)) + } + + /// locate components, retrieve basic information only + #[instrument(skip(self, connection), err)] + pub async fn retrieve_components( + &self, + query: impl Into> + Debug, + paginated: Paginated, + connection: &C, + ) -> Result, Error> { + let query = query.into(); + + let distinct_sbom_ids = self.load_graphs_query(connection, query).await?; + let components = self.collect_graph( + query, + distinct_sbom_ids, + Vec::new, + |components, _, _, node| { + components.push(BaseSummary::from(node)); + }, + ); + + Ok(paginated.paginate_array(&components)) + } + + /// check if a node in the graph matches the provided query + fn filter(graph: &Graph, query: &GraphQuery, i: NodeIndex) -> bool { + match query { + GraphQuery::Component(ComponentReference::Id(component_id)) => graph + .node_weight(i) + .map(|node| node.node_id.eq(component_id)) + .unwrap_or(false), + GraphQuery::Component(ComponentReference::Name(component_name)) => graph + .node_weight(i) + .map(|node| node.name.eq(component_name)) + .unwrap_or(false), + GraphQuery::Component(ComponentReference::Purl(component_purl)) => { + if let Some(node) = graph.node_weight(i) { + node.purl.contains(component_purl) + } else { + false // Return false if the node does not exist + } + } + GraphQuery::Component(ComponentReference::Cpe(component_cpe)) => { + if let Some(node) = graph.node_weight(i) { + node.cpe.contains(component_cpe) + } else { + false // Return false if the node does not exist + } + } + GraphQuery::Query(query) => graph + .node_weight(i) + .map(|node| { + query.apply(&HashMap::from([ + ("sbom_id", Value::String(&node.sbom_id)), + ("node_id", Value::String(&node.node_id)), + ("name", Value::String(&node.name)), + ("version", Value::String(&node.version)), + ])) + }) + .unwrap_or(false), + } + } +} diff --git a/modules/analysis/src/service/query.rs b/modules/analysis/src/service/query.rs new file mode 100644 index 000000000..fbe3f82dd --- /dev/null +++ b/modules/analysis/src/service/query.rs @@ -0,0 +1,57 @@ +use trustify_common::{cpe::Cpe, db::query::Query, purl::Purl}; + +#[derive(Copy, Clone, Debug)] +pub enum ComponentReference<'a> { + /// The ID of the component. + /// + /// This is the ID provided by the document. For CycloneDX, this is the `bom-ref`. + Id(&'a str), + /// The name of the component + Name(&'a str), + /// A PURL of the component + Purl(&'a Purl), + /// A CPE of the component + Cpe(&'a Cpe), +} + +impl<'a> From<&'a Cpe> for ComponentReference<'a> { + fn from(value: &'a Cpe) -> Self { + Self::Cpe(value) + } +} + +impl<'a> From<&'a Purl> for ComponentReference<'a> { + fn from(value: &'a Purl) -> Self { + Self::Purl(value) + } +} + +#[derive(Copy, Clone, Debug)] +pub enum GraphQuery<'a> { + Component(ComponentReference<'a>), + Query(&'a Query), +} + +impl<'a> From> for GraphQuery<'a> { + fn from(reference: ComponentReference<'a>) -> Self { + Self::Component(reference) + } +} + +impl<'a> From<&'a Cpe> for GraphQuery<'a> { + fn from(value: &'a Cpe) -> Self { + Self::Component(ComponentReference::Cpe(value)) + } +} + +impl<'a> From<&'a Purl> for GraphQuery<'a> { + fn from(value: &'a Purl) -> Self { + Self::Component(ComponentReference::Purl(value)) + } +} + +impl<'a> From<&'a Query> for GraphQuery<'a> { + fn from(query: &'a Query) -> Self { + Self::Query(query) + } +} diff --git a/modules/analysis/src/service/test.rs b/modules/analysis/src/service/test.rs new file mode 100644 index 000000000..8e8fa8f2f --- /dev/null +++ b/modules/analysis/src/service/test.rs @@ -0,0 +1,461 @@ +use super::*; +use crate::test::*; +use std::{str::FromStr, time::SystemTime}; +use test_context::test_context; +use test_log::test; +use trustify_common::{ + cpe::Cpe, db::query::Query, model::Paginated, purl::Purl, sbom::spdx::fix_license, +}; +use trustify_test_context::{document, spdx::fix_spdx_rels, TrustifyContext}; + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_simple_analysis_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["spdx/simple.json", "spdx/simple.json"]) + .await?; //double ingestion intended + + let service = AnalysisService::new(); + + let analysis_graph = service + .retrieve_root_components(&Query::q("DD"), Paginated::default(), &ctx.db) + .await?; + + assert_eq!( + analysis_graph + .items + .last() + .unwrap() + .ancestors + .last() + .unwrap() + .purl, + vec![Purl::from_str("pkg:rpm/redhat/AA@0.0.0?arch=src")?] + ); + assert_eq!( + analysis_graph + .items + .last() + .unwrap() + .ancestors + .last() + .unwrap() + .node_id, + "SPDXRef-AA".to_string() + ); + assert_eq!(analysis_graph.total, 1); + + // ensure we set implicit relationship on component with no defined relationships + let analysis_graph = service + .retrieve_root_components(&Query::q("EE"), Paginated::default(), &ctx.db) + .await?; + assert_eq!(analysis_graph.total, 1); + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_simple_analysis_cyclonedx_service( + ctx: &TrustifyContext, +) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["cyclonedx/simple.json", "cyclonedx/simple.json"]) + .await?; //double ingestion intended + + let service = AnalysisService::new(); + + let analysis_graph = service + .retrieve_root_components(&Query::q("DD"), Paginated::default(), &ctx.db) + .await?; + + assert_eq!( + analysis_graph + .items + .last() + .unwrap() + .ancestors + .last() + .unwrap() + .purl, + vec![Purl::from_str("pkg:rpm/redhat/AA@0.0.0?arch=src")?] + ); + let node = analysis_graph + .items + .last() + .unwrap() + .ancestors + .last() + .unwrap(); + assert_eq!(node.node_id, "aa".to_string()); + assert_eq!(node.name, "AA".to_string()); + assert_eq!(analysis_graph.total, 1); + + // ensure we set implicit relationship on component with no defined relationships + let analysis_graph = service + .retrieve_root_components(&Query::q("EE"), Paginated::default(), &ctx.db) + .await?; + assert_eq!(analysis_graph.total, 1); + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_simple_by_name_analysis_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let service = AnalysisService::new(); + + let analysis_graph = service + .retrieve_root_components(ComponentReference::Name("B"), Paginated::default(), &ctx.db) + .await?; + + assert_ancestors(&analysis_graph.items, |ancestors| { + assert_eq!( + ancestors, + &[&[ + Node { + id: "SPDXRef-A", + name: "A", + version: "1", + cpes: &["cpe:/a:redhat:simple:1:*:el9:*"], + purls: &["pkg:rpm/redhat/A@0.0.0?arch=src"], + }, + Node { + id: "SPDXRef-DOCUMENT", + name: "simple", + version: "", + cpes: &[], + purls: &[], + }, + ]] + ); + }); + + assert_eq!(analysis_graph.total, 1); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_simple_by_purl_analysis_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let service = AnalysisService::new(); + + let component_purl: Purl = Purl::from_str("pkg:rpm/redhat/B@0.0.0").map_err(Error::Purl)?; + + let analysis_graph = service + .retrieve_root_components(&component_purl, Paginated::default(), &ctx.db) + .await?; + + assert_ancestors(&analysis_graph.items, |ancestors| { + assert_eq!( + ancestors, + [[ + Node { + id: "SPDXRef-A", + name: "A", + version: "1", + purls: &["pkg:rpm/redhat/A@0.0.0?arch=src"], + cpes: &["cpe:/a:redhat:simple:1:*:el9:*"], + }, + Node { + id: "SPDXRef-DOCUMENT", + name: "simple", + version: "", + cpes: &[], + purls: &[], + } + ]] + ); + }); + + assert_eq!(analysis_graph.total, 1); + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_quarkus_analysis_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents([ + "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", + "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", + ]) + .await?; + + let service = AnalysisService::new(); + + let analysis_graph = service + .retrieve_root_components(&Query::q("spymemcached"), Paginated::default(), &ctx.db) + .await?; + + assert_ancestors(&analysis_graph.items, |ancestors| { + assert!( + matches!(ancestors, [ + [..], + [ + Node { + id: "SPDXRef-DOCUMENT", + name: "quarkus-bom-3.2.12.Final-redhat-00002", + version: "", + .. + }, + Node { + id: "SPDXRef-e24fec28-1001-499c-827f-2e2e5f2671b5", + name: "quarkus-bom", + version: "3.2.12.Final-redhat-00002", + cpes: [ + "cpe:/a:redhat:quarkus:3.2:*:el8:*", + ], + purls: [ + "pkg:maven/com.redhat.quarkus.platform/quarkus-bom@3.2.12.Final-redhat-00002?repository_url=https://maven.repository.redhat.com/ga/&type=pom" + ], + }, + ] + ]), + "must match: {ancestors:#?}" + ); + }); + + assert_eq!(analysis_graph.total, 2); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_status_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let service = AnalysisService::new(); + let _load_all_graphs = service.load_all_graphs(&ctx.db).await; + let analysis_status = service.status(&ctx.db).await?; + + assert_eq!(analysis_status.sbom_count, 1); + assert_eq!(analysis_status.graph_count, 1); + + let _clear_all_graphs = service.clear_all_graphs(); + + ctx.ingest_documents([ + "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", + "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", + ]) + .await?; + + let analysis_status = service.status(&ctx.db).await?; + + assert_eq!(analysis_status.sbom_count, 3); + assert_eq!(analysis_status.graph_count, 0); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_simple_deps_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let service = AnalysisService::new(); + + let analysis_graph = service + .retrieve_deps(&Query::q("AA"), Paginated::default(), &ctx.db) + .await?; + + assert_eq!(analysis_graph.total, 1); + + // ensure we set implicit relationship on component with no defined relationships + let analysis_graph = service + .retrieve_root_components(&Query::q("EE"), Paginated::default(), &ctx.db) + .await?; + assert_eq!(analysis_graph.total, 1); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_simple_deps_cyclonedx_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["cyclonedx/simple.json"]).await?; + + let service = AnalysisService::new(); + + let analysis_graph = service + .retrieve_deps(&Query::q("AA"), Paginated::default(), &ctx.db) + .await?; + + assert_eq!(analysis_graph.total, 1); + + // ensure we set implicit relationship on component with no defined relationships + let analysis_graph = service + .retrieve_root_components(&Query::q("EE"), Paginated::default(), &ctx.db) + .await?; + assert_eq!(analysis_graph.total, 1); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_simple_by_name_deps_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let service = AnalysisService::new(); + + let analysis_graph = service + .retrieve_deps(ComponentReference::Name("A"), Paginated::default(), &ctx.db) + .await?; + + assert_eq!(analysis_graph.items.len(), 1); + assert_eq!(analysis_graph.total, 1); + + assert_eq!( + analysis_graph.items[0].purl, + vec![Purl::from_str("pkg:rpm/redhat/A@0.0.0?arch=src")?] + ); + assert_eq!( + analysis_graph.items[0].cpe, + vec![Cpe::from_str("cpe:/a:redhat:simple:1::el9")?] + ); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_simple_by_purl_deps_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["spdx/simple.json"]).await?; + + let service = AnalysisService::new(); + + let component_purl: Purl = + Purl::from_str("pkg:rpm/redhat/AA@0.0.0?arch=src").map_err(Error::Purl)?; + + let analysis_graph = service + .retrieve_deps(&component_purl, Paginated::default(), &ctx.db) + .await?; + + assert_eq!( + analysis_graph.items[0].purl, + vec![Purl::from_str("pkg:rpm/redhat/AA@0.0.0?arch=src")?] + ); + + assert_eq!(analysis_graph.total, 1); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_quarkus_deps_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents([ + "spdx/quarkus-bom-3.2.11.Final-redhat-00001.json", + "spdx/quarkus-bom-3.2.12.Final-redhat-00002.json", + ]) + .await?; + + let service = AnalysisService::new(); + + let analysis_graph = service + .retrieve_deps(&Query::q("spymemcached"), Paginated::default(), &ctx.db) + .await?; + + assert_eq!(analysis_graph.total, 2); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_circular_deps_cyclonedx_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["cyclonedx/cyclonedx-circular.json"]) + .await?; + + let service = AnalysisService::new(); + + let analysis_graph = service + .retrieve_deps( + ComponentReference::Name("junit-bom"), + Paginated::default(), + &ctx.db, + ) + .await?; + + assert_eq!(analysis_graph.total, 1); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_circular_deps_spdx_service(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["spdx/loop.json"]).await?; + + let service = AnalysisService::new(); + + let analysis_graph = service + .retrieve_deps(ComponentReference::Name("A"), Paginated::default(), &ctx.db) + .await?; + + assert_eq!(analysis_graph.total, 1); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn test_retrieve_all_sbom_roots_by_name(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + ctx.ingest_documents(["spdx/quarkus-bom-3.2.11.Final-redhat-00001.json"]) + .await?; + + let service = AnalysisService::new(); + let component_name = "quarkus-vertx-http".to_string(); + + let analysis_graph = service + .retrieve_root_components(&Query::q(&component_name), Paginated::default(), &ctx.db) + .await?; + + log::debug!("Result: {analysis_graph:#?}"); + + let sbom_id = analysis_graph + .items + .last() + .unwrap() + .sbom_id + .parse::()?; + + let roots = service + .retrieve_all_sbom_roots_by_name(sbom_id, component_name, &ctx.db) + .await?; + + assert_eq!( + roots.last().unwrap().name, + "quarkus-bom-3.2.11.Final-redhat-00001" + ); + + Ok(()) +} + +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn load_performance(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let (spdx, _) = + document::("openshift-container-storage-4.8.z.json.xz").await?; + let (spdx, _) = fix_license(&(), spdx); + let spdx = fix_spdx_rels(serde_json::from_value(spdx)?); + + log::info!("Start ingestion"); + + ctx.ingest_json(spdx).await?; + + log::info!("Start populating graph"); + + let start = SystemTime::now(); + let service = AnalysisService::new(); + service.load_all_graphs(&ctx.db).await?; + + log::info!( + "Loading took: {}", + humantime::format_duration(start.elapsed()?) + ); + + Ok(()) +} diff --git a/modules/analysis/src/test.rs b/modules/analysis/src/test.rs index 0dc5f8576..2a55e0caa 100644 --- a/modules/analysis/src/test.rs +++ b/modules/analysis/src/test.rs @@ -1,4 +1,8 @@ -use crate::endpoints::configure; +use crate::{ + endpoints::configure, + model::{AncNode, AncestorSummary}, +}; +use itertools::Itertools; use trustify_test_context::{ call::{self, CallService}, TrustifyContext, @@ -7,3 +11,98 @@ use trustify_test_context::{ pub async fn caller(ctx: &TrustifyContext) -> anyhow::Result { call::caller(|svc| configure(svc, ctx.db.clone())).await } + +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +pub struct Node<'a> { + pub id: &'a str, + pub name: &'a str, + pub version: &'a str, + + pub cpes: &'a [&'a str], + pub purls: &'a [&'a str], +} + +#[derive(PartialEq, Eq, Debug, Clone)] +struct OwnedNode<'a> { + pub id: &'a str, + pub name: &'a str, + pub version: &'a str, + + pub cpes: Vec, + pub purls: Vec, +} + +#[derive(PartialEq, Eq, Debug, Clone)] +struct RefNode<'a> { + pub id: &'a str, + pub name: &'a str, + pub version: &'a str, + + pub cpes: Vec<&'a str>, + pub purls: Vec<&'a str>, +} + +impl<'a> From<&'a AncNode> for OwnedNode<'a> { + fn from(value: &'a AncNode) -> Self { + Self { + id: &value.node_id, + name: &value.name, + version: &value.version, + cpes: value.cpe.iter().map(ToString::to_string).collect(), + purls: value.purl.iter().map(ToString::to_string).collect(), + } + } +} + +pub fn assert_ancestors(ancestors: &[AncestorSummary], f: F) +where + F: for<'a> FnOnce(&'a [&'a [Node]]), +{ + let ancestors = ancestors + .iter() + .sorted_by_key(|a| &a.node_id) + .map(|item| { + item.ancestors + .iter() + .map(OwnedNode::from) + .sorted_by_key(|n| n.id.to_string()) + .collect::>() + }) + .collect::>(); + + let ancestors = ancestors + .iter() + .map(|a| { + a.iter() + .map(|node| RefNode { + id: node.id, + name: node.name, + version: node.version, + cpes: node.cpes.iter().map(|s| s.as_str()).collect(), + purls: node.purls.iter().map(|s| s.as_str()).collect(), + }) + .collect::>() + }) + .collect::>(); + + let ancestors = ancestors + .iter() + .map(|a| { + a.iter() + .map(|node| Node { + id: node.id, + name: node.name, + version: node.version, + cpes: node.cpes.as_slice(), + purls: node.purls.as_slice(), + }) + .collect::>() + }) + .collect::>(); + + let ancestors = ancestors.iter().map(|a| a.as_slice()).collect::>(); + + log::debug!("Ancestors: {ancestors:#?}"); + + f(ancestors.as_slice()) +} diff --git a/modules/fundamental/Cargo.toml b/modules/fundamental/Cargo.toml index ac2b13818..ca1038fcc 100644 --- a/modules/fundamental/Cargo.toml +++ b/modules/fundamental/Cargo.toml @@ -10,6 +10,7 @@ trustify-auth = { workspace = true } trustify-common = { workspace = true } trustify-cvss = { workspace = true } trustify-entity = { workspace = true } +trustify-module-analysis = { workspace = true } trustify-module-ingestor = { workspace = true } trustify-module-storage = { workspace = true } diff --git a/modules/fundamental/src/ai/service/tools/package_info.rs b/modules/fundamental/src/ai/service/tools/package_info.rs index c857ace08..6bb9b74ea 100644 --- a/modules/fundamental/src/ai/service/tools/package_info.rs +++ b/modules/fundamental/src/ai/service/tools/package_info.rs @@ -1,3 +1,4 @@ +use crate::sbom::model::SbomExternalPackageReference; use crate::{ai::service::tools, purl::service::PurlService, sbom::service::SbomService}; use async_trait::async_trait; use langchain_rust::tools::Tool; @@ -127,7 +128,12 @@ Input: The package name, its Identifier URI, or UUID. }; let sboms = sbom_service - .find_related_sboms(item.head.uuid, Default::default(), Default::default(), db) + .find_related_sboms( + SbomExternalPackageReference::Purl(&item.head.purl), + Default::default(), + Default::default(), + db, + ) .await?; #[derive(Serialize)] @@ -228,7 +234,7 @@ mod tests { "pkg:rpm/redhat/libsepol@3.5-1.el9?arch=s390x", r#" { - "identifier": "pkg:rpm/redhat/libsepol@3.5-1.el9?arch=ppc64le", + "identifier": "pkg:rpm/redhat/libsepol@3.5-1.el9?arch=s390x", "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "name": "libsepol", "version": "3.5-1.el9", diff --git a/modules/fundamental/src/endpoints.rs b/modules/fundamental/src/endpoints.rs index 3531f90a7..02efc5db8 100644 --- a/modules/fundamental/src/endpoints.rs +++ b/modules/fundamental/src/endpoints.rs @@ -1,5 +1,6 @@ use actix_web::web; use trustify_common::db::Database; +use trustify_module_analysis::service::AnalysisService; use trustify_module_ingestor::graph::Graph; use trustify_module_ingestor::service::IngestorService; use trustify_module_storage::service::dispatch::DispatchBackend; @@ -16,8 +17,9 @@ pub fn configure( config: Config, db: Database, storage: impl Into, + analysis: AnalysisService, ) { - let ingestor_service = IngestorService::new(Graph::new(db.clone()), storage); + let ingestor_service = IngestorService::new(Graph::new(db.clone()), storage, Some(analysis)); svc.app_data(web::Data::new(ingestor_service)); crate::advisory::endpoints::configure(svc, db.clone(), config.advisory_upload_limit); diff --git a/modules/fundamental/src/license/endpoints/test.rs b/modules/fundamental/src/license/endpoints/test.rs index 1192f3b2c..7b6c53b8a 100644 --- a/modules/fundamental/src/license/endpoints/test.rs +++ b/modules/fundamental/src/license/endpoints/test.rs @@ -19,7 +19,7 @@ async fn list_spdx_licenses(ctx: &TrustifyContext) -> Result<(), anyhow::Error> let response: PaginatedResults = app.call_and_read_body_json(request).await; - assert_eq!(673, response.total); + assert_eq!(687, response.total); Ok(()) } diff --git a/modules/fundamental/src/purl/endpoints/test.rs b/modules/fundamental/src/purl/endpoints/test.rs index 5e5c41d8e..c72b030c5 100644 --- a/modules/fundamental/src/purl/endpoints/test.rs +++ b/modules/fundamental/src/purl/endpoints/test.rs @@ -346,23 +346,3 @@ async fn package_with_status(ctx: &TrustifyContext) -> Result<(), anyhow::Error> Ok(()) } - -#[test_context(TrustifyContext)] -#[test(actix_web::test)] -async fn purl_relationships(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - let app = caller(ctx).await?; - ctx.ingest_documents(["cyclonedx/openssl-3.0.7-18.el9_2.cdx_1.6.sbom.json"]) - .await?; - - let src = "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src"; - let bin = "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=x86_64"; - - let uri = format!("/api/v2/purl/{}", urlencoding::encode(bin)); - let request = TestRequest::get().uri(&uri).to_request(); - let response: Value = app.call_and_read_body_json(request).await; - log::debug!("{response:#?}"); - - assert_eq!(src, response["relationships"]["generated_from"][0]); - - Ok(()) -} diff --git a/modules/fundamental/src/purl/model/details/purl.rs b/modules/fundamental/src/purl/model/details/purl.rs index 33186c66b..6759ff9c5 100644 --- a/modules/fundamental/src/purl/model/details/purl.rs +++ b/modules/fundamental/src/purl/model/details/purl.rs @@ -21,10 +21,10 @@ use trustify_common::{ }; use trustify_cvss::cvss3::{score::Score, severity::Severity, Cvss3Base}; use trustify_entity::{ - advisory, base_purl, cpe, cvss3, license, organization, package_relates_to_package, product, - product_status, product_version, product_version_range, purl_license_assertion, purl_status, - qualified_purl, relationship::Relationship, sbom, sbom_package, sbom_package_purl_ref, status, - version_range, versioned_purl, vulnerability, + advisory, base_purl, cpe, cvss3, license, organization, product, product_status, + product_version, product_version_range, purl_license_assertion, purl_status, qualified_purl, + sbom, sbom_package, sbom_package_purl_ref, status, version_range, versioned_purl, + vulnerability, }; use trustify_module_ingestor::common::{Deprecation, DeprecationForExt}; use utoipa::ToSchema; @@ -38,7 +38,6 @@ pub struct PurlDetails { pub base: BasePurlHead, pub advisories: Vec, pub licenses: Vec, - pub relationships: HashMap>, } impl PurlDetails { @@ -115,27 +114,12 @@ impl PurlDetails { .all(tx) .await?; - let relationships: HashMap> = - package_relates_to_package::Entity::find() - .filter( - package_relates_to_package::Column::LeftNodeId - .eq(qualified_package.purl.to_string()), - ) - .all(tx) - .await? - .into_iter() - .fold(HashMap::new(), |mut h, m| { - h.entry(m.relationship).or_default().push(m.right_node_id); - h - }); - Ok(PurlDetails { head: PurlHead::from_entity(&package, &package_version, qualified_package, tx).await?, version: VersionedPurlHead::from_entity(&package, &package_version, tx).await?, base: BasePurlHead::from_entity(&package).await?, advisories: PurlAdvisory::from_entities(purl_statuses, product_statuses, tx).await?, licenses: PurlLicenseSummary::from_entities(&licenses, tx).await?, - relationships, }) } } diff --git a/modules/fundamental/src/purl/service/mod.rs b/modules/fundamental/src/purl/service/mod.rs index 98ec77b98..136a650ce 100644 --- a/modules/fundamental/src/purl/service/mod.rs +++ b/modules/fundamental/src/purl/service/mod.rs @@ -21,7 +21,11 @@ use trustify_common::{ model::{Paginated, PaginatedResults}, purl::{Purl, PurlErr}, }; -use trustify_entity::{base_purl, qualified_purl, versioned_purl}; +use trustify_entity::{ + base_purl, + qualified_purl::{self, CanonicalPurl}, + versioned_purl, +}; use trustify_module_ingestor::common::Deprecation; #[derive(Default)] @@ -232,33 +236,16 @@ impl PurlService { deprecation: Deprecation, connection: &C, ) -> Result, Error> { - if let Some(version) = &purl.version { - let mut query = qualified_purl::Entity::find() - .left_join(versioned_purl::Entity) - .left_join(base_purl::Entity) - .filter(base_purl::Column::Type.eq(&purl.ty)) - .filter(base_purl::Column::Name.eq(&purl.name)) - .filter(versioned_purl::Column::Version.eq(version)); - - if let Some(ns) = &purl.namespace { - query = query.filter(base_purl::Column::Namespace.eq(ns)); - } else { - query = query.filter(base_purl::Column::Namespace.is_null()); - } - - let purl = query.one(connection).await?; - - if let Some(purl) = purl { - Ok(Some( - PurlDetails::from_entity(None, None, &purl, deprecation, connection).await?, - )) - } else { - Ok(None) - } - } else { - Err(Error::Purl(PurlErr::MissingVersion( - "A fully-qualified pURL requires a version".to_string(), - ))) + let canonical = CanonicalPurl::from(purl.clone()); + match qualified_purl::Entity::find() + .filter(qualified_purl::Column::Purl.eq(canonical)) + .one(connection) + .await? + { + Some(purl) => Ok(Some( + PurlDetails::from_entity(None, None, &purl, deprecation, connection).await?, + )), + None => Ok(None), } } @@ -269,16 +256,14 @@ impl PurlService { deprecation: Deprecation, connection: &C, ) -> Result, Error> { - if let Some(qualified_package) = qualified_purl::Entity::find_by_id(*purl_uuid) + match qualified_purl::Entity::find_by_id(*purl_uuid) .one(connection) .await? { - Ok(Some( - PurlDetails::from_entity(None, None, &qualified_package, deprecation, connection) - .await?, - )) - } else { - Ok(None) + Some(pkg) => Ok(Some( + PurlDetails::from_entity(None, None, &pkg, deprecation, connection).await?, + )), + None => Ok(None), } } diff --git a/modules/fundamental/src/purl/service/test.rs b/modules/fundamental/src/purl/service/test.rs index 00ad6f6eb..3c6b5d29c 100644 --- a/modules/fundamental/src/purl/service/test.rs +++ b/modules/fundamental/src/purl/service/test.rs @@ -816,25 +816,33 @@ async fn ingest_some_log4j_data(ctx: &TrustifyContext) -> Result<(), anyhow::Err &ctx.db, ) .await?; + + log4j_123 + .ingest_qualified_package( + &Purl::from_str("pkg:maven/org.apache/log4j@1.2.3")?, + &ctx.db, + ) + .await?; Ok(()) } #[test_context(TrustifyContext)] #[test(actix_web::test)] -async fn purl_by_purl(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { +async fn unqualified_purl_by_purl(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { let service = PurlService::new(); ingest_some_log4j_data(ctx).await?; + let purl = "pkg:maven/org.apache/log4j@1.2.3"; + let results = service - .purl_by_purl( - &Purl::from_str("pkg:maven/org.apache/log4j@1.2.3")?, - Default::default(), - &ctx.db, - ) - .await?; + .purl_by_purl(&Purl::from_str(purl)?, Default::default(), &ctx.db) + .await? + .unwrap(); - assert_eq!(results.unwrap().version.version, "1.2.3"); + log::debug!("{results:#?}"); + assert_eq!(results.head.purl.to_string(), purl); + assert_eq!(results.version.version, "1.2.3"); Ok(()) } diff --git a/modules/fundamental/src/sbom/endpoints/mod.rs b/modules/fundamental/src/sbom/endpoints/mod.rs index c54bb22c5..b50872856 100644 --- a/modules/fundamental/src/sbom/endpoints/mod.rs +++ b/modules/fundamental/src/sbom/endpoints/mod.rs @@ -1,29 +1,27 @@ mod config; mod label; +mod query; #[cfg(test)] mod test; +pub use query::*; + use crate::{ purl::service::PurlService, sbom::{ model::{ - details::SbomAdvisory, SbomPackage, SbomPackageReference, SbomPackageRelation, - SbomSummary, Which, + details::SbomAdvisory, SbomExternalPackageReference, SbomNodeReference, SbomPackage, + SbomPackageRelation, SbomSummary, Which, }, service::SbomService, }, Error::{self, Internal}, }; -use actix_http::body::BoxBody; -use actix_web::{delete, get, http::header, post, web, HttpResponse, Responder, ResponseError}; +use actix_web::{delete, get, http::header, post, web, HttpResponse, Responder}; use config::Config; use futures_util::TryStreamExt; -use sea_orm::prelude::Uuid; -use sea_orm::TransactionTrait; -use std::{ - fmt::{Display, Formatter}, - str::FromStr, -}; +use sea_orm::{prelude::Uuid, TransactionTrait}; +use std::str::FromStr; use trustify_auth::{ all, authenticator::user::UserInformation, @@ -33,10 +31,8 @@ use trustify_auth::{ use trustify_common::{ db::{query::Query, Database}, decompress::decompress_async, - error::ErrorInformation, id::Id, model::{BinaryData, Paginated, PaginatedResults}, - purl::Purl, }; use trustify_entity::{labels::Labels, relationship::Relationship}; use trustify_module_ingestor::{ @@ -101,56 +97,6 @@ pub async fn all( Ok(HttpResponse::Ok().json(result)) } -#[derive(Clone, Debug, serde::Deserialize, utoipa::IntoParams, utoipa::ToSchema)] -struct AllRelatedQuery { - /// Find by PURL - #[serde(default)] - pub purl: Option, - /// Find by an ID of a package - #[serde(default)] - pub id: Option, -} - -#[derive(Debug)] -pub struct AllRelatedQueryParseError(AllRelatedQuery); - -impl Display for AllRelatedQueryParseError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Requires either `purl` or `id` (got - purl: {:?}, id: {:?})", - self.0.purl, self.0.id - ) - } -} - -impl ResponseError for AllRelatedQueryParseError { - fn error_response(&self) -> HttpResponse { - HttpResponse::BadRequest().json(ErrorInformation { - error: "IdOrPurl".into(), - message: "Requires either `purl` or `id`".to_string(), - details: Some(format!( - "Received - PURL: {:?}, ID: {:?}", - self.0.purl, self.0.id - )), - }) - } -} - -impl TryFrom for Uuid { - type Error = AllRelatedQueryParseError; - - fn try_from(value: AllRelatedQuery) -> Result { - Ok(match (&value.purl, &value.id) { - (Some(purl), None) => purl.qualifier_uuid(), - (None, Some(id)) => *id, - _ => { - return Err(AllRelatedQueryParseError(value)); - } - }) - } -} - /// Find all SBOMs containing the provided package. /// /// The package can be provided either via a PURL or using the ID of a package as returned by @@ -161,7 +107,7 @@ impl TryFrom for Uuid { params( Query, Paginated, - AllRelatedQuery, + ExternalReferenceQuery, ), responses( (status = 200, description = "Matching SBOMs", body = PaginatedResults), @@ -173,13 +119,13 @@ pub async fn all_related( db: web::Data, web::Query(search): web::Query, web::Query(paginated): web::Query, - web::Query(all_related): web::Query, + web::Query(all_related): web::Query, authorizer: web::Data, user: UserInformation, ) -> actix_web::Result { authorizer.require(&user, Permission::ReadSbom)?; - let id = all_related.try_into()?; + let id = (&all_related).try_into()?; let result = sbom .find_related_sboms(id, paginated, search, db.as_ref()) @@ -196,7 +142,7 @@ pub async fn all_related( tag = "sbom", operation_id = "countRelatedSboms", params( - AllRelatedQuery, + ExternalReferenceQuery, ), responses( (status = 200, description = "Number of matching SBOMs per package", body = Vec), @@ -206,12 +152,12 @@ pub async fn all_related( pub async fn count_related( sbom: web::Data, db: web::Data, - web::Json(ids): web::Json>, + web::Json(ids): web::Json>, _: Require, ) -> actix_web::Result { let ids = ids - .into_iter() - .map(Uuid::try_from) + .iter() + .map(SbomExternalPackageReference::try_from) .collect::, _>>()?; let result = sbom.count_related_sboms(ids, db.as_ref()).await?; @@ -389,8 +335,8 @@ pub async fn related( paginated, related.which, match &related.reference { - None => SbomPackageReference::All, - Some(id) => SbomPackageReference::Package(id), + None => SbomNodeReference::All, + Some(id) => SbomNodeReference::Package(id), }, related.relationship, db.as_ref(), diff --git a/modules/fundamental/src/sbom/endpoints/query.rs b/modules/fundamental/src/sbom/endpoints/query.rs new file mode 100644 index 000000000..6862d1ed8 --- /dev/null +++ b/modules/fundamental/src/sbom/endpoints/query.rs @@ -0,0 +1,61 @@ +use crate::sbom::model::SbomExternalPackageReference; +use actix_http::body::BoxBody; +use actix_web::{HttpResponse, ResponseError}; +use std::fmt::{Display, Formatter}; +use trustify_common::{cpe::Cpe, error::ErrorInformation, purl::Purl}; + +#[derive(Clone, Debug, serde::Deserialize, utoipa::IntoParams, utoipa::ToSchema)] +pub struct ExternalReferenceQuery { + /// Find by PURL + #[serde(default)] + pub purl: Option, + /// Find by CPE + #[serde(default)] + pub cpe: Option, +} + +#[derive(Debug)] +pub struct ExternalReferenceQueryParseError(ExternalReferenceQuery); + +impl Display for ExternalReferenceQueryParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Requires either `purl` or `cpe` (got - purl: {:?}, cpe: {:?})", + self.0.purl, self.0.cpe + ) + } +} + +impl ResponseError for ExternalReferenceQueryParseError { + fn error_response(&self) -> HttpResponse { + HttpResponse::BadRequest().json(ErrorInformation { + error: "CpeOrPurl".into(), + message: "Requires either `purl` or `cpe`".to_string(), + details: Some(format!( + "Received - PURL: {:?}, CPE: {:?}", + self.0.purl, self.0.cpe + )), + }) + } +} + +impl<'a> TryFrom<&'a ExternalReferenceQuery> for SbomExternalPackageReference<'a> { + type Error = ExternalReferenceQueryParseError; + + fn try_from(value: &'a ExternalReferenceQuery) -> Result { + Ok(match value { + ExternalReferenceQuery { + purl: Some(purl), + cpe: None, + } => SbomExternalPackageReference::Purl(purl), + ExternalReferenceQuery { + purl: None, + cpe: Some(cpe), + } => SbomExternalPackageReference::Cpe(cpe), + _ => { + return Err(ExternalReferenceQueryParseError(value.clone())); + } + }) + } +} diff --git a/modules/fundamental/src/sbom/model/mod.rs b/modules/fundamental/src/sbom/model/mod.rs index 6402f0492..427b662e3 100644 --- a/modules/fundamental/src/sbom/model/mod.rs +++ b/modules/fundamental/src/sbom/model/mod.rs @@ -8,7 +8,7 @@ use async_graphql::SimpleObject; use sea_orm::{prelude::Uuid, ConnectionTrait, ModelTrait, PaginatorTrait}; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; -use trustify_common::model::Paginated; +use trustify_common::{cpe::Cpe, model::Paginated, purl::Purl}; use trustify_entity::{ labels::Labels, relationship::Relationship, sbom, sbom_node, sbom_package, source_document, }; @@ -109,28 +109,46 @@ pub struct SbomPackage { pub cpe: Vec, } -// TODO: think about a way to add CPE and PURLs too -#[derive(Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum SbomPackageReference<'a> { + Internal(&'a str), + External(SbomExternalPackageReference<'a>), +} + +impl<'a> From> for SbomPackageReference<'a> { + fn from(value: SbomExternalPackageReference<'a>) -> Self { + Self::External(value) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum SbomExternalPackageReference<'a> { + Purl(&'a Purl), + Cpe(&'a Cpe), +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum SbomNodeReference<'a> { /// Reference all packages of the SBOM. All, /// Reference a package inside an SBOM, by its node id. + // TODO: replace with `SbomPackageReference` Package(&'a str), } -impl<'a> From<&'a str> for SbomPackageReference<'a> { +impl<'a> From<&'a str> for SbomNodeReference<'a> { fn from(value: &'a str) -> Self { Self::Package(value) } } -impl From<()> for SbomPackageReference<'_> { +impl From<()> for SbomNodeReference<'_> { fn from(_value: ()) -> Self { Self::All } } -impl<'a> From<&'a SbomPackage> for SbomPackageReference<'a> { +impl<'a> From<&'a SbomPackage> for SbomNodeReference<'a> { fn from(value: &'a SbomPackage) -> Self { Self::Package(&value.id) } diff --git a/modules/fundamental/src/sbom/service/sbom.rs b/modules/fundamental/src/sbom/service/sbom.rs index 493cc9bd7..9afaebe58 100644 --- a/modules/fundamental/src/sbom/service/sbom.rs +++ b/modules/fundamental/src/sbom/service/sbom.rs @@ -2,8 +2,8 @@ use super::SbomService; use crate::{ purl::model::summary::purl::PurlSummary, sbom::model::{ - details::SbomDetails, SbomPackage, SbomPackageReference, SbomPackageRelation, SbomSummary, - Which, + details::SbomDetails, SbomExternalPackageReference, SbomNodeReference, SbomPackage, + SbomPackageRelation, SbomSummary, Which, }, Error, }; @@ -62,7 +62,6 @@ impl SbomService { &self, id: Id, statuses: Vec, - connection: &C, ) -> Result, Error> { Ok(match self.fetch_sbom(id, connection).await? { @@ -210,7 +209,7 @@ impl SbomService { Default::default(), paginated, Which::Right, - SbomPackageReference::All, + SbomNodeReference::All, Some(Relationship::DescribedBy), db, ) @@ -221,30 +220,76 @@ impl SbomService { #[instrument(skip(self, connection), err(level=tracing::Level::INFO))] pub async fn count_related_sboms( &self, - qualified_package_ids: Vec, + references: Vec>, connection: &C, ) -> Result, Error> { - let query = sbom::Entity::find() - .join(JoinType::Join, sbom::Relation::Packages.def()) - .join(JoinType::Join, sbom_package::Relation::Purl.def()) - .filter( - sbom_package_purl_ref::Column::QualifiedPurlId.is_in(qualified_package_ids.clone()), - ) - .group_by(sbom_package_purl_ref::Column::QualifiedPurlId) - .select_only() - .column(sbom_package_purl_ref::Column::QualifiedPurlId) - .column_as(sbom_package::Column::SbomId.count(), "count") - .into_tuple::<(Uuid, i64)>() - .all(connection) - .await?; + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] + enum Id { + Cpe(Uuid), + Purl(Uuid), + } + + let ids = references + .iter() + .map(|r| match r { + SbomExternalPackageReference::Cpe(c) => Id::Cpe(c.uuid()), + SbomExternalPackageReference::Purl(p) => Id::Purl(p.qualifier_uuid()), + }) + .collect::>(); + + let mut counts_map = HashMap::new(); + + let cpes = ids + .iter() + .filter_map(|id| match id { + Id::Cpe(id) => Some(*id), + _ => None, + }) + .collect::>(); + + counts_map.extend( + sbom::Entity::find() + .join(JoinType::Join, sbom::Relation::Packages.def()) + .join(JoinType::Join, sbom_package::Relation::Cpe.def()) + .filter(sbom_package_cpe_ref::Column::CpeId.is_in(cpes)) + .group_by(sbom_package_cpe_ref::Column::CpeId) + .select_only() + .column(sbom_package_cpe_ref::Column::CpeId) + .column_as(sbom_package::Column::SbomId.count(), "count") + .into_tuple::<(Uuid, i64)>() + .all(connection) + .await? + .into_iter() + .map(|(id, count)| (Id::Cpe(id), count)), + ); - // turn result into a map + let purls = ids + .iter() + .filter_map(|id| match id { + Id::Purl(id) => Some(*id), + _ => None, + }) + .collect::>(); - let counts_map = query.into_iter().collect::>(); + counts_map.extend( + sbom::Entity::find() + .join(JoinType::Join, sbom::Relation::Packages.def()) + .join(JoinType::Join, sbom_package::Relation::Purl.def()) + .filter(sbom_package_purl_ref::Column::QualifiedPurlId.is_in(purls)) + .group_by(sbom_package_purl_ref::Column::QualifiedPurlId) + .select_only() + .column(sbom_package_purl_ref::Column::QualifiedPurlId) + .column_as(sbom_package::Column::SbomId.count(), "count") + .into_tuple::<(Uuid, i64)>() + .all(connection) + .await? + .into_iter() + .map(|(id, count)| (Id::Purl(id), count)), + ); // now use the inbound order and retrieve results in that order - let result: Vec = qualified_package_ids + let result: Vec = ids .into_iter() .map(|id| counts_map.get(&id).copied().unwrap_or_default()) .collect(); @@ -257,17 +302,23 @@ impl SbomService { #[instrument(skip(self, connection), err(level=tracing::Level::INFO))] pub async fn find_related_sboms( &self, - qualified_package_id: Uuid, + package_ref: SbomExternalPackageReference<'_>, paginated: Paginated, query: Query, connection: &C, ) -> Result, Error> { - let query = sbom::Entity::find() - .join(JoinType::Join, sbom::Relation::Packages.def()) - .join(JoinType::Join, sbom_package::Relation::Purl.def()) - .filter(sbom_package_purl_ref::Column::QualifiedPurlId.eq(qualified_package_id)) - .filtering(query)? - .find_also_linked(SbomNodeLink); + let select = sbom::Entity::find().join(JoinType::Join, sbom::Relation::Packages.def()); + + let select = match package_ref { + SbomExternalPackageReference::Purl(purl) => select + .join(JoinType::Join, sbom_package::Relation::Purl.def()) + .filter(sbom_package_purl_ref::Column::QualifiedPurlId.eq(purl.qualifier_uuid())), + SbomExternalPackageReference::Cpe(cpe) => select + .join(JoinType::Join, sbom_package::Relation::Cpe.def()) + .filter(sbom_package_cpe_ref::Column::CpeId.eq(cpe.uuid())), + }; + + let query = select.filtering(query)?.find_also_linked(SbomNodeLink); // limit and execute @@ -296,12 +347,10 @@ impl SbomService { search: Query, paginated: Paginated, which: Which, - reference: impl Into> + Debug, + reference: impl Into> + Debug, relationship: Option, db: &C, ) -> Result, Error> { - // let db = self.db.connection(connection); - // which way log::debug!("Which: {which:?}"); @@ -346,11 +395,11 @@ impl SbomService { // filter for reference query = match reference.into() { - SbomPackageReference::All => { + SbomNodeReference::All => { // sbom - add join to sbom table query.join(JoinType::Join, sbom_node::Relation::Sbom.def()) } - SbomPackageReference::Package(node_id) => { + SbomNodeReference::Package(node_id) => { // package - set node id filter query.filter(filter.eq(node_id)) } @@ -401,7 +450,7 @@ impl SbomService { &self, sbom_id: Uuid, relationship: impl Into>, - pkg: impl Into> + Debug, + pkg: impl Into> + Debug, tx: &C, ) -> Result, Error> { let result = self @@ -604,32 +653,6 @@ struct PurlDto { qualifiers: Qualifiers, } -/* -impl From for Purl { - fn from(value: PurlDto) -> Self { - let PurlDto { - r#type, - name, - namespace, - version, - qualifiers, - } = value; - Self { - ty: r#type, - name, - namespace, - version: if version.is_empty() { - None - } else { - Some(version) - }, - qualifiers: qualifiers.0, - } - } -} - - */ - #[derive(Debug)] pub struct QueryCatcher { pub advisory: advisory::Model, diff --git a/modules/fundamental/src/sbom/service/test.rs b/modules/fundamental/src/sbom/service/test.rs index 246376041..ad810f582 100644 --- a/modules/fundamental/src/sbom/service/test.rs +++ b/modules/fundamental/src/sbom/service/test.rs @@ -1,9 +1,9 @@ -use crate::sbom::service::SbomService; +use crate::{sbom::model::SbomExternalPackageReference, sbom::service::SbomService}; use std::str::FromStr; use test_context::test_context; use test_log::test; -use trustify_common::id::Id; -use trustify_common::purl::Purl; +use trustify_common::cpe::Cpe; +use trustify_common::{id::Id, purl::Purl}; use trustify_test_context::TrustifyContext; #[test_context(TrustifyContext)] @@ -53,21 +53,29 @@ async fn count_sboms(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { let service = SbomService::new(ctx.db.clone()); - let neither = Purl::from_str("pkg:maven/io.smallrye/smallrye-graphql@0.0.0.redhat-00000?repository_url=https://maven.repository.redhat.com/ga/&type=jar")?; - let both = Purl::from_str("pkg:maven/io.smallrye/smallrye-graphql@2.2.3.redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar")?; - let one = Purl::from_str("pkg:maven/io.quarkus/quarkus-kubernetes-service-binding-deployment@3.2.12.Final-redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar")?; + let neither_purl = Purl::from_str("pkg:maven/io.smallrye/smallrye-graphql@0.0.0.redhat-00000?repository_url=https://maven.repository.redhat.com/ga/&type=jar")?; + let both_purl = Purl::from_str("pkg:maven/io.smallrye/smallrye-graphql@2.2.3.redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar")?; + let one_purl = Purl::from_str("pkg:maven/io.quarkus/quarkus-kubernetes-service-binding-deployment@3.2.12.Final-redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar")?; + + let neither_cpe = Cpe::from_str("cpe:/a:redhat:quarkus:0.0::el8")?; + let both_cpe = Cpe::from_str("cpe:/a:redhat:quarkus:3.2::el8")?; + + assert_ne!(neither_cpe.uuid(), both_cpe.uuid()); + let counts = service .count_related_sboms( vec![ - neither.qualifier_uuid(), - both.qualifier_uuid(), - one.qualifier_uuid(), + SbomExternalPackageReference::Cpe(&neither_cpe), + SbomExternalPackageReference::Cpe(&both_cpe), + SbomExternalPackageReference::Purl(&neither_purl), + SbomExternalPackageReference::Purl(&both_purl), + SbomExternalPackageReference::Purl(&one_purl), ], &ctx.db, ) .await?; - assert_eq!(counts, vec![0, 2, 1]); + assert_eq!(counts, vec![0, 2, 0, 2, 1]); Ok(()) } diff --git a/modules/fundamental/src/test/common.rs b/modules/fundamental/src/test/common.rs index cc430a48f..5ecee76ae 100644 --- a/modules/fundamental/src/test/common.rs +++ b/modules/fundamental/src/test/common.rs @@ -1,3 +1,4 @@ +use trustify_module_analysis::service::AnalysisService; use trustify_test_context::{ call::{self, CallService}, TrustifyContext, @@ -11,5 +12,6 @@ async fn caller_with( ctx: &TrustifyContext, config: Config, ) -> anyhow::Result { - call::caller(|svc| configure(svc, config, ctx.db.clone(), ctx.storage.clone())).await + let analysis = AnalysisService::new(); + call::caller(|svc| configure(svc, config, ctx.db.clone(), ctx.storage.clone(), analysis)).await } diff --git a/modules/fundamental/src/vulnerability/service/test.rs b/modules/fundamental/src/vulnerability/service/test.rs index 8b26a4691..e59a28789 100644 --- a/modules/fundamental/src/vulnerability/service/test.rs +++ b/modules/fundamental/src/vulnerability/service/test.rs @@ -299,7 +299,7 @@ async fn product_statuses(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { // Ensure that purl->vuln mapping is good let purl = purl_service .purl_by_purl( - &Purl::try_from("pkg:maven/io.quarkus/quarkus-vertx-http@2.13.8.Final-redhat-00004")?, + &Purl::try_from("pkg:maven/io.quarkus/quarkus-vertx-http@2.13.8.Final-redhat-00004?repository_url=https://maven.repository.redhat.com/ga/&type=jar")?, Default::default(), &ctx.db, ) diff --git a/modules/fundamental/tests/dataset.rs b/modules/fundamental/tests/dataset.rs index 3a6443a83..9b56f7ad3 100644 --- a/modules/fundamental/tests/dataset.rs +++ b/modules/fundamental/tests/dataset.rs @@ -88,6 +88,29 @@ async fn ingest(ctx: TrustifyContext) -> anyhow::Result<()> { assert_eq!(content.len(), 1174356); + let sbom_details = service + .fetch_sbom_details(sbom.id.clone(), vec![], &ctx.db) + .await?; + assert!(sbom_details.is_some()); + let sbom_details = sbom_details.unwrap(); + assert_eq!(sbom_details.summary.head.name, "quarkus-bom"); + + // test advisories + + let advisories = sbom_details.advisories; + assert_eq!(advisories.len(), 22); + + let advisories_affected = advisories + .into_iter() + .filter(|advisory| { + advisory + .status + .iter() + .any(|sbom_status| sbom_status.status == "affected") + }) + .collect::>(); + assert_eq!(advisories_affected.len(), 11); + // done Ok(()) diff --git a/modules/fundamental/tests/sbom/cyclonedx/mod.rs b/modules/fundamental/tests/sbom/cyclonedx/mod.rs index 2dd9bdf05..6f71e6ff1 100644 --- a/modules/fundamental/tests/sbom/cyclonedx/mod.rs +++ b/modules/fundamental/tests/sbom/cyclonedx/mod.rs @@ -1,4 +1,5 @@ mod cpe; +mod purl; use super::*; use std::str::FromStr; diff --git a/modules/fundamental/tests/sbom/cyclonedx/purl.rs b/modules/fundamental/tests/sbom/cyclonedx/purl.rs new file mode 100644 index 000000000..90dce989c --- /dev/null +++ b/modules/fundamental/tests/sbom/cyclonedx/purl.rs @@ -0,0 +1,77 @@ +use itertools::Itertools; +use test_context::test_context; +use test_log::test; +use trustify_entity::relationship::Relationship; +use trustify_module_fundamental::sbom::model::{SbomNodeReference, Which}; +use trustify_module_fundamental::{ + purl::model::summary::purl::PurlSummary, sbom::service::SbomService, +}; +use trustify_test_context::TrustifyContext; + +fn to_string(purl: &PurlSummary) -> String { + purl.head.purl.to_string() +} + +fn to_strings(purls: &[PurlSummary]) -> Vec { + purls.iter().map(to_string).sorted_unstable().collect() +} + +/// test with multiple purls +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn simple_ref(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let service = SbomService::new(ctx.db.clone()); + + let result = ctx + .ingest_document("cyclonedx/openssl-3.0.7-18.el9_2.cdx_1.6_aliases.sbom.json") + .await?; + + let sbom_id = result.id.try_as_uid().expect("Must be a UID"); + + // fetch describes + + let packages = service + .describes_packages(sbom_id, Default::default(), &ctx.db) + .await?; + + assert_eq!(packages.total, 1); + assert_eq!(packages.items.len(), 1); + + let package = &packages.items[0]; + assert_eq!( + to_strings(&package.purl), + // we must find two purls here, one from the main section, the other from the evidence + vec![ + "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src", + "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src&foo=bar" + ] + ); + + // now check component + + let result = service + .fetch_related_packages( + sbom_id, + Default::default(), + Default::default(), + Which::Right, + SbomNodeReference::Package("pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src" /* this is actually the bom-ref value */), + Some(Relationship::AncestorOf), + &ctx.db, + ) + .await?; + + assert_eq!(result.total, 1); + let relation = &result.items[0]; + assert_eq!(to_strings(&relation.package.purl), + // we must find two purls here, one from the main section, the other from the evidence + vec![ + "pkg:generic/openssl@3.0.7?checksum=SHA-512:1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e&download_url=https://pkgs.devel.redhat.com/repo/openssl/openssl-3.0.7-hobbled.tar.gz/sha512/1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e/openssl-3.0.7-hobbled.tar.gz", + "pkg:generic/openssl@3.0.7?checksum=SHA-512:1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e&download_url=https://pkgs.devel.redhat.com/repo/openssl/openssl-3.0.7-hobbled.tar.gz/sha512/1aea183b0b6650d9d5e7ba87b613bb1692c71720b0e75377b40db336b40bad780f7e8ae8dfb9f60841eeb4381f4b79c4c5043210c96e7cb51f90791b80c8285e/openssl-3.0.7-hobbled.tar.gz&foo=bar" + ] + ); + + // done + + Ok(()) +} diff --git a/modules/fundamental/tests/sbom/mod.rs b/modules/fundamental/tests/sbom/mod.rs index 9cd43850d..338bb2a2f 100644 --- a/modules/fundamental/tests/sbom/mod.rs +++ b/modules/fundamental/tests/sbom/mod.rs @@ -12,7 +12,7 @@ use trustify_common::{db::Database, hashing::Digests}; use trustify_module_fundamental::sbom::service::SbomService; use trustify_module_ingestor::{ graph::{ - sbom::{self, spdx::parse_spdx, SbomContext, SbomInformation}, + sbom::{self, SbomContext, SbomInformation}, Graph, }, service::Discard, diff --git a/modules/fundamental/tests/sbom/reingest.rs b/modules/fundamental/tests/sbom/reingest.rs index 399c0e4d6..8b03edf3f 100644 --- a/modules/fundamental/tests/sbom/reingest.rs +++ b/modules/fundamental/tests/sbom/reingest.rs @@ -7,6 +7,7 @@ use tracing::instrument; use trustify_common::db::query::Query; use trustify_common::model::Paginated; use trustify_common::purl::Purl; +use trustify_module_fundamental::sbom::model::SbomExternalPackageReference; use trustify_module_fundamental::sbom::{model::details::SbomDetails, service::SbomService}; use trustify_module_ingestor::service::Format; use trustify_test_context::{document_bytes, TrustifyContext}; @@ -86,7 +87,7 @@ async fn quarkus(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { let sboms = sbom .find_related_sboms( - Purl::from_str(purl).expect("must parse").qualifier_uuid(), + SbomExternalPackageReference::Purl(&Purl::from_str(purl).expect("must parse")), Paginated::default(), Query::default(), &ctx.db, diff --git a/modules/fundamental/tests/sbom/spdx.rs b/modules/fundamental/tests/sbom/spdx.rs index f0dc49c9e..17e2535eb 100644 --- a/modules/fundamental/tests/sbom/spdx.rs +++ b/modules/fundamental/tests/sbom/spdx.rs @@ -1,3 +1,4 @@ +mod aliases; mod corner_cases; mod issue_552; mod perf; @@ -9,8 +10,7 @@ use test_context::test_context; use test_log::test; use time::OffsetDateTime; use tracing::instrument; -use trustify_common::id::Id; -use trustify_common::purl::Purl; +use trustify_common::{id::Id, purl::Purl, sbom::spdx::parse_spdx}; use trustify_entity::relationship::Relationship; use trustify_module_fundamental::{ purl::model::{summary::purl::PurlSummary, PurlHead}, @@ -37,7 +37,7 @@ async fn parse_spdx_quarkus(ctx: &TrustifyContext) -> Result<(), anyhow::Error> assert_eq!(first.name, "quarkus-bom"); assert_eq!(first.version, Some("2.13.8.Final-redhat-00004".to_string())); - assert!( matches!( + assert!(matches!( &first.purl[0], PurlSummary { head: PurlHead { @@ -49,6 +49,11 @@ async fn parse_spdx_quarkus(ctx: &TrustifyContext) -> Result<(), anyhow::Error> if *purl == Purl::from_str("pkg:maven/com.redhat.quarkus.platform/quarkus-bom@2.13.8.Final-redhat-00004?repository_url=https://maven.repository.redhat.com/ga/&type=pom")? )); + assert_eq!( + first.cpe[0], + "cpe:/a:redhat:quarkus:2.13:*:el8:*" + ); + let contains = service .related_packages( sbom.sbom.sbom_id, diff --git a/modules/fundamental/tests/sbom/spdx/aliases.rs b/modules/fundamental/tests/sbom/spdx/aliases.rs new file mode 100644 index 000000000..ee4f64b2c --- /dev/null +++ b/modules/fundamental/tests/sbom/spdx/aliases.rs @@ -0,0 +1,59 @@ +use anyhow::bail; +use itertools::Itertools; +use test_context::test_context; +use test_log::test; +use trustify_common::id::Id; +use trustify_module_analysis::service::{AnalysisService, ComponentReference}; +use trustify_test_context::TrustifyContext; + +/// A test to see that we can handle multiple CPEs and PURLs per package/component with SPDX. +#[test_context(TrustifyContext)] +#[test(tokio::test)] +async fn cpe_purl(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let result = ctx + .ingest_document("spdx/openssl-3.0.7-18.el9_2.spdx.alias.json") + .await?; + + let Id::Uuid(_id) = result.id else { + bail!("must be an id") + }; + + let service = AnalysisService::new(); + + let result = service + .retrieve_components( + ComponentReference::Id("SPDXRef-SRPM"), + Default::default(), + &ctx.db, + ) + .await?; + + // must be exactly one node, as we query by ID and only have one SBOM + assert_eq!(result.items.len(), 1); + + let item = &result.items[0]; + assert_eq!( + item.cpe + .iter() + .map(ToString::to_string) + .sorted() + .collect::>(), + vec![ + "cpe:/a:redhat:openssl-foo:3.0.7:*:el9:*", + "cpe:/a:redhat:openssl:3.0.7:*:el9:*", + ] + ); + assert_eq!( + item.purl + .iter() + .map(ToString::to_string) + .sorted() + .collect::>(), + vec![ + "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src", + "pkg:rpm/redhat/openssl@3.0.7-18.el9_2?arch=src&foo=bar" + ] + ); + + Ok(()) +} diff --git a/modules/fundamental/tests/sbom/spdx/corner_cases.rs b/modules/fundamental/tests/sbom/spdx/corner_cases.rs index aa2da457f..686840567 100644 --- a/modules/fundamental/tests/sbom/spdx/corner_cases.rs +++ b/modules/fundamental/tests/sbom/spdx/corner_cases.rs @@ -9,7 +9,7 @@ use test_context::test_context; use test_log::test; use trustify_common::{id::Id, purl::Purl}; use trustify_entity::relationship::Relationship; -use trustify_module_fundamental::{sbom::model::SbomPackageReference, sbom::service::SbomService}; +use trustify_module_fundamental::{sbom::model::SbomNodeReference, sbom::service::SbomService}; use trustify_module_ingestor::graph::{ purl::qualified_package::QualifiedPackageContext, sbom::SbomContext, }; @@ -63,7 +63,7 @@ async fn infinite_loop(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { assert_eq!(packages.total, 1); let packages = service - .related_packages(id, None, SbomPackageReference::All, &ctx.db) + .related_packages(id, None, SbomNodeReference::All, &ctx.db) .await?; log::info!("Packages: {packages:#?}"); @@ -99,7 +99,7 @@ async fn double_ref(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { assert_eq!(packages.len(), 3); let packages = service - .related_packages(id, None, SbomPackageReference::All, &ctx.db) + .related_packages(id, None, SbomNodeReference::All, &ctx.db) .await?; log::info!("Packages: {packages:#?}"); @@ -135,7 +135,7 @@ async fn self_ref(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { assert_eq!(packages.len(), 0); let packages = service - .related_packages(id, None, SbomPackageReference::All, &ctx.db) + .related_packages(id, None, SbomNodeReference::All, &ctx.db) .await?; log::info!("Packages: {packages:#?}"); @@ -171,7 +171,7 @@ async fn self_ref_package(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { assert_eq!(packages.len(), 1); let packages = service - .related_packages(id, None, SbomPackageReference::All, &ctx.db) + .related_packages(id, None, SbomNodeReference::All, &ctx.db) .await?; log::info!("Packages: {packages:#?}"); @@ -179,12 +179,7 @@ async fn self_ref_package(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { assert_eq!(packages.len(), 1); let packages = service - .related_packages( - id, - None, - SbomPackageReference::Package("SPDXRef-A"), - &ctx.db, - ) + .related_packages(id, None, SbomNodeReference::Package("SPDXRef-A"), &ctx.db) .await?; log::info!("Packages: {packages:#?}"); diff --git a/modules/graphql/src/advisory.rs b/modules/graphql/src/advisory.rs index ff8941301..75f83be87 100644 --- a/modules/graphql/src/advisory.rs +++ b/modules/graphql/src/advisory.rs @@ -11,7 +11,7 @@ pub struct AdvisoryQuery; #[Object] impl AdvisoryQuery { - async fn get_advisory_by_id<'a>(&self, ctx: &Context<'a>, id: Uuid) -> FieldResult { + async fn get_advisory_by_id(&self, ctx: &Context<'_>, id: Uuid) -> FieldResult { let db = ctx.data::>()?; let graph = ctx.data::>()?; let advisory = graph.get_advisory_by_id(id, db.as_ref()).await; @@ -36,7 +36,7 @@ impl AdvisoryQuery { } } - async fn get_advisories<'a>(&self, ctx: &Context<'a>) -> FieldResult> { + async fn get_advisories(&self, ctx: &Context<'_>) -> FieldResult> { let db = ctx.data::>()?; let graph = ctx.data::>()?; diff --git a/modules/graphql/src/organization.rs b/modules/graphql/src/organization.rs index e528dd375..b5d6d78cc 100644 --- a/modules/graphql/src/organization.rs +++ b/modules/graphql/src/organization.rs @@ -9,9 +9,9 @@ pub struct OrganizationQuery; #[Object] impl OrganizationQuery { - async fn get_organization_by_name<'a>( + async fn get_organization_by_name( &self, - ctx: &Context<'a>, + ctx: &Context<'_>, name: String, ) -> FieldResult { let db = ctx.data::>()?; diff --git a/modules/graphql/src/sbom.rs b/modules/graphql/src/sbom.rs index 74badbcaf..a9940b557 100644 --- a/modules/graphql/src/sbom.rs +++ b/modules/graphql/src/sbom.rs @@ -10,7 +10,7 @@ pub struct SbomQuery; #[Object] impl SbomQuery { - async fn get_sbom_by_id<'a>(&self, ctx: &Context<'a>, id: Uuid) -> FieldResult { + async fn get_sbom_by_id(&self, ctx: &Context<'_>, id: Uuid) -> FieldResult { let db = ctx.data::>()?; let graph = ctx.data::>()?; let sbom = graph.locate_sbom_by_id(id, db.as_ref()).await; @@ -31,9 +31,9 @@ impl SbomQuery { } } - async fn get_sboms_by_labels<'a>( + async fn get_sboms_by_labels( &self, - ctx: &Context<'a>, + ctx: &Context<'_>, labels: String, ) -> FieldResult> { let db = ctx.data::>()?; diff --git a/modules/graphql/src/sbomstatus.rs b/modules/graphql/src/sbomstatus.rs index 7df0cd735..4dadacaca 100644 --- a/modules/graphql/src/sbomstatus.rs +++ b/modules/graphql/src/sbomstatus.rs @@ -21,9 +21,9 @@ pub struct SbomStatusQuery; #[Object] impl SbomStatusQuery { - async fn cves_by_sbom<'a>( + async fn cves_by_sbom( &self, - ctx: &Context<'a>, + ctx: &Context<'_>, id: Uuid, ) -> FieldResult> { let db = ctx.data::>()?; diff --git a/modules/graphql/src/vulnerability.rs b/modules/graphql/src/vulnerability.rs index 488c1de7a..c271e8523 100644 --- a/modules/graphql/src/vulnerability.rs +++ b/modules/graphql/src/vulnerability.rs @@ -9,9 +9,9 @@ pub struct VulnerabilityQuery; #[Object] impl VulnerabilityQuery { - async fn get_vulnerability_by_id<'a>( + async fn get_vulnerability_by_id( &self, - ctx: &Context<'a>, + ctx: &Context<'_>, identifier: String, ) -> FieldResult { let db = ctx.data::>()?; @@ -33,7 +33,7 @@ impl VulnerabilityQuery { } } - async fn get_vulnerabilities<'a>(&self, ctx: &Context<'a>) -> FieldResult> { + async fn get_vulnerabilities(&self, ctx: &Context<'_>) -> FieldResult> { let db = ctx.data::>()?; let graph = ctx.data::>()?; let vulnerabilities = graph diff --git a/modules/importer/Cargo.toml b/modules/importer/Cargo.toml index c5af6ee46..5848cd811 100644 --- a/modules/importer/Cargo.toml +++ b/modules/importer/Cargo.toml @@ -9,6 +9,7 @@ license.workspace = true trustify-auth = { workspace = true } trustify-common = { workspace = true } trustify-entity = { workspace = true } +trustify-module-analysis = { workspace = true } trustify-module-ingestor = { workspace = true } trustify-module-storage = { workspace = true } diff --git a/modules/importer/src/runner/clearly_defined/mod.rs b/modules/importer/src/runner/clearly_defined/mod.rs index 1ff3b713f..f42393416 100644 --- a/modules/importer/src/runner/clearly_defined/mod.rs +++ b/modules/importer/src/runner/clearly_defined/mod.rs @@ -19,7 +19,11 @@ impl super::ImportRunner { clearly_defined: ClearlyDefinedImporter, continuation: serde_json::Value, ) -> Result { - let ingestor = IngestorService::new(Graph::new(self.db.clone()), self.storage.clone()); + let ingestor = IngestorService::new( + Graph::new(self.db.clone()), + self.storage.clone(), + self.analysis.clone(), + ); let report = Arc::new(Mutex::new(ReportBuilder::new())); let continuation = serde_json::from_value(continuation).unwrap_or_default(); diff --git a/modules/importer/src/runner/clearly_defined_curation/mod.rs b/modules/importer/src/runner/clearly_defined_curation/mod.rs index 57911c718..a5ffab280 100644 --- a/modules/importer/src/runner/clearly_defined_curation/mod.rs +++ b/modules/importer/src/runner/clearly_defined_curation/mod.rs @@ -77,7 +77,11 @@ impl super::ImportRunner { clearly_defined: ClearlyDefinedCurationImporter, continuation: serde_json::Value, ) -> Result { - let ingestor = IngestorService::new(Graph::new(self.db.clone()), self.storage.clone()); + let ingestor = IngestorService::new( + Graph::new(self.db.clone()), + self.storage.clone(), + self.analysis.clone(), + ); let report = Arc::new(Mutex::new(ReportBuilder::new())); let continuation = serde_json::from_value(continuation).unwrap_or_default(); diff --git a/modules/importer/src/runner/csaf/mod.rs b/modules/importer/src/runner/csaf/mod.rs index addc29fde..7f0982856 100644 --- a/modules/importer/src/runner/csaf/mod.rs +++ b/modules/importer/src/runner/csaf/mod.rs @@ -64,7 +64,11 @@ impl super::ImportRunner { // storage (called by validator) - let ingestor = IngestorService::new(Graph::new(self.db.clone()), self.storage.clone()); + let ingestor = IngestorService::new( + Graph::new(self.db.clone()), + self.storage.clone(), + self.analysis.clone(), + ); let storage = storage::StorageVisitor { context, ingestor, diff --git a/modules/importer/src/runner/cve/mod.rs b/modules/importer/src/runner/cve/mod.rs index fa94c89ea..f87ab06e2 100644 --- a/modules/importer/src/runner/cve/mod.rs +++ b/modules/importer/src/runner/cve/mod.rs @@ -77,7 +77,11 @@ impl super::ImportRunner { cve: CveImporter, continuation: serde_json::Value, ) -> Result { - let ingestor = IngestorService::new(Graph::new(self.db.clone()), self.storage.clone()); + let ingestor = IngestorService::new( + Graph::new(self.db.clone()), + self.storage.clone(), + self.analysis.clone(), + ); let report = Arc::new(Mutex::new(ReportBuilder::new())); let continuation = serde_json::from_value(continuation).unwrap_or_default(); diff --git a/modules/importer/src/runner/cwe/mod.rs b/modules/importer/src/runner/cwe/mod.rs index 4b3684d76..3bc85c460 100644 --- a/modules/importer/src/runner/cwe/mod.rs +++ b/modules/importer/src/runner/cwe/mod.rs @@ -20,7 +20,11 @@ impl super::ImportRunner { cwe_catalog: CweImporter, continuation: serde_json::Value, ) -> Result { - let ingestor = IngestorService::new(Graph::new(self.db.clone()), self.storage.clone()); + let ingestor = IngestorService::new( + Graph::new(self.db.clone()), + self.storage.clone(), + self.analysis.clone(), + ); let report = Arc::new(Mutex::new(ReportBuilder::new())); let continuation = serde_json::from_value(continuation).unwrap_or_default(); diff --git a/modules/importer/src/runner/mod.rs b/modules/importer/src/runner/mod.rs index 4f916a457..011ecd0a5 100644 --- a/modules/importer/src/runner/mod.rs +++ b/modules/importer/src/runner/mod.rs @@ -20,12 +20,14 @@ use std::path::PathBuf; use time::OffsetDateTime; use tracing::instrument; use trustify_common::db::Database; +use trustify_module_analysis::service::AnalysisService; use trustify_module_storage::service::dispatch::DispatchBackend; pub struct ImportRunner { pub db: Database, pub storage: DispatchBackend, pub working_dir: Option, + pub analysis: Option, } impl ImportRunner { diff --git a/modules/importer/src/runner/osv/mod.rs b/modules/importer/src/runner/osv/mod.rs index cc157ad81..63e64e8a2 100644 --- a/modules/importer/src/runner/osv/mod.rs +++ b/modules/importer/src/runner/osv/mod.rs @@ -104,7 +104,11 @@ impl super::ImportRunner { osv: OsvImporter, continuation: serde_json::Value, ) -> Result { - let ingestor = IngestorService::new(Graph::new(self.db.clone()), self.storage.clone()); + let ingestor = IngestorService::new( + Graph::new(self.db.clone()), + self.storage.clone(), + self.analysis.clone(), + ); let report = Arc::new(Mutex::new(ReportBuilder::new())); let continuation = serde_json::from_value(continuation).unwrap_or_default(); diff --git a/modules/importer/src/runner/sbom/mod.rs b/modules/importer/src/runner/sbom/mod.rs index 4ed9109ae..8fa64e899 100644 --- a/modules/importer/src/runner/sbom/mod.rs +++ b/modules/importer/src/runner/sbom/mod.rs @@ -65,7 +65,11 @@ impl super::ImportRunner { // storage (called by validator) - let ingestor = IngestorService::new(Graph::new(self.db.clone()), self.storage.clone()); + let ingestor = IngestorService::new( + Graph::new(self.db.clone()), + self.storage.clone(), + self.analysis.clone(), + ); let storage = storage::StorageVisitor { context, source, diff --git a/modules/importer/src/server/mod.rs b/modules/importer/src/server/mod.rs index 0096757a0..d05e03566 100644 --- a/modules/importer/src/server/mod.rs +++ b/modules/importer/src/server/mod.rs @@ -15,6 +15,7 @@ use time::OffsetDateTime; use tokio::time::MissedTickBehavior; use tracing::instrument; use trustify_common::db::Database; +use trustify_module_analysis::service::AnalysisService; use trustify_module_storage::service::dispatch::DispatchBackend; /// run the importer loop @@ -22,11 +23,13 @@ pub async fn importer( db: Database, storage: DispatchBackend, working_dir: Option, + analysis: Option, ) -> anyhow::Result<()> { Server { db, storage, working_dir, + analysis, } .run() .await @@ -52,6 +55,7 @@ struct Server { db: Database, storage: DispatchBackend, working_dir: Option, + analysis: Option, } impl Server { @@ -90,6 +94,7 @@ impl Server { db: self.db.clone(), storage: self.storage.clone(), working_dir: self.working_dir.clone(), + analysis: self.analysis.clone(), }; let (last_error, report, continuation) = match runner diff --git a/modules/ingestor/src/endpoints.rs b/modules/ingestor/src/endpoints.rs index 566be81ce..041ba0482 100644 --- a/modules/ingestor/src/endpoints.rs +++ b/modules/ingestor/src/endpoints.rs @@ -6,6 +6,7 @@ use actix_web::{post, web, HttpResponse, Responder}; use trustify_auth::{authorizer::Require, UploadDataset}; use trustify_common::{db::Database, model::BinaryData}; use trustify_entity::labels::Labels; +use trustify_module_analysis::service::AnalysisService; use trustify_module_storage::service::dispatch::DispatchBackend; use utoipa::IntoParams; @@ -15,8 +16,9 @@ pub fn configure( config: Config, db: Database, storage: impl Into, + analysis: Option, ) { - let ingestor_service = IngestorService::new(Graph::new(db), storage); + let ingestor_service = IngestorService::new(Graph::new(db), storage, analysis); svc.app_data(web::Data::new(ingestor_service)) .app_data(web::Data::new(config)) diff --git a/modules/ingestor/src/graph/advisory/advisory_vulnerability.rs b/modules/ingestor/src/graph/advisory/advisory_vulnerability.rs index 8be4e0a25..767263bb0 100644 --- a/modules/ingestor/src/graph/advisory/advisory_vulnerability.rs +++ b/modules/ingestor/src/graph/advisory/advisory_vulnerability.rs @@ -133,7 +133,7 @@ impl<'g> From<(&AdvisoryContext<'g>, entity::advisory_vulnerability::Model)> } } -impl<'g> AdvisoryVulnerabilityContext<'g> { +impl AdvisoryVulnerabilityContext<'_> { pub async fn vulnerability( &self, connection: &C, diff --git a/modules/ingestor/src/graph/purl/package_version.rs b/modules/ingestor/src/graph/purl/package_version.rs index b7035c100..e761e3356 100644 --- a/modules/ingestor/src/graph/purl/package_version.rs +++ b/modules/ingestor/src/graph/purl/package_version.rs @@ -67,21 +67,4 @@ impl<'g> PackageVersionContext<'g> { Ok(found.map(|model| QualifiedPackageContext::new(self, model))) } - - /// Retrieve known variants of this package version. - /// - /// Non-mutating to the fetch. - pub async fn get_variants( - &self, - _pkg: Purl, - connection: &C, - ) -> Result, Error> { - Ok(entity::qualified_purl::Entity::find() - .filter(entity::qualified_purl::Column::VersionedPurlId.eq(self.package_version.id)) - .all(connection) - .await? - .into_iter() - .map(|base| QualifiedPackageContext::new(self, base)) - .collect()) - } } diff --git a/modules/ingestor/src/graph/sbom/common/license.rs b/modules/ingestor/src/graph/sbom/common/license.rs index f71d0c597..49bcb5f94 100644 --- a/modules/ingestor/src/graph/sbom/common/license.rs +++ b/modules/ingestor/src/graph/sbom/common/license.rs @@ -95,10 +95,6 @@ impl LicenseCreator { where C: ConnectionTrait, { - if self.licenses.is_empty() { - return Ok(()); - } - for batch in &self.licenses.into_values().chunked() { license::Entity::insert_many(batch) .on_conflict( diff --git a/modules/ingestor/src/graph/sbom/cyclonedx.rs b/modules/ingestor/src/graph/sbom/cyclonedx.rs index 4c79dca5d..7959626db 100644 --- a/modules/ingestor/src/graph/sbom/cyclonedx.rs +++ b/modules/ingestor/src/graph/sbom/cyclonedx.rs @@ -8,8 +8,10 @@ use crate::graph::{ }, }; use sea_orm::ConnectionTrait; -use serde_cyclonedx::cyclonedx::v_1_6::{Component, CycloneDx, LicenseChoiceUrl}; -use std::{collections::HashMap, str::FromStr}; +use serde_cyclonedx::cyclonedx::v_1_6::{ + Component, ComponentEvidenceIdentity, CycloneDx, LicenseChoiceUrl, RefLinkType, +}; +use std::str::FromStr; use time::{format_description::well_known::Iso8601, OffsetDateTime}; use tracing::instrument; use trustify_common::{cpe::Cpe, purl::Purl}; @@ -95,7 +97,6 @@ impl SbomContext { mut sbom: CycloneDx, connection: &C, ) -> Result<(), anyhow::Error> { - let mut license_creator = LicenseCreator::new(); let mut creator = Creator::new(self.sbom.sbom_id); // extract "describes" @@ -147,67 +148,19 @@ impl SbomContext { creator.add_all(&sbom.components); - // record licenses - - for component in sbom.components.iter().flatten() { - if let Some(licenses) = &component.licenses { - match licenses { - LicenseChoiceUrl::Variant0(licenses) => { - 'l: for license in licenses { - let license = if let Some(id) = license.license.id.clone() { - id - } else if let Some(name) = license.license.name.clone() { - name - } else { - continue 'l; - }; - - let license = LicenseInfo { - license, - refs: Default::default(), - }; - - license_creator.add(&license); - creator.add_license_relation(component, &license); - } - } - LicenseChoiceUrl::Variant1(licenses) => { - for license in licenses { - let license = LicenseInfo { - license: license.expression.clone(), - refs: Default::default(), - }; - - license_creator.add(&license); - creator.add_license_relation(component, &license); - } - } - } - } - } - // create relationships for left in sbom.dependencies.iter().flatten() { - for right in left.depends_on.iter().flatten() { - creator.relate(right.clone(), Relationship::DependencyOf, left.ref_.clone()); - } + creator.relate_all(&left.ref_, Relationship::DependencyOf, &left.depends_on); // https://github.com/trustification/trustify/issues/1131 // Do we need to qualify this so that only "arch=src" refs // get the GeneratedFrom relationship? - for right in left.provides.iter().flatten() { - creator.relate( - right.clone(), - Relationship::GeneratedFrom, - left.ref_.clone(), - ); - } + creator.relate_all(&left.ref_, Relationship::GeneratedFrom, &left.provides); } // create - license_creator.create(connection).await?; creator.create(connection).await?; // done @@ -222,7 +175,6 @@ struct Creator<'a> { sbom_id: Uuid, components: Vec<&'a Component>, relations: Vec<(String, Relationship, String)>, - license_relations: HashMap>, } impl<'a> Creator<'a> { @@ -231,7 +183,6 @@ impl<'a> Creator<'a> { sbom_id, components: Default::default(), relations: Default::default(), - license_relations: Default::default(), } } @@ -244,19 +195,6 @@ impl<'a> Creator<'a> { self.extend(component.components.iter().flatten()); } - pub fn add_license_relation(&mut self, component: &'a Component, license: &LicenseInfo) { - let node_id = component - .bom_ref - .as_ref() - .cloned() - .unwrap_or_else(|| component.name.to_string()); - - self.license_relations - .entry(node_id) - .or_default() - .push(license.clone()); - } - pub fn extend(&mut self, i: I) where I: IntoIterator, @@ -270,51 +208,34 @@ impl<'a> Creator<'a> { self.relations.push((left, rel, right)); } + pub fn relate_all( + &mut self, + source: &RefLinkType, + rel: Relationship, + targets: &Option>, + ) { + for target in targets.iter().flatten() { + self.relate(target.clone(), rel, source.clone()); + } + } + pub async fn create(self, db: &impl ConnectionTrait) -> anyhow::Result<()> { let mut purls = PurlCreator::new(); let mut cpes = CpeCreator::new(); let mut packages = PackageCreator::with_capacity(self.sbom_id, self.components.len()); let mut relationships = RelationshipCreator::with_capacity(self.sbom_id, self.relations.len()); + let mut licenses = LicenseCreator::new(); for comp in self.components { - let node_id = comp - .bom_ref - .clone() - .unwrap_or_else(|| Uuid::new_v4().to_string()); - - let mut refs = vec![]; - - if let Some(purl) = &comp.purl { - if let Ok(purl) = Purl::from_str(purl.as_ref()) { - refs.push(PackageReference::Purl { - versioned_purl: purl.version_uuid(), - qualified_purl: purl.qualifier_uuid(), - }); - purls.add(purl); - } - } - if let Some(cpe) = &comp.cpe { - if let Ok(cpe) = Cpe::from_str(cpe.as_ref()) { - let id = cpe.uuid(); - cpes.add(cpe); - refs.push(PackageReference::Cpe(id)); - } - } - - let license_refs = self - .license_relations - .get(&node_id) - .cloned() - .unwrap_or_default(); - - packages.add( - node_id, - comp.name.to_string(), - comp.version.as_ref().map(|v| v.to_string()), - refs, - license_refs, + let creator = ComponentCreator::new( + &mut cpes, + &mut purls, + &mut licenses, + &mut packages, + &mut relationships, ); + creator.create(comp); } for (left, rel, right) in self.relations { @@ -323,9 +244,201 @@ impl<'a> Creator<'a> { purls.create(db).await?; cpes.create(db).await?; + licenses.create(db).await?; + packages.create(db).await?; relationships.create(db).await?; Ok(()) } } + +struct ComponentCreator<'a> { + cpes: &'a mut CpeCreator, + purls: &'a mut PurlCreator, + licenses: &'a mut LicenseCreator, + packages: &'a mut PackageCreator, + relationships: &'a mut RelationshipCreator, + + refs: Vec, + license_relations: Vec, +} + +impl<'a> ComponentCreator<'a> { + pub fn new( + cpes: &'a mut CpeCreator, + purls: &'a mut PurlCreator, + licenses: &'a mut LicenseCreator, + packages: &'a mut PackageCreator, + relationships: &'a mut RelationshipCreator, + ) -> Self { + Self { + cpes, + purls, + licenses, + refs: Default::default(), + license_relations: Default::default(), + packages, + relationships, + } + } + + pub fn create(mut self, comp: &Component) { + let node_id = comp + .bom_ref + .clone() + .unwrap_or_else(|| Uuid::new_v4().to_string()); + + self.add_license(comp); + + if let Some(cpe) = &comp.cpe { + if let Ok(cpe) = Cpe::from_str(cpe.as_ref()) { + self.add_cpe(cpe); + } + } + + if let Some(purl) = &comp.purl { + if let Ok(purl) = Purl::from_str(purl.as_ref()) { + self.add_purl(purl); + } + } + + for identity in comp + .evidence + .as_ref() + .and_then(|evidence| evidence.identity.as_ref()) + .iter() + .flat_map(|id| match id { + ComponentEvidenceIdentity::Variant0(value) => value.iter().collect::>(), + ComponentEvidenceIdentity::Variant1(value) => vec![value], + }) + { + match (identity.field.as_str(), &identity.concluded_value) { + ("cpe", Some(cpe)) => { + if let Ok(cpe) = Cpe::from_str(cpe.as_ref()) { + self.add_cpe(cpe); + } + } + ("purl", Some(purl)) => { + if let Ok(purl) = Purl::from_str(purl.as_ref()) { + self.add_purl(purl); + } + } + + _ => {} + } + } + + self.packages.add( + node_id.clone(), + comp.name.to_string(), + comp.version.as_ref().map(|v| v.to_string()), + self.refs, + self.license_relations, + ); + + for ancestor in comp + .pedigree + .iter() + .flat_map(|pedigree| pedigree.ancestors.iter().flatten()) + { + let target = ancestor + .bom_ref + .clone() + .unwrap_or_else(|| Uuid::new_v4().to_string()); + + // create the component + + let creator = ComponentCreator::new( + self.cpes, + self.purls, + self.licenses, + self.packages, + self.relationships, + ); + + creator.create(ancestor); + + // and store a relationship + self.relationships + .relate(target, Relationship::AncestorOf, node_id.clone()); + } + + for variant in comp + .pedigree + .iter() + .flat_map(|pedigree| pedigree.variants.iter().flatten()) + { + let target = variant + .bom_ref + .clone() + .unwrap_or_else(|| Uuid::new_v4().to_string()); + + // create the component + + let creator = ComponentCreator::new( + self.cpes, + self.purls, + self.licenses, + self.packages, + self.relationships, + ); + + creator.create(variant); + + self.relationships + .relate(target, Relationship::VariantOf, node_id.clone()); + } + } + + pub fn add_cpe(&mut self, cpe: Cpe) { + let id = cpe.uuid(); + self.refs.push(PackageReference::Cpe(id)); + self.cpes.add(cpe); + } + + pub fn add_purl(&mut self, purl: Purl) { + self.refs.push(PackageReference::Purl { + versioned_purl: purl.version_uuid(), + qualified_purl: purl.qualifier_uuid(), + }); + self.purls.add(purl); + } + + fn add_license(&mut self, component: &Component) { + if let Some(licenses) = &component.licenses { + match licenses { + LicenseChoiceUrl::Variant0(licenses) => { + 'l: for license in licenses { + let license = if let Some(id) = license.license.id.clone() { + id + } else if let Some(name) = license.license.name.clone() { + name + } else { + continue 'l; + }; + + let license = LicenseInfo { + license, + refs: Default::default(), + }; + + self.licenses.add(&license); + self.license_relations.push(license.clone()); + } + } + LicenseChoiceUrl::Variant1(licenses) => { + for license in licenses { + let license = LicenseInfo { + license: license.expression.clone(), + refs: Default::default(), + }; + + self.licenses.add(&license); + self.license_relations.push(license.clone()); + } + } + } + } + } +} diff --git a/modules/ingestor/src/graph/sbom/spdx.rs b/modules/ingestor/src/graph/sbom/spdx.rs index a4a9e9d10..12a86780f 100644 --- a/modules/ingestor/src/graph/sbom/spdx.rs +++ b/modules/ingestor/src/graph/sbom/spdx.rs @@ -12,7 +12,6 @@ use crate::{ }; use sbom_walker::report::{check, ReportSink}; use sea_orm::ConnectionTrait; -use serde_json::Value; use spdx_rs::models::{RelationshipType, SPDX}; use std::{collections::HashMap, str::FromStr}; use time::OffsetDateTime; @@ -251,31 +250,32 @@ impl<'spdx> TryFrom<(&'spdx str, &'spdx RelationshipType, &'spdx str)> for SpdxR (left, rel, right): (&'spdx str, &'spdx RelationshipType, &'spdx str), ) -> Result { match rel { - RelationshipType::Contains => Ok((right, Relationship::ContainedBy, left)), + RelationshipType::AncestorOf => Ok((left, Relationship::AncestorOf, right)), + RelationshipType::BuildToolOf => Ok((left, Relationship::BuildToolOf, right)), RelationshipType::ContainedBy => Ok((left, Relationship::ContainedBy, right)), - RelationshipType::Describes => Ok((right, Relationship::DescribedBy, left)), - RelationshipType::DescribedBy => Ok((left, Relationship::DescribedBy, right)), - RelationshipType::DependsOn => Ok((right, Relationship::DependencyOf, left)), + RelationshipType::Contains => Ok((right, Relationship::ContainedBy, left)), RelationshipType::DependencyOf => Ok((left, Relationship::DependencyOf, right)), + RelationshipType::DependsOn => Ok((right, Relationship::DependencyOf, left)), + RelationshipType::DescendantOf => Ok((right, Relationship::AncestorOf, left)), + RelationshipType::DescribedBy => Ok((left, Relationship::DescribedBy, right)), + RelationshipType::Describes => Ok((right, Relationship::DescribedBy, left)), RelationshipType::DevDependencyOf => Ok((left, Relationship::DevDependencyOf, right)), + RelationshipType::DevToolOf => Ok((left, Relationship::DevToolOf, right)), + RelationshipType::ExampleOf => Ok((left, Relationship::ExampleOf, right)), + RelationshipType::GeneratedFrom => Ok((left, Relationship::GeneratedFrom, right)), + RelationshipType::Generates => Ok((right, Relationship::GeneratedFrom, left)), RelationshipType::OptionalDependencyOf => { Ok((left, Relationship::OptionalDependencyOf, right)) } + RelationshipType::PackageOf => Ok((right, Relationship::PackageOf, left)), RelationshipType::ProvidedDependencyOf => { Ok((left, Relationship::ProvidedDependencyOf, right)) } - RelationshipType::TestDependencyOf => Ok((left, Relationship::TestDependencyOf, right)), RelationshipType::RuntimeDependencyOf => { Ok((left, Relationship::RuntimeDependencyOf, right)) } - RelationshipType::ExampleOf => Ok((left, Relationship::ExampleOf, right)), - RelationshipType::Generates => Ok((right, Relationship::GeneratedFrom, left)), - RelationshipType::GeneratedFrom => Ok((left, Relationship::GeneratedFrom, right)), - RelationshipType::AncestorOf => Ok((left, Relationship::AncestorOf, right)), - RelationshipType::DescendantOf => Ok((right, Relationship::AncestorOf, left)), + RelationshipType::TestDependencyOf => Ok((left, Relationship::TestDependencyOf, right)), RelationshipType::VariantOf => Ok((left, Relationship::VariantOf, right)), - RelationshipType::BuildToolOf => Ok((left, Relationship::BuildToolOf, right)), - RelationshipType::DevToolOf => Ok((left, Relationship::DevToolOf, right)), _ => Err(()), } .map(|(left, rel, right)| Self(left, rel, right)) @@ -294,33 +294,3 @@ impl<'spdx> TryFrom<&'spdx spdx_rs::models::Relationship> for SpdxRelationship<' .try_into() } } - -/// Check the document for invalid SPDX license expressions and replace them with `NOASSERTION`. -pub fn fix_license(report: &dyn ReportSink, mut json: Value) -> (Value, bool) { - let mut changed = false; - if let Some(packages) = json["packages"].as_array_mut() { - for package in packages { - if let Some(declared) = package["licenseDeclared"].as_str() { - if let Err(err) = spdx_expression::SpdxExpression::parse(declared) { - package["licenseDeclared"] = "NOASSERTION".into(); - changed = true; - - let message = - format!("Replacing faulty SPDX license expression with NOASSERTION: {err}"); - log::debug!("{message}"); - report.error(message); - } - } - } - } - - (json, changed) -} - -/// Parse a SPDX document, possibly replacing invalid license expressions. -/// -/// Returns the parsed document and a flag indicating if license expressions got replaced. -pub fn parse_spdx(report: &dyn ReportSink, json: Value) -> Result<(SPDX, bool), serde_json::Error> { - let (json, changed) = fix_license(report, json); - Ok((serde_json::from_value(json)?, changed)) -} diff --git a/modules/ingestor/src/service/advisory/osv/loader.rs b/modules/ingestor/src/service/advisory/osv/loader.rs index 84a7ee96a..35a37bdf4 100644 --- a/modules/ingestor/src/service/advisory/osv/loader.rs +++ b/modules/ingestor/src/service/advisory/osv/loader.rs @@ -15,7 +15,7 @@ use crate::{ Error, Warnings, }, }; -use osv::schema::{Event, Range, RangeType, ReferenceType, SeverityType, Vulnerability}; +use osv::schema::{Ecosystem, Event, Range, RangeType, ReferenceType, SeverityType, Vulnerability}; use sbom_walker::report::ReportSink; use sea_orm::{ConnectionTrait, TransactionTrait}; use std::{fmt::Debug, str::FromStr}; @@ -142,12 +142,38 @@ impl<'g> OsvLoader<'g> { } for range in affected.ranges.iter().flatten() { - match range.range_type { - RangeType::Semver => { - create_package_status_semver(&advisory_vuln, &purl, range, &tx) - .await?; + match (&range.range_type, &package.ecosystem) { + (RangeType::Semver, _) => { + create_package_status( + &advisory_vuln, + &purl, + range, + &VersionScheme::Semver, + &tx, + ) + .await?; + } + (RangeType::Git, _) => { + create_package_status( + &advisory_vuln, + &purl, + range, + &VersionScheme::Git, + &tx, + ) + .await?; + } + (RangeType::Ecosystem, Ecosystem::Maven(_)) => { + create_package_status( + &advisory_vuln, + &purl, + range, + &VersionScheme::Maven, + &tx, + ) + .await?; } - _ => { + (_, _) => { create_package_status_versions( &advisory_vuln, &purl, @@ -306,11 +332,12 @@ async fn ingest_exact( .await?) } -/// create a package status from a semver range -async fn create_package_status_semver( +/// create a package/purl status +async fn create_package_status( advisory_vuln: &AdvisoryVulnerabilityContext<'_>, purl: &Purl, range: &Range, + version_scheme: &VersionScheme, connection: &C, ) -> Result<(), Error> { let parsed_range = events_to_range(&range.events); @@ -338,7 +365,7 @@ async fn create_package_status_semver( purl, "affected", VersionInfo { - scheme: VersionScheme::Semver, + scheme: *version_scheme, spec, }, connection, @@ -353,7 +380,7 @@ async fn create_package_status_semver( purl, "fixed", VersionInfo { - scheme: VersionScheme::Semver, + scheme: *version_scheme, spec: VersionSpec::Exact(fixed.clone()), }, connection, diff --git a/modules/ingestor/src/service/format.rs b/modules/ingestor/src/service/format.rs index 396f9939e..2e27c0db7 100644 --- a/modules/ingestor/src/service/format.rs +++ b/modules/ingestor/src/service/format.rs @@ -39,11 +39,11 @@ pub enum Format { Unknown, } -impl<'g> Format { +impl Format { #[instrument(skip(self, graph, buffer))] pub async fn load( &self, - graph: &'g Graph, + graph: &'_ Graph, labels: Labels, issuer: Option, digests: &Digests, @@ -104,8 +104,8 @@ impl<'g> Format { match Self::advisory_from_bytes(bytes) { Err(Error::UnsupportedFormat(ea)) => match Self::sbom_from_bytes(bytes) { Err(Error::UnsupportedFormat(es)) => match Self::is_cwe_catalog(bytes) { - Ok(_) => Ok(Self::CweCatalog), - Err(_) => Err(Error::UnsupportedFormat(format!("{ea}\n{es}"))), + Ok(true) => Ok(Self::CweCatalog), + _ => Err(Error::UnsupportedFormat(format!("{ea}\n{es}"))), }, x => x, }, @@ -237,7 +237,7 @@ impl<'g> Format { // First tag was apparently not the droids we were looking for. return Ok(false); } - Err(_) => return Ok(false), + Err(_) | Ok(Event::Eof) => return Ok(false), _ => { // not an error or a start tag, keep on looping buf.clear() @@ -299,6 +299,9 @@ mod test { let spdx = document_bytes("ubi9-9.2-755.1697625012.json").await?; assert!(matches!(Format::from_bytes(&spdx), Ok(Format::SPDX))); + let indigestable = document_bytes("indigestable.json").await?; + assert!(Format::from_bytes(&indigestable).is_err()); + let cwe = document_read("cwec_latest.xml.zip")?; let mut cwe = ZipArchive::new(cwe)?; let mut cwe = cwe.by_index(0)?; diff --git a/modules/ingestor/src/service/mod.rs b/modules/ingestor/src/service/mod.rs index d557a5469..260fc13e7 100644 --- a/modules/ingestor/src/service/mod.rs +++ b/modules/ingestor/src/service/mod.rs @@ -143,13 +143,19 @@ impl ResponseError for Error { pub struct IngestorService { graph: Graph, storage: DispatchBackend, + analysis: Option, } impl IngestorService { - pub fn new(graph: Graph, storage: impl Into) -> Self { + pub fn new( + graph: Graph, + storage: impl Into, + analysis: Option, + ) -> Self { Self { graph, storage: storage.into(), + analysis, } } @@ -196,31 +202,33 @@ impl IngestorService { .load(&self.graph, labels.into(), issuer, &result.digests, bytes) .await?; - match fmt { - Format::SPDX | Format::CycloneDX => { - let analysis_service = AnalysisService::new(); - if result.id.to_string().starts_with("urn:uuid:") { - match analysis_service // TODO: today we chop off 'urn:uuid:' prefix using .split_off on result.id - .load_graphs( - vec![result.id.to_string().split_off("urn:uuid:".len())], - &self.graph.db, - ) - .await - { - Ok(_) => log::debug!( - "Analysis graph for sbom: {} loaded successfully.", - result.id.value() - ), - Err(e) => log::warn!( - "Error loading sbom {} into analysis graph : {}", - result.id.value(), - e - ), + if let Some(analysis) = &self.analysis { + match fmt { + Format::SPDX | Format::CycloneDX => { + if result.id.to_string().starts_with("urn:uuid:") { + match analysis + .load_graphs( + &self.graph.db, + // TODO: today we chop off 'urn:uuid:' prefix using .split_off on result.id + &vec![result.id.to_string().split_off("urn:uuid:".len())], + ) + .await + { + Ok(_) => log::debug!( + "Analysis graph for sbom: {} loaded successfully.", + result.id.value() + ), + Err(e) => log::warn!( + "Error loading sbom {} into analysis graph : {}", + result.id.value(), + e + ), + } } } - } - _ => {} - }; + _ => {} + }; + } let duration = Instant::now() - start; log::debug!( diff --git a/modules/ingestor/src/service/sbom/clearly_defined.rs b/modules/ingestor/src/service/sbom/clearly_defined.rs index 021daa173..881e0468b 100644 --- a/modules/ingestor/src/service/sbom/clearly_defined.rs +++ b/modules/ingestor/src/service/sbom/clearly_defined.rs @@ -175,7 +175,7 @@ mod test { #[test(tokio::test)] async fn ingest_clearly_defined(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { let graph = Graph::new(ctx.db.clone()); - let ingestor = IngestorService::new(graph, ctx.storage.clone()); + let ingestor = IngestorService::new(graph, ctx.storage.clone(), Default::default()); let data = document_bytes("clearly-defined/aspnet.mvc-4.0.40804.json").await?; diff --git a/modules/ingestor/src/service/sbom/clearly_defined_curation.rs b/modules/ingestor/src/service/sbom/clearly_defined_curation.rs index f1c272e82..60c1f15b2 100644 --- a/modules/ingestor/src/service/sbom/clearly_defined_curation.rs +++ b/modules/ingestor/src/service/sbom/clearly_defined_curation.rs @@ -62,7 +62,7 @@ mod test { #[test(tokio::test)] async fn ingest_clearly_defined(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { let graph = Graph::new(ctx.db.clone()); - let ingestor = IngestorService::new(graph, ctx.storage.clone()); + let ingestor = IngestorService::new(graph, ctx.storage.clone(), Default::default()); let data = document_bytes("clearly-defined/chrono.yaml").await?; diff --git a/modules/ingestor/src/service/sbom/cyclonedx.rs b/modules/ingestor/src/service/sbom/cyclonedx.rs index b4d93134a..9c5e64b65 100644 --- a/modules/ingestor/src/service/sbom/cyclonedx.rs +++ b/modules/ingestor/src/service/sbom/cyclonedx.rs @@ -83,7 +83,7 @@ mod test { let graph = Graph::new(db.clone()); let data = document_bytes("zookeeper-3.9.2-cyclonedx.json").await?; - let ingestor = IngestorService::new(graph, ctx.storage.clone()); + let ingestor = IngestorService::new(graph, ctx.storage.clone(), Default::default()); ingestor .ingest(&data, Format::CycloneDX, ("source", "test"), None) diff --git a/modules/ingestor/src/service/sbom/spdx.rs b/modules/ingestor/src/service/sbom/spdx.rs index 7b6d55396..b6f75003e 100644 --- a/modules/ingestor/src/service/sbom/spdx.rs +++ b/modules/ingestor/src/service/sbom/spdx.rs @@ -1,6 +1,6 @@ use crate::{ graph::{ - sbom::spdx::{self, parse_spdx}, + sbom::spdx::{self}, Graph, }, model::IngestResult, @@ -9,7 +9,7 @@ use crate::{ use sea_orm::TransactionTrait; use serde_json::Value; use tracing::instrument; -use trustify_common::{hashing::Digests, id::Id}; +use trustify_common::{hashing::Digests, id::Id, sbom::spdx::parse_spdx}; use trustify_entity::labels::Labels; pub struct SpdxLoader<'g> { @@ -83,7 +83,7 @@ mod test { let graph = Graph::new(ctx.db.clone()); let data = document_bytes("ubi9-9.2-755.1697625012.json").await?; - let ingestor = IngestorService::new(graph, ctx.storage.clone()); + let ingestor = IngestorService::new(graph, ctx.storage.clone(), Default::default()); ingestor .ingest(&data, Format::SPDX, ("source", "test"), None) diff --git a/modules/ingestor/tests/common.rs b/modules/ingestor/tests/common.rs index 809193166..d3f2798b2 100644 --- a/modules/ingestor/tests/common.rs +++ b/modules/ingestor/tests/common.rs @@ -1,3 +1,4 @@ +use trustify_module_analysis::service::AnalysisService; use trustify_module_ingestor::endpoints::{configure, Config}; use trustify_test_context::{ call::{self, CallService}, @@ -8,5 +9,15 @@ pub async fn caller_with( ctx: &TrustifyContext, config: Config, ) -> anyhow::Result { - call::caller(|svc| configure(svc, config, ctx.db.clone(), ctx.storage.clone())).await + let analysis = AnalysisService::new(); + call::caller(|svc| { + configure( + svc, + config, + ctx.db.clone(), + ctx.storage.clone(), + Some(analysis), + ) + }) + .await } diff --git a/modules/ingestor/tests/parallel.rs b/modules/ingestor/tests/parallel.rs index 5085e6569..2fc7b0b46 100644 --- a/modules/ingestor/tests/parallel.rs +++ b/modules/ingestor/tests/parallel.rs @@ -7,12 +7,12 @@ use std::{collections::HashMap, str::FromStr}; use test_context::{futures, test_context}; use test_log::test; use tracing::instrument; -use trustify_common::{cpe::Cpe, purl::Purl}; +use trustify_common::{cpe::Cpe, purl::Purl, sbom::spdx::parse_spdx}; use trustify_module_ingestor::{ graph::{ cpe::CpeCreator, purl::creator::PurlCreator, - sbom::{spdx::parse_spdx, LicenseCreator, LicenseInfo}, + sbom::{LicenseCreator, LicenseInfo}, }, service::{Discard, Format}, }; diff --git a/modules/ingestor/tests/reingest/spdx.rs b/modules/ingestor/tests/reingest/spdx.rs index eda88774f..04cd29558 100644 --- a/modules/ingestor/tests/reingest/spdx.rs +++ b/modules/ingestor/tests/reingest/spdx.rs @@ -8,6 +8,7 @@ use trustify_common::{cpe::Cpe, id::Id}; use trustify_entity::{cpe::CpeDto, sbom}; use trustify_module_ingestor::model::IngestResult; use trustify_test_context::TrustifyContext; +use uuid::Uuid; #[test_context(TrustifyContext)] #[test(tokio::test)] @@ -41,7 +42,7 @@ async fn reingest(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { .into_iter() .map(|purl| purl.qualified_package.id) .collect::>(), - vec![] + Vec::::new() ); // get product diff --git a/openapi.yaml b/openapi.yaml index e87fc40b5..417aae42b 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -5,7 +5,7 @@ info: license: name: Apache License, Version 2.0 identifier: Apache-2.0 - version: 0.2.0 + version: 0.2.1 paths: /.well-known/trustify: get: @@ -459,6 +459,25 @@ paths: description: The tool request was invalid '404': description: The tool was not found + /api/v2/analysis/component/{key}: + get: + tags: + - analysis + operationId: getComponent + parameters: + - name: key + in: path + description: provide component name, URL-encoded pURL, or CPE itself + required: true + schema: + type: string + responses: + '200': + description: Retrieve component(s) root components by name, pURL, or CPE. + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedResults_BaseSummary' /api/v2/analysis/dep: get: tags: @@ -503,7 +522,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/DepSummary' + $ref: '#/components/schemas/PaginatedResults_DepSummary' /api/v2/analysis/dep/{key}: get: tags: @@ -522,7 +541,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/DepSummary' + $ref: '#/components/schemas/PaginatedResults_DepSummary' /api/v2/analysis/root-component: get: tags: @@ -567,7 +586,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AncestorSummary' + $ref: '#/components/schemas/PaginatedResults_AncestorSummary' /api/v2/analysis/root-component/{key}: get: tags: @@ -576,17 +595,17 @@ paths: parameters: - name: key in: path - description: provide component name or URL-encoded pURL itself + description: provide component name, URL-encoded pURL, or CPE itself required: true schema: type: string responses: '200': - description: Retrieve component(s) root components by name or pURL. + description: Retrieve component(s) root components by name, pURL, or CPE. content: application/json: schema: - $ref: '#/components/schemas/AncestorSummary' + $ref: '#/components/schemas/PaginatedResults_AncestorSummary' /api/v2/analysis/status: get: tags: @@ -1626,15 +1645,14 @@ paths: oneOf: - type: 'null' - $ref: '#/components/schemas/Purl' - - name: id + - name: cpe in: query - description: Find by an ID of a package + description: Find by CPE required: false schema: - type: - - string - - 'null' - format: uuid + oneOf: + - type: 'null' + - $ref: '#/components/schemas/Cpe' responses: '200': description: Matching SBOMs @@ -1660,22 +1678,21 @@ paths: oneOf: - type: 'null' - $ref: '#/components/schemas/Purl' - - name: id + - name: cpe in: path - description: Find by an ID of a package + description: Find by CPE required: true schema: - type: - - string - - 'null' - format: uuid + oneOf: + - type: 'null' + - $ref: '#/components/schemas/Cpe' requestBody: content: application/json: schema: type: array items: - $ref: '#/components/schemas/AllRelatedQuery' + $ref: '#/components/schemas/ExternalReferenceQuery' required: true responses: '200': @@ -2369,20 +2386,6 @@ components: name: type: string parameters: {} - AllRelatedQuery: - type: object - properties: - id: - type: - - string - - 'null' - format: uuid - description: Find by an ID of a package - purl: - oneOf: - - type: 'null' - - $ref: '#/components/schemas/Purl' - description: Find by PURL AnalysisStatus: type: object required: @@ -2392,9 +2395,13 @@ components: graph_count: type: integer format: int32 + description: The number of graphs loaded in memory + minimum: 0 sbom_count: type: integer format: int32 + description: The number of SBOMs found in the database + minimum: 0 AncNode: type: object required: @@ -2402,15 +2409,22 @@ components: - node_id - relationship - purl + - cpe - name - version properties: + cpe: + type: array + items: + $ref: '#/components/schemas/Cpe' name: type: string node_id: type: string purl: - type: string + type: array + items: + $ref: '#/components/schemas/Purl' relationship: type: string sbom_id: @@ -2418,23 +2432,61 @@ components: version: type: string AncestorSummary: + allOf: + - $ref: '#/components/schemas/BaseSummary' + - type: object + required: + - ancestors + properties: + ancestors: + type: array + items: + $ref: '#/components/schemas/AncNode' + BasePurlDetails: + allOf: + - $ref: '#/components/schemas/BasePurlHead' + - type: object + required: + - versions + properties: + versions: + type: array + items: + $ref: '#/components/schemas/VersionedPurlSummary' + BasePurlHead: + type: object + required: + - uuid + - purl + properties: + purl: + $ref: '#/components/schemas/Purl' + description: The actual base PURL + uuid: + type: string + format: uuid + description: The ID of the base PURL + BasePurlSummary: + allOf: + - $ref: '#/components/schemas/BasePurlHead' + BaseSummary: type: object required: - sbom_id - node_id - purl + - cpe - name - version - published - document_id - product_name - product_version - - ancestors properties: - ancestors: + cpe: type: array items: - $ref: '#/components/schemas/AncNode' + $ref: '#/components/schemas/Cpe' document_id: type: string name: @@ -2448,38 +2500,13 @@ components: published: type: string purl: - type: string + type: array + items: + $ref: '#/components/schemas/Purl' sbom_id: type: string version: type: string - BasePurlDetails: - allOf: - - $ref: '#/components/schemas/BasePurlHead' - - type: object - required: - - versions - properties: - versions: - type: array - items: - $ref: '#/components/schemas/VersionedPurlSummary' - BasePurlHead: - type: object - required: - - uuid - - purl - properties: - purl: - $ref: '#/components/schemas/Purl' - description: The actual base PURL - uuid: - type: string - format: uuid - description: The ID of the base PURL - BasePurlSummary: - allOf: - - $ref: '#/components/schemas/BasePurlHead' BinaryByteSize: type: string ChatMessage: @@ -2602,6 +2629,9 @@ components: updated_at: type: string format: date-time + Cpe: + type: string + format: uri CsafImporter: allOf: - $ref: '#/components/schemas/CommonImporter' @@ -2658,10 +2688,15 @@ components: - node_id - relationship - purl + - cpe - name - version - deps properties: + cpe: + type: array + items: + $ref: '#/components/schemas/Cpe' deps: type: array items: @@ -2671,7 +2706,9 @@ components: node_id: type: string purl: - type: string + type: array + items: + $ref: '#/components/schemas/Purl' relationship: type: string sbom_id: @@ -2679,41 +2716,29 @@ components: version: type: string DepSummary: + allOf: + - $ref: '#/components/schemas/BaseSummary' + - type: object + required: + - deps + properties: + deps: + type: array + items: + $ref: '#/components/schemas/DepNode' + ExternalReferenceQuery: type: object - required: - - sbom_id - - node_id - - purl - - name - - version - - published - - document_id - - product_name - - product_version - - deps properties: - deps: - type: array - items: - $ref: '#/components/schemas/DepNode' - document_id: - type: string - name: - type: string - node_id: - type: string - product_name: - type: string - product_version: - type: string - published: - type: string + cpe: + oneOf: + - type: 'null' + - $ref: '#/components/schemas/Cpe' + description: Find by CPE purl: - type: string - sbom_id: - type: string - version: - type: string + oneOf: + - type: 'null' + - $ref: '#/components/schemas/Purl' + description: Find by PURL Id: type: string description: A hash/digest prefixed with its type. @@ -3020,6 +3045,29 @@ components: type: integer format: int64 minimum: 0 + PaginatedResults_AncestorSummary: + type: object + required: + - items + - total + properties: + items: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseSummary' + - type: object + required: + - ancestors + properties: + ancestors: + type: array + items: + $ref: '#/components/schemas/AncNode' + total: + type: integer + format: int64 + minimum: 0 PaginatedResults_BasePurlSummary: type: object required: @@ -3035,6 +3083,56 @@ components: type: integer format: int64 minimum: 0 + PaginatedResults_BaseSummary: + type: object + required: + - items + - total + properties: + items: + type: array + items: + type: object + required: + - sbom_id + - node_id + - purl + - cpe + - name + - version + - published + - document_id + - product_name + - product_version + properties: + cpe: + type: array + items: + $ref: '#/components/schemas/Cpe' + document_id: + type: string + name: + type: string + node_id: + type: string + product_name: + type: string + product_version: + type: string + published: + type: string + purl: + type: array + items: + $ref: '#/components/schemas/Purl' + sbom_id: + type: string + version: + type: string + total: + type: integer + format: int64 + minimum: 0 PaginatedResults_ConversationSummary: type: object required: @@ -3062,6 +3160,29 @@ components: type: integer format: int64 minimum: 0 + PaginatedResults_DepSummary: + type: object + required: + - items + - total + properties: + items: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseSummary' + - type: object + required: + - deps + properties: + deps: + type: array + items: + $ref: '#/components/schemas/DepNode' + total: + type: integer + format: int64 + minimum: 0 PaginatedResults_ImporterReport: type: object required: @@ -3486,7 +3607,6 @@ components: - base - advisories - licenses - - relationships properties: advisories: type: array @@ -3498,31 +3618,6 @@ components: type: array items: $ref: '#/components/schemas/PurlLicenseSummary' - relationships: - type: object - additionalProperties: - type: array - items: - type: string - propertyNames: - type: string - enum: - - contained_by - - dependency_of - - dev_dependency_of - - optional_dependency_of - - provided_dependency_of - - test_dependency_of - - runtime_dependency_of - - example_of - - generated_from - - ancestor_of - - variant_of - - build_tool_of - - dev_tool_of - - described_by - - package_of - - undefined version: $ref: '#/components/schemas/VersionedPurlHead' PurlHead: diff --git a/rust-toolchain.toml b/rust-toolchain.toml index c6d8cdf27..a6cb31813 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.83.0" +channel = "1.84.0" components = [ "rustfmt", "clippy" ] diff --git a/server/src/profile/api.rs b/server/src/profile/api.rs index 695247c87..d2dbacca4 100644 --- a/server/src/profile/api.rs +++ b/server/src/profile/api.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "garage-door")] +use crate::embedded_oidc; use crate::{endpoints, sample_data}; use actix_web::{ body::MessageBody, @@ -32,6 +34,7 @@ use trustify_infrastructure::{ tracing::Tracing, Infrastructure, InfrastructureConfig, InitContext, Metrics, }; +use trustify_module_analysis::service::AnalysisService; use trustify_module_graphql::RootQuery; use trustify_module_importer::server::importer; use trustify_module_ingestor::graph::Graph; @@ -47,9 +50,6 @@ use utoipa::{ use utoipa_rapidoc::RapiDoc; use utoipa_redoc::{Redoc, Servable}; -#[cfg(feature = "garage-door")] -use crate::embedded_oidc; - /// Run the API server #[derive(clap::Args, Debug)] pub struct Run { @@ -413,6 +413,8 @@ pub(crate) fn configure(svc: &mut utoipa_actix_web::service_config::ServiceConfi // register REST API & UI + let analysis = AnalysisService::new(); + svc.app_data(graph) .configure(|svc| { endpoints::configure(svc, auth.clone()); @@ -427,12 +429,14 @@ pub(crate) fn configure(svc: &mut utoipa_actix_web::service_config::ServiceConfi ingestor, db.clone(), storage.clone(), + Some(analysis.clone()), ); trustify_module_fundamental::endpoints::configure( svc, fundamental, db.clone(), storage, + analysis, ); trustify_module_analysis::endpoints::configure(svc, db.clone()); trustify_module_user::endpoints::configure(svc, db.clone()); diff --git a/server/src/profile/importer.rs b/server/src/profile/importer.rs index 322c314d0..dae538ac5 100644 --- a/server/src/profile/importer.rs +++ b/server/src/profile/importer.rs @@ -133,7 +133,17 @@ impl InitData { let db = self.db; let storage = self.storage; - let importer = async { importer(db, storage, self.working_dir).await }.boxed_local(); + let importer = async { + importer( + db, + storage, + self.working_dir, + // Running the importer, we don't need an analysis graph update + None, + ) + .await + } + .boxed_local(); let tasks = vec![importer]; diff --git a/test-context/src/lib.rs b/test-context/src/lib.rs index bbab13151..7bdef6943 100644 --- a/test-context/src/lib.rs +++ b/test-context/src/lib.rs @@ -8,20 +8,21 @@ pub mod spdx; use futures::Stream; use peak_alloc::PeakAlloc; use postgresql_embedded::PostgreSQL; -use std::env; -use std::io::{Read, Seek}; -use std::path::{Path, PathBuf}; +use serde::Serialize; +use std::{ + env, + io::{Read, Seek}, + path::{Path, PathBuf}, +}; use test_context::AsyncTestContext; -use tokio_util::bytes::Bytes; -use tokio_util::io::{ReaderStream, SyncIoBridge}; +use tokio_util::{bytes::Bytes, io::ReaderStream}; use tracing::instrument; -use trustify_common as common; -use trustify_common::db; -use trustify_common::decompress::decompress_async; -use trustify_common::hashing::{Digests, HashingRead}; -use trustify_module_ingestor::graph::Graph; -use trustify_module_ingestor::model::IngestResult; -use trustify_module_ingestor::service::{Format, IngestorService}; +use trustify_common::{self as common, db, decompress::decompress_async, hashing::Digests}; +use trustify_module_ingestor::{ + graph::Graph, + model::IngestResult, + service::{Format, IngestorService}, +}; use trustify_module_storage::service::fs::FileSystemBackend; #[allow(dead_code)] @@ -43,7 +44,7 @@ impl TrustifyContext { .await .expect("initializing the storage backend"); let graph = Graph::new(db.clone()); - let ingestor = IngestorService::new(graph.clone(), storage.clone()); + let ingestor = IngestorService::new(graph.clone(), storage.clone(), Default::default()); let mem_limit_mb = env::var("MEM_LIMIT_MB") .unwrap_or("500".into()) .parse() @@ -103,6 +104,16 @@ impl TrustifyContext { .await?) } + /// Ingest a document by ingesting its JSON representation + pub async fn ingest_json(&self, doc: S) -> Result { + let bytes = serde_json::to_vec(&doc)?; + + Ok(self + .ingestor + .ingest(&bytes, Format::Unknown, ("source", "TrustifyContext"), None) + .await?) + } + pub fn absolute_path(&self, path: impl AsRef) -> anyhow::Result { absolute(path) } @@ -179,20 +190,16 @@ pub fn document_read(path: &str) -> Result { Ok(std::fs::File::open(absolute(path)?)?) } +/// Read a document and parse it as JSON. pub async fn document(path: &str) -> Result<(T, Digests), anyhow::Error> where T: serde::de::DeserializeOwned + Send + 'static, { - let file = tokio::fs::File::open(absolute(path)?).await?; - let mut reader = HashingRead::new(SyncIoBridge::new(file)); - let f = || match serde_json::from_reader(&mut reader) { - Ok(v) => match reader.finish() { - Ok(digests) => Ok((v, digests)), - Err(e) => Err(anyhow::Error::new(e)), - }, - Err(e) => Err(anyhow::Error::new(e)), - }; - tokio::task::spawn_blocking(f).await? + let data = document_bytes(path).await?; + let digests = Digests::digest(&data); + let f = move || Ok::<_, anyhow::Error>(serde_json::from_slice::(&data)?); + + Ok((tokio::task::spawn_blocking(f).await??, digests)) } #[cfg(test)] diff --git a/xtask/src/dataset.rs b/xtask/src/dataset.rs index 66300bb93..723883f01 100644 --- a/xtask/src/dataset.rs +++ b/xtask/src/dataset.rs @@ -102,6 +102,8 @@ impl GenerateDump { db: db.clone(), storage: storage.into(), working_dir: self.working_dir.as_ref().map(|wd| wd.join("wd")), + // The xtask doesn't need the analysis graph + analysis: None, }; // ingest documents