Skip to content

Commit 4e82474

Browse files
committed
[credentialhelper] Add command-line flag for configuring
Progress on bazelbuild#15856 RELNOTES: TODO(yannic): Add release notes for credential helper.
1 parent 33516e2 commit 4e82474

File tree

10 files changed

+484
-34
lines changed

10 files changed

+484
-34
lines changed

src/main/java/com/google/devtools/build/lib/authandtls/AuthAndTLSOptions.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,24 @@ public class AuthAndTLSOptions extends OptionsBase {
131131
+ "granularity; it is an error to set a value less than one second. If keep-alive "
132132
+ "pings are disabled, then this setting is ignored.")
133133
public Duration grpcKeepaliveTimeout;
134+
135+
@Option(
136+
name = "credential_helper",
137+
defaultValue = "null",
138+
allowMultiple = true,
139+
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
140+
effectTags = {OptionEffectTag.UNKNOWN},
141+
help = "TODO")
142+
public List<String> credentialHelpers;
143+
144+
@Option(
145+
name = "credential_helper_timeout",
146+
defaultValue = "5s",
147+
converter = DurationConverter.class,
148+
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
149+
effectTags = {OptionEffectTag.UNKNOWN},
150+
help =
151+
"Configures the timeout for the Credential Helper.\n\n"
152+
+ "Credential Helpers failing to respond within this timeout will fail the invocation.")
153+
public Duration credentialHelperTimeout;
134154
}

src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ java_library(
1717
"//src/main/java/com/google/devtools/build/lib/events",
1818
"//src/main/java/com/google/devtools/build/lib/shell",
1919
"//src/main/java/com/google/devtools/build/lib/vfs",
20+
"//third_party:auth",
2021
"//third_party:auto_value",
2122
"//third_party:error_prone_annotations",
2223
"//third_party:gson",

src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public final class CredentialHelper {
4949
}
5050

5151
@VisibleForTesting
52-
Path getPath() {
52+
public Path getPath() {
5353
return path;
5454
}
5555

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.google.devtools.build.lib.authandtls.credentialhelper;
2+
3+
import com.google.auth.Credentials;
4+
import com.google.common.base.Preconditions;
5+
import com.google.common.collect.ImmutableMap;
6+
import java.io.IOException;
7+
import java.net.URI;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Optional;
11+
12+
/**
13+
* Implementation of {@link Credentials} which fetches credentials by invoking a {@code credential
14+
* helper} as subprocess.
15+
*/
16+
public class CredentialHelperCredentials extends Credentials {
17+
private final CredentialHelperProvider credentialHelperProvider;
18+
private final CredentialHelperEnvironment credentialHelperEnvironment;
19+
private final Optional<Credentials> fallbackCredentials;
20+
21+
public CredentialHelperCredentials(
22+
CredentialHelperProvider credentialHelperProvider,
23+
CredentialHelperEnvironment credentialHelperEnvironment,
24+
Optional<Credentials> fallbackCredentials) {
25+
this.credentialHelperProvider = Preconditions.checkNotNull(credentialHelperProvider);
26+
this.credentialHelperEnvironment = Preconditions.checkNotNull(credentialHelperEnvironment);
27+
this.fallbackCredentials = Preconditions.checkNotNull(fallbackCredentials);
28+
}
29+
30+
@Override
31+
public String getAuthenticationType() {
32+
if (fallbackCredentials.isPresent()) {
33+
return "credential-helper-with-fallback-" + fallbackCredentials.get().getAuthenticationType();
34+
}
35+
36+
return "credential-helper";
37+
}
38+
39+
@Override
40+
public Map<String, List<String>> getRequestMetadata(URI uri) throws IOException {
41+
Preconditions.checkNotNull(uri);
42+
43+
Optional<Map<String, List<String>>> credentials =
44+
getRequestMetadataFromCredentialHelper(uri);
45+
if (credentials.isPresent()) {
46+
return credentials.get();
47+
}
48+
49+
if (fallbackCredentials.isPresent()) {
50+
return fallbackCredentials.get().getRequestMetadata(uri);
51+
}
52+
53+
return ImmutableMap.of();
54+
}
55+
56+
private Optional<Map<String, List<String>>> getRequestMetadataFromCredentialHelper(
57+
URI uri) throws IOException {
58+
Preconditions.checkNotNull(uri);
59+
60+
Optional<CredentialHelper> maybeCredentialHelper = credentialHelperProvider.findCredentialHelper(uri);
61+
if (!maybeCredentialHelper.isPresent()) {
62+
return Optional.empty();
63+
}
64+
CredentialHelper credentialHelper = maybeCredentialHelper.get();
65+
66+
GetCredentialsResponse response;
67+
try {
68+
response = credentialHelper.getCredentials(credentialHelperEnvironment, uri);
69+
} catch (InterruptedException e) {
70+
throw new RuntimeException(e);
71+
}
72+
73+
// The cast is needed to convert value type of map from `ImmutableList` to `List`.
74+
return Optional.of((Map)response.getHeaders());
75+
}
76+
77+
@Override
78+
public boolean hasRequestMetadata() {
79+
return true;
80+
}
81+
82+
@Override
83+
public boolean hasRequestMetadataOnly() {
84+
return false;
85+
}
86+
87+
@Override
88+
public void refresh() throws IOException {
89+
if (fallbackCredentials.isPresent()) {
90+
fallbackCredentials.get().refresh();
91+
}
92+
}
93+
}

src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelperProvider.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,17 @@ public Builder add(String pattern, Path helper) throws IOException {
170170
return this;
171171
}
172172

173+
public Builder add(Optional<String> pattern, Path helper) throws IOException {
174+
Preconditions.checkNotNull(pattern);
175+
Preconditions.checkNotNull(helper);
176+
177+
if (pattern.isPresent()) {
178+
return add(pattern.get(), helper);
179+
} else {
180+
return add(helper);
181+
}
182+
}
183+
173184
/** Converts a pattern to Punycode (see https://en.wikipedia.org/wiki/Punycode). */
174185
private final String toPunycodePattern(String pattern) {
175186
Preconditions.checkNotNull(pattern);

src/main/java/com/google/devtools/build/lib/remote/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ java_library(
5959
"//src/main/java/com/google/devtools/build/lib/analysis:top_level_artifact_context",
6060
"//src/main/java/com/google/devtools/build/lib/analysis/platform:platform_utils",
6161
"//src/main/java/com/google/devtools/build/lib/authandtls",
62+
"//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper",
6263
"//src/main/java/com/google/devtools/build/lib/bazel/repository/downloader",
6364
"//src/main/java/com/google/devtools/build/lib/buildeventstream",
6465
"//src/main/java/com/google/devtools/build/lib/clock",
@@ -90,6 +91,7 @@ java_library(
9091
"//src/main/java/com/google/devtools/build/lib/sandbox:sandbox_helpers",
9192
"//src/main/java/com/google/devtools/build/lib/skyframe:mutable_supplier",
9293
"//src/main/java/com/google/devtools/build/lib/skyframe:tree_artifact_value",
94+
"//src/main/java/com/google/devtools/build/lib/util",
9395
"//src/main/java/com/google/devtools/build/lib/util:abrupt_exit_exception",
9496
"//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code",
9597
"//src/main/java/com/google/devtools/build/lib/util:exit_code",
@@ -102,6 +104,7 @@ java_library(
102104
"//src/main/java/com/google/devtools/common/options",
103105
"//src/main/protobuf:failure_details_java_proto",
104106
"//third_party:auth",
107+
"//third_party:auto_value",
105108
"//third_party:caffeine",
106109
"//third_party:flogger",
107110
"//third_party:guava",

src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java

Lines changed: 104 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import build.bazel.remote.execution.v2.DigestFunction;
2020
import build.bazel.remote.execution.v2.ServerCapabilities;
2121
import com.google.auth.Credentials;
22+
import com.google.auto.value.AutoValue;
2223
import com.google.common.annotations.VisibleForTesting;
2324
import com.google.common.base.Ascii;
2425
import com.google.common.base.Preconditions;
@@ -50,6 +51,9 @@
5051
import com.google.devtools.build.lib.authandtls.Netrc;
5152
import com.google.devtools.build.lib.authandtls.NetrcCredentials;
5253
import com.google.devtools.build.lib.authandtls.NetrcParser;
54+
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperCredentials;
55+
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperEnvironment;
56+
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperProvider;
5357
import com.google.devtools.build.lib.bazel.repository.downloader.Downloader;
5458
import com.google.devtools.build.lib.buildeventstream.BuildEventArtifactUploader;
5559
import com.google.devtools.build.lib.buildeventstream.LocalFilesArtifactUploader;
@@ -78,6 +82,7 @@
7882
import com.google.devtools.build.lib.runtime.BuildEventArtifactUploaderFactory;
7983
import com.google.devtools.build.lib.runtime.Command;
8084
import com.google.devtools.build.lib.runtime.CommandEnvironment;
85+
import com.google.devtools.build.lib.runtime.CommandLinePathFactory;
8186
import com.google.devtools.build.lib.runtime.RepositoryRemoteExecutor;
8287
import com.google.devtools.build.lib.runtime.RepositoryRemoteExecutorFactory;
8388
import com.google.devtools.build.lib.runtime.ServerBuilder;
@@ -103,7 +108,6 @@
103108
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
104109
import java.io.IOException;
105110
import java.net.URI;
106-
import java.net.URISyntaxException;
107111
import java.util.HashSet;
108112
import java.util.List;
109113
import java.util.Map;
@@ -217,12 +221,12 @@ private void initHttpAndDiskCache(
217221
try {
218222
creds =
219223
newCredentials(
220-
env.getClientEnv(),
224+
env.getCommandLinePathFactory(),
225+
newCredentialHelperEnvironment(env, authAndTlsOptions),
221226
env.getRuntime().getFileSystem(),
222-
env.getReporter(),
223227
authAndTlsOptions,
224228
remoteOptions);
225-
} catch (IOException e) {
229+
} catch (IOException | IllegalArgumentException e) {
226230
handleInitFailure(env, e, Code.CREDENTIALS_INIT_FAILURE);
227231
return;
228232
}
@@ -432,12 +436,12 @@ public void beforeCommand(CommandEnvironment env) throws AbruptExitException {
432436
callCredentialsProvider =
433437
GoogleAuthUtils.newCallCredentialsProvider(
434438
newCredentials(
435-
env.getClientEnv(),
439+
env.getCommandLinePathFactory(),
440+
newCredentialHelperEnvironment(env, authAndTlsOptions),
436441
env.getRuntime().getFileSystem(),
437-
env.getReporter(),
438442
authAndTlsOptions,
439443
remoteOptions));
440-
} catch (IOException e) {
444+
} catch (IOException | IllegalArgumentException e) {
441445
handleInitFailure(env, e, Code.CREDENTIALS_INIT_FAILURE);
442446
return;
443447
}
@@ -645,7 +649,7 @@ public void beforeCommand(CommandEnvironment env) throws AbruptExitException {
645649
}
646650

647651
private static void handleInitFailure(
648-
CommandEnvironment env, IOException e, Code remoteExecutionCode) {
652+
CommandEnvironment env, Exception e, Code remoteExecutionCode) {
649653
env.getReporter().handle(Event.error(e.getMessage()));
650654
env.getBlazeModuleEnvironment()
651655
.exit(
@@ -1096,38 +1100,108 @@ static Credentials newCredentialsFromNetrc(Map<String, String> clientEnv, FileSy
10961100
*/
10971101
@VisibleForTesting
10981102
static Credentials newCredentials(
1099-
Map<String, String> clientEnv,
1103+
CommandLinePathFactory commandLinePathFactory,
1104+
CredentialHelperEnvironment credentialHelperEnvironment,
11001105
FileSystem fileSystem,
1101-
Reporter reporter,
11021106
AuthAndTLSOptions authAndTlsOptions,
11031107
RemoteOptions remoteOptions)
11041108
throws IOException {
1105-
Credentials creds = GoogleAuthUtils.newCredentials(authAndTlsOptions);
1109+
// TODO(yannic): Change `GoogleAuthUtils.newCredentials` to return `Optional`.
1110+
Optional<Credentials> creds = Optional.ofNullable(GoogleAuthUtils.newCredentials(authAndTlsOptions));
1111+
creds = creds.or(() -> {
1112+
// Fallback to .netrc if it exists.
11061113

1107-
// Fallback to .netrc if it exists
1108-
if (creds == null) {
11091114
try {
1110-
creds = newCredentialsFromNetrc(clientEnv, fileSystem);
1115+
// TODO(yannic): Change `newCredentialsFromNetrc` to return `Optional`.
1116+
return Optional.ofNullable(
1117+
newCredentialsFromNetrc(
1118+
credentialHelperEnvironment.getClientEnvironment(), fileSystem));
11111119
} catch (IOException e) {
1112-
reporter.handle(Event.warn(e.getMessage()));
1120+
credentialHelperEnvironment.getEventReporter().handle(Event.warn(e.getMessage()));
1121+
return Optional.empty();
11131122
}
1114-
1115-
try {
1116-
if (creds != null
1117-
&& remoteOptions.remoteCache != null
1118-
&& Ascii.toLowerCase(remoteOptions.remoteCache).startsWith("http://")
1119-
&& !creds.getRequestMetadata(new URI(remoteOptions.remoteCache)).isEmpty()) {
1120-
reporter.handle(
1121-
Event.warn(
1122-
"Username and password from .netrc is transmitted in plaintext to "
1123-
+ remoteOptions.remoteCache
1124-
+ ". Please consider using an HTTPS endpoint."));
1125-
}
1126-
} catch (URISyntaxException e) {
1127-
throw new IOException(e.getMessage(), e);
1123+
});
1124+
1125+
if (creds.isPresent()) {
1126+
if (remoteOptions.remoteCache != null
1127+
&& Ascii.toLowerCase(remoteOptions.remoteCache).startsWith("http://")
1128+
&& !creds.get().getRequestMetadata(URI.create(remoteOptions.remoteCache)).isEmpty()) {
1129+
credentialHelperEnvironment.getEventReporter().handle(
1130+
Event.warn(
1131+
"Username and password from .netrc is transmitted in plaintext to "
1132+
+ remoteOptions.remoteCache
1133+
+ ". Please consider using an HTTPS endpoint."));
11281134
}
11291135
}
11301136

1131-
return creds;
1137+
CredentialHelperProvider credentialHelperProvider =
1138+
newCredentialHelperProvider(
1139+
credentialHelperEnvironment,
1140+
commandLinePathFactory,
1141+
authAndTlsOptions.credentialHelpers);
1142+
return new CredentialHelperCredentials(
1143+
credentialHelperProvider, credentialHelperEnvironment, creds);
1144+
}
1145+
1146+
private CredentialHelperEnvironment newCredentialHelperEnvironment(
1147+
CommandEnvironment env, AuthAndTLSOptions authAndTlsOptions) {
1148+
Preconditions.checkNotNull(env);
1149+
Preconditions.checkNotNull(authAndTlsOptions);
1150+
1151+
return CredentialHelperEnvironment.newBuilder()
1152+
.setEventReporter(env.getReporter())
1153+
.setWorkspacePath(env.getWorkspace())
1154+
.setClientEnvironment(env.getClientEnv())
1155+
.setHelperExecutionTimeout(authAndTlsOptions.credentialHelperTimeout)
1156+
.build();
1157+
}
1158+
1159+
@VisibleForTesting
1160+
static CredentialHelperProvider newCredentialHelperProvider(
1161+
CredentialHelperEnvironment environment,
1162+
CommandLinePathFactory pathFactory,
1163+
List<String> inputs)
1164+
throws IOException {
1165+
Preconditions.checkNotNull(environment);
1166+
Preconditions.checkNotNull(pathFactory);
1167+
Preconditions.checkNotNull(inputs);
1168+
1169+
CredentialHelperProvider.Builder builder = CredentialHelperProvider.builder();
1170+
for (String input : inputs) {
1171+
ScopedCredentialHelper helper = parseCredentialHelperFlag(environment, pathFactory, input);
1172+
builder.add(helper.getScope(), helper.getPath());
1173+
}
1174+
return builder.build();
1175+
}
1176+
1177+
@VisibleForTesting
1178+
static ScopedCredentialHelper parseCredentialHelperFlag(
1179+
CredentialHelperEnvironment environment, CommandLinePathFactory pathFactory, String input)
1180+
throws IOException {
1181+
Preconditions.checkNotNull(environment);
1182+
Preconditions.checkNotNull(pathFactory);
1183+
Preconditions.checkNotNull(input);
1184+
1185+
int pos = input.indexOf('=');
1186+
if (pos > 0) {
1187+
String scope = input.substring(0, pos);
1188+
String path = input.substring(pos + 1);
1189+
return new AutoValue_RemoteModule_ScopedCredentialHelper(
1190+
Optional.of(scope), pathFactory.create(environment.getClientEnvironment(), path));
1191+
}
1192+
1193+
// `input` does not specify a scope.
1194+
return new AutoValue_RemoteModule_ScopedCredentialHelper(
1195+
Optional.empty(), pathFactory.create(environment.getClientEnvironment(), input));
1196+
}
1197+
1198+
@VisibleForTesting
1199+
@AutoValue
1200+
static abstract class ScopedCredentialHelper {
1201+
/** Returns the scope of the credential helper (if any). */
1202+
public abstract Optional<String> getScope();
1203+
1204+
/** Returns the path of the credential helper. */
1205+
public abstract Path getPath();
11321206
}
11331207
}

0 commit comments

Comments
 (0)