|
| 1 | +INCLUDE(generate_submodule_info) |
| 2 | +INCLUDE(ExternalProject) |
| 3 | + |
| 4 | + |
| 5 | +# Extract user name and repository name from a github URL. |
| 6 | +FUNCTION (EXTRACT_REPO_NAME_AND_USER repo_url repo_name_var repo_user_var) |
| 7 | + IF(repo_url MATCHES "^git@") |
| 8 | + # normalize to https-style URLs |
| 9 | + STRING(REGEX REPLACE "^git@([^:]+):(.*)$" "https://\\1/\\2" repo_url "${repo_url}") |
| 10 | + ENDIF() |
| 11 | + # Extract the repository user |
| 12 | + STRING(REGEX REPLACE "https://([^/]+)/([^/]+)/.*" "\\2" repo_user "${repo_url}") |
| 13 | + |
| 14 | + STRING(REGEX REPLACE ".*/([^/]*)$" "\\1" repo_name "${repo_url}") |
| 15 | + STRING(REGEX REPLACE "\\.git$" "" repo_name "${repo_name}") |
| 16 | + |
| 17 | + SET(${repo_name_var} ${repo_name} PARENT_SCOPE) |
| 18 | + SET(${repo_user_var} ${repo_user} PARENT_SCOPE) |
| 19 | +ENDFUNCTION() |
| 20 | + |
| 21 | +# Add a known 3rd party dependency for SBOM generation |
| 22 | +# Currently used for "vendored" (part of our repository) source code we know about |
| 23 | +# such as zlib, as well ExternalProject_Add() projects |
| 24 | +MACRO(ADD_THIRD_PARTY_DEPENDENCY name url tag rev version description) |
| 25 | + LIST(FIND ALL_THIRD_PARTY ${name} idx) |
| 26 | + IF (idx GREATER -1) |
| 27 | + MESSAGE(FATAL_ERROR "${name} is already in ALL_THIRD_PARTY") |
| 28 | + ENDIF() |
| 29 | + SET(${name}_URL ${url}) |
| 30 | + SET(${name}_TAG ${tag}) |
| 31 | + SET(${name}_REVISION ${rev}) |
| 32 | + SET(${name}_DESCRIPTION "${description}") |
| 33 | + SET(${name}_VERSION "${version}") |
| 34 | + LIST(APPEND ALL_THIRD_PARTY ${name}) |
| 35 | +ENDMACRO() |
| 36 | + |
| 37 | +# Get CPE ID ( https://en.wikipedia.org/wiki/Common_Platform_Enumeration ) |
| 38 | +# for given project name and version |
| 39 | +# Only "known" CPEs are handled here, e.g currently no CPE for rocksdb |
| 40 | +FUNCTION(SBOM_GET_CPE name version var) |
| 41 | + SET(cpe_prefix_map |
| 42 | + "zlib" "zlib:zlib" |
| 43 | + "mariadb-connector-c" "mariadb:connector\\\\/c" |
| 44 | + "wolfssl" "wolfssl:wolfssl" |
| 45 | + "minizip" "zlib:zlib" |
| 46 | + "pcre2" "pcre:pcre2" |
| 47 | + "fmt" "fmt:fmt" |
| 48 | + "boost" "boost:boost" |
| 49 | + "thrift" "apache:thrift" |
| 50 | + ) |
| 51 | + LIST(FIND cpe_prefix_map "${name}" i) |
| 52 | + IF(i GREATER -1) |
| 53 | + MATH(EXPR next_idx "${i}+1") |
| 54 | + LIST(GET cpe_prefix_map ${next_idx} cpe_name_and_vendor) |
| 55 | + STRING(REGEX REPLACE "[^0-9\\.]" "" cleaned_version "${version}") |
| 56 | + SET(${var} "cpe:2.3:a:${cpe_name_and_vendor}:${cleaned_version}:*:*:*:*:*:*:*" PARENT_SCOPE) |
| 57 | + ELSE() |
| 58 | + SET(${var} "" PARENT_SCOPE) |
| 59 | + ENDIF() |
| 60 | +ENDFUNCTION() |
| 61 | + |
| 62 | +# Add dependency on CMake ExternalProject. |
| 63 | +# Currently, only works for github hosted projects, |
| 64 | +# URL property of the external project needs to point to release source download |
| 65 | +MACRO(ADD_CMAKE_EXTERNAL_PROJECT_DEPENDENCY name) |
| 66 | + ExternalProject_GET_PROPERTY(${name} URL) |
| 67 | + STRING(REGEX REPLACE "https://github.com/([^/]+/[^/]+)/releases/download/([^/]+)/.*-([^-]+)\\..*" "\\1;\\2;\\3" parsed "${URL}") |
| 68 | + # Split the result into components |
| 69 | + LIST(LENGTH parsed parsed_length) |
| 70 | + IF(parsed_length EQUAL 3) |
| 71 | + LIST(GET parsed 0 project_path) |
| 72 | + LIST(GET parsed 1 tag) |
| 73 | + LIST(GET parsed 2 ver) |
| 74 | + ELSE() |
| 75 | + STRING(REGEX REPLACE "https://github.com/([^/]+/[^/]+)/archive/refs/tags/([^/]+)\\.(tar\\.gz|zip)$" "\\1;\\2;\\3" parsed "${URL}") |
| 76 | + LIST(LENGTH parsed parsed_length) |
| 77 | + IF(parsed_length GREATER 1) |
| 78 | + LIST(GET parsed 0 project_path) |
| 79 | + LIST(GET parsed 1 tag) |
| 80 | + STRING(REGEX REPLACE "[^0-9.]" "" ver "${tag}") |
| 81 | + ELSE() |
| 82 | + MESSAGE(FATAL_ERROR "Unexpected format for the download URL of project ${name} : (${URL})") |
| 83 | + ENDIF() |
| 84 | + ENDIF() |
| 85 | + ADD_THIRD_PARTY_DEPENDENCY(${name} "https://github.com/${project_path}" "${tag}" "${tag}" "${ver}" "") |
| 86 | +ENDMACRO() |
| 87 | + |
| 88 | + |
| 89 | +# Match third party component with supplier |
| 90 | +# CyclonDX documentation says it is |
| 91 | +# "The organization that supplied the component. |
| 92 | +# The supplier may often be the manufacturer, but may also be a distributor or repackager." |
| 93 | +# |
| 94 | +# Perhaps it can always be "MariaDB", but security team recommendation is different |
| 95 | +# more towards "author" |
| 96 | +FUNCTION (sbom_get_supplier repo_name repo_user varname) |
| 97 | + IF("${repo_name_SUPPLIER}") |
| 98 | + SET(${varname} "${repo_name_SUPPLIER}" PARENT_SCOPE) |
| 99 | + ELSEIF (repo_name MATCHES "zlib|minizip") |
| 100 | + # stuff that is checked into out repos |
| 101 | + SET(${varname} "MariaDB" PARENT_SCOPE) |
| 102 | + ELSEIF (repo_name MATCHES "boost") |
| 103 | + SET(${varname} "Boost.org" PARENT_SCOPE) |
| 104 | + ELSE() |
| 105 | + IF(repo_user MATCHES "mariadb-corporation|mariadb") |
| 106 | + set(repo_user "MariaDB") |
| 107 | + ENDIF() |
| 108 | + # Capitalize just first letter in repo_user |
| 109 | + STRING(SUBSTRING "${repo_user}" 0 1 first_letter) |
| 110 | + STRING(SUBSTRING "${repo_user}" 1 -1 rest) |
| 111 | + STRING(TOUPPER "${first_letter}" first_letter_upper) |
| 112 | + SET(${varname} "${first_letter_upper}${rest}" PARENT_SCOPE) |
| 113 | + ENDIF() |
| 114 | +ENDFUNCTION() |
| 115 | + |
| 116 | +# Generate sbom.json in the top-level build directory |
| 117 | +FUNCTION(GENERATE_SBOM) |
| 118 | + IF(EXISTS ${PROJECT_SOURCE_DIR}/cmake/submodule_info.cmake) |
| 119 | + INCLUDE(${PROJECT_SOURCE_DIR}/cmake/submodule_info.cmake) |
| 120 | + ELSE() |
| 121 | + GENERATE_SUBMODULE_INFO(${PROJECT_BINARY_DIR}/cmake/submodule_info.cmake) |
| 122 | + INCLUDE(${PROJECT_BINARY_DIR}/cmake/submodule_info.cmake) |
| 123 | + ENDIF() |
| 124 | + # Remove irrelevant for the current build submodule information |
| 125 | + # That is, if we do not build say columnstore, do not include |
| 126 | + # dependency info into SBOM |
| 127 | + IF(NOT TARGET wolfssl) |
| 128 | + # using openssl, rather than wolfssl |
| 129 | + LIST(FILTER ALL_SUBMODULES EXCLUDE REGEX wolfssl) |
| 130 | + ENDIF() |
| 131 | + IF(NOT WITH_WSREP) |
| 132 | + # wsrep is not compiled |
| 133 | + LIST(FILTER ALL_SUBMODULES EXCLUDE REGEX wsrep) |
| 134 | + ENDIF() |
| 135 | + IF(NOT TARGET columnstore) |
| 136 | + LIST(FILTER ALL_SUBMODULES EXCLUDE REGEX columnstore) |
| 137 | + ENDIF() |
| 138 | + IF(NOT TARGET rocksdb) |
| 139 | + # Rocksdb is not compiled |
| 140 | + LIST(FILTER ALL_SUBMODULES EXCLUDE REGEX rocksdb) |
| 141 | + ENDIF() |
| 142 | + IF(NOT TARGET s3) |
| 143 | + # S3 aria is not compiled |
| 144 | + LIST(FILTER ALL_SUBMODULES EXCLUDE REGEX storage/maria/libmarias3) |
| 145 | + ENDIF() |
| 146 | + # libmariadb/docs is not a library, so remove it |
| 147 | + LIST(FILTER ALL_SUBMODULES EXCLUDE REGEX libmariadb/docs) |
| 148 | + |
| 149 | + # It is possible to provide EXTRA_SBOM_DEPENDENCIES |
| 150 | + # and accompanying per-dependency data, to extend generared sbom |
| 151 | + # document. |
| 152 | + # Example below injects an extra "ncurses" dependency using several |
| 153 | + # command line parameters for CMake. |
| 154 | + # -DEXTRA_SBOM_DEPENDENCIES=ncurses |
| 155 | + # -Dncurses_URL=https://github.com/mirror/ncurses |
| 156 | + # -Dncurses_TAG=v6.4 |
| 157 | + # -Dncurses_VERSION=6.4 |
| 158 | + # -Dncurses_DESCRIPTION="A fake extra dependency" |
| 159 | + SET(ALL_THIRD_PARTY ${ALL_SUBMODULES} ${EXTRA_SBOM_DEPENDENCIES}) |
| 160 | + |
| 161 | + # Add dependencies on cmake ExternalProjects |
| 162 | + FOREACH(ext_proj libfmt pcre2) |
| 163 | + IF(TARGET ${ext_proj}) |
| 164 | + ADD_CMAKE_EXTERNAL_PROJECT_DEPENDENCY(${ext_proj}) |
| 165 | + ENDIF() |
| 166 | + ENDFOREACH() |
| 167 | + |
| 168 | + # ZLIB |
| 169 | + IF(TARGET zlib OR TARGET connect) |
| 170 | + # Path to the zlib.h file |
| 171 | + SET(ZLIB_HEADER_PATH "${PROJECT_SOURCE_DIR}/zlib/zlib.h") |
| 172 | + # Variable to store the extracted version |
| 173 | + SET(ZLIB_VERSION "") |
| 174 | + # Read the version string from the file |
| 175 | + file(STRINGS "${ZLIB_HEADER_PATH}" ZLIB_VERSION_LINE REGEX "#define ZLIB_VERSION.*") |
| 176 | + # Extract the version number using a regex |
| 177 | + IF (ZLIB_VERSION_LINE) |
| 178 | + STRING(REGEX MATCH "\"([^\"]+)\"" ZLIB_VERSION_MATCH "${ZLIB_VERSION_LINE}") |
| 179 | + IF (ZLIB_VERSION_MATCH) |
| 180 | + STRING(REPLACE "\"" "" ZLIB_VERSION "${ZLIB_VERSION_MATCH}") |
| 181 | + IF(NOT ("${ZLIB_VERSION}" MATCHES "[0-9]+\\.[0-9]+\\.[0-9]+")) |
| 182 | + MESSAGE(FATAL_ERROR "Unexpected zlib version '${ZLIB_VERSION}' parsed from ${ZLIB_HEADER_PATH}") |
| 183 | + ENDIF() |
| 184 | + ELSE() |
| 185 | + MESSAGE(FATAL_ERROR "Could not extract ZLIB version from the line: ${ZLIB_VERSION_LINE}") |
| 186 | + ENDIF() |
| 187 | + ELSE() |
| 188 | + MESSAGE(FATAL_ERROR "ZLIB_VERSION definition not found in ${ZLIB_HEADER_PATH}") |
| 189 | + ENDIF() |
| 190 | + IF(TARGET zlib) |
| 191 | + ADD_THIRD_PARTY_DEPENDENCY(zlib "https://github.com/madler/zlib" |
| 192 | + "v${ZLIB_VERSION}" "v${ZLIB_VERSION}" "${ZLIB_VERSION}" "Vendored zlib included into server source") |
| 193 | + ENDIF() |
| 194 | + IF(TARGET ha_connect OR TARGET connect) |
| 195 | + SET(minizip_PURL "pkg:github/madler/zlib@${ZLIB_VERSION}?path=contrib/minizip") |
| 196 | + ADD_THIRD_PARTY_DEPENDENCY(minizip "https://github.com/madler/zlib?path=contrib/minizip" |
| 197 | + "v${ZLIB_VERSION}-minizip" "v${ZLIB_VERSION}-minizip" "${ZLIB_VERSION}" |
| 198 | + "Vendored minizip (zip.c, unzip.c, ioapi.c) in connect engine, copied from zlib/contributions") |
| 199 | + ENDIF() |
| 200 | + ENDIF() |
| 201 | + |
| 202 | + IF(TARGET columnstore) |
| 203 | + # Determining if Columnstore builds Boost is tricky. |
| 204 | + # The presence of the external_boost target isn't reliable, it is always |
| 205 | + # present. Instead, we check indirectly by verifying if one of the libraries |
| 206 | + # built by the external project exists in the build directory. |
| 207 | + IF(TARGET external_boost AND TARGET boost_filesystem) |
| 208 | + GET_TARGET_PROPERTY(boost_filesystem_loc boost_filesystem IMPORTED_LOCATION) |
| 209 | + STRING(FIND "${boost_filesystem_loc}" "${CMAKE_BINARY_DIR}" idx) |
| 210 | + IF(idx EQUAL 0) |
| 211 | + # Now we can be reasonably sure, external_boost is indeed an external project |
| 212 | + ExternalProject_GET_PROPERTY(external_boost URL) |
| 213 | + # Extract the version from the URL using string manipulation. |
| 214 | + STRING(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" BOOST_VERSION ${URL}) |
| 215 | + SET(tag boost-${BOOST_VERSION}) |
| 216 | + ADD_THIRD_PARTY_DEPENDENCY(boost |
| 217 | + "https://github.com/boostorg/boost" "${tag}" "${tag}" "${BOOST_VERSION}" |
| 218 | + "Boost library, linked with columnstore engine") |
| 219 | + ENDIF() |
| 220 | + ENDIF() |
| 221 | + IF(TARGET external_thrift) |
| 222 | + ADD_CMAKE_EXTERNAL_PROJECT_DEPENDENCY(external_thrift) |
| 223 | + ENDIF() |
| 224 | + ENDIF() |
| 225 | + |
| 226 | + SET(sbom_components "") |
| 227 | + SET(sbom_dependencies "\n { |
| 228 | + \"ref\": \"${CPACK_PACKAGE_NAME}\", |
| 229 | + \"dependsOn\": [" ) |
| 230 | + |
| 231 | + SET(first ON) |
| 232 | + FOREACH(dep ${ALL_THIRD_PARTY}) |
| 233 | + # Extract the part after the last "/" from URL |
| 234 | + SET(revision ${${dep}_REVISION}) |
| 235 | + SET(tag ${${dep}_TAG}) |
| 236 | + SET(desc ${${dep}_DESCRIPTION}) |
| 237 | + IF((tag STREQUAL "no-tag") OR (NOT tag)) |
| 238 | + SET(tag ${revision}) |
| 239 | + ENDIF() |
| 240 | + IF (NOT "${revision}" AND "${tag}") |
| 241 | + SET(revision ${tag}) |
| 242 | + ENDIF() |
| 243 | + SET(version ${${dep}_VERSION}) |
| 244 | + |
| 245 | + IF (version) |
| 246 | + ELSEIF(tag) |
| 247 | + SET(version ${tag}) |
| 248 | + ELSEIF(revision) |
| 249 | + SET(version ${revision}) |
| 250 | + ENDIF() |
| 251 | + |
| 252 | + EXTRACT_REPO_NAME_AND_USER("${${dep}_URL}" repo_name repo_user) |
| 253 | + |
| 254 | + IF(first) |
| 255 | + SET(first OFF) |
| 256 | + ELSE() |
| 257 | + STRING(APPEND sbom_components ",") |
| 258 | + STRING(APPEND sbom_dependencies ",") |
| 259 | + ENDIF() |
| 260 | + SET(bom_ref "${repo_name}-${version}") |
| 261 | + IF(desc) |
| 262 | + SET(desc_line "\n \"description\": \"${desc}\",") |
| 263 | + ELSE() |
| 264 | + SET(desc_line "") |
| 265 | + ENDIF() |
| 266 | + STRING(TOLOWER "${repo_user}" repo_user_lower) |
| 267 | + STRING(TOLOWER "${repo_name}" repo_name_lower) |
| 268 | + IF (${repo_name_lower}_PURL) |
| 269 | + SET(purl "${${repo_name_lower}_PURL}") |
| 270 | + ELSE() |
| 271 | + SET(purl "pkg:github/${repo_user_lower}/${repo_name_lower}@${revision}") |
| 272 | + ENDIF() |
| 273 | + SBOM_GET_SUPPLIER(${repo_name_lower} ${repo_user_lower} supplier) |
| 274 | + SBOM_GET_CPE(${repo_name_lower} "${version}" cpe) |
| 275 | + IF(cpe) |
| 276 | + SET(cpe "\n \"cpe\": \"${cpe}\",") |
| 277 | + ENDIF() |
| 278 | + STRING(APPEND sbom_components " |
| 279 | + { |
| 280 | + \"bom-ref\": \"${bom_ref}\", |
| 281 | + \"type\": \"library\", |
| 282 | + \"name\": \"${repo_name}\", |
| 283 | + \"version\": \"${version}\",${desc_line} |
| 284 | + \"purl\": \"${purl}\",${cpe} |
| 285 | + \"supplier\": { |
| 286 | + \"name\": \"${supplier}\" |
| 287 | + } |
| 288 | + }") |
| 289 | + STRING(APPEND sbom_dependencies " |
| 290 | + \"${bom_ref}\"") |
| 291 | + STRING(APPEND reflist ",\n {\"ref\": \"${bom_ref}\"}") |
| 292 | + ENDFOREACH() |
| 293 | + STRING(APPEND sbom_dependencies "\n ]\n }${reflist}\n") |
| 294 | + STRING(UUID UUID NAMESPACE ee390ca3-e70f-4b35-808e-a512489156f5 NAME SBOM TYPE SHA1) |
| 295 | + STRING(TIMESTAMP TIMESTAMP "%Y-%m-%dT%H:%M:%SZ" UTC) |
| 296 | + EXTRACT_REPO_NAME_AND_USER("${GIT_REMOTE_ORIGIN_URL}" GITHUB_REPO_NAME GITHUB_REPO_USER) |
| 297 | + #github-purl needs lowercased user and project names |
| 298 | + STRING(TOLOWER "${GITHUB_REPO_NAME}" GITHUB_REPO_NAME) |
| 299 | + STRING(TOLOWER "${GITHUB_REPO_USER}" GITHUB_REPO_USER) |
| 300 | + IF(NOT DEFINED CPACK_PACKAGE_VERSION) |
| 301 | + SET(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") |
| 302 | + ENDIF() |
| 303 | + configure_file(${CMAKE_CURRENT_LIST_DIR}/cmake/sbom.json.in ${CMAKE_BINARY_DIR}/sbom.json) |
| 304 | +ENDFUNCTION() |
0 commit comments