An executable rule which can be executed via
bazel run is the natural
way to model interactions with external systems in Bazel such as uploading
build artifacts to a remote artifact repository. For example, imagine
rules_artifactory ruleset which includes a rule
executable rule which uploads a compiled
.dpkg to an Artifactory
apt repository, or a
rules_docker ruleset which has a rule
docker_push() which pushes a Docker image to a remote image repository.
One key design consideration for these rules is “how will users securely pass authentication credentials (e.g. a username and a password) to the push job”? After all, it is impractical to have the rules know about every possible secret management system that a user might use. For example, one user could be using HashiCorp’s Vault, another Azure’s Key Vault, another Thycotic’s Secret Server, and there are probably dozens more.
A commonly-used technique is for these tools to allow users pass in
secrets via environment variables. For example, Terraform allows
specifying an Azure service principal using the
ARM_TENANT_ID environment variables.
But how to pass these environment variables to the
bazel run job?
One option is to use a
env rule attribute, as in:
But this is obviously insecure, as we’d be storing secrets in source control.
The technique I’ve been using lately is to wrap the executable rule
sh_binary() and have the wrapping
sh_binary() be responsible
for retrieving the secrets from the secret management system, setting
the appropriate environment variables, and then invoking the original
bazel run job.
For example, let’s say you store secrets in Azure Key Vault and you have an authenticated Azure CLI set up (likely using interactive authentication for individual developers and Azure Managed Service Identity in CI). Here’s some example code on how to apply the above technique:
You can then perform an authenticated push using
bazel run //:authenticated_my_push.
This technique can be applied to wrap any
bazel run action with