Skip to content

Commit

Permalink
buildbox: Add new cross-compiling buildbox for Teleport (#44130)
Browse files Browse the repository at this point in the history
* buildbox: Add new cross-compiling buildbox for Teleport

Add Dockerfiles and make targets to build a new buildbox that has a set
of cross compilers for the four architectures we target for Linux -
amd64, arm64, 386, and arm (32-bit). The buildbox also contains the
third-party C libraries that Teleport needs to statically link against
for a full set of features, again compiled for each target architecture.

The cross compilers are built using crosstool-NG. The compilers must
have a "vendor" field in the "target triple" of "unknown" (default).
Setting it to anything else means rust does not find the cross compilers
properly. Using "unknown" causes the triple to match the rust target.
(This may ultimately be unnecessary to make these match, but is needed
if we have the boring crate build boringssl itself - see last
paragraph).

Two container images are produced:
* `buildbox-thirdparty` - a base image that contains the third-party
  tools (compilers, etc) and libraries that we build from source. Once
  build, this should never need to be rebuilt unless we change the
  version of one of the components it builds.
* `buildbox-ng` - the buildbox used to build Teleport. It copies out the
  third-party components from the previous image and installs whatever
  other tools are needed by the build, whether from the distro archive
  or directly from upstream (such as Go and Rust compilers).

Additionally, the three intermediate build stages of
`buildbox-thirdparty` can be built for working on the buildbox; `ctng`,
`compilers` and `tplibs`.

`buildbox-ng` will become just `buildbox` once it can replace the others
entirely, at which point those others will be removed.

Currently the build tools to build a FIPS version of teleport is not in
the buildbox. That has been troublesome as the rust boring crate wants
to build boringssl itself and it is not being done correctly with the
cross compilers. Further work is needed here, likely building boringssl
ourselves and pointing the rust boring crate at the pre-built libraries.

* buildbox: Add libelf.pc for pkg-config/pkconf

Add a libelf.pc file taken from elfutils and minorly adjusted for how we
install it separately. libelf was taken out of elfutils but did not take
the libelf.pc file, so we do that ourselves.

This will allow the build to use pkg-config/pkconf for selecting the
libraries to link teleport to as some later versions of libelf also need
`-lzstd`, such as this buildbox but also ubuntu-24.04.

* buildbox: Set prefix for zstd, add sh-cross-vars target

Set `PREFIX` when building `zstd` to ensure it's pkg-config file has the
correct prefix set in it, otherwise it has `-L/usr/local/lib` which we
do not want for cross-compiling.

Add a `sh-cross-vars` target to echo the cross-compiling vars for an
architecture, in a form that can be sourced by the shell:

    eval $(make -s -f cross-compile.mk ARCH=arm64 sh-cross-vars)

This will set up your shell for cross-compiling for the given
architecture, which is useful when working on the libraries in the
buildbox.

* Address Roman's review comments

* Rename ctng to crosstoolng throughout
* Rename tplibs.mk to thirdparty-libs.mk
* Fix spelling mistakes.

* Address Jakub's review comments

* Remove `--hostname $(HOSTNAME)` when running containers.
* Remove `--volume /tmp:/tmp` when running containers.
* Rename bbcommon.mk to buildbox-common.mk

* buildbox: Fix up broken renaming

Renaming things was done poorly in the last few commits. Fix all that
up.

* buildbox: Use gold linker for arm64 builds

Use the `gold` linker when building arm64 binaries, as Enterprise
Teleport will not build with the binutils (bfd) linker; it gives
numerous errors when linking of the form:

    something.rs: (.text.unlikely._XXX): relocation truncated to fit: R_AARCH64_CALL26 against symbol ...

These errors do not occur when using the `gold` linker, which is already
included and built by crosstool-NG.

* buildbox: Change crosstool arm tuple to match rust

Tweak the crosstool-NG configuration for the arm cross-compiler so that
the tuple for it matches the tuple currently used in the build by rust
for the same target. This is mostly to cause less confusion as there's
no real need for them to be different.

* buildbox: Add support for rust cross-compiling

Add environment variables so that cargo can find the appropriate
architecture-specific linker when cross-compiling. This is needed now as
we have a rust binary (fdpass-teleport) in the build, as opposed to just
rust compiled to a library linked to Go code. Rust does not have a cross
platform linker and relies an a linker in the toolchain for the target
architecture.

To make this work without needing per-architecture setup when building,
all of the toolchain binaries are symlinked into
/opt/thirdparty/host/bin so they are all in the path. Because each of
the binaries is prefixed with the target tuple, there are no name
clashes.

* buildbox: Add stages to buildbox Dockerfile for better caching

Download Go and Rust in separate stages and copy their installation into
the final container image. This helps as these stages no longer have
unrelated layers before them so they cache better. The Go and Rust
stages should only need to be rebuilt if the versions of the compilers
change, or some other aspect of the installation of the compilers
change. Previously adding a new package to be installed would cause Go
and Rust to be re-installed. This no longer happens.

* buildbox: Remove include of buildbox-common.mk in thirdparty-libs.mk

Remove the include of `buildbox-common.mk` from `thirdparty-libs.mk` as
it is included by `cross-compile.mk`. This caused some duplicate target
warnings when make ran.

* build: Add support for buildbox-ng to Makefile

Add supoprt for building in buildbox-ng to the build by including the
cross-compiling definitions if we are running in that buildbox and
gating some older cross-compiling definitions from being used.

* build: Add release targets using buildbox-ng

Add a set of targets to build releases using buildbox-ng instead of the
standard buildboxes. Ultimately these targets will be removed when the
old buildboxes are removed. These new targets are temporary until that
happens supporting testing of the new buildbox.

* buildbox: Add teleport user, expand comments

Add the teleport user (1000:1000) to the buildbox for running in CI
where a repository is checked-out inside the container instead of
mounting a volume on to /home/teleport. Make that home directory
world-writable so that it can still be used if 1000:1000 cannot be used
for some reason.

Expand and clean up comments.

Re-order ENV vars a little for better grouping.

Put Rust and Go temp directories under /tmp/build so a single volume can
be mounted there for persistent caches across runs.

* build: Fix invocation of pkg-config in $(shell ...)

Explicitly set `PKG_CONFIG_PATH` when running `$(PKGCONF)` as when
running that in a `$(shell ...)` expression, variables exported in the
Makefile are not exported for that shell, so `$(PKGCONF)` does not see
`PKG_CONFIG_PATH`. GNU make 4.4 changes this so that exported variables
are exported to `$(shell ...)` expressions, but the buildbox has GNU
make 4.3.

This was causing libbpf to not be detected properly so was building
teleport without bpf.
  • Loading branch information
camscale authored Jul 23, 2024
1 parent 95291b1 commit 5f7e1f6
Show file tree
Hide file tree
Showing 17 changed files with 1,031 additions and 8 deletions.
18 changes: 14 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ ARCH ?= $(GO_ENV_ARCH)
FIPS ?=
RELEASE = teleport-$(GITTAG)-$(OS)-$(ARCH)-bin

# If we're building inside the cross-compiling buildbox, include the
# cross compilation definitions so we select the correct compilers and
# libraries.
ifeq ($(BUILDBOX_MODE),cross)
include build.assets/buildbox/cross-compile.mk
endif

# Include common makefile shared between OSS and Ent.
include common.mk

Expand Down Expand Up @@ -254,18 +261,21 @@ TEST_LOG_DIR = ${abspath ./test-logs}

# Set CGOFLAG and BUILDFLAGS as needed for the OS/ARCH.
ifeq ("$(OS)","linux")
# True if $ARCH == amd64 || $ARCH == arm64
ifeq ("$(ARCH)","arm64")
ifeq (,$(IS_NATIVE_BUILD))
CGOFLAG += CC=aarch64-linux-gnu-gcc
endif
ifneq ($(BUILDBOX_MODE),cross)
ifeq (,$(IS_NATIVE_BUILD))
CGOFLAG += CC=aarch64-linux-gnu-gcc
endif
endif
else ifeq ("$(ARCH)","arm")
CGOFLAG = CGO_ENABLED=1

# ARM builds need to specify the correct C compiler
ifneq ($(BUILDBOX_MODE),cross)
ifeq (,$(IS_NATIVE_BUILD))
CC=arm-linux-gnueabihf-gcc
endif
endif

# Add -debugtramp=2 to work around 24 bit CALL/JMP instruction offset.
# Add "-extldflags -Wl,--long-plt" to avoid ld assertion failure on large binaries
Expand Down
131 changes: 131 additions & 0 deletions build.assets/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,64 @@ build-binaries-fips: buildbox-centos7-fips webassets
make -C $(SRCDIR)/e ADDFLAGS='$(ADDFLAGS)' VERSION=$(VERSION) GITTAG=v$(VERSION) PIV=$(PIV) FIPS=yes clean full

#
# Build the buildbox thirdparty components. This rarely needs to be rebuilt and is
# slow to build, so it is done separately from the main buildbox
#
.PHONY: buildbox-thirdparty
buildbox-thirdparty:
docker buildx build \
--cache-from $(BUILDBOX_THIRDPARTY) \
--cache-to type=inline \
$(if $(PUSH),--push,--load) \
--tag $(BUILDBOX_THIRDPARTY) \
-f buildbox/Dockerfile-thirdparty \
buildbox

#
# A generic build rule to build a stage of Dockerfile-thirdparty based
# on the $(STAGE) variable. These stage builds are used for development
# of the thirdparty buildbox, whether to configure crosstool-NG
# (see config/buildbox-ng), or when adding additional third party
# libraries using either the compilers stage or libs stage.
#
.PHONY: buildbox-thirdparty-stage
buildbox-thirdparty-stage:
docker buildx build \
--load \
--tag buildbox-thirdparty-$(STAGE):$(BUILDBOX_VERSION) \
-f buildbox/Dockerfile-thirdparty \
--target $(STAGE) \
buildbox

.PHONY: buildbox-thirdparty-crosstoolng
buildbox-thirdparty-crosstoolng: STAGE=crosstoolng
buildbox-thirdparty-crosstoolng: buildbox-thirdparty-stage

.PHONY: buildbox-thirdparty-compilers
buildbox-thirdparty-compilers: STAGE=compilers
buildbox-thirdparty-compilers: buildbox-thirdparty-stage

.PHONY: buildbox-thirdparty-libs
buildbox-thirdparty-libs: STAGE=libs
buildbox-thirdparty-libs: buildbox-thirdparty-stage

#
# Build the buildbox-ng using the pre-built third party components from the
# buildbox-thirdparty image
#
.PHONY: buildbox-ng
buildbox-ng:
docker buildx build \
--build-arg THIRDPARTY_IMAGE=$(BUILDBOX_THIRDPARTY) \
--build-arg GOLANG_VERSION=$(GOLANG_VERSION) \
--build-arg RUST_VERSION=$(RUST_VERSION) \
--cache-from $(BUILDBOX_NG) \
--cache-to type=inline \
$(if $(PUSH),--push,--load) \
--tag $(BUILDBOX_NG) \
-f buildbox/Dockerfile \
buildbox

# Builds a Docker container which is used for building official Teleport binaries
# If running in CI and there is no image with the buildbox name:tag combination present locally,
# the image is pulled from the Docker repository. If this pull fails (i.e. when a new Go runtime is
Expand Down Expand Up @@ -381,6 +439,52 @@ enter-root: buildbox
-e HOME=$(SRCDIR)/build.assets -w $(SRCDIR) $(BUILDBOX) /bin/bash

#
# Reconfigure crosstool-NG for the given ARCH via its menuconfig system.
# e.g.
# $ make config/buildbox-ng CROSSTOOLNG_ARCH=arm64
#
# After saving and exiting in the menuconfig, you should have an updated
# defconfig file in build.assets/buildbox/crosstoolng-configs
#
# You need to have a local copy of the configuration container image
# created with: make buildbox-thirdparty-crosstoolng
#
.PHONY: config/buildbox-ng
config/buildbox-ng:
@: $(or $(CROSSTOOLNG_ARCH),$(error CROSSTOOLNG_ARCH variable must be set))
@: $(or $(wildcard buildbox/crosstoolng-configs/$(CROSSTOOLNG_ARCH).defconfig),\
$(error No config for arch $(CROSSTOOLNG_ARCH)))
docker run -it --rm \
--volume $(shell pwd)/..:/home/teleport \
--workdir /home/teleport \
buildbox-thirdparty-crosstoolng:$(BUILDBOX_VERSION) \
make -C build.assets/buildbox -f crosstoolng.mk crosstoolng-menuconfig \
ARCH=$(CROSSTOOLNG_ARCH)


#
# Starts a shell in the buildbox-ng container
# We don't use $(DOCKERFLAGS) as it contains stuff for the old buildbox which is
# not relevant or incorrect for this one.
.PHONY: enter/buildbox-ng
enter/buildbox-ng:
docker run -it --rm \
--volume $(shell pwd)/..:/home/teleport \
--workdir /home/teleport \
--user $(shell id -u):$(shell id -g) \
$(BUILDBOX_NG)

#
# Starts a root shell in the buildbox-ng container
#
.PHONY: enter-root/buildbox-ng
enter-root/buildbox-ng:
docker run -it --rm \
--volume $(shell pwd)/..:/home/teleport \
--workdir /home/teleport \
--user 0:0 \
$(BUILDBOX_NG)
#
# Starts shell inside the centos7 container
#
.PHONY:enter/centos7
Expand Down Expand Up @@ -487,6 +591,33 @@ release: $(BUILDBOX_TARGET)
docker run $(DOCKERFLAGS) $(NOROOT) $(BUILDBOX) \
/usr/bin/make $(RELEASE_TARGET) -e ADDFLAGS="$(ADDFLAGS)" OS=$(OS) ARCH=$(ARCH) RUNTIME=$(GOLANG_VERSION) FIDO2=$(FIDO2) PIV=$(PIV) REPRODUCIBLE=yes


.PHONY: release-ng-amd64 release-ng-arm64 release-ng-386 release-ng-arm
release-ng-amd64:
$(MAKE) release-ng ARCH=amd64 FIDO2=yes PIV=yes
release-ng-arm64:
$(MAKE) release-ng ARCH=arm64 FIDO2=yes PIV=yes
release-ng-386:
$(MAKE) release-ng ARCH=386
release-ng-arm:
$(MAKE) release-ng ARCH=arm

.PHONY: release-ng
release-ng: webassets buildbox-ng
docker run --rm --interactive $(shell test -t 0 && echo --tty) \
--volume $(shell pwd)/..:/home/teleport \
--workdir /home/teleport \
--user $(shell id -u):$(shell id -g) \
$(BUILDBOX_NG) \
make -e release-unix-preserving-webassets \
ADDFLAGS="$(ADDFLAGS)" \
OS="$(OS)" \
ARCH="$(ARCH)" \
RUNTIME="$(GOLANG_VERSION)" \
FIDO2="$(FIDO2)" \
PIV="$(PIV)" \
REPRODUCIBLE=yes

#
# Create a Teleport FIPS package using the build container.
# This is a special case because it only builds and packages the Enterprise FIPS binaries, no OSS.
Expand Down
178 changes: 178 additions & 0 deletions build.assets/buildbox/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# syntax=docker/dockerfile:1
#
# Build the Teleport buildbox with the pre-built crosstool-NG cross compilers,
# pre-built third-party C libraries for the four supported architectures, the
# Go toolchain and the Rust toolchain.
#
# The pre-built crosstool-NG cross compilers and third-party C libraries come
# from a image specified by the THIRDPARTY_IMAGE arg, which is built by the
# Dockerfile-thirdparty dockerfile.
#
# All the compilers/toolchains are owned by the buildbox:buildbox (99:99) user.
# They are not intended to be modifiable when the buildbox runs. By default the
# image runs as user teleport:teleport (1000:1000) and can write to
# /home/teleport. /home/teleport is also world-writable so if the UID/GID
# 1000/1000 cannot be used for some reason, the user can still check out
# repositories into that directory.
#
# Alternatively, map an external repository directory as a volume on to
# /home/teleport and set the uid/gid to the owner of that directory.
#
# The default GOPATH is /tmp/go, with the build cache (GOCACHE) in
# /tmp/go/build. CARGO_HOME is /tmp/rust. This allows Go and Rust to run and
# have a place to cache builds and install binaries.

ARG BASE_IMAGE=ubuntu:22.04
ARG THIRDPARTY_IMAGE

# ----------------------------------------------------------------------------
# Define a simple base image for installing various compilers which are then
# copied into the final image. This helps with caching as the download+install
# does not depend on previous layers.

FROM ${BASE_IMAGE} AS base

# Install curl as it is needed by the Go and Rust stages to download the compilers.
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*

ARG BUILDBOX_UID=99
ARG BUILDBOX_GID=99
RUN groupadd -g $BUILDBOX_GID buildbox
RUN useradd -d /home/buildbox -m -g $BUILDBOX_GID -u $BUILDBOX_UID -s /bin/bash buildbox

ARG TELEPORT_UID=1000
ARG TELEPORT_GID=1000
RUN groupadd -g $TELEPORT_GID teleport
RUN useradd -d /home/teleport -m -g $TELEPORT_GID -u $TELEPORT_UID -s /bin/bash teleport
RUN chmod 777 /home/teleport

# ----------------------------------------------------------------------------
# Reference the thirdparty image for copying from later.

FROM ${THIRDPARTY_IMAGE} AS thirdparty

# ----------------------------------------------------------------------------
# Install Go
#
# Go is installed into the base image and copied across to the final image in
# the last stage. This make the downloading and installation of the Go
# toolchain dependent on nothing but the base.

FROM base AS go

RUN install -d -m 0775 -o buildbox -g buildbox /opt/go
USER buildbox

ARG BUILDARCH
ARG GOLANG_VERSION
# Set BUILDARCH if not set when not using buildkit. Only works for arm64 and amd64.
RUN BUILDARCH=${BUILDARCH:-$(uname -m | sed 's/aarch64/arm64/g; s/x86_64/amd64/g')}; \
curl -fsSL https://storage.googleapis.com/golang/${GOLANG_VERSION}.linux-${BUILDARCH}.tar.gz | \
tar -C /opt -xz && \
/opt/go/bin/go version

# ----------------------------------------------------------------------------
# Install Rust
#
# Rust is installed into the base image and copied across to the final image in
# the last stage. This make the downloading and installation of the Rust
# toolchain dependent on nothing but the base.

FROM base AS rust

RUN install -d -m 0775 -o buildbox -g buildbox /opt/rust
USER buildbox

ARG RUST_VERSION
ENV RUSTUP_HOME=/opt/rust
ENV CARGO_HOME=/opt/rust
RUN curl --proto =https --tlsv1.2 -fsSL https://sh.rustup.rs | \
sh -s -- -y --profile minimal --default-toolchain ${RUST_VERSION} && \
${CARGO_HOME}/bin/rustup --version && \
${CARGO_HOME}/bin/cargo --version && \
${CARGO_HOME}/bin/rustc --version && \
${CARGO_HOME}/bin/rustup target add \
x86_64-unknown-linux-gnu \
aarch64-unknown-linux-gnu \
i686-unknown-linux-gnu \
arm-unknown-linux-gnueabihf \
wasm32-unknown-unknown

# ----------------------------------------------------------------------------
# buildbox image
#
# Build the final buildbox image by installing required packages and copying
# the toolchains from the previous stages/images.

FROM base AS buildbox

RUN apt-get update && apt-get install -y \
autoconf \
automake \
autopoint \
bison \
clang-12 \
cmake \
flex \
gettext \
git \
libtool \
make \
ninja-build \
pkg-config \
sed \
w3m \
wget \
xsltproc \
xz-utils \
&& rm -rf /var/lib/apt/lists/*

RUN install -d -m 1777 -o teleport -g teleport /tmp/build

USER buildbox

# Copy compilers from other images
ARG THIRDPARTY_DIR=/opt/thirdparty
COPY --from=thirdparty ${THIRDPARTY_DIR} ${THIRDPARTY_DIR}
COPY --from=rust /opt/rust /opt/rust
COPY --from=go /opt/go /opt/go

# Set RUSTUP_HOME so cargo does not warn/error about not finding it at ~/.cargo
ENV RUSTUP_HOME=/opt/rust

ENV PATH=/opt/go/bin:/opt/rust/bin:${THIRDPARTY_DIR}/host/bin:${PATH}

# Ensure THIRDPARTY_DIR gets propagated to the makefiles if the provided arg
# is not the default value.
ENV THIRDPARTY_DIR=${THIRDPARTY_DIR}

# Set up env vars for rust to cross-compile binaries. I needs a linker for the
# appropriate architecture, which is invoked via `cc`. These compilers are all
# on the PATH.
ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-unknown-linux-gnu-gcc
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-unknown-linux-gnu-gcc
ENV CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER=i686-unknown-linux-gnu-gcc
ENV CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-unknown-linux-gnueabihf-gcc

# Set CARGO_HOME, GOPATH and GOCACHE to somewhere writable as the user of the
# buildbox will have a UID/GID different to the buildbox user. Also create
# /home/teleport as a world-writable directory so that can be used as a
# workspace when cloning a git repo directly in the buildbox as opposed to
# mapping a volume from outside.
# The /tmp/build directory can be a volume so the build/package cache can be
# carried across builds.
ENV CARGO_HOME=/tmp/build/rust
ENV GOPATH=/tmp/build/go
ENV GOCACHE=/tmp/build/go/build

# Add the writable cargo and go bin directories to the path so we will find
# binaries build with `cargo install` and `go install` during a build.
ENV PATH=${CARGO_HOME}/bin:${GOPATH}/bin:${PATH}

# Set a var so the build system can know it's running in this buildbox.
ENV BUILDBOX_MODE=cross

USER teleport:teleport
WORKDIR /home/teleport
Loading

0 comments on commit 5f7e1f6

Please sign in to comment.