Awesome
Aqua policy + Bazel example
This repo serves as a demonstration of an interaction between Aqua (a commandline tool version manager) and Bazel (a build tool).
To be specific, this is a demonstration of a failure caused by the interaction between Aqua's policy enforcement mechanism,
triggered by the use of a custom package registry, and the execution environment provided by Bazel when invoking
tools through bazel run
.
Setup
Prereq: this repo assumes that Aqua has been set up on your machine. If it hasn't follow the directions at https://aquaproj.github.io/docs/install/.
We have a small shell script, hello_world.sh
, that is implemented in part using a tool
supplied through Aqua, cowsay
. This tool is somewhat niche and hasn't been packaged in
Aqua's standard
registry; this repo provides a suitable package definition for it using
a custom, supplemental registry.
For security reasons, Aqua requries
that users explicitly opt-in before downloading or running packages from custom registries.
To opt-in users acknowledge the policy described in aqua-policy.yaml
using the command
aqua policy allow aqua-policy.yaml
After which they will be able to install the packages provided by the custom registry and necessary to the execution of our script of interest.
aqua install
In addition to direct invocation of the shell script, we also provide for invocation of the script by way of Bazel's command-execution verb
bazel run :hello_world
Using Bazelisk to mediate versions, this becomes
bazelisk run :hello_world
Bazelisk is availabel through Aqua, and has been installed in this registry; we will be using it to invoke Bazel from here on.
Results
Without Bazel
When directly as a shell script, the script functions as intended.
The cowsay
tool is located through Aqua and executed flawlessly.
❯ ./hello_world.sh
______________
< Hello World! >
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
With Bazel
By contrast, when we invoke the exact same script that just worked, but through
Bazel, we experience Aqua rejecting our policy and refusing to execute the
cowsay
tool.
❯ bazelisk run :hello_world
INFO: Invocation ID: 0fd43c61-e10b-4d93-888b-4bbd154e298a
INFO: Analyzed target //:hello_world (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:hello_world up-to-date:
bazel-bin/hello_world
INFO: Elapsed time: 0.088s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/hello_world
WARN[0000] The policy file is ignored unless it is allowed by "aqua policy allow" command.
$ aqua policy allow "/private/var/tmp/_bazel_peter/e270edd33a131681f9346244c348d651/execroot/_main/aqua-policy.yaml"
If you want to keep ignoring the policy file without the warning, please run "aqua policy deny" command.
$ aqua policy deny "/private/var/tmp/_bazel_peter/e270edd33a131681f9346244c348d651/execroot/_main/aqua-policy.yaml"
aqua_version=2.28.1 doc="https://aquaproj.github.io/docs/reference/codes/003" env=darwin/arm64 exe_name=cowsay package_name=Code-Hex/Neo-cowsay package_version=v2.0.4 policy_file=/private/var/tmp/_bazel_peter/e270edd33a131681f9346244c348d651/execroot/_main/aqua-policy.yaml program=aqua
FATA[0000] aqua failed aqua_version=2.28.1 doc="https://aquaproj.github.io/docs/reference/codes/002" env=darwin/arm64 error="install the package: this package isn't allowed" exe_name=cowsay package_name=Code-Hex/Neo-cowsay package_version=v2.0.4 program=aqua
Why did this happen!? If we look closely at the failure, we see the policy reported as rejected
is in some weird temp directory /private/var/tmp/_bazel_peter/e270edd33a131681f9346244c348d651/execroot/_main/aqua-policy.yaml
.
Why would we be looking there!? It turns out this is what Bazel calls the
execroot,
and it is where Bazel spawns its subprocesses. It does this mainly for sandboxing build work, but it spawns the
bazel run
process there as well.
So what's this unrecognized policy file? Why are Aqua policies we didn't author showing up on my machine? Seems sketchy, right? Well actually, this is the policy file from our repo; the same one that we acknowledged to get our tools installed. That's just a bit hidden behind a symlink or two.
❯ realpath /private/var/tmp/_bazel_peter/e270edd33a131681f9346244c348d651/execroot/_main/aqua-policy.yaml
/Users/peter/aqua-bazel-policy-demo/aqua-policy.yaml
With Bazel + explicit working directory
We can make Aqua happy by having Bazel spawn our program in an explicitly chosen directory: the current workign directory.
When we do this, Aqua finds a policy it considers accepted again and allows execution of the cowsay
program.
❯ bazelisk run --run_under="cd $(pwd); exec" :hello_world
INFO: Invocation ID: 495c8d84-c400-4c6e-a1d4-fb14f4bb3b07
INFO: Analyzed target //:hello_world (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:hello_world up-to-date:
bazel-bin/hello_world
INFO: Elapsed time: 0.085s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: /bin/bash -c 'cd /Users/peter/aqua-bazel-policy-demo; exec bazel-bin/hello_world '
______________
< Hello World! >
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
So what?
As you might have already guessed, this was never really about verbose bovines. This is the reduction to a toy example of a real situation that might arise in real programs, when run under Bazel, and when the system supplies tool dependencies through Aqua.
In real usage, invocation through Bazel is not nearly this optional. Usually, invocation this way is done because some or all of the program is built through Bazel immediately preceding its execution; there would not be a full program to invoke directly without going through Bazel.
Likewise, in reality, we do not always have the easy option to change our working directory and have everything continue to function. Bazel starting programs in the execroot can be very convenient because it preserves the relative locations of files within the build system; by changing directory, we give that up and as a result might experience failures due to missing library files (dynamic languages), missing JARs (Java), missing shared libraries (C/C++), and missing data files (all).
At present, this appears to be an unresolved defect in the interaction between these two tools. It would be nice to have an understanding about how it should be treated.