Skip to content

Commit

Permalink
Wire up credential helper to command-line flag(s) (bazelbuild#15976)
Browse files Browse the repository at this point in the history
RELNOTES: Add support for fetching RPC credentials from credential helper.

Progress on bazelbuild#15856

Closes bazelbuild#15947.

PiperOrigin-RevId: 463083408
Change-Id: I286aa82fbb3c2376e4dba82cbb01e541c40e405e

Co-authored-by: Yannic Bonenberger <yannic@engflow.com>
  • Loading branch information
ckolli5 and Yannic authored Jul 25, 2022
1 parent 508f185 commit 14c944a
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,44 @@ public class AuthAndTLSOptions extends OptionsBase {
+ "pings are disabled, then this setting is ignored.")
public Duration grpcKeepaliveTimeout;

@Option(
name = "experimental_credential_helper",
defaultValue = "null",
allowMultiple = true,
converter = UnresolvedScopedCredentialHelperConverter.class,
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.UNKNOWN},
help =
"Configures Credential Helpers to use for retrieving credentials for the provided scope"
+ " (domain).\n\n"
+ "Credentials from Credential Helpers take precedence over credentials from"
+ " <code>--google_default_credentials</code>, `--google_credentials`, or"
+ " <code>.netrc</code>.\n\n"
+ "See https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md"
+ " for details.")
public List<UnresolvedScopedCredentialHelper> credentialHelpers;

@Option(
name = "experimental_credential_helper_timeout",
defaultValue = "5s",
converter = DurationConverter.class,
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.UNKNOWN},
help =
"Configures the timeout for the Credential Helper.\n\n"
+ "Credential Helpers failing to respond within this timeout will fail the"
+ " invocation.")
public Duration credentialHelperTimeout;

@Option(
name = "experimental_credential_helper_cache_duration",
defaultValue = "30m",
converter = DurationConverter.class,
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.UNKNOWN},
help = "Configures the duration for which credentials from Credential Helpers are cached.")
public Duration credentialHelperCacheTimeout;

/** One of the values of the `--credential_helper` flag. */
@AutoValue
public abstract static class UnresolvedScopedCredentialHelper {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperCredentials;
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperEnvironment;
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperProvider;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.runtime.CommandLinePathFactory;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.Path;
Expand Down Expand Up @@ -222,36 +222,49 @@ public static CallCredentialsProvider newCallCredentialsProvider(@Nullable Crede
}

/**
* Create a new {@link Credentials} with following order:
* Create a new {@link Credentials} retrieving call credentials in the following order:
*
* <ol>
* <li>If authentication enabled by flags, use it to create credentials
* <li>Use .netrc to provide credentials if exists
* <li>Otherwise, return {@code null}
* <li>If a Credential Helper is configured for the scope, use the credentials provided by the
* helper.
* <li>If (Google) authentication is enabled by flags, use it to create credentials.
* <li>Use {@code .netrc} to provide credentials if exists.
* </ol>
*
* @throws IOException in case the credentials can't be constructed.
*/
@Nullable
public static Credentials newCredentials(
Reporter reporter,
Map<String, String> clientEnv,
CredentialHelperEnvironment credentialHelperEnvironment,
CommandLinePathFactory commandLinePathFactory,
FileSystem fileSystem,
AuthAndTLSOptions authAndTlsOptions)
throws IOException {
Preconditions.checkNotNull(credentialHelperEnvironment);
Preconditions.checkNotNull(commandLinePathFactory);
Preconditions.checkNotNull(fileSystem);
Preconditions.checkNotNull(authAndTlsOptions);

Optional<Credentials> credentials = newGoogleCredentials(authAndTlsOptions);

if (credentials.isEmpty()) {
// Fallback to .netrc if it exists.
try {
credentials = newCredentialsFromNetrc(clientEnv, fileSystem);
credentials =
newCredentialsFromNetrc(credentialHelperEnvironment.getClientEnvironment(), fileSystem);
} catch (IOException e) {
// TODO(yannic): Make this fail the build.
reporter.handle(Event.warn(e.getMessage()));
credentialHelperEnvironment.getEventReporter().handle(Event.warn(e.getMessage()));
}
}

return credentials.orElse(null);
return new CredentialHelperCredentials(
newCredentialHelperProvider(
credentialHelperEnvironment,
commandLinePathFactory,
authAndTlsOptions.credentialHelpers),
credentialHelperEnvironment,
credentials,
authAndTlsOptions.credentialHelperCacheTimeout);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/shell",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//third_party:auth",
"//third_party:auto_value",
"//third_party:caffeine",
"//third_party:error_prone_annotations",
"//third_party:gson",
"//third_party:guava",
"//third_party:jsr305",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright 2022 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.devtools.build.lib.authandtls.credentialhelper;

import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.auth.Credentials;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;

/**
* Implementation of {@link Credentials} which fetches credentials by invoking a {@code credential
* helper} as subprocess, falling back to another {@link Credentials} if no suitable helper exists.
*/
public class CredentialHelperCredentials extends Credentials {
private final Optional<Credentials> fallbackCredentials;

private final LoadingCache<URI, GetCredentialsResponse> credentialCache;

public CredentialHelperCredentials(
CredentialHelperProvider credentialHelperProvider,
CredentialHelperEnvironment credentialHelperEnvironment,
Optional<Credentials> fallbackCredentials,
Duration cacheTimeout) {
Preconditions.checkNotNull(credentialHelperProvider);
Preconditions.checkNotNull(credentialHelperEnvironment);
this.fallbackCredentials = Preconditions.checkNotNull(fallbackCredentials);
Preconditions.checkNotNull(cacheTimeout);
Preconditions.checkArgument(
!cacheTimeout.isNegative() && !cacheTimeout.isZero(),
"Cache timeout must be greater than 0");

credentialCache =
Caffeine.newBuilder()
.expireAfterWrite(cacheTimeout)
.build(
new CredentialHelperCacheLoader(
credentialHelperProvider, credentialHelperEnvironment));
}

@Override
public String getAuthenticationType() {
if (fallbackCredentials.isPresent()) {
return "credential-helper-with-fallback-" + fallbackCredentials.get().getAuthenticationType();
}

return "credential-helper";
}

@Override
public Map<String, List<String>> getRequestMetadata(URI uri) throws IOException {
Preconditions.checkNotNull(uri);

Optional<Map<String, List<String>>> credentials = getRequestMetadataFromCredentialHelper(uri);
if (credentials.isPresent()) {
return credentials.get();
}

if (fallbackCredentials.isPresent()) {
return fallbackCredentials.get().getRequestMetadata(uri);
}

return ImmutableMap.of();
}

@SuppressWarnings("unchecked") // Map<String, ImmutableList<String>> to Map<String<List<String>>
private Optional<Map<String, List<String>>> getRequestMetadataFromCredentialHelper(URI uri) {
Preconditions.checkNotNull(uri);

GetCredentialsResponse response = credentialCache.get(uri);

return Optional.ofNullable(response).map(value -> (Map) value.getHeaders());
}

@Override
public boolean hasRequestMetadata() {
return true;
}

@Override
public boolean hasRequestMetadataOnly() {
return false;
}

@Override
public void refresh() throws IOException {
if (fallbackCredentials.isPresent()) {
fallbackCredentials.get().refresh();
}

credentialCache.invalidateAll();
}

private static final class CredentialHelperCacheLoader
implements CacheLoader<URI, GetCredentialsResponse> {
private final CredentialHelperProvider credentialHelperProvider;
private final CredentialHelperEnvironment credentialHelperEnvironment;

public CredentialHelperCacheLoader(
CredentialHelperProvider credentialHelperProvider,
CredentialHelperEnvironment credentialHelperEnvironment) {
this.credentialHelperProvider = Preconditions.checkNotNull(credentialHelperProvider);
this.credentialHelperEnvironment = Preconditions.checkNotNull(credentialHelperEnvironment);
}

@Nullable
@Override
public GetCredentialsResponse load(URI uri) throws IOException, InterruptedException {
Preconditions.checkNotNull(uri);

Optional<CredentialHelper> maybeCredentialHelper =
credentialHelperProvider.findCredentialHelper(uri);
if (maybeCredentialHelper.isEmpty()) {
return null;
}
CredentialHelper credentialHelper = maybeCredentialHelper.get();

return credentialHelper.getCredentials(credentialHelperEnvironment, uri);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib:runtime",
"//src/main/java/com/google/devtools/build/lib/analysis:test/test_configuration",
"//src/main/java/com/google/devtools/build/lib/authandtls",
"//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper",
"//src/main/java/com/google/devtools/build/lib/bugreport",
"//src/main/java/com/google/devtools/build/lib/buildeventservice/client",
"//src/main/java/com/google/devtools/build/lib/buildeventstream",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.authandtls.GoogleAuthUtils;
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperEnvironment;
import com.google.devtools.build.lib.buildeventservice.client.BuildEventServiceClient;
import com.google.devtools.build.lib.buildeventservice.client.BuildEventServiceGrpcClient;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
Expand Down Expand Up @@ -86,8 +87,13 @@ protected BuildEventServiceClient getBesClient(

Credentials credentials =
GoogleAuthUtils.newCredentials(
env.getReporter(),
env.getClientEnv(),
CredentialHelperEnvironment.newBuilder()
.setEventReporter(env.getReporter())
.setWorkspacePath(env.getWorkspace())
.setClientEnvironment(env.getClientEnv())
.setHelperExecutionTimeout(authAndTLSOptions.credentialHelperTimeout)
.build(),
env.getCommandLinePathFactory(),
env.getRuntime().getFileSystem(),
newConfig.authAndTLSOptions());

Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/google/devtools/build/lib/remote/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ java_library(
":Retrier",
"//src/main/java/com/google/devtools/build/lib:build-request-options",
"//src/main/java/com/google/devtools/build/lib:runtime",
"//src/main/java/com/google/devtools/build/lib:runtime/command_line_path_factory",
"//src/main/java/com/google/devtools/build/lib/actions",
"//src/main/java/com/google/devtools/build/lib/actions:action_input_helper",
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
Expand Down
Loading

0 comments on commit 14c944a

Please sign in to comment.