Skip to content

Commit 6714c30

Browse files
Yanniccopybara-github
authored andcommitted
[credentialhelper] Implement invoking credential helper as subprocess
Progress on https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md Progress on #15856 Closes #15861. PiperOrigin-RevId: 461159351 Change-Id: I28eb4817ced8db8f095a1f35092fdefba28e0ede
1 parent 6a6d1b2 commit 6714c30

File tree

7 files changed

+439
-2
lines changed

7 files changed

+439
-2
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ java_library(
1414
name = "credentialhelper",
1515
srcs = glob(["*.java"]),
1616
deps = [
17+
"//src/main/java/com/google/devtools/build/lib/events",
18+
"//src/main/java/com/google/devtools/build/lib/shell",
1719
"//src/main/java/com/google/devtools/build/lib/vfs",
1820
"//third_party:auto_value",
1921
"//third_party:error_prone_annotations",

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

+115-1
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,32 @@
1414

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

17+
import static java.nio.charset.StandardCharsets.UTF_8;
18+
1719
import com.google.common.annotations.VisibleForTesting;
1820
import com.google.common.base.Preconditions;
21+
import com.google.common.collect.ImmutableList;
22+
import com.google.common.io.CharStreams;
23+
import com.google.devtools.build.lib.shell.Subprocess;
24+
import com.google.devtools.build.lib.shell.SubprocessBuilder;
1925
import com.google.devtools.build.lib.vfs.Path;
2026
import com.google.errorprone.annotations.Immutable;
27+
import com.google.gson.Gson;
28+
import com.google.gson.JsonSyntaxException;
29+
import java.io.IOException;
30+
import java.io.InputStreamReader;
31+
import java.io.OutputStreamWriter;
32+
import java.io.Reader;
33+
import java.io.Writer;
34+
import java.net.URI;
35+
import java.util.Locale;
36+
import java.util.Objects;
2137

2238
/** Wraps an external tool used to obtain credentials. */
2339
@Immutable
2440
public final class CredentialHelper {
41+
private static final Gson GSON = new Gson();
42+
2543
// `Path` is immutable, but not annotated.
2644
@SuppressWarnings("Immutable")
2745
private final Path path;
@@ -35,5 +53,101 @@ Path getPath() {
3553
return path;
3654
}
3755

38-
// TODO(yannic): Implement running the helper subprocess.
56+
/**
57+
* Fetches credentials for the specified {@link URI} by invoking the credential helper as
58+
* subprocess according to the <a
59+
* href="https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md">credential
60+
* helper protocol</a>.
61+
*
62+
* @param environment The environment to run the subprocess in.
63+
* @param uri The {@link URI} to fetch credentials for.
64+
* @return The response from the subprocess.
65+
*/
66+
public GetCredentialsResponse getCredentials(CredentialHelperEnvironment environment, URI uri)
67+
throws InterruptedException, IOException {
68+
Preconditions.checkNotNull(environment);
69+
Preconditions.checkNotNull(uri);
70+
71+
Subprocess process = spawnSubprocess(environment, "get");
72+
try (Reader stdout = new InputStreamReader(process.getInputStream(), UTF_8);
73+
Reader stderr = new InputStreamReader(process.getErrorStream(), UTF_8)) {
74+
try (Writer stdin = new OutputStreamWriter(process.getOutputStream(), UTF_8)) {
75+
GSON.toJson(GetCredentialsRequest.newBuilder().setUri(uri).build(), stdin);
76+
}
77+
78+
process.waitFor();
79+
if (process.timedout()) {
80+
throw new IOException(
81+
String.format(
82+
Locale.US,
83+
"Failed to get credentials for '%s' from helper '%s': process timed out",
84+
uri,
85+
path));
86+
}
87+
if (process.exitValue() != 0) {
88+
throw new IOException(
89+
String.format(
90+
Locale.US,
91+
"Failed to get credentials for '%s' from helper '%s': process exited with code %d."
92+
+ " stderr: %s",
93+
uri,
94+
path,
95+
process.exitValue(),
96+
CharStreams.toString(stderr)));
97+
}
98+
99+
try {
100+
GetCredentialsResponse response = GSON.fromJson(stdout, GetCredentialsResponse.class);
101+
if (response == null) {
102+
throw new IOException(
103+
String.format(
104+
Locale.US,
105+
"Failed to get credentials for '%s' from helper '%s': process exited without"
106+
+ " output. stderr: %s",
107+
uri,
108+
path,
109+
CharStreams.toString(stderr)));
110+
}
111+
return response;
112+
} catch (JsonSyntaxException e) {
113+
throw new IOException(
114+
String.format(
115+
Locale.US,
116+
"Failed to get credentials for '%s' from helper '%s': error parsing output. stderr:"
117+
+ " %s",
118+
uri,
119+
path,
120+
CharStreams.toString(stderr)),
121+
e);
122+
}
123+
}
124+
}
125+
126+
private Subprocess spawnSubprocess(CredentialHelperEnvironment environment, String... args)
127+
throws IOException {
128+
Preconditions.checkNotNull(environment);
129+
Preconditions.checkNotNull(args);
130+
131+
return new SubprocessBuilder()
132+
.setArgv(ImmutableList.<String>builder().add(path.getPathString()).add(args).build())
133+
.setWorkingDirectory(environment.getWorkspacePath().getPathFile())
134+
.setEnv(environment.getClientEnvironment())
135+
.setTimeoutMillis(environment.getHelperExecutionTimeout().toMillis())
136+
.start();
137+
}
138+
139+
@Override
140+
public boolean equals(Object o) {
141+
if (o instanceof CredentialHelper) {
142+
CredentialHelper that = (CredentialHelper) o;
143+
return Objects.equals(this.getPath(), that.getPath());
144+
}
145+
146+
return false;
147+
}
148+
149+
@Override
150+
public int hashCode() {
151+
return Objects.hashCode(getPath());
152+
}
39153
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.authandtls.credentialhelper;
16+
17+
import com.google.auto.value.AutoValue;
18+
import com.google.common.collect.ImmutableMap;
19+
import com.google.devtools.build.lib.events.Reporter;
20+
import com.google.devtools.build.lib.vfs.Path;
21+
import java.time.Duration;
22+
23+
/** Environment for running {@link CredentialHelper}s in. */
24+
@AutoValue
25+
public abstract class CredentialHelperEnvironment {
26+
/** Returns the reporter for reporting events related to {@link CredentialHelper}s. */
27+
public abstract Reporter getEventReporter();
28+
29+
/**
30+
* Returns the (absolute) path to the workspace.
31+
*
32+
* <p>Used as working directory when invoking the subprocess.
33+
*/
34+
public abstract Path getWorkspacePath();
35+
36+
/**
37+
* Returns the environment from the Bazel client.
38+
*
39+
* <p>Passed as environment variables to the subprocess.
40+
*/
41+
public abstract ImmutableMap<String, String> getClientEnvironment();
42+
43+
/** Returns the execution timeout for the helper subprocess. */
44+
public abstract Duration getHelperExecutionTimeout();
45+
46+
/** Returns a new builder for {@link CredentialHelperEnvironment}. */
47+
public static CredentialHelperEnvironment.Builder newBuilder() {
48+
return new AutoValue_CredentialHelperEnvironment.Builder();
49+
}
50+
51+
/** Builder for {@link CredentialHelperEnvironment}. */
52+
@AutoValue.Builder
53+
public abstract static class Builder {
54+
/** Sets the reporter for reporting events related to {@link CredentialHelper}s. */
55+
public abstract Builder setEventReporter(Reporter reporter);
56+
57+
/**
58+
* Sets the (absolute) path to the workspace to use as working directory when invoking the
59+
* subprocess.
60+
*/
61+
public abstract Builder setWorkspacePath(Path path);
62+
63+
/**
64+
* Sets the environment from the Bazel client to pass as environment variables to the
65+
* subprocess.
66+
*/
67+
public abstract Builder setClientEnvironment(ImmutableMap<String, String> environment);
68+
69+
/** Sets the execution timeout for the helper subprocess. */
70+
public abstract Builder setHelperExecutionTimeout(Duration timeout);
71+
72+
/** Returns the newly constructed {@link CredentialHelperEnvironment}. */
73+
public abstract CredentialHelperEnvironment build();
74+
}
75+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public static Builder newBuilder() {
5050
/** Builder for {@link GetCredentialsResponse}. */
5151
@AutoValue.Builder
5252
public abstract static class Builder {
53-
protected abstract ImmutableMap.Builder<String, ImmutableList<String>> headersBuilder();
53+
public abstract ImmutableMap.Builder<String, ImmutableList<String>> headersBuilder();
5454

5555
/** Returns the newly constructed {@link GetCredentialsResponse}. */
5656
public abstract GetCredentialsResponse build();

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

+11
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,29 @@ filegroup(
1717
java_test(
1818
name = "credentialhelper",
1919
srcs = glob(["*.java"]),
20+
data = [
21+
":test_credential_helper",
22+
],
2023
test_class = "com.google.devtools.build.lib.AllTests",
2124
runtime_deps = [
2225
"//src/test/java/com/google/devtools/build/lib:test_runner",
2326
],
2427
deps = [
2528
"//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper",
29+
"//src/main/java/com/google/devtools/build/lib/events",
30+
"//src/main/java/com/google/devtools/build/lib/util:os",
2631
"//src/main/java/com/google/devtools/build/lib/vfs",
2732
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
2833
"//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs",
2934
"//third_party:gson",
3035
"//third_party:guava",
3136
"//third_party:junit4",
3237
"//third_party:truth",
38+
"@bazel_tools//tools/java/runfiles",
3339
],
3440
)
41+
42+
py_binary(
43+
name = "test_credential_helper",
44+
srcs = ["test_credential_helper.py"],
45+
)

0 commit comments

Comments
 (0)