diff --git a/.hadolint.yaml b/.hadolint.yaml index d700141..4cc136b 100644 --- a/.hadolint.yaml +++ b/.hadolint.yaml @@ -1,3 +1,4 @@ ignored: + - DL3003 - DL3006 - - DL3008 + - DL3018 diff --git a/Dockerfile b/Dockerfile index 7871a6b..1ec0790 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,40 +1,50 @@ ARG BUILD_FROM FROM $BUILD_FROM -# Set shell -SHELL ["/bin/bash", "-o", "pipefail", "-c"] # Setup locals -RUN apt-get update && apt-get install -y --no-install-recommends \ - jq \ - git \ - python3-setuptools \ - && rm -rf /var/lib/apt/lists/* \ -ENV LANG C.UTF-8 - -# Install docker -# https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/ -RUN apt-get update && apt-get install -y --no-install-recommends \ - apt-transport-https \ - ca-certificates \ - curl \ - software-properties-common \ - gpg-agent \ - && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - \ - && add-apt-repository "deb https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \ - && apt-get update && apt-get install -y --no-install-recommends \ - docker-ce \ - docker-ce-cli \ - containerd.io \ - && rm -rf /var/lib/apt/lists/* +ENV \ + VCN_OTP_EMPTY=true \ + LANG=C.UTF-8 -# Setup arm binary support ARG BUILD_ARCH -RUN if [ "$BUILD_ARCH" != "amd64" ]; then exit 0; else \ - apt-get update && apt-get install -y --no-install-recommends \ - qemu-user-static \ - binfmt-support \ - && rm -rf /var/lib/apt/lists/*; fi +ARG VCN_VERSION + +RUN \ + set -x \ + && apk add --no-cache \ + git \ + docker \ + && apk add --no-cache --virtual .build-dependencies \ + build-base \ + go \ + \ + && git clone -b v${VCN_VERSION} --depth 1 \ + https://github.com/codenotary/vcn \ + && cd vcn \ + \ + # Fix: https://github.com/codenotary/vcn/issues/131 + && go get github.com/codenotary/immudb@4cf9e2ae06ac2e6ec98a60364c3de3eab5524757 \ + \ + && if [ "${BUILD_ARCH}" = "armhf" ]; then \ + GOARM=6 GOARCH=arm go build -o vcn -ldflags="-s -w" ./cmd/vcn; \ + elif [ "${BUILD_ARCH}" = "armv7" ]; then \ + GOARM=7 GOARCH=arm go build -o vcn -ldflags="-s -w" ./cmd/vcn; \ + elif [ "${BUILD_ARCH}" = "aarch64" ]; then \ + GOARCH=arm64 go build -o vcn -ldflags="-s -w" ./cmd/vcn; \ + elif [ "${BUILD_ARCH}" = "i386" ]; then \ + GOARCH=386 go build -o vcn -ldflags="-s -w" ./cmd/vcn; \ + elif [ "${BUILD_ARCH}" = "amd64" ]; then \ + GOARCH=amd64 go build -o vcn -ldflags="-s -w" ./cmd/vcn; \ + else \ + exit 1; \ + fi \ + \ + && rm -rf /root/go /root/.cache \ + && mv vcn /usr/bin/vcn \ + \ + && apk del .build-dependencies \ + && rm -rf /usr/src/vcn COPY builder.sh /usr/bin/ diff --git a/build.json b/build.json index d2398a4..1a81912 100644 --- a/build.json +++ b/build.json @@ -1,9 +1,14 @@ { "image": "homeassistant/{arch}-builder", "build_from": { - "aarch64": "homeassistant/aarch64-base-ubuntu:18.04", - "armv7": "homeassistant/armv7-base-ubuntu:18.04", - "amd64": "homeassistant/amd64-base-ubuntu:18.04" + "aarch64": "homeassistant/aarch64-base:3.13", + "armv7": "homeassistant/armv7-base:3.13", + "armhf": "homeassistant/armhf-base:3.13", + "amd64": "homeassistant/amd64-base:3.13", + "i386": "homeassistant/i386-base:3.13" + }, + "args": { + "VCN_VERSION": "0.9.4" }, "labels": { "io.hass.type": "builder" diff --git a/builder.sh b/builder.sh index 0bb4ded..04622f1 100755 --- a/builder.sh +++ b/builder.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bashio ###################### -# Hass.io Build-env +# Home Assistant Build-env ###################### set -e set +u @@ -17,6 +17,8 @@ DOCKER_PUSH=true DOCKER_USER= DOCKER_PASSWORD= DOCKER_LOCAL=false +VCN_NOTARY=false +VCN_FROM= SELF_CACHE=false CUSTOM_CACHE_TAG= RELEASE_TAG=false @@ -24,10 +26,10 @@ GIT_REPOSITORY= GIT_BRANCH="master" TARGET= VERSION= +VERSION_BASE= +VERSION_FROM= IMAGE= RELEASE= -PYTHON= -ALPINE= BUILD_LIST=() BUILD_TYPE="addon" BUILD_TASKS=() @@ -80,6 +82,8 @@ Options: Additional version information like for base images. --release-tag Use this as main tag. + --version-from + Use this to set build_from tag if not specified. Architecture --armhf @@ -123,22 +127,21 @@ Options: Default on. Run all things for an addon build. --generic Build based on the build.json - --builder-wheels - Build the wheels builder for Home Assistant. --base Build our base images. - --base-python - Build our base python images. - --base-raspbian - Build our base raspbian images. - --base-ubuntu - Build our base ubuntu images. - --base-debian - Build our base debian images. --homeassisant-landingpage Build the landingpage for machines. --homeassistant-machine Build the machine based image for a release. + + Security: + --with-codenotary + Enable signing images with CodeNotary. Need set follow env: + VCN_USER + VCN_PASSWORD + VCN_NOTARIZATION_PASSWORD + --validate-from + Validate the FROM image which is used to build the image. EOF bashio::exit.nok @@ -263,6 +266,9 @@ function run_build() { docker_cli+=("--build-arg" "BUILD_ARCH=$build_arch") fi + # Validate the base image + codenotary_validate "$build_from" + # Build image bashio::log.info "Run build for $repository/$image:$version" docker build --pull -t "$repository/$image:$version" \ @@ -312,125 +318,87 @@ function run_build() { done done fi + + # Singing image + codenotary_sign "${repository}/${image}:${version}" } -#### HassIO functions #### +#### Build functions #### function build_base_image() { - local build_arch=$1 + local build_arch=${1} local build_from="" - local image="{arch}-base" - local docker_cli=() - local docker_tags=() - - # Set type - docker_cli+=("--label" "io.hass.type=base") - docker_cli+=("--label" "io.hass.base.version=$RELEASE") - docker_cli+=("--label" "io.hass.base.name=alpine") - docker_cli+=("--label" "io.hass.base.image=$DOCKER_HUB/$image") - - # Start build - run_build "$TARGET/$build_arch" "$DOCKER_HUB" "$image" "$VERSION" \ - "$build_from" "$build_arch" docker_cli[@] docker_tags[@] -} - -function build_base_python_image() { - local build_arch=$1 - - local image="{arch}-base-python" - local build_from="homeassistant/${build_arch}-base:${ALPINE}" - local version="${VERSION}-alpine${ALPINE}" + local image="" + local repository="" + local raw_image="" + local version_tag=false + local args="" local docker_cli=() local docker_tags=() - # If latest python version/build - if [ "$RELEASE_TAG" == "true" ]; then - docker_tags=("$VERSION") + # Read build.json + if bashio::fs.file_exists "${TARGET}/build.json"; then + build_from="$(jq --raw-output ".build_from.${build_arch} // empty" "${TARGET}/build.json")" + args="$(jq --raw-output '.args // empty | keys[]' "${TARGET}/build.json")" + labels="$(jq --raw-output '.labels // empty | keys[]' "${TARGET}/build.json")" + raw_image="$(jq --raw-output '.image // empty' "${TARGET}/build.json")" + version_tag="$(jq --raw-output '.version_tag // false' "${TARGET}/build.json")" fi - # Set type - docker_cli+=("--label" "io.hass.type=base") - docker_cli+=("--label" "io.hass.base.version=$RELEASE") - docker_cli+=("--label" "io.hass.base.name=python") - docker_cli+=("--label" "io.hass.base.image=$DOCKER_HUB/$image") - - # Start build - run_build "$TARGET/$VERSION" "$DOCKER_HUB" "$image" "$version" \ - "$build_from" "$build_arch" docker_cli[@] docker_tags[@] -} - - -function build_base_ubuntu_image() { - local build_arch=$1 - - local build_from="" - local image="{arch}-base-ubuntu" - local docker_cli=() - local docker_tags=() - - # Select builder image - if [ "$build_arch" == "armhf" ]; then - bashio::log.error "$build_arch not supported for ubuntu" + # Set defaults build things + if ! bashio::var.has_value "${build_from}"; then + bashio::log.error "${build_arch} not supported for this build" return 1 fi - # Set type - docker_cli+=("--label" "io.hass.type=base") - docker_cli+=("--label" "io.hass.base.version=$RELEASE") - docker_cli+=("--label" "io.hass.base.name=ubuntu") - docker_cli+=("--label" "io.hass.base.image=$DOCKER_HUB/$image") - - # Start build - run_build "$TARGET/$build_arch" "$DOCKER_HUB" "$image" "$VERSION" \ - "$build_from" "$build_arch" docker_cli[@] docker_tags[@] -} - - -function build_base_debian_image() { - local build_arch=$1 - - local build_from="" - local image="{arch}-base-debian" - local docker_cli=() - local docker_tags=() - - # Set type - docker_cli+=("--label" "io.hass.type=base") - docker_cli+=("--label" "io.hass.base.version=$RELEASE") - docker_cli+=("--label" "io.hass.base.name=debian") - docker_cli+=("--label" "io.hass.base.image=$DOCKER_HUB/$image") - - # Start build - run_build "$TARGET/$build_arch" "$DOCKER_HUB" "$image" "$VERSION" \ - "$build_from" "$build_arch" docker_cli[@] docker_tags[@] -} + # Modify build_from + if [[ "${build_from}" =~ :$ ]]; then + if bashio::var.has_value "${VERSION_FROM}"; then + build_from="${build_from}:${VERSION_FROM}" + else + build_from="${build_from}:${VERSION_BASE}" + fi + fi + # Read data from image + if ! bashio::var.has_value "${raw_image}"; then + bashio::log.error "Can't find the image tag on build.json" + return 1 + fi + repository="$(echo "${raw_image}" | cut -f 1 -d '/')" + image="$(echo "${raw_image}" | cut -f 2 -d '/')" -function build_base_raspbian_image() { - local build_arch=$1 + # Additional build args + if bashio::var.has_value "${args}"; then + for arg in ${args}; do + value="$(jq --raw-output ".args.${arg}" "${TARGET}/build.json")" + docker_cli+=("--build-arg" "${arg}=${value}") + done + fi - local build_from="$VERSION" - local image="{arch}-base-raspbian" - local docker_cli=() - local docker_tags=() + # Additional build labels + if bashio::var.has_value "${labels}"; then + for label in ${labels}; do + value="$(jq --raw-output ".labels.\"${label}\"" "${TARGET}/build.json")" + docker_cli+=("--label" "${label}=${value}") + done + fi - # Select builder image - if [ "$build_arch" != "armhf" ]; then - bashio::log.error "$build_arch not supported for raspbian" - return 1 + # Tag with version/build + if bashio::var.true "${RELEASE_TAG}"; then + docker_tags=("${VERSION}") fi # Set type docker_cli+=("--label" "io.hass.type=base") - docker_cli+=("--label" "io.hass.base.version=$RELEASE") - docker_cli+=("--label" "io.hass.base.name=raspbian") - docker_cli+=("--label" "io.hass.base.image=$DOCKER_HUB/$image") + docker_cli+=("--label" "io.hass.base.version=${RELEASE}") + docker_cli+=("--label" "io.hass.base.image=${build_from}") # Start build - run_build "$TARGET" "$DOCKER_HUB" "$image" "$VERSION" \ - "$build_from" "$build_arch" docker_cli[@] docker_tags[@] + run_build "${TARGET}" "${repository}" "${image}" "${VERSION_BASE}" \ + "${build_from}" "${build_arch}" docker_cli[@] docker_tags[@] } @@ -624,36 +592,6 @@ function build_homeassistant_landingpage() { } -function build_wheels() { - local build_arch=$1 - - local version="" - local image="{arch}-wheels" - local build_from="homeassistant/${build_arch}-base-python:${PYTHON}" - local docker_cli=() - local docker_tags=() - - # Read version - if [ "$VERSION" == "dev" ]; then - version="dev" - else - version="$(python3 "$TARGET/setup.py" -V)" - fi - - # If latest python version/build - if [ "$RELEASE_TAG" == "true" ]; then - docker_tags=("$version") - fi - - # Metadata - docker_cli+=("--label" "io.hass.type=wheels") - - # Start build - run_build "$TARGET" "$DOCKER_HUB" "$image" "$version-${PYTHON}" \ - "$build_from" "$build_arch" docker_cli[@] docker_tags[@] -} - - function extract_machine_build() { local list=$1 local array=() @@ -688,6 +626,59 @@ function init_crosscompile() { > /dev/null 2>&1 || bashio::log.warning "Can't enable crosscompiling feature" } +#### Security CodeNotary #### + +function codenotary_probe() { + if ! bashio::var.has_value "${VCN_USER}" || ! bashio::var.has_value "${VCN_PASSWORD}" || ! bashio::var.has_value "${VCN_NOTARIZATION_PASSWORD}"; then + bashio::exit.nok "Missing ENV values for CodeNotary" + fi +} + + +function codenotary_setup() { + if bashio::var.false "${DOCKER_PUSH}" || bashio::var.false "${VCN_NOTARY}"; then + return 0 + fi + + vcn login /dev/null 2>&1 || bashio::exit.nok "Login to CodeNotary fails!" +} + +function codenotary_sign() { + local image=$1 + + if bashio::var.false "${DOCKER_PUSH}" || bashio::var.false "${VCN_NOTARY}"; then + return 0 + fi + + vcn notarize --public "docker://${image}" +} + +function codenotary_validate() { + local image=$1 + local state= + local vcn_cli=() + + if ! bashio::var.has_value "${VCN_FROM}"; then + return 0 + fi + + bashio::log.info "Download base image ${image} for CodeNotary validation" + docker pull "${image}" > /dev/null 2>&1 || bashio::exit.nok "Can't pull image ${image}" + + if [[ "${VCN_FROM}" =~ 0x.* ]]; then + vcn_cli+=("--signerID" "${VCN_FROM}") + else + vcn_cli+=("--org" "${VCN_FROM}") + fi + + state="$(vcn authenticate "${vcn_cli[@]}" --output json "docker://{image}" | jq '.verification.status // 2')" + if [[ "${state}" != "0" ]]; then + bashio::exit.nok "Validation of base image fails!" + fi + bashio::log.info "Base imge ${image} is trusted" +} + + #### Error handling #### function error_handling() { @@ -753,6 +744,10 @@ while [[ $# -gt 0 ]]; do DOCKER_HUB=$2 shift ;; + --version-from) + VERSION_FROM=$2 + shift + ;; --docker-hub-check) DOCKER_HUB_CHECK=true ;; @@ -788,32 +783,7 @@ while [[ $# -gt 0 ]]; do --base) BUILD_TYPE="base" SELF_CACHE=true - VERSION=$2 - shift - ;; - --base-python) - BUILD_TYPE="base-python" - SELF_CACHE=true - VERSION="$(echo "$2" | cut -d '=' -f 1)" - ALPINE="$(echo "$2" | cut -d '=' -f 2)" - shift - ;; - --base-ubuntu) - BUILD_TYPE="base-ubuntu" - SELF_CACHE=true - VERSION=$2 - shift - ;; - --base-debian) - BUILD_TYPE="base-debian" - SELF_CACHE=true - VERSION=$2 - shift - ;; - --base-raspbian) - BUILD_TYPE="base-raspbian" - SELF_CACHE=true - VERSION=$2 + VERSION_BASE=$2 shift ;; --generic) @@ -836,13 +806,14 @@ while [[ $# -gt 0 ]]; do extract_machine_build "$(echo "$2" | cut -d '=' -f 2)" shift ;; - --builder-wheels) - BUILD_TYPE="builder-wheels" - PYTHON=$2 - SELF_CACHE=true + --with-codenotary) + VCN_NOTARY=true + ;; + --validate-from) + codenotary_probe + VCN_FROM=$2 shift ;; - *) bashio::exit.nok "$0 : Argument '$1' unknown" ;; @@ -856,7 +827,7 @@ if [ "${#BUILD_LIST[@]}" -eq 0 ] && ! [[ "$BUILD_TYPE" =~ ^homeassistant-(machin fi # Check other args -if [ "$BUILD_TYPE" != "addon" ] && [ "$BUILD_TYPE" != "generic" ] && [ -z "$DOCKER_HUB" ]; then +if [[ "$BUILD_TYPE" =~ (addon|generic|base) ]] && ! bashio::var.has_value "$DOCKER_HUB"; then bashio::exit.nok "Please set a docker hub!" fi @@ -869,10 +840,11 @@ mkdir -p /data init_crosscompile start_docker -# Login into dockerhub +# Login into dockerhub & setup CodeNotary if [ -n "$DOCKER_USER" ] && [ -n "$DOCKER_PASSWORD" ]; then docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD" fi +codenotary_setup # Load external repository if [ -n "$GIT_REPOSITORY" ]; then @@ -891,16 +863,6 @@ if [ "${#BUILD_LIST[@]}" -ne 0 ]; then (build_generic "$arch") & elif [ "$BUILD_TYPE" == "base" ]; then (build_base_image "$arch") & - elif [ "$BUILD_TYPE" == "base-python" ]; then - (build_base_python_image "$arch") & - elif [ "$BUILD_TYPE" == "base-ubuntu" ]; then - (build_base_ubuntu_image "$arch") & - elif [ "$BUILD_TYPE" == "base-debian" ]; then - (build_base_debian_image "$arch") & - elif [ "$BUILD_TYPE" == "base-raspbian" ]; then - (build_base_raspbian_image "$arch") & - elif [ "$BUILD_TYPE" == "builder-wheels" ]; then - (build_wheels "$arch") & elif [[ "$BUILD_TYPE" =~ ^homeassistant-(machine|landingpage)$ ]]; then continue # Handled in the loop below else