Skip to content

Commit

Permalink
[credentialhelper] Implement invoking credential helper as subprocess (
Browse files Browse the repository at this point in the history
…bazelbuild#15900)

Progress on https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md
Progress on bazelbuild#15856

Closes bazelbuild#15861.

PiperOrigin-RevId: 461159351
Change-Id: I28eb4817ced8db8f095a1f35092fdefba28e0ede

Co-authored-by: Yannic Bonenberger <[email protected]>
  • Loading branch information
sgowroji and Yannic authored Jul 18, 2022
1 parent dc65cff commit 7bbdddd
Show file tree
Hide file tree
Showing 7 changed files with 439 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ java_library(
name = "credentialhelper",
srcs = glob(["*.java"]),
deps = [
"//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:auto_value",
"//third_party:error_prone_annotations",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,32 @@

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

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
import com.google.devtools.build.lib.shell.Subprocess;
import com.google.devtools.build.lib.shell.SubprocessBuilder;
import com.google.devtools.build.lib.vfs.Path;
import com.google.errorprone.annotations.Immutable;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.util.Locale;
import java.util.Objects;

/** Wraps an external tool used to obtain credentials. */
@Immutable
public final class CredentialHelper {
private static final Gson GSON = new Gson();

// `Path` is immutable, but not annotated.
@SuppressWarnings("Immutable")
private final Path path;
Expand All @@ -35,5 +53,101 @@ Path getPath() {
return path;
}

// TODO(yannic): Implement running the helper subprocess.
/**
* Fetches credentials for the specified {@link URI} by invoking the credential helper as
* subprocess according to the <a
* href="https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md">credential
* helper protocol</a>.
*
* @param environment The environment to run the subprocess in.
* @param uri The {@link URI} to fetch credentials for.
* @return The response from the subprocess.
*/
public GetCredentialsResponse getCredentials(CredentialHelperEnvironment environment, URI uri)
throws InterruptedException, IOException {
Preconditions.checkNotNull(environment);
Preconditions.checkNotNull(uri);

Subprocess process = spawnSubprocess(environment, "get");
try (Reader stdout = new InputStreamReader(process.getInputStream(), UTF_8);
Reader stderr = new InputStreamReader(process.getErrorStream(), UTF_8)) {
try (Writer stdin = new OutputStreamWriter(process.getOutputStream(), UTF_8)) {
GSON.toJson(GetCredentialsRequest.newBuilder().setUri(uri).build(), stdin);
}

process.waitFor();
if (process.timedout()) {
throw new IOException(
String.format(
Locale.US,
"Failed to get credentials for '%s' from helper '%s': process timed out",
uri,
path));
}
if (process.exitValue() != 0) {
throw new IOException(
String.format(
Locale.US,
"Failed to get credentials for '%s' from helper '%s': process exited with code %d."
+ " stderr: %s",
uri,
path,
process.exitValue(),
CharStreams.toString(stderr)));
}

try {
GetCredentialsResponse response = GSON.fromJson(stdout, GetCredentialsResponse.class);
if (response == null) {
throw new IOException(
String.format(
Locale.US,
"Failed to get credentials for '%s' from helper '%s': process exited without"
+ " output. stderr: %s",
uri,
path,
CharStreams.toString(stderr)));
}
return response;
} catch (JsonSyntaxException e) {
throw new IOException(
String.format(
Locale.US,
"Failed to get credentials for '%s' from helper '%s': error parsing output. stderr:"
+ " %s",
uri,
path,
CharStreams.toString(stderr)),
e);
}
}
}

private Subprocess spawnSubprocess(CredentialHelperEnvironment environment, String... args)
throws IOException {
Preconditions.checkNotNull(environment);
Preconditions.checkNotNull(args);

return new SubprocessBuilder()
.setArgv(ImmutableList.<String>builder().add(path.getPathString()).add(args).build())
.setWorkingDirectory(environment.getWorkspacePath().getPathFile())
.setEnv(environment.getClientEnvironment())
.setTimeoutMillis(environment.getHelperExecutionTimeout().toMillis())
.start();
}

@Override
public boolean equals(Object o) {
if (o instanceof CredentialHelper) {
CredentialHelper that = (CredentialHelper) o;
return Objects.equals(this.getPath(), that.getPath());
}

return false;
}

@Override
public int hashCode() {
return Objects.hashCode(getPath());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.vfs.Path;
import java.time.Duration;

/** Environment for running {@link CredentialHelper}s in. */
@AutoValue
public abstract class CredentialHelperEnvironment {
/** Returns the reporter for reporting events related to {@link CredentialHelper}s. */
public abstract Reporter getEventReporter();

/**
* Returns the (absolute) path to the workspace.
*
* <p>Used as working directory when invoking the subprocess.
*/
public abstract Path getWorkspacePath();

/**
* Returns the environment from the Bazel client.
*
* <p>Passed as environment variables to the subprocess.
*/
public abstract ImmutableMap<String, String> getClientEnvironment();

/** Returns the execution timeout for the helper subprocess. */
public abstract Duration getHelperExecutionTimeout();

/** Returns a new builder for {@link CredentialHelperEnvironment}. */
public static CredentialHelperEnvironment.Builder newBuilder() {
return new AutoValue_CredentialHelperEnvironment.Builder();
}

/** Builder for {@link CredentialHelperEnvironment}. */
@AutoValue.Builder
public abstract static class Builder {
/** Sets the reporter for reporting events related to {@link CredentialHelper}s. */
public abstract Builder setEventReporter(Reporter reporter);

/**
* Sets the (absolute) path to the workspace to use as working directory when invoking the
* subprocess.
*/
public abstract Builder setWorkspacePath(Path path);

/**
* Sets the environment from the Bazel client to pass as environment variables to the
* subprocess.
*/
public abstract Builder setClientEnvironment(ImmutableMap<String, String> environment);

/** Sets the execution timeout for the helper subprocess. */
public abstract Builder setHelperExecutionTimeout(Duration timeout);

/** Returns the newly constructed {@link CredentialHelperEnvironment}. */
public abstract CredentialHelperEnvironment build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static Builder newBuilder() {
/** Builder for {@link GetCredentialsResponse}. */
@AutoValue.Builder
public abstract static class Builder {
protected abstract ImmutableMap.Builder<String, ImmutableList<String>> headersBuilder();
public abstract ImmutableMap.Builder<String, ImmutableList<String>> headersBuilder();

/** Returns the newly constructed {@link GetCredentialsResponse}. */
public abstract GetCredentialsResponse build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,29 @@ filegroup(
java_test(
name = "credentialhelper",
srcs = glob(["*.java"]),
data = [
":test_credential_helper",
],
test_class = "com.google.devtools.build.lib.AllTests",
runtime_deps = [
"//src/test/java/com/google/devtools/build/lib:test_runner",
],
deps = [
"//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper",
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/util:os",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
"//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs",
"//third_party:gson",
"//third_party:guava",
"//third_party:junit4",
"//third_party:truth",
"@bazel_tools//tools/java/runfiles",
],
)

py_binary(
name = "test_credential_helper",
srcs = ["test_credential_helper.py"],
)
Loading

0 comments on commit 7bbdddd

Please sign in to comment.