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

Add netrc support to --bes_backend #15930

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/com/google/devtools/build/lib/authandtls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ java_library(
srcs = glob(["*.java"]),
deps = [
"//src/main/java/com/google/devtools/build/lib/concurrent",
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/common/options",
"//third_party:auth",
"//third_party:auto_value",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +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.events.Event;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.Path;
import io.grpc.CallCredentials;
import io.grpc.ClientInterceptor;
import io.grpc.ManagedChannel;
Expand All @@ -41,6 +45,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -185,20 +191,6 @@ private static NettyChannelBuilder newNettyChannelBuilder(String targetUrl, Stri
return newUnixNettyChannelBuilder(proxy).overrideAuthority(targetUrl);
}

/**
* Create a new {@link CallCredentials} object.
*
* @throws IOException in case the call credentials can't be constructed.
*/
@Nullable
public static CallCredentials newCallCredentials(AuthAndTLSOptions options) throws IOException {
Credentials creds = newCredentials(options);
if (creds != null) {
return MoreCallCredentials.from(creds);
}
return null;
}

/**
* Create a new {@link CallCredentialsProvider} object from {@link Credentials} or return {@link
* CallCredentialsProvider#NO_CREDENTIALS} if it is {@code null}.
Expand All @@ -211,18 +203,47 @@ public static CallCredentialsProvider newCallCredentialsProvider(@Nullable Crede
}

/**
* Create a new {@link Credentials} object, or {@code null} if no options are provided.
* Create a new {@link Credentials} with 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}
* </ol>
*
* @throws IOException in case the credentials can't be constructed.
*/
@Nullable
public static Credentials newCredentials(@Nullable AuthAndTLSOptions options) throws IOException {
public static Credentials newCredentials(
Reporter reporter,
Map<String, String> clientEnv,
FileSystem fileSystem,
AuthAndTLSOptions authAndTlsOptions)
throws IOException {
Optional<Credentials> credentials = newGoogleCredentials(authAndTlsOptions);

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

return credentials.orElse(null);
}

@VisibleForTesting
public static Optional<Credentials> newGoogleCredentials(
@Nullable AuthAndTLSOptions options) throws IOException {
if (options == null) {
return null;
return Optional.empty();
} else if (options.googleCredentials != null) {
// Credentials from file
try (InputStream authFile = new FileInputStream(options.googleCredentials)) {
return newCredentials(authFile, options.googleAuthScopes);
return Optional.of(newGoogleCredentialsFromFile(authFile, options.googleAuthScopes));
} catch (FileNotFoundException e) {
String message =
String.format(
Expand All @@ -231,10 +252,10 @@ public static Credentials newCredentials(@Nullable AuthAndTLSOptions options) th
throw new IOException(message, e);
}
} else if (options.useGoogleDefaultCredentials) {
return newCredentials(
null /* Google Application Default Credentials */, options.googleAuthScopes);
return Optional.of(newGoogleCredentialsFromFile(
null /* Google Application Default Credentials */, options.googleAuthScopes));
}
return null;
return Optional.empty();
}

/**
Expand All @@ -243,7 +264,7 @@ public static Credentials newCredentials(@Nullable AuthAndTLSOptions options) th
* @throws IOException in case the credentials can't be constructed.
*/
@VisibleForTesting
public static Credentials newCredentials(
public static Credentials newGoogleCredentialsFromFile(
@Nullable InputStream credentialsFile, List<String> authScopes) throws IOException {
try {
GoogleCredentials creds =
Expand All @@ -259,4 +280,54 @@ public static Credentials newCredentials(
throw new IOException(message, e);
}
}

@Nullable
@VisibleForTesting
public static CallCredentials newGoogleCallCredentialsForTesting(
AuthAndTLSOptions options) throws IOException {
Optional<Credentials> creds = newGoogleCredentials(options);
if (creds.isPresent()) {
return MoreCallCredentials.from(creds.get());
}
return null;
}

/**
* Create a new {@link Credentials} object by parsing the .netrc file with following order to
* search it:
*
* <ol>
* <li>If environment variable $NETRC exists, use it as the path to the .netrc file
* <li>Fallback to $HOME/.netrc
* </ol>
*
* @return the {@link Credentials} object or {@code null} if there is no .netrc file.
* @throws IOException in case the credentials can't be constructed.
*/
@VisibleForTesting
static Optional<Credentials> newCredentialsFromNetrc(Map<String, String> clientEnv, FileSystem fileSystem)
throws IOException {
Optional<String> netrcFileString =
Optional.ofNullable(clientEnv.get("NETRC"))
.or(
() ->
Optional.ofNullable(clientEnv.get("HOME"))
.map(home -> home + "/.netrc"));
if (netrcFileString.isEmpty()) {
return Optional.empty();
}

Path netrcFile = fileSystem.getPath(netrcFileString.get());
if (!netrcFile.exists()) {
return Optional.empty();
}

try {
Netrc netrc = NetrcParser.parseAndClose(netrcFile.getInputStream());
return Optional.of(new NetrcCredentials(netrc));
} catch (IOException e) {
throw new IOException(
"Failed to parse " + netrcFile.getPathString() + ": " + e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/util/io:out-err",
"//src/main/java/com/google/devtools/common/options",
"//src/main/protobuf:failure_details_java_proto",
"//third_party:auth",
"//third_party:auto_value",
"//third_party:flogger",
"//third_party:guava",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@

package com.google.devtools.build.lib.buildeventservice;

import com.google.auth.Credentials;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
Expand All @@ -24,9 +26,11 @@
import com.google.devtools.build.lib.authandtls.GoogleAuthUtils;
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;
import io.grpc.ClientInterceptor;
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.auth.MoreCallCredentials;
import io.grpc.stub.MetadataUtils;
import java.io.IOException;
import java.util.Map;
Expand Down Expand Up @@ -70,15 +74,25 @@ protected Class<BuildEventServiceOptions> optionsClass() {

@Override
protected BuildEventServiceClient getBesClient(
BuildEventServiceOptions besOptions, AuthAndTLSOptions authAndTLSOptions) throws IOException {
CommandEnvironment env, BuildEventServiceOptions besOptions, AuthAndTLSOptions authAndTLSOptions) throws IOException {
BackendConfig newConfig = BackendConfig.create(besOptions, authAndTLSOptions);
if (client == null || !Objects.equals(config, newConfig)) {
clearBesClient();
Preconditions.checkState(config == null);
Preconditions.checkState(client == null);

Credentials credentials =
GoogleAuthUtils.newCredentials(
env.getReporter(),
env.getClientEnv(),
env.getRuntime().getFileSystem(),
newConfig.authAndTLSOptions());

config = newConfig;
client =
new BuildEventServiceGrpcClient(
newGrpcChannel(config),
GoogleAuthUtils.newCallCredentials(config.authAndTLSOptions()),
credentials != null ? MoreCallCredentials.from(credentials) : null,
makeGrpcInterceptor(config));
}
return client;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ private BuildEventServiceTransport createBesTransport(

final BuildEventServiceClient besClient;
try {
besClient = getBesClient(besOptions, authTlsOptions);
besClient = getBesClient(cmdEnv, besOptions, authTlsOptions);
} catch (IOException | OptionsParsingException e) {
reportError(
reporter,
Expand Down Expand Up @@ -845,7 +845,7 @@ private static AbruptExitException createAbruptExitException(
protected abstract Class<OptionsT> optionsClass();

protected abstract BuildEventServiceClient getBesClient(
OptionsT besOptions, AuthAndTLSOptions authAndTLSOptions)
CommandEnvironment env, OptionsT besOptions, AuthAndTLSOptions authAndTLSOptions)
throws IOException, OptionsParsingException;

protected abstract void clearBesClient();
Expand Down
120 changes: 28 additions & 92 deletions src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions.UnresolvedScopedCredentialHelper;
import com.google.devtools.build.lib.authandtls.CallCredentialsProvider;
import com.google.devtools.build.lib.authandtls.GoogleAuthUtils;
import com.google.devtools.build.lib.authandtls.Netrc;
import com.google.devtools.build.lib.authandtls.NetrcCredentials;
import com.google.devtools.build.lib.authandtls.NetrcParser;
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperEnvironment;
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperProvider;
import com.google.devtools.build.lib.bazel.repository.downloader.Downloader;
Expand Down Expand Up @@ -1047,95 +1044,6 @@ RemoteActionContextProvider getActionContextProvider() {
return actionContextProvider;
}

/**
* Create a new {@link Credentials} object by parsing the .netrc file with following order to
* search it:
*
* <ol>
* <li>If environment variable $NETRC exists, use it as the path to the .netrc file
* <li>Fallback to $HOME/.netrc
* </ol>
*
* @return the {@link Credentials} object or {@code null} if there is no .netrc file.
* @throws IOException in case the credentials can't be constructed.
*/
@Nullable
@VisibleForTesting
static Credentials newCredentialsFromNetrc(Map<String, String> clientEnv, FileSystem fileSystem)
throws IOException {
String netrcFileString =
Optional.ofNullable(clientEnv.get("NETRC"))
.orElseGet(
() ->
Optional.ofNullable(clientEnv.get("HOME"))
.map(home -> home + "/.netrc")
.orElse(null));
if (netrcFileString == null) {
return null;
}

Path netrcFile = fileSystem.getPath(netrcFileString);
if (netrcFile.exists()) {
try {
Netrc netrc = NetrcParser.parseAndClose(netrcFile.getInputStream());
return new NetrcCredentials(netrc);
} catch (IOException e) {
throw new IOException(
"Failed to parse " + netrcFile.getPathString() + ": " + e.getMessage(), e);
}
} else {
return null;
}
}

/**
* Create a new {@link Credentials} with 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}
* </ol>
*
* @throws IOException in case the credentials can't be constructed.
*/
@VisibleForTesting
static Credentials newCredentials(
Map<String, String> clientEnv,
FileSystem fileSystem,
Reporter reporter,
AuthAndTLSOptions authAndTlsOptions,
RemoteOptions remoteOptions)
throws IOException {
Credentials creds = GoogleAuthUtils.newCredentials(authAndTlsOptions);

// Fallback to .netrc if it exists
if (creds == null) {
try {
creds = newCredentialsFromNetrc(clientEnv, fileSystem);
} catch (IOException e) {
reporter.handle(Event.warn(e.getMessage()));
}

try {
if (creds != null
&& remoteOptions.remoteCache != null
&& Ascii.toLowerCase(remoteOptions.remoteCache).startsWith("http://")
&& !creds.getRequestMetadata(new URI(remoteOptions.remoteCache)).isEmpty()) {
reporter.handle(
Event.warn(
"Username and password from .netrc is transmitted in plaintext to "
+ remoteOptions.remoteCache
+ ". Please consider using an HTTPS endpoint."));
}
} catch (URISyntaxException e) {
throw new IOException(e.getMessage(), e);
}
}

return creds;
}

@VisibleForTesting
static CredentialHelperProvider newCredentialHelperProvider(
CredentialHelperEnvironment environment,
Expand All @@ -1159,6 +1067,34 @@ static CredentialHelperProvider newCredentialHelperProvider(
return builder.build();
}

static Credentials newCredentials(
Map<String, String> clientEnv,
FileSystem fileSystem,
Reporter reporter,
AuthAndTLSOptions authAndTlsOptions,
RemoteOptions remoteOptions) throws IOException {
Credentials credentials =
GoogleAuthUtils.newCredentials(reporter, clientEnv, fileSystem, authAndTlsOptions);

try {
if (credentials != null
&& remoteOptions.remoteCache != null
&& Ascii.toLowerCase(remoteOptions.remoteCache).startsWith("http://")
&& !credentials.getRequestMetadata(new URI(remoteOptions.remoteCache)).isEmpty()) {
// TODO(yannic): Make this a error aborting the build.
reporter.handle(
Event.warn(
"Credentials are transmitted in plaintext to "
+ remoteOptions.remoteCache
+ ". Please consider using an HTTPS endpoint."));
}
} catch (URISyntaxException e) {
throw new IOException(e.getMessage(), e);
}

return credentials;
}

@VisibleForTesting
@AutoValue
abstract static class ScopedCredentialHelper {
Expand Down
Loading