Authentication¶
This document describes how to setup server and client authentication for a EngFlow Remote Execution cluster.
Server Authentication¶
The only supported mechanism for server authentication is TLS. In order to use
it, you have to set the --tls_certificate
and
--tls_key
flags. You can either use an existing certificate
authority, or you can use self-signed certificates.
For Bazel users: by default, Bazel trusts the set of root certificates that
is shipped with the JDK. If you are using a non-standard certificate
authority, you have to configure Bazel to accept its certificate using Bazel's
--tls_certificate
flag.
Beware that Bazel only allows specifying one custom CA certificate. If your
--remote_executor
, --remote_cache
, and --bes_backend
endpoints are
different, their TLS certificates must all be signed by this authority, or by a
publicly trusted authority.
Client Authentication (gRPC)¶
The EngFlow Remote Execution Service supports several mechanisms to authenticate gRPC clients and authorize requests.
To configure authentication, use the --client_auth
flag:
none
, mtls
, gcp_rbe
, and external
.
To configure authorization, check out the details of the specific authentication mechanisms below.
Which --client_auth
to use?¶
If you already setup GCP RBE access roles, and every user has a Google account,
then we recommend using the gcp_rbe
mechanism, which allows continued use of
your existing IAM setup. This method relies on Google authenticating the
clients, so schedulers need to be able to talk to Google's servers.
If you want to use a custom authentication service, use external
.
Please contact us for details; you'll need to
implement a binary with a specific gRPC interface.
If you already have a TLS certificate authority and distribute client
certificates to your clients, and you use a client that supports mTLS
(e.g. Bazel 3.1 or newer), then we recommend using the mtls
mechanism.
This mechanism works even without internet access, because the client CA's
certificate is deployed to schedulers.
Otherwise you will have to decide whether to setup a certificate authority
(which may require setting up additional infrastructure, such as HashiCorp
Vault or smallstep), or
use a VPN (none
). Like mtls
, these mechanism don't rely on external identity
providers, so they work on clusters that have no internet access.
None¶
Usage: --client_auth=none
If you disable client authentication, anyone who can initiate a network connection to the cluster can use it. This must only be used in combination with network-based usage restrictions, e.g., over a VPN.
Authorization: The --principal_based_permissions
flag
controls permissions. With --client_auth=none
, the principal name is always
empty, so the only meaningful setting is --principal_based_permissions=*->ROLE
(where ROLE
is for example user
or admin
.)
Version support:
- v1.54 and older: The default value of
--principal_based_permissions
is["*->user"]
, therefore every client has theuser
permissions (to read and write to the CAS and read and execute actions, but not directly write actions). - v1.55 and newer: The default value of
--principal_based_permissions
is[]
: nobody can do anything unless you specify--principal_based_permissions
values.
Deny¶
Usage: --client_auth=deny
Denies all gRPC requests.
Apart from testing, this could be used to cut off gRPC access to a cluster
without shutting it down. The cluster could still serve HTTP requests with
the right --http_auth
flag.
Authorization: With --client_auth=deny
, no principals can be authorized.
Version support:
- v1.54 and older: The default value of
--principal_based_permissions
is["*->user"]
, therefore every authenticated client has theuser
permissions (to read and write to the CAS and read and execute actions, but not directly write actions). - v1.55 and newer: The default value of
--principal_based_permissions
is[]
: nobody can do anything unless you specify--principal_based_permissions
values.
Example:
Generating a htpasswd
file:
Bash | |
---|---|
The generated entry will look like:
Text Only | |
---|---|
By default, all authenticated users have the "user
" role. You can specify
more detailed permissions with --principal_based_permissions
.
Example (/etc/engflow/config
snippet):
Text Only | |
---|---|
(Note the =
and +=
operators:
--principal_based_permissions
is a list-type flag; =
overrides
the default flag value.)
If you are using Bazel, create a ~/.netrc
file with an entry for the remote
execution service address.
Example (~/.netrc
):
To use an alternative netrc file, set its path in the NETRC
environment
variable before running Bazel.
mTLS¶
Usage: --client_auth=mtls
mTLS or mutual TLS authentication requires each client to present a signed
client TLS certificate whenever it establishes a connection to the cluster.
Use the --tls_trusted_certificate
flag to configure the
certificate authority that is trusted to authenticate clients.
Remember to configure fine-grained permissions with
--principal_based_permissions
flag. The principal is the
Common Name field in the client TLS certificate.
If you are using Bazel, you can configure it using the
--tls_client_certificate
and --tls_client_key
flags.
Authorization: The --principal_based_permissions
flag
controls permissions. With --client_auth=mtls
, the principal name is the
Common Name (CN) of the client certificate. The CN can be any string supported
by X.509 certificates, such as plain user names, email addresses, or FQDNs. If
it's an email address then you can use the *@example.com
syntax to mean
everyone under that email domain. Otherwise you must use the exact principal
name, or *
to mean "everyone".
Text Only | |
---|---|
Version support:
- v1.54 and older: The default value of
--principal_based_permissions
is["*->user"]
, therefore every authenticated client has theuser
permissions (to read and write to the CAS and read and execute actions, but not directly write actions). - v1.55 and newer: The default value of
--principal_based_permissions
is[]
: nobody can do anything unless you specify--principal_based_permissions
values.
Version support:
- v1.54 and older: The default value of
--principal_based_permissions
is["*->user"]
, therefore every authenticated client has theuser
permissions (to read and write to the CAS and read and execute actions, but not directly write actions). - v1.55 and newer: The default value of
--principal_based_permissions
is[]
: nobody can do anything unless you specify--principal_based_permissions
values.
GCP RBE¶
Usage: --client_auth=gcp_rbe
GCP RBE-based authentication also uses GCP OAuth 2.0 bearer tokens. However,
instead of relying on verified email addresses, it queries GCP's IAM for the
Google-defined Remote Build Executor
permissions, and unlike other
--client_auth
methods it therefore ignores
--principal_based_permissions
.
In order to use this authentication mechanism, you must specify a GCP project
using the --gcp_rbe_auth_project
flag.
If you are using Bazel, you can configure it using either
--google_default_credentials
or --google_credentials
.
Authorization: Unlike other --client_auth
mechanisms, the
--principal_based_permissions
flag is ignored with
--client_auth=gcp_rbe
. You have to specify permissions in GCP IAM,
as described below.
The Google GCP permissions and roles are documented here: https://cloud.google.com/iam/docs/understanding-roles#other-roles
The mapping between GCP permissions and EngFlow permissions is incomplete: the EngFlow Remote Execution service supports only a subset of the RBE permissions and roles, and GCP defines only a subset of the permissions that EngFlow supports.
GCP RBE Permissions:
remotebuildexecution.actions.create
to run an action remotelyremotebuildexecution.actions.delete
to delete an action cache entry (this is not used by Bazel)remotebuildexecution.actions.get
to read an action cache entryremotebuildexecution.actions.write
to write an action cache entry from a client (remotely executed actions always have write access to the action cache)remotebuildexecution.blobs.create
to write an entry to the CASremotebuildexecution.blobs.get
to read an entry from the CAS or query whether an entry is in the CAS based on its digest
These are the relevant roles, though note that you can create custom roles for different subsets of permissions:
-
Remote Build Execution Artifact Creator aka
roles/remotebuildexecution.artifactCreator
Can run actions remotely. This is the most commonly used role, and maps to the
user
role defined in EngFlow RE. -
Remote Build Execution Artifact Admin aka
roles/remotebuildexecution.artifactAdmin
Can run actions remotely, and also delete actions. Cannot write actions to the cache.
-
Remote Build Execution Action Cache Writer aka
roles/remotebuildexecution.actionCacheWriter
Can write CAS and action cache entries. This is primarily useful when using the system as a pure cache without remote execution. In this use case, the CI system should be allowed to read and write the cache (requires both this role and the Remote Build Execution Artifact Viewer), while individual engineers are only allowed to read the cache.
-
Remote Build Execution Artifact Viewer aka
roles/remotebuildexecution.artifactViewer
Can read CAS and action cache entries. This is primarily useful when using the system as a pure cache without remote execution.
Build Event Streams with --client_auth=gcp_rbe
¶
As of 2021-09-29, GCP IAM has no permissions to control Build Event Stream uploads that we could map to EngFlow Remote Execution permissions.
When using --client_auth=gcp_rbe
, EngFlow RE versions support Build Event Stream uploads
the following way:
v1.52 and older: don't let clients upload Build Event Streams.
v1.53 and newer: allow clients with remotebuildexecution.blobs.create
permission to
also upload Build Event Streams.
External¶
Usage: --client_auth=external
Since v1.53 you can use an external service to authenticate and authorize gRPC client requests.
This must be a service running on localhost next to the scheduler, communicating over gRPC. Please contact us for details.
Authorization: Unlike other --client_auth
mechanisms, the
--principal_based_permissions
flag is ignored with
--client_auth=external
. The external service is responsible for authorization
too.
GitHub Tokens¶
Usage: --client_auth=github_token
--experimental_github_auth_container=foo/hello-world:1.0
You can use GitHub-signed tokens as credentials. The token can be a GITHUB_TOKEN
(available to GitHub Actions runners) or a Personal Access Token. More info: https://docs.github.com/en/actions/security-guides/automatic-token-authentication
Assuming the token is stored in an envvar ($GITHUB_TOKEN
), run Bazel with --remote_header=Authorization=Bearer $GITHUB_TOKEN
.
Example (.github/workflows/engflow.yml
GitHub Actions snippet):
YAML | |
---|---|
In order to use GitHub
tokens you need to add to your remote configuration a GitHub
container using --experimental_github_auth_container
.
It specifies an existing container on ghcr.io
.
The container should be private. Only GitHub Action runners of the organisation
with a valid GITHUB_TOKEN
should have access. Other than that the container can
be anything; it won't be pulled nor ran, only have its existence checked.
Example: for the foo
GitHub organization with a private container hello-world:1.0
:
Authorization: The --principal_based_permissions
flag
controls permissions. Set this to --principal_based_permissions=*->admin
.
This kind of client_auth
is still a work in progress. You should try to use other methods if possible.
If you have any questions, please contact the EngFlow team.
Client Authentication (HTTP)¶
The EngFlow Remote Execution service can be configured to store build events and
serve a build results UI; see --enable_bes
.
When enabled, you can visit the Remote Execution address in a browser and view
build results. Client request authentication can be configured with the
--http_auth
flag. As of v2.29 the supported values
include: none
, deny
, basic
, google_login
, and oidc_login
.
None¶
Usage: --http_auth=none
If you disable client authentication, anyone who can initiate a network connection to the cluster can use it. This must only be used in combination with network-based usage restrictions, e.g., over a VPN.
In order to change the default permissions, use the
--principal_based_permissions
flag, for example
--principal_based_permissions=*->admin
. For guidance, see
UI Access Permissions.
Authorization: same as --client_auth=none
.
Deny¶
Usage: --http_auth=deny
Denies all HTTP requests.
This is useful when the Build Event Service is disabled on the cluster, or when
you want to cut off HTTP access to a cluster without shutting it down. The
cluster could still serve gRPC requests with the right
--client_auth
flag.
Authorization: same as --client_auth=deny
.
Basic¶
Usage: --http_auth=basic
Basic authentication uses user name / password pairs.
Starting with v2.15.0
, this does not use HTTP basic auth. Instead, it
requires login through a web page.
To enable basic authentication: create a htpasswd file with the user-password
pairs, copy it to every scheduler instance, then add its path to the config
file with --basic_auth_htpasswd
.
The password entries must be in apr1 format (Apache MD5). See https://httpd.apache.org/docs/2.4/misc/password_encryptions.html for details.
Authorization: The --principal_based_permissions
flag
controls permissions. With --http_auth=basic
, the principal name is the
user name in the Basic Auth credentials. An example setting:
Text Only | |
---|---|
Additionally, review the version support for restrictions and customization options.
Google Login¶
Usage: --http_auth=google_login
Warning
This value is deprecated. Use --http_auth=oidc_login
instead.
The Google-specific setup instructions still apply, except
Authorized redirect URIs need to include
https://YOUR-CLUSTER.cluster.engflow.com/oidc-redirect
, rather than
https://YOUR-CLUSTER.cluster.engflow.com/google-redirect
.
Authenticates clients through a Google sign-in page. You need to create an
OAuth 2.0 Client ID in GCP, then configure --principal_based_permissions
for allowed clients.
Authorization: The --principal_based_permissions
flag
controls permissions. With --http_auth=google_login
, the principal name is the
email address of the client. You can use exact principal names, or
*@example.com
to mean everyone under that email domain, or *
to mean
"everyone".
Additionally, review the version support for restrictions and customization options.
Setup¶
-
Configure the OAuth Consent Screen in GCP.
You can use the same GCP project as the one hosting the Remote Execution cluster, or use a different one.
If all clients are in the same organization (e.g. everyone has an
@your-company.com
Google account), then you can set User Type to Internal.To support clients from multiple email domains (e.g.
*@your-company.com
,*@partner-company.com
), you need to set User Type to External.Do not upload a Logo, and don't configure any App Domains nor any Scopes. If you do, the app must be verified by Google. Once you upload a Logo it's not possible to take it down, nor to cancel the verification process). So just leave every non-mandatory field blank.
Once you've completed the steps, click on Publish. This will immediately change the status to In Production, without needing verification.
-
Under Credentials, create an OAuth 2.0 Client ID in GCP.
For Application Type, select Web application.
For Authorized JavaScript origins, add the base URL of the cluster's HTTP endpoint, e.g.
https://YOUR-CLUSTER.cluster.engflow.com/
.For Authorized redirect URIs, add
https://YOUR-CLUSTER.cluster.engflow.com/google-redirect
, adjusting the base URL to the cluster's HTTP endpoint.Once you are done, note down the Client ID. You do not need the Client secret.
-
Set the Client ID, and allowed principals in the EngFlow config.
Example:
OpenID Connect Login¶
Usage: --http_auth=oidc_login
Authenticates clients through OpenID Connect (OIDC). You need to create an
OAuth 2.0 Client ID with your identity provider (IdP), then configure
--principal_based_permissions
for allowed clients.
Authorization: The --principal_based_permissions
flag
controls permissions. With --http_auth=oidc_login
, the principal name is the
email address of the client. You can use exact principal names, or
*@example.com
to mean everyone under that email domain, or *
to mean
"everyone".
Additionally, review the version support for restrictions and customization options.
Setup: EngFlow supports using OIDC's Implicit Flow, Authorization Code flow and Hybrid Flow.
- Implicit flow: Upon authentication, the IdP directly returns an ID token.
- Authorization flow: The IdP returns an authorization code. An ID token can be requested from the IdP using the authorization code and a client secret.
- Hybrid flow: Combines both models. Tokens may be returned directly or an authorization code is sent.
Your identity provider may support multiple flows. In this case, choose the flow according to your organization's policy. EngFlow only requests very basic information about the account:
- scope
openid
to enable OIDC login, - scope
email
to identify the user, - optionally scope
profile
. Ifprofile[picture]
is supported by the IdP, signed-in users will see their public profile picture, instead of a default profile icon.
Setup: Authorization Code flow¶
-
Create an OAuth 2.0 Client ID configured for the authorization code flow.
- supported scopes: include
openid
,email
, and optionallyprofile
- sign-out redirect URIs: include your cluster's HTTP endpoint, e.g.
https://YOUR-CLUSTER.engflow.com:8080/
- sign-in redirect URIs: include the path
/oidc-redirect
(or a wildcard*
) for your cluster's HTTP endpoint, e.g.https://YOUR-CLUSTER.engflow.com:8080/oidc-redirect
https://YOUR-CLUSTER.engflow.com:8080/*
- supported scopes: include
-
Note down the
client_id
andclient_secret
. -
Locate your IdP's Issuer discovery URI, below referenced as
discovery_uri
.- Google:
https://accounts.google.com/.well-known/openid-configuration
. - okta: Commonly
https://{yourOktaDomain}/.well-known/openid-configuration
. If you have set up a custom Authorization Server, the URI is listed as the Metadata URI in the details of the Authorization Server, found under Security > API. Then the URI usually has the formhttps://{yourOktaDomain}/oauth2/{authorizationServerName}/.well-known/oauth-authorization-server
. - Keycloak: the URI is listed under Endpoints in the Realm Settings
> General . Depending on the version of Keycloak you are using, the
URI may take on one of the following forms:
http://{keycloakhost:keycloakport}/auth/realms/{realm}/.well-known/openid-configuration
http://{keycloakhost:keycloakport}/realms/{realm}/.well-known/openid-configuration
Without Issuer discovery
If your IdP does not support Issuer discovery, instead locate the following endpoints:
The token endpoint is only required for the authorization code flow, not the implicit flow.authorization_endpoint
,token_endpoint
andjwks_uri
. Then create a JSON file with the following contents, rather than those included in the next step: - Google:
-
Create a JSON file with the following contents:
-
Update the EngFlow config.
Example:
Text Only As the config for the authorization flow includes the
client_secret
, the JSON file should be stored in a secrets manager. The value for--oidc_config
then does not specify an absolute path to the file, but is prepended bysecretstore://
, followed by the secret's location, e.g.Text Only
Setup: Implicit flow¶
The setup is similar to that for the Authorization code flow, with the following differences to the JSON file:
- Omit the entry for the
client_secret
. - When not using Issuer discovery, the
token_endpoint
can also be omitted.
Setup: Implicit flow in Okta¶
The below gives a more detailed example for setting up the implicit flow in okta. For other IdPs, follow a similar process.
-
Create an OAuth 2.0 Client ID in okta using the App Integration Wizard (AIW).
In the Applications section, create a app integration.
For Sign-in method, select OIDC - OpenID Connect.
For Application Type, select Web Application.
For Grant type, select Implicit (hybrid).
For Sign-in redirect URIs, add
https://YOUR-CLUSTER.engflow.com:8080/oidc-redirect
, adjusting the base URL to the cluster's HTTP endpoint.For Sign-out redirect URIs, add the base URL of the cluster's HTTP endpoint, e.g.
https://YOUR-CLUSTER.engflow.com:8080/
.For Assignments choose the appropriate setting for your use case.
Upon saving, ensure the Grant type setting has Allow ID Token with implicit grant type enabled. Allow Access Token with implicit grant type is not required.
In the Okta API Scopes of your application grant the scope okta.users.read.self.
Once you are done, note down the Client ID. You do not need the Client secret.
Visit okta's Help Center for more information on how to create a OIDC app integrations.
-
Determine the discovery URI.
By default, the discovery endpoint should be available at the URI
https://{yourOktaDomain}/.well-known/openid-configuration
.Verify this URI loads a JSON file with entries
authorization_endpoint
,jwks_uri
etc. The entryscopes_supported
should at least includeopenid
andemail
.If you want to set up a custom Authorization Server, do so in the Security section, subsection API. Ensure the server includes the scopes
openid
andemail
. Once done, note down the Metadata URI as the discovery URI. It usually has the formhttps://{yourOktaDomain}/oauth2/{authorizationServerName}/.well-known/oauth-authorization-server
. Verify this URI loads the expected JSON file as described above. -
Create a JSON file with the following contents:
-
Update the EngFlow config.
Example:
Issuer-specific login buttons¶
If the --oidc_config
does not include an entry for "issuer"
, the login page
shows a generic OIDC login button.
The following issuer-specific buttons are supported: GOOGLE
, KEYCLOAK
,
OKTA
.
Example:
Text Only | |
---|---|
Using multiple Identity Providers¶
EngFlow supports specifying multiple OIDC configurations. The UI login page then shows one login button for each IdP configured. Especially in this case, use issuer-specific login buttons, so users can differentiate the buttons. Reach out to us if you want a custom button for a not-yet listed IdP.
Example configuration:
Text Only | |
---|---|
Expiration¶
You can set the expiry of credentials using
--experimental_web_login_expiration
.
Version Support¶
UI Access Permissions¶
To grant access to the UI
--principal_based_permissions
needs to be specified.
- v1.54 and older:
- The default value of
--principal_based_permissions
is["*->user"]
, therefore every client has theuser
permissions. - However, clients need
admin
access to view the UI.
- The default value of
- v1.55 to v2.14:
- The default value of
--principal_based_permissions
is[]
. - Clients need
admin
access to view the UI.
- The default value of
- v2.15 and newer:
- The default value of
--principal_based_permissions
is[]
. - Clients need
admin
oruser
access to view the UI.
- The default value of
Note that a user may authenticate even if no permission is set up for their principal. However, they will not be able to access the UI (or any other resources), unless a permission is set up that authorizes them to do so. This means it is safe to use a less restrictive authentication mechanism (e.g. a common Google or okta auth setup), and control access here more strictly.
Multi-value support¶
For v2.14 and older --http_auth
can only have one value.
For v2.15 and newer, you can enable multiple authentication methods for HTTP
client authentication, provided they all create a cookie with a JSON Web Token
(JWT) for authentication. These include basic
, google_login
and, since
v2.29, oidc_login
. Other methods cannot be combined.
Example:
Note the =
and +=
operators:
Both --http_auth
and
--principal_based_permissions
are list-type flags. The =
operator
drops all previous values, whereas +=
adds another value.