Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: secure hermetic-build docker image #3196

Draft
wants to merge 40 commits into
base: main
Choose a base branch
from

Conversation

diegomarquezp
Copy link
Contributor

@diegomarquezp diegomarquezp commented Sep 12, 2024

This PR improves the Hermetic Build Docker image in a few different ways:

  • Use secure base images
  • Use a secure repository for Python dependencies. This was done by pointing to the Airlock Python registries, which requires a couple of special configuration files and a build-time secret.
    • The build-time secret must be configured before building the image locally (instructions added in the development guide). Cloud Build automatically injects the secrets and no special setup is needed.
    • A few dependencies were submitted for approval and they were automatically added to the staging repository. Once they become part of the "trusted" repository, we will use the "safest" trusted repository.
  • Use multiple stages where only the relevant binaries are copied into the final image
    • The library_generation python package and its dependencies are built in a separate stage
  • Use Alpine-based images
    • Since this kind of distro is not compatible with glibc-based binaries, a special compatibility enablement step was added
    • Since the UNIX tools are from FreeBSD, the -d flag in the rm command is not supported. This is why we removed its usage in the scripts
  • Reduction of image size to ~1 GB. Most space is used by:
    • OpenJDK: ~300MB
    • GLIBC compatibility: ~250MB
  • Reduction of build time to 150 seconds (parallel stage builds via buildkit).

Pending items

  • Discuss a way to pass the Application Default Credentials to the now (now failing - see job output) hermetic_library_generation workflow in sdk-platform-java (other repos don't need to build the image). We can add a secret in the repo or migrate this action to Cloud Build.
  • Discuss a strategy for the integration tests now that we need a secret to passed it to the image building workflow. The current approach is to use Cloud Build instead of GitHub Actions since it has out-of-the box compatibility with APPLICATION_DEFAULT_CREDENTIAL secrets such as the one declared in the Dockerfile. In the meantime, I left a build workflow in .cloudbuild to replace the integration tests and removed the integration tests portion of the existing GitHub Action. Another possible approach is to have a repository secret and keep using the GitHub Action.
    • If approach is validated, create the Cloud Build trigger

@diegomarquezp diegomarquezp added the do not merge Indicates a pull request not ready for merge, due to either quality or timing. label Sep 12, 2024
@product-auto-label product-auto-label bot added the size: l Pull request size is large. label Sep 12, 2024
@diegomarquezp diegomarquezp removed the do not merge Indicates a pull request not ready for merge, due to either quality or timing. label Sep 17, 2024
@diegomarquezp diegomarquezp changed the title chore: [wip] secure hermetic-build docker image chore: secure hermetic-build docker image Sep 17, 2024
@diegomarquezp diegomarquezp marked this pull request as ready for review September 17, 2024 03:37
# We use the esbuild tool. See https://esbuild.github.io/
COPY ./.cloudbuild/library_generation/image-configuration/owlbot-cli-build-config.js .
RUN npm i esbuild
RUN node owlbot-cli-build-config.js
Copy link
Collaborator

@JoeWang1127 JoeWang1127 Sep 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to create a SEA, do we have to bundle source code into a single js file?

Can we combine the two steps into one?

Copy link
Contributor Author

@diegomarquezp diegomarquezp Sep 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, bundling is necessary:

# This is because SEA (see below) cannot
# resolve external modules in a multi-file project.

From the docs: The single executable application feature currently only supports running a single embedded script using the CommonJS module system.

Can be combine the two steps?

Do you mean using a single RUN + &&?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was referring whether we can build a SEA from multiple js source code, looks like it has to be bundled together first.

# from the previous stages. We use the node base image for it to be compatible
# with the standalone binary owl-bot compiled in the previous stage
# node:22.1-alpine
FROM us-docker.pkg.dev/artifact-foundry-prod/docker-3p-trusted/node@sha256:487dc5d5122d578e13f2231aa4ac0f63068becd921099c4c677c850df93bede8 as final
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we use a python base image?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The standalone owlbot seems to need a few runtime libraries (.so) in the linux environment that are available in the node image by default (besides the node runtime). The python image doesn't have them.
If we want to have a python image as base, I can try to use a python base and install node or whichever libraries the executable needs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From discussion with @blakeli0 and @JoeWang1127, let's use a vanilla Alpine based image.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change the image name in this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this about the _IMAGE_ID of the image? We can change it, but how can we improve its name?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this about the _IMAGE_ID of the image?

Yes.

Can we create a image repo in AR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we leave the changes related to the AR migration as a follow up? Should be a small PR.

Copy link

sonarcloud bot commented Sep 17, 2024

Copy link

sonarcloud bot commented Sep 17, 2024

Quality Gate Passed Quality Gate passed for 'java_showcase_integration_tests'

Issues
0 New issues
0 Accepted issues

Measures
0 Security Hotspots
0.0% Coverage on New Code
0.0% Duplication on New Code

See analysis details on SonarCloud

Copy link
Collaborator

@blakeli0 blakeli0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add a secret in the repo or migrate this action to Cloud Build.

Agree that we should migrate them to Cloud Build, same as the integration tests. This gets us closer to not dependent on Github specific features.

# with the transferred source code and jars
FROM gcr.io/cloud-devrel-public-resources/java21 AS ggj-build
# node:22.1-alpine
FROM us-docker.pkg.dev/artifact-foundry-prod/docker-3p-trusted/node@sha256:487dc5d5122d578e13f2231aa4ac0f63068becd921099c4c677c850df93bede8 as owlbot-cli-build
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we plan to update these base images? It might be fine to not update this one, but for the Java and Python one, we may want to update the Maven/JDK/Python version regularly.

RUN mvn install -B -ntp -DskipTests -Dclirr.skip -Dcheckstyle.skip
RUN cp "/root/.m2/repository/com/google/api/gapic-generator-java/${DOCKER_GAPIC_GENERATOR_VERSION}/gapic-generator-java-${DOCKER_GAPIC_GENERATOR_VERSION}.jar" \
"./gapic-generator-java.jar"
# use Docker Buildkit caching for faster local builds
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does Docker Buildkit caching mean? Since the build does not take too much time(~1.5 minute), I think it's OK to download the maven dependencies every time. What I'm trying to avoid is that we build an image with stale dependencies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Buildkit caching allows to reuse the specified folders in the specific step. This would not impact any CI pipeline as they don't preserve any kind of cache. The main purpose is to speed up local builds for development, and can be disabled via docker build --no-cache.

My perspective is that we probably won't work on modifying the java source code when working on the Docker image, so several image builds may benefit from caching the mvn install output.

@diegomarquezp diegomarquezp marked this pull request as draft September 18, 2024 19:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size: l Pull request size is large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants