Added signing stage for livepatches pipeline. (#5101)

This commit is contained in:
Pawel Winogrodzki 2023-03-24 19:31:42 -07:00 коммит произвёл GitHub
Родитель f76930d345
Коммит 6e6bf3edbe
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 1010 добавлений и 320 удалений

Просмотреть файл

@ -0,0 +1,29 @@
{
"tool": "Credential Scanner",
"suppressions": [
{
"file": "\\SPECS\\shadow-utils\\shadow-utils.signatures.json",
"_justification": "This is a false positive, probably because it says the word password and has a hash. It is just a measurement of the pam.d system-password configuration file."
},
{
"file": "\\toolkit\\tools\\imagegen\\configuration\\configuration_test.go",
"_justification": "Dummy secrets used to unit test configuration code."
},
{
"file": "\\toolkit\\tools\\imagegen\\configuration\\testdata\\test_configuration.json",
"_justification": "Dummy secrets used to unit test configuration code."
},
{
"file": "toolkit\\tools\\imagegen\\configuration\\user_test.go",
"_justification": "Dummy secrets used to unit test configuration code."
},
{
"file": "\\toolkit\\docs\\quick_start\\quickstart.md",
"_justification": "Secrets for sample, non-production Mariner images."
},
{
"file": "\\toolkit\\imageconfigs\\read-only-root-efi.json",
"_justification": "Secret for a sample, non-production Mariner image."
}
]
}

6
.github/CODEOWNERS поставляемый
Просмотреть файл

@ -4,6 +4,12 @@
# Modification to this file require admin approval.
/.github/CODEOWNERS @microsoft/cbl-mariner-admins
# Modifications to the build pipelines require admin approval.
/pipelines/* @microsoft/cbl-mariner-admins
# Modifications to the CredScan exceptions require admin approval.
/.config/CredScanSuppressions.json @microsoft/cbl-mariner-admins
# Modification to what is considered "core packages" require admin approval.
/SPECS/core-packages/* @microsoft/cbl-mariner-admins

Просмотреть файл

@ -0,0 +1,318 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
__SAVED_SCRIPT_DIR="$SCRIPT_DIR"
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
# shellcheck source=file_tools.sh
source "$SCRIPT_DIR/file_tools.sh"
SCRIPT_DIR="$__SAVED_SCRIPT_DIR"
unset __SAVED_SCRIPT_DIR
# Hydrates the local build environment.
# Can hydrate RPMs, SRPMs, and toolchain.
# At least one of the above must be specified.
#
# Arguments:
#
# -c -> Put the RPMs inside the external cache, so that the out/RPMs directory will contain only the locally-built packages.
# -d -> repository root directory.
# -r -> Path to the RPMs archive or a directory containing it.
# In the latter case we look for "rpms.tar.gz".
# -s -> Path to the SRPMs archive or a directory containing it.
# In the latter case we look for "srpms.tar.gz".
# -t -> Path to the toolchain archive or a directory containing it.
# In the latter case we look for "toolchain_built_rpms_all.tar.gz".
hydrate_artifacts() {
local exit_code
local out_dir
local repo_dir
local rpms_archive
local rpms_input
local rpms_dir
local srpms_archive
local srpms_input
local toolchain_archive
local toolchain_input
local toolkit_dir
local OPTIND
while getopts "cd:r:s:t:" OPTIONS
do
case "${OPTIONS}" in
c ) rpms_dir="RPMS_DIR=../build/rpms_cache/cache" ;;
d ) repo_dir="$OPTARG" ;;
r ) rpms_input="$OPTARG" ;;
s ) srpms_input="$OPTARG" ;;
t ) toolchain_input="$OPTARG" ;;
\? )
echo "ERROR: Invalid Option: -$OPTARG" 1>&2
exit 1
;;
: )
echo "ERROR: Invalid Option: -$OPTARG requires an argument" 1>&2
exit 1
;;
esac
done
if [[ -z "$toolchain_input" && -z "$rpms_input" && -z "$srpms_input" ]]
then
echo "ERROR: must specify at least one of the following archives to hydrate: RPMs, SRPMs, or toolchain." >&2
return 1
fi
if [[ -n "$rpms_input" ]]
then
rpms_archive="$(find_file_fullpath "$rpms_input" "rpms.tar.gz")"
if [[ ! -f "$rpms_archive" ]]
then
echo "ERROR: No RPMs archive 'rpms.tar.gz' found inside '$rpms_input'." >&2
return 1
fi
fi
if [[ -n "$srpms_input" ]]
then
srpms_archive="$(find_file_fullpath "$srpms_input" "srpms.tar.gz")"
if [[ ! -f "$srpms_archive" ]]
then
echo "ERROR: No SRPMs archive 'srpms.tar.gz' found inside '$srpms_input'." >&2
return 1
fi
fi
if [[ -n "$toolchain_input" ]]
then
toolchain_archive="$(find_file_fullpath "$toolchain_input" "toolchain_built_rpms_all.tar.gz")"
if [[ ! -f "$toolchain_archive" ]]
then
echo "ERROR: No toolchain archive 'toolchain_built_rpms_all.tar.gz' found inside '$toolchain_input'." >&2
return 1
fi
fi
repo_dir="$(resolve_repo_dir "$repo_dir")"
out_dir="$repo_dir/out"
toolkit_dir="$repo_dir/toolkit"
if [[ -n "$rpms_archive" ]]
then
echo "-- Hydrating cache of repo '$repo_dir' with RPMs from '$rpms_archive'."
sudo make -C "$toolkit_dir" -j"$(nproc)" hydrate-rpms PACKAGE_ARCHIVE="$rpms_archive" $rpms_dir
exit_code=$?
if [[ $exit_code != 0 ]]; then
echo "ERROR: failed to hydrate repo's RPMs." >&2
return $exit_code
fi
fi
if [[ -n "$srpms_archive" ]]
then
echo "-- Hydrating cache of repo '$repo_dir' with SRPMs from '$srpms_archive'."
tar --skip-old-files -C "$out_dir" -xf "$srpms_archive"
exit_code=$?
if [[ $exit_code != 0 ]]; then
echo "ERROR: failed to hydrate repo's SRPMs." >&2
return $exit_code
fi
fi
if [[ -n "$toolchain_archive" ]]
then
echo "-- Hydrating cache of repo '$repo_dir' with toolchain from '$toolchain_archive'."
sudo make -C "$toolkit_dir" -j"$(nproc)" toolchain chroot-tools TOOLCHAIN_ARCHIVE="$toolchain_archive"
exit_code=$?
if [[ $exit_code != 0 ]]; then
echo "ERROR: failed to hydrate repo's toolchain." >&2
return $exit_code
fi
fi
}
# Overwrites local toolkit with the one from the provide archive.
#
# Arguments:
#
# -d -> repository root directory.
# -t -> Path to the toolkit archive or a directory containing it.
overwrite_toolkit() {
local repo_dir
local toolkit_input
local toolkit_tarball
local OPTIND
while getopts "d:t:" OPTIONS
do
case "${OPTIONS}" in
d ) repo_dir="$OPTARG" ;;
t ) toolkit_input="$OPTARG" ;;
\? )
echo "ERROR: Invalid Option: -$OPTARG" 1>&2
exit 1
;;
: )
echo "ERROR: Invalid Option: -$OPTARG requires an argument" 1>&2
exit 1
;;
esac
done
if [[ -z "$toolkit_input" ]]
then
echo "ERROR: must specify a path for overwriting the toolkit." >&2
return 1
fi
toolkit_tarball="$(find_file_fullpath "$toolkit_input" "toolkit-*.tar.gz")"
if [[ ! -f "$toolkit_tarball" ]]
then
echo "ERROR: No toolkit tarball found in '$toolkit_input'." >&2
return 1
fi
repo_dir="$(resolve_repo_dir "$repo_dir")"
toolkit_dir="$repo_dir/toolkit"
echo "-- Extracting toolkit from '$toolkit_tarball' into '$repo_dir'."
rm -rf "$toolkit_dir"
tar -C "$repo_dir" -xf "$toolkit_tarball"
}
parse_pipeline_boolean() {
[[ "$1" =~ [Tt]rue ]] && echo true || echo false
}
print_variable() {
local arg_name
local padding
arg_name="$1"
padding=" "
printf -- "-- %s%s-> %s\n" "$arg_name" "${padding:${#arg_name}}" "${!arg_name}"
}
print_variable_with_check() {
if [[ -n ${!1} ]]
then
print_variable "$arg_name"
else
echo "ERROR: Argument '$arg_name' is required." >&2
return 1
fi
}
print_variables_with_check() {
local check_status=0
for arg_name in "$@"
do
if ! print_variable_with_check "$arg_name"
then
check_status=1
fi
done
return $check_status
}
publish_build_artifacts() {
local artifact_publish_dir
local make_status
local repo_dir
local toolkit_dir
local out_dir
artifact_publish_dir="$1"
repo_dir="$(resolve_repo_dir "$2")"
out_dir="$repo_dir/out"
toolkit_dir="$repo_dir/toolkit"
echo "-- Packing built RPMs and SRPMs."
sudo make -C "$toolkit_dir" -j"$(nproc)" compress-srpms compress-rpms
make_status=$?
if [[ $make_status != 0 ]]; then
echo "ERROR: cannot pack built RPMs and SRPMs." >&2
return $make_status
fi
echo "-- Publishing built RPMs and SRPMs to '$artifact_publish_dir'."
mkdir -p "$artifact_publish_dir"
sudo mv "$out_dir/"{,s}rpms.tar.gz "$artifact_publish_dir"
}
publish_build_logs() {
local logs_dir
local logs_publish_dir
local package_build_artifacts_dir
local repo_dir
logs_publish_dir="$1"
repo_dir="$(resolve_repo_dir "$2")"
logs_dir="$repo_dir/build/logs"
package_build_artifacts_dir="$repo_dir/build/pkg_artifacts"
mkdir -p "$logs_publish_dir"
echo "-- Packing package build logs."
if [[ -d "$logs_dir" ]]
then
tar -C "$logs_dir" -czf "$logs_publish_dir/pkggen.logs.tar.gz" .
else
echo "WARNING: no '$logs_dir' directory found." >&2
fi
echo "-- Packing package build artifacts."
if [[ -d "$package_build_artifacts_dir" ]]
then
tar -C "$package_build_artifacts_dir" -czf "$logs_publish_dir/pkg_artifacts.tar.gz" .
else
echo "WARNING: no '$package_build_artifacts_dir' directory found." >&2
fi
}
publish_toolkit() {
local make_status
local repo_dir
local toolkit_dir
local toolkit_publish_dir
toolkit_publish_dir="$1"
repo_dir="$(resolve_repo_dir "$2")"
toolkit_dir="$repo_dir/toolkit"
echo "-- Packing toolkit."
sudo make -C "$toolkit_dir" -j"$(nproc)" REBUILD_TOOLS=y package-toolkit
make_status=$?
if [[ $make_status != 0 ]]; then
echo "ERROR: failed to pack toolkit." >&2
return $make_status
fi
echo "-- Publishing toolkit to '$toolkit_publish_dir'."
mkdir -p "$toolkit_publish_dir"
sudo mv "$repo_dir"/out/toolkit*.tar.gz "$toolkit_publish_dir"
}
resolve_repo_dir() {
local repo_dir
repo_dir="$1"
if [[ -z "$repo_dir" ]]
then
repo_dir="$(git rev-parse --show-toplevel)"
fi
realpath "$repo_dir"
}

Просмотреть файл

@ -0,0 +1,47 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
TEMP_DIR=""
command_diff() {
local input_command
local first_file_path
local second_file_path
input_command="$1"
first_file_path="$2"
second_file_path="$3"
if ! diff <(eval "$input_command" "$first_file_path") <(eval "$input_command" "$second_file_path"); then
echo "ERROR: comparison for command ($input_command) failed! See lines above for details." >&2
return 1
fi
}
find_file_fullpath() {
local file_pattern
local input_path
local output_path
input_path="$1"
file_pattern="$2"
output_path="$input_path"
if [[ ! -f "$input_path" ]]
then
output_path="$(find "$input_path" -name "$file_pattern" -type f -print -quit)"
fi
if [[ -f "$output_path" ]]
then
realpath "$output_path"
fi
}
temp_dir_cleanup() {
if [[ -d "$TEMP_DIR" ]]
then
echo "Cleaning up '$TEMP_DIR'."
rm -rf "$TEMP_DIR"
fi
}

Просмотреть файл

@ -0,0 +1,144 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Extracts files from the specified RPM file.
# If no pattern is specified, all files are extracted.
#
# Arguments:
#
# -f -> flatten the extracted files into the output directory
# WARNING: this will overwrite files with the same name.
# -i -> input RPM file.
# -o -> output directory.
# -p -> files pattern.
# Default: '*'.
# -w -> temporary work directory.
rpm_extract_file() {
local files_pattern="*"
local flatten
local output_dir
local rpm_name
local rpm_path
local work_dir
local OPTIND
while getopts "fi:o:p:w:" OPTIONS
do
case "${OPTIONS}" in
f ) flatten=true ;;
i ) rpm_path="$OPTARG" ;;
o ) output_dir="$OPTARG" ;;
p ) files_pattern="$OPTARG" ;;
w ) work_dir="$OPTARG" ;;
\? )
echo "ERROR: Invalid Option: -$OPTARG" 1>&2
exit 1
;;
: )
echo "ERROR: Invalid Option: -$OPTARG requires an argument" 1>&2
exit 1
;;
esac
done
if [[ ! -f "$rpm_path" ]]
then
echo "ERROR: RPM file ($rpm_path) not found." >&2
return 1
fi
if [[ -z "$output_dir" ]]
then
echo "ERROR: output path not specified." >&2
return 1
fi
if [[ -f "$output_dir" ]]
then
echo "ERROR: output path ($output_dir) is a file. Expected a directory or a non-existing path." >&2
return 1
fi
rpm_name="$(basename "$rpm_path" .rpm)"
if [[ ! -d "$work_dir" ]]
then
work_dir="$(mktemp -d)"
fi
work_dir="$work_dir/$rpm_name"
mkdir -p "$work_dir"
mkdir -p "$output_dir"
echo "Extracting ($files_pattern) from ($rpm_path) into ($output_dir)."
rpm2cpio "$rpm_path" | cpio --quiet -D "$work_dir" -idmv "$files_pattern"
if $flatten
then
find "$work_dir" -name "$files_pattern" -exec mv -v {} "$output_dir" \;
else
mv "$work_dir" "$output_dir"
fi
}
# Extracts files from the specified directory or RPM file.
# If no pattern is specified, all files are extracted.
# Arguments:
# -f -> flatten the extracted files into the output directory
# WARNING: this will overwrite files with the same name.
# -i -> input directory or RPM file.
# -o -> output directory.
# -p -> files pattern.
# Default: '*'.
# -w -> temporary work directory.
rpm_extract_files() {
local files_pattern="*"
local flatten_arg
local input
local output_dir
local output_subdir
local rpm_name
local rpm_path
local work_dir
local OPTIND
while getopts "fi:o:p:w:" OPTIONS
do
case "${OPTIONS}" in
f ) flatten_arg="-f" ;;
i ) input="$OPTARG" ;;
o ) output_dir="$OPTARG" ;;
p ) files_pattern="$OPTARG" ;;
w ) work_dir="$OPTARG" ;;
\? )
echo "ERROR: Invalid Option: -$OPTARG" 1>&2
exit 1
;;
: )
echo "ERROR: Invalid Option: -$OPTARG requires an argument" 1>&2
exit 1
;;
esac
done
if [[ ! -d "$input" && ! -f "$input" ]]
then
echo "ERROR: input ($input) not found." >&2
return 1
fi
if ! find "$input" -name "*.rpm" -type f -print0 | grep -q .
then
echo "ERROR: input ($input) is neither an RPM file nor a directory containing RPM files." >&2
return 1
fi
while IFS= read -r -d '' rpm_path
do
rpm_name="$(basename "$rpm_path" .rpm)"
output_subdir="$output_dir/$rpm_name"
rpm_extract_file -i "$rpm_path" -p "$files_pattern" -w "$work_dir" -o "$output_subdir" $flatten_arg
done < <(find "$input" -name "*.rpm" -type f -print0)
}

Просмотреть файл

@ -0,0 +1,97 @@
#!/bin/bash
set -e
ROOT_DIR="$(git rev-parse --show-toplevel)"
# shellcheck source=../common/libs/build_tools.sh
source "$ROOT_DIR/pipelines/common/libs/build_tools.sh"
compress_rpms() {
local artifacts_dir
local compressed_dir
local compressed_dir_lowercase
local output_dir
artifacts_dir="$1"
output_dir="$2"
mkdir -p "$output_dir"
for compressed_dir in RPMS SRPMS
do
if [[ -d "$artifacts_dir/$compressed_dir" ]]
then
compressed_dir_lowercase="${compressed_dir,,}"
echo "-- Compressing the '$compressed_dir' directory into ($output_dir)."
tar -C "$artifacts_dir" -cf "$output_dir/$compressed_dir_lowercase.tar.gz" "$compressed_dir"
fi
done
}
validate_rpm_signatures() {
local artifacts_dir
local key_id
local rpm_name
local rpm_path
local signatures_ok
artifacts_dir="$1"
key_id="$2"
signatures_ok=true
echo "-- Validating RPM signatures with key ID ($key_id)."
while IFS= read -r -d '' rpm_path
do
rpm_name="$(basename "$rpm_path")"
echo "-- Validating signature key for ($rpm_name)."
verification_output="$(rpm -v --checksig "$rpm_path" 2>&1)"
if ! grep -q "key ID $key_id: OK" <<< "$verification_output"
then
echo -e "ERROR: RPM signature key validation failed for ($rpm_name). Details:\n$verification_output" >&2
signatures_ok=false
fi
done < <(find "$artifacts_dir" -name "*.rpm")
$signatures_ok
}
# Script parameters:
#
# -a -> input artifacts directory path
# -k -> signing key ID to verify the RPMs' signatures
# -o -> output directory
while getopts "a:k:o:" OPTIONS
do
case "${OPTIONS}" in
a ) ARTIFACTS_DIR=$OPTARG ;;
k ) EXPECTED_KEY_ID=$OPTARG ;;
o ) OUTPUT_DIR=$OPTARG ;;
\? )
echo "ERROR: Invalid Option: -$OPTARG" 1>&2
exit 1
;;
: )
echo "ERROR: Invalid Option: -$OPTARG requires an argument" 1>&2
exit 1
;;
esac
done
if [[ ! -d "$ARTIFACTS_DIR/RPMS" && ! -d "$ARTIFACTS_DIR/SRPMS" ]]
then
echo "ERROR: input directory ($ARTIFACTS_DIR) must contain at least the 'RPMS' or 'SRPMS' subdirectory." >&2
exit 1
fi
print_variables_with_check ARTIFACTS_DIR EXPECTED_KEY_ID OUTPUT_DIR
validate_rpm_signatures "$ARTIFACTS_DIR" "$EXPECTED_KEY_ID"
compress_rpms "$ARTIFACTS_DIR" "$OUTPUT_DIR"

Просмотреть файл

@ -1,97 +0,0 @@
#!/bin/bash
set -e
echo "Configuring CBL-Mariner build machine."
function verify_image {
echo "verify_image"
# Verify proper filesystem type for /mnt
#
# Good: /dev/sda1 ext4 126G 94M 119G 1% /mnt
# Bad: /dev/sda1 fuseblk 128G 125M 128G 1% /mnt
MNT_FILESYSTEM_TYPE=$(df -Th | grep '/mnt$' | awk '{ print $2 }')
if [[ "$MNT_FILESYSTEM_TYPE" == "ext4" ]]; then
echo "Detected proper /mnt filesystem type is 'ext4'. Continuing."
else
echo "Error detected: /mnt filesystem type is '$MNT_FILESYSTEM_TYPE' instead of 'ext4'. Failing build." >&2
return 1
fi
}
function configure_image {
echo "configure_image"
# Install apps applicable to both X64 and ARM64
sudo tdnf -y install dotnet-sdk-6.0 createrepo jq yum-utils rpm cpio dnf-utils acl
# ensure golang 1.17 is installed
sudo tdnf -y install "golang < 1.18"
sudo tdnf -y install git make tar rpm-build gcc glibc-devel binutils \
kernel-headers wget curl rpm qemu-img cdrkit python bison \
gawk parted dosfstools pigz moby-engine moby-cli
# Enable Docker daemon at boot
sudo systemctl enable --now docker.service
# Add current user to docker group
sudo usermod -aG docker "$USER"
# Install architecture-specific
if [[ "$(uname -i)" == "x86_64" ]]; then
configure_image_x64
else
configure_image_arm64
fi
}
function configure_image_x64 {
echo "configure_image_x64"
# These are only available for X64.
sudo tdnf -y install azure-cli powershell
}
function configure_image_arm64 {
echo "configure_image_arm64"
# Install Azure CLI via script. This requires python3-devel.
sudo tdnf -y install python3-devel
install_az_cli_with_script
}
function install_az_cli_with_script {
echo "install_az_cli_with_script"
pushd /tmp/
#Work-around to make the script non-interactive and install az with default values
cat > /tmp/non_interact.sh <<EOF
sed -i 's/def prompt_input_with_default(msg, default):/def prompt_input_with_default(msg, default):\n return default/g' \$1
sed -i 's/def prompt_y_n(msg, default=None):/def prompt_y_n(msg, default=None):\n return "y"/g' \$1
# The no-indentation is intentional. EOF with indentation is not considered as EOF.
EOF
chmod +x /tmp/non_interact.sh
curl -sL https://aka.ms/InstallAzureCli > /tmp/azcli_ins.sh
sed -i 's/echo "Running install script."/echo "Running install script."\n\/tmp\/non_interact.sh \$install_script/g' /tmp/azcli_ins.sh
sed -i "s/\$python_cmd \$install_script < \$_TTY/\$python_cmd \$install_script/g" /tmp/azcli_ins.sh
bash /tmp/azcli_ins.sh
sudo ln -svf /home/"$USER"/bin/az /usr/bin/az | true
az extension add --name azure-devops
az devops configure --defaults organization="https://dev.azure.com/mariner-org" project="mariner"
az version
popd
}
function print_app_versions {
echo "print_app_versions"
echo "Kernel version: $(uname -r)"
echo "Architecture: $(uname -m)"
docker --version
go version
echo "Dotnet version: $(dotnet --version)"
# Note: powershell and azcli might not be available (on mariner arm64). Ignore errors
pwsh --version || true
echo "Az cli version: "
az version || true
}
configure_image
verify_image
print_app_versions

Просмотреть файл

@ -1,163 +0,0 @@
#!/bin/bash
hydrate_cache() {
local make_status
local repo_dir
local rpms_archive
local toolchain_archive
local toolkit_dir
toolchain_archive="$1"
rpms_archive="$2"
repo_dir="$(resolve_repo_dir "$3")"
toolkit_dir="$repo_dir/toolkit"
echo "-- Hydrating cache of repo '$repo_dir' with toolchain from '$toolchain_archive'."
sudo make -C "$toolkit_dir" -j"$(nproc)" toolchain chroot-tools TOOLCHAIN_ARCHIVE="$toolchain_archive"
make_status=$?
if [[ $make_status != 0 ]]; then
echo "-- ERROR: failed to hydrate repo's toolchain." >&2
return $make_status
fi
if [[ -n "$rpms_archive" ]]
then
# We put the RPMs into the cache directory, so that the built RPMs directory only contains the built packages.
echo "-- Hydrating cache of repo '$repo_dir' with RPMS from '$rpms_archive'."
sudo make -C "$toolkit_dir" -j"$(nproc)" hydrate-rpms PACKAGE_ARCHIVE="$rpms_archive" RPMS_DIR="$repo_dir/build/rpms_cache/cache"
make_status=$?
if [[ $make_status != 0 ]]; then
echo "-- ERROR: failed to hydrate repo's RPMs." >&2
return $make_status
fi
fi
}
overwrite_toolkit() {
local repo_dir
local toolkit_tarball
toolkit_tarball="$1"
repo_dir="$(resolve_repo_dir "$2")"
toolkit_dir="$repo_dir/toolkit"
pushd "$repo_dir" > /dev/null || true
echo "-- Extracting toolkit from '$toolkit_tarball' into '$repo_dir'."
rm -rf "$toolkit_dir"
tar -C "$repo_dir" -xf "$toolkit_tarball"
popd > /dev/null || true
}
parse_pipeline_boolean() {
[[ "$1" =~ [Tt]rue ]] && echo true || echo false
}
print_variable() {
local arg_name
local padding
arg_name="$1"
padding=" "
printf -- "-- %s%s-> %s\n" "$arg_name" "${padding:${#arg_name}}" "${!arg_name}"
}
print_variable_with_check() {
if [[ -n ${!1} ]]
then
print_variable "$arg_name"
else
echo "ERROR: Argument '$arg_name' is required." >&2
return 1
fi
}
print_variables_with_check() {
local check_status=0
for arg_name in "$@"
do
if ! print_variable_with_check "$arg_name"
then
check_status=1
fi
done
return $check_status
}
publish_build_artifacts() {
local artifact_publish_dir
local make_status
local repo_dir
local toolkit_dir
local out_dir
artifact_publish_dir="$1"
repo_dir="$(resolve_repo_dir "$2")"
toolkit_dir="$repo_dir/toolkit"
out_dir="$repo_dir/out"
echo "-- Packing built RPMs and SRPMs."
sudo make -C "$toolkit_dir" -j"$(nproc)" compress-srpms compress-rpms
make_status=$?
if [[ $make_status != 0 ]]; then
echo "-- ERROR: cannot pack built RPMs and SRPMs." >&2
return $make_status
fi
echo "-- Publishing built RPMs and SRPMs to '$artifact_publish_dir'."
mkdir -p "$artifact_publish_dir"
sudo mv "$out_dir/"{,s}rpms.tar.gz "$artifact_publish_dir"
}
publish_build_logs() {
local logs_dir
local logs_publish_dir
local package_build_artifacts_dir
local repo_dir
logs_publish_dir="$1"
repo_dir="$(resolve_repo_dir "$2")"
logs_dir="$repo_dir/build/logs"
package_build_artifacts_dir="$repo_dir/build/pkg_artifacts"
mkdir -p "$logs_publish_dir"
echo "-- Packing package build logs."
if [[ -d "$logs_dir" ]]
then
tar -C "$logs_dir" -czf "$logs_publish_dir/pkggen.logs.tar.gz" .
else
echo "-- WARNING: no '$logs_dir' directory found." >&2
fi
echo "-- Packing package build artifacts."
if [[ -d "$package_build_artifacts_dir" ]]
then
tar -C "$package_build_artifacts_dir" -czf "$logs_publish_dir/pkg_artifacts.tar.gz" .
else
echo "-- WARNING: no '$package_build_artifacts_dir' directory found."
fi
}
resolve_repo_dir() {
local repo_dir
repo_dir="$1"
if [[ -z "$repo_dir" ]]
then
repo_dir="$(git rev-parse --show-toplevel)"
fi
realpath "$repo_dir"
}

Просмотреть файл

@ -4,8 +4,8 @@ set -e
ROOT_DIR="$(git rev-parse --show-toplevel)"
# shellcheck source=../common/utilities/build_tools.sh
source "$ROOT_DIR/pipelines/common/utilities/build_tools.sh"
# shellcheck source=../common/libs/build_tools.sh
source "$ROOT_DIR/pipelines/common/libs/build_tools.sh"
build_package() {
local package_name
@ -33,75 +33,46 @@ build_package() {
LOG_LEVEL=info
}
hydrate_build_artifacts() {
local artifacts_dir
local rpms_archive
local toolchain_archive
local toolkit_archive
artifacts_dir="$1"
rpms_archive="$(find "$artifacts_dir" -name '*rpms.tar.gz' -type f -print -quit)"
if [[ ! -f "$rpms_archive" ]]
then
echo "ERROR: No RPMs archive found in '$artifacts_dir'." >&2
return 1
fi
toolchain_archive="$(find "$artifacts_dir" -name '*toolchain_built_rpms_all.tar.gz' -type f -print -quit)"
if [[ ! -f "$toolchain_archive" ]]
then
echo "ERROR: No toolchain archive found in '$artifacts_dir'." >&2
return 1
fi
toolkit_archive="$(find "$artifacts_dir" -name '*toolkit-*.tar.gz' -type f -print -quit)"
if [[ ! -f "$toolkit_archive" ]]
then
echo "ERROR: No toolkit tarball found in '$artifacts_dir'." >&2
return 1
fi
overwrite_toolkit "$toolkit_archive"
hydrate_cache "$toolchain_archive" "$rpms_archive"
}
# Script parameters:
#
# -a -> input artifacts directory path
# -k -> kernel version to be livepatched
# -l -> published logs directory path
# -p -> published artifacts directory path
# -o -> built artifacts' output directory path
# -s -> use toolkit's RPMs snapshot to populate the packages cache
while getopts "a:k:l:p:s:" OPTIONS
while getopts "a:k:l:o:s:" OPTIONS
do
case "${OPTIONS}" in
a ) ARTIFACTS_DIR=$OPTARG ;;
k ) KERNEL_VERSION=$OPTARG ;;
l ) LOG_PUBLISH_DIR=$OPTARG ;;
p ) ARTIFACTS_PUBLISH_DIR=$OPTARG ;;
o ) ARTIFACTS_OUTPUT_DIR=$OPTARG ;;
s ) USE_RPMS_SNAPSHOT="$(parse_pipeline_boolean "$OPTARG")" ;;
\? )
echo "Error - Invalid Option: -$OPTARG" 1>&2
echo "ERROR: Invalid Option: -$OPTARG" 1>&2
exit 1
;;
: )
echo "Error - Invalid Option: -$OPTARG requires an argument" 1>&2
echo "ERROR: Invalid Option: -$OPTARG requires an argument" 1>&2
exit 1
;;
esac
done
print_variables_with_check ARTIFACTS_DIR KERNEL_VERSION LOG_PUBLISH_DIR ARTIFACTS_PUBLISH_DIR USE_RPMS_SNAPSHOT
print_variables_with_check ARTIFACTS_DIR KERNEL_VERSION LOG_PUBLISH_DIR ARTIFACTS_OUTPUT_DIR USE_RPMS_SNAPSHOT
hydrate_build_artifacts "$ARTIFACTS_DIR"
overwrite_toolkit -t "$ARTIFACTS_DIR"
hydrate_artifacts -c -t "$ARTIFACTS_DIR" -r "$ARTIFACTS_DIR"
# Making sure we publish build logs even if the build fails.
build_package "livepatch-$KERNEL_VERSION" "$USE_RPMS_SNAPSHOT" || BUILD_SUCCEEDED=false
publish_build_logs "$LOG_PUBLISH_DIR"
publish_build_artifacts "$ARTIFACTS_PUBLISH_DIR"
publish_build_artifacts "$ARTIFACTS_OUTPUT_DIR"
publish_toolkit "$ARTIFACTS_OUTPUT_DIR"
${BUILD_SUCCEEDED:-true}

Просмотреть файл

@ -0,0 +1,137 @@
#!/bin/bash
set -e
ROOT_DIR="$(git rev-parse --show-toplevel)"
# shellcheck source=../common/libs/build_tools.sh
source "$ROOT_DIR/pipelines/common/libs/build_tools.sh"
# shellcheck source=../common/libs/file_tools.sh
source "$ROOT_DIR/pipelines/common/libs/file_tools.sh"
build_package_signed() {
local package_cache_summary
local package_name
local toolkit_dir
local use_rpms_snapshot
package_name="$1"
use_rpms_snapshot="$2"
toolkit_dir="toolkit"
if $use_rpms_snapshot
then
package_cache_summary="$toolkit_dir/rpms_snapshot.json"
echo "-- Using RPMs snapshot as package cache summary."
fi
sudo make -C "toolkit" -j"$(nproc)" build-packages \
CONFIG_FILE= \
REBUILD_TOOLS=y \
PACKAGE_BUILD_LIST="$package_name" \
PACKAGE_CACHE_SUMMARY="$package_cache_summary" \
SRPM_PACK_LIST="$package_name-signed" \
SRPM_FILE_SIGNATURE_HANDLING=update \
SPECS_DIR="../SPECS-SIGNED" \
LOG_LEVEL=info
}
hydrate_signed_sources() {
local kernel_modules_dir
local livepatch_folder
livepatch_folder="livepatch-$1"
kernel_modules_dir="$2"
cp "$kernel_modules_dir/$livepatch_folder"*/*.ko SPECS-SIGNED/"$livepatch_folder-signed"
}
verify_built_package() {
local kernel_version
local no_errors
local rpm_name
local signed_rpm_path
local tmpdir
local unsigned_rpm_path
kernel_version="$1"
tmpdir="$2"
while IFS= read -r -d '' signed_rpm_path
do
rpm_name="$(basename "$signed_rpm_path")"
echo "Verifying RPM ($rpm_name)."
unsigned_rpm_path="$(find "$tmpdir" -name "$rpm_name" -print -quit)"
if [[ -z "$unsigned_rpm_path" ]]
then
echo "ERROR: RPM ($rpm_name) not found in the unsigned version." >&2
no_errors=false
continue
fi
if ! command_diff "rpm -qp --provides" "$unsigned_rpm_path" "$signed_rpm_path" || \
! command_diff "rpm -qp --requires" "$unsigned_rpm_path" "$signed_rpm_path" || \
! command_diff "rpm -qlp" "$unsigned_rpm_path" "$signed_rpm_path"
then
no_errors=false
fi
done < <(find "out/RPMS" -name "livepatch-$kernel_version*.rpm" -and -not -name "*debuginfo*" -print0)
${no_errors:-true}
}
# Script parameters:
#
# -a -> input artifacts directory path
# -k -> kernel version to be livepatched
# -l -> published logs directory path
# -o -> built artifacts' output directory path
# -s -> use toolkit's RPMs snapshot to populate the packages cache
while getopts "a:k:l:o:s:" OPTIONS
do
case "${OPTIONS}" in
a ) ARTIFACTS_DIR=$OPTARG ;;
k ) KERNEL_VERSION=$OPTARG ;;
l ) LOG_PUBLISH_DIR=$OPTARG ;;
o ) ARTIFACTS_OUTPUT_DIR=$OPTARG ;;
s ) USE_RPMS_SNAPSHOT="$(parse_pipeline_boolean "$OPTARG")" ;;
\? )
echo "ERROR: Invalid Option: -$OPTARG" 1>&2
exit 1
;;
: )
echo "ERROR: Invalid Option: -$OPTARG requires an argument" 1>&2
exit 1
;;
esac
done
print_variables_with_check ARTIFACTS_DIR KERNEL_VERSION LOG_PUBLISH_DIR ARTIFACTS_OUTPUT_DIR USE_RPMS_SNAPSHOT
TEMP_DIR="$(mktemp -d)"
trap temp_dir_cleanup EXIT
overwrite_toolkit -t "$ARTIFACTS_DIR"
hydrate_artifacts -r "$ARTIFACTS_DIR" -s "$ARTIFACTS_DIR"
# Saving the unsigned version for the sake of comparing with the signed one after it's built.
find "out/RPMS" -name "livepatch-$KERNEL_VERSION*.rpm" -and -not -name "*debuginfo*" -exec mv {} "$TEMP_DIR" \;
hydrate_signed_sources "$KERNEL_VERSION" "$ARTIFACTS_DIR"
# Making sure we publish build logs even if the build fails.
build_package_signed "livepatch-$KERNEL_VERSION" "$USE_RPMS_SNAPSHOT" || BUILD_SUCCEEDED=false
publish_build_logs "$LOG_PUBLISH_DIR"
publish_build_artifacts "$ARTIFACTS_OUTPUT_DIR"
${BUILD_SUCCEEDED:-true}
verify_built_package "$KERNEL_VERSION" "$TEMP_DIR"

Просмотреть файл

@ -1,19 +1,37 @@
trigger: none
variables:
# Required by used templates.
- group: "Mariner Automation"
- name: 'buildArtifactsFolder'
value: build-artifacts
- name: 'buildLogsFolder'
value: 'LOGS'
- name: 'pipelinesRepoFolder'
value: 'CBL-Mariner-Pipelines'
- name: 'pipelinesRepoRoot'
value: '$(Agent.BuildDirectory)/$(pipelinesRepoFolder)'
- name: 'selfRepoFolder'
value: 'CBL-Mariner'
- name: 'selfRepoRoot'
value: '$(Agent.BuildDirectory)/$(selfRepoFolder)'
- name: 'rpmSigningKeyID'
value: '3135ce90'
parameters:
- name: livepatchKernelVersion
displayName: 'Version of the kernel, which is used to build the livepatch (example: 5.15.92.1-1.cm2).'
type: string
- name: useKernelBuildArtifacts
displayName: 'Use toolkit and packages from the time the kernel was built.'
- name: downloadKernelBuildArtifacts
displayName: 'Download toolkit and packages from the time the kernel was built.'
type: boolean
default: false
@ -22,12 +40,22 @@ parameters:
type: boolean
default: false
- name: publishRPMs
displayName: 'PRODUCTION RUN. WILL PUBLISH PACKAGES TO PMC.'
type: boolean
default: false
resources:
repositories:
- repository: 1ESPipelineTemplates
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/canary
ref: refs/tags/release
- repository: CBL-Mariner-Pipelines
type: git
name: mariner/CBL-Mariner-Pipelines
ref: refs/heads/pawelwi/signing_livepatches
extends:
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
@ -52,22 +80,18 @@ extends:
- output: pipelineArtifact
displayName: 'Publish build artifacts'
targetPath: $(Build.ArtifactStagingDirectory)/$(buildArtifactsFolder)
artifactName: build-artifacts
artifactName: $(buildArtifactsFolder)_BUILD
- output: pipelineArtifact
displayName: 'Publish logs'
targetPath: $(Build.ArtifactStagingDirectory)/$(buildLogsFolder)
artifactName: LOGS
artifactName: LOGS_BUILD
steps:
- bash: |
pipelines/common/utilities/Configure-1ES.sh
displayName: 'Install 1ES host machine applications'
- bash: |
mariner_version="$(make -sC toolkit printvar-RELEASE_MAJOR_ID CONFIG_FILE=)"
download_tags="$mariner_version-stable"
if [[ "${{ parameters.useKernelBuildArtifacts }}" =~ [Tt]rue ]]
if [[ "${{ parameters.downloadKernelBuildArtifacts }}" =~ [Tt]rue ]]
then
download_tags+=",kernel-${{ parameters.livepatchKernelVersion }}"
fi
@ -86,28 +110,155 @@ extends:
tags: '$(downloadTags)'
artifact: $(buildArtifactsFolder)
patterns: |
**/toolkit-*.tar.gz
**/toolchain_built_rpms_all.tar.gz
**/rpms.tar.gz
**/toolchain_built_rpms_all.tar.gz
**/toolkit-*.tar.gz
path: '$(System.ArtifactsDirectory)'
- task: Bash@3
displayName: 'Build livepatch'
inputs:
filePath: '$(Build.SourcesDirectory)/pipelines/livepatching/BuildLivepatch.sh'
filePath: 'pipelines/livepatching/BuildLivepatch.sh'
arguments: '-a "$(System.ArtifactsDirectory)"
-k "${{ parameters.livepatchKernelVersion }}"
-l "$(Build.ArtifactStagingDirectory)/$(buildLogsFolder)"
-p "$(Build.ArtifactStagingDirectory)/$(buildArtifactsFolder)"
-o "$(Build.ArtifactStagingDirectory)/$(buildArtifactsFolder)"
-s "${{ parameters.useRPMsSnapshot }}"'
- stage: Sign
jobs:
- job:
- job: Kernel_modules
templateContext:
outputs:
- output: pipelineArtifact
displayName: 'Publish build artifacts'
targetPath: $(Build.ArtifactStagingDirectory)/$(buildArtifactsFolder)
artifactName: $(buildArtifactsFolder)_KERNEL_MODULES
pool:
# The kernel modules signing task works only on Ubuntu.
name: mariner-core-x64-1es-ubuntu2204-gpt-test
steps:
- checkout: self
path: '$(selfRepoFolder)'
- checkout: CBL-Mariner-Pipelines
path: '$(pipelinesRepoFolder)'
- task: DownloadPipelineArtifact@2
displayName: 'Download livepatch RPMs'
inputs:
source: current
artifact: '$(buildArtifactsFolder)_BUILD'
patterns: '**/rpms.tar.gz'
path: '$(System.ArtifactsDirectory)'
- task: Bash@3
displayName: 'Extract kernel modules'
inputs:
filePath: '$(selfRepoRoot)/pipelines/livepatching/ExtractKernelModules.sh'
arguments: '-a "$(System.ArtifactsDirectory)"
-o "$(Build.ArtifactStagingDirectory)/$(buildArtifactsFolder)"'
workingDirectory: '$(selfRepoRoot)'
- template: .pipelines/templates/KernelModulesSigning.yml@CBL-Mariner-Pipelines
parameters:
artifactsFolder: '$(Build.ArtifactStagingDirectory)/$(buildArtifactsFolder)'
repoRootPath: '$(pipelinesRepoRoot)'
# Once we enable signing kernel modules on Mariner images, merge this job with the previous one to simplify the pipeline.
- job: Kernel_modules_packaging
dependsOn: Kernel_modules
templateContext:
outputs:
- output: pipelineArtifact
displayName: 'Publish build artifacts'
targetPath: $(Build.ArtifactStagingDirectory)/$(buildArtifactsFolder)
artifactName: $(buildArtifactsFolder)_KERNEL_MODULES_PACKAGING
- output: pipelineArtifact
displayName: 'Publish logs'
targetPath: $(Build.ArtifactStagingDirectory)/$(buildLogsFolder)
artifactName: LOGS_KERNEL_MODULES_PACKAGING
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download RPMs and toolkit archives'
inputs:
source: current
artifact: '$(buildArtifactsFolder)_BUILD'
patterns: |
**/*rpms.tar.gz
**/toolkit-*.tar.gz
path: '$(System.ArtifactsDirectory)'
- task: DownloadPipelineArtifact@2
displayName: 'Download signed kernel modules'
inputs:
source: current
artifact: '$(buildArtifactsFolder)_KERNEL_MODULES'
patterns: '**/*.ko'
path: '$(System.ArtifactsDirectory)'
- task: Bash@3
displayName: 'Build livepatch-signed RPM'
inputs:
filePath: 'pipelines/livepatching/BuildLivepatchSigned.sh'
arguments: '-a "$(System.ArtifactsDirectory)"
-k "${{ parameters.livepatchKernelVersion }}"
-l "$(Build.ArtifactStagingDirectory)/$(buildLogsFolder)"
-o "$(Build.ArtifactStagingDirectory)/$(buildArtifactsFolder)"
-s "${{ parameters.useRPMsSnapshot }}"'
- job: RPMs
dependsOn: Kernel_modules_packaging
templateContext:
outputs:
- output: pipelineArtifact
displayName: 'Publish build artifacts'
targetPath: $(Build.ArtifactStagingDirectory)/$(buildArtifactsFolder)
artifactName: $(buildArtifactsFolder)_RPMS
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download RPMs and SRPMs archives'
inputs:
source: current
artifact: '$(buildArtifactsFolder)_KERNEL_MODULES_PACKAGING'
patterns: '**/*rpms.tar.gz'
path: '$(System.ArtifactsDirectory)'
- bash: |
sleep 1
displayName: 'Signing placeholder'
find "$(System.ArtifactsDirectory)" -name '*rpms.tar.gz' -exec tar -C "$(System.ArtifactsDirectory)" -xf {} \;
displayName: 'Expand SRPM/RPM tarballs'
# JSON config generated from https://portal.esrp.microsoft.com/Onboarding/JSONGenerator
- task: EsrpCodeSigning@2
displayName: 'Sign RPMs'
inputs:
ConnectedServiceName: 'ESRP RPM Signing 1ES PTs'
FolderPath: '$(System.ArtifactsDirectory)'
Pattern: '*.rpm'
signConfigType: inlineSignParams
inlineOperation: |
[
{
"KeyCode": "CP-459159-Pgp",
"OperationCode": "LinuxSign",
"Parameters": {},
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
- task: Bash@3
displayName: 'Validate and package signed RPMs'
inputs:
filePath: 'pipelines/common/scripts/PackageSignedRPMs.sh'
arguments: '-a "$(System.ArtifactsDirectory)"
-k "$(rpmSigningKeyID)"
-o "$(Build.ArtifactStagingDirectory)/$(buildArtifactsFolder)"'
- stage: Test
jobs:
@ -118,6 +269,7 @@ extends:
displayName: 'Test placeholder'
- stage: Publish
condition: ${{ parameters.publishRPMs }}
jobs:
- job:
steps:

Просмотреть файл

@ -0,0 +1,49 @@
#!/bin/bash
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
ROOT_DIR="$(git rev-parse --show-toplevel)"
# shellcheck source=../common/libs/file_tools.sh
source "$ROOT_DIR"/pipelines/common/libs/file_tools.sh
# shellcheck source=../common/libs/rpm_tools.sh
source "$ROOT_DIR"/pipelines/common/libs/rpm_tools.sh
set -e
# Script parameters:
#
# -a -> artifacts directory where the RPMs archive is located.
# -o -> output directory where the RPMs will be extracted.
while getopts "a:o:" OPTIONS
do
case "${OPTIONS}" in
a ) ARTIFACTS_DIR=$OPTARG ;;
o ) OUTPUT_DIR=$OPTARG ;;
\? )
echo "ERROR: Invalid Option: -$OPTARG" 1>&2
exit 1
;;
: )
echo "ERROR: Invalid Option: -$OPTARG requires an argument" 1>&2
exit 1
;;
esac
done
rpms_archive="$(find_file_fullpath "$ARTIFACTS_DIR" "rpms.tar.gz")"
if [[ ! -f "$rpms_archive" ]]
then
echo "ERROR: No RPMs archive found in '$ARTIFACTS_DIR'." >&2
exit 1
fi
TEMP_DIR="$(mktemp -d)"
trap temp_dir_cleanup EXIT
tar -C "$TEMP_DIR" -xf "$rpms_archive"
rpm_extract_files -f -i "$TEMP_DIR" -p "*.ko" -o "$OUTPUT_DIR" -w "$TEMP_DIR"