Add .netrc support for remote build

This PR enables .netrc support for remote build by default. Existing authentication methods take higher priority than .netrc if they are enabled by flags.

Bazel search for .netrc in the following order:
1. If environment variable `$NETRC` exists, use it as the path to the .netrc file
2. Fallback to `$HOME/.netrc`

If .netrc doesn't exists, Bazel simply go forward without authentication.

Closes #12130.

PiperOrigin-RevId: 336591061
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteModuleTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteModuleTest.java
index f9d1cea..83bb188 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteModuleTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteModuleTest.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.lib.remote;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -25,11 +26,15 @@
 import build.bazel.remote.execution.v2.ExecutionCapabilities;
 import build.bazel.remote.execution.v2.GetCapabilitiesRequest;
 import build.bazel.remote.execution.v2.ServerCapabilities;
+import com.google.auth.Credentials;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.analysis.ServerDirectories;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.analysis.config.CoreOptions;
 import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
+import com.google.devtools.build.lib.authandtls.BasicHttpAuthenticationEncoder;
 import com.google.devtools.build.lib.exec.BinTools;
 import com.google.devtools.build.lib.exec.ExecutionOptions;
 import com.google.devtools.build.lib.pkgcache.PackageOptions;
@@ -46,6 +51,7 @@
 import com.google.devtools.build.lib.testutil.Scratch;
 import com.google.devtools.build.lib.util.AbruptExitException;
 import com.google.devtools.build.lib.vfs.DigestHashFunction;
+import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
 import com.google.devtools.common.options.Options;
 import com.google.devtools.common.options.OptionsParser;
@@ -58,7 +64,10 @@
 import io.grpc.stub.StreamObserver;
 import io.grpc.util.MutableHandlerRegistry;
 import java.io.IOException;
+import java.net.URI;
 import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -442,4 +451,82 @@
       cacheServer.awaitTermination();
     }
   }
+
+  @Test
+  public void testNetrc_emptyEnv_shouldIgnore() throws Exception {
+    Map<String, String> clientEnv = ImmutableMap.of();
+    FileSystem fileSystem = new InMemoryFileSystem(DigestHashFunction.SHA256);
+
+    Credentials credentials = RemoteModule.newCredentialsFromNetrc(clientEnv, fileSystem);
+
+    assertThat(credentials).isNull();
+  }
+
+  @Test
+  public void testNetrc_netrcNotExist_shouldIgnore() throws Exception {
+    String home = "/home/foo";
+    Map<String, String> clientEnv = ImmutableMap.of("HOME", home);
+    FileSystem fileSystem = new InMemoryFileSystem(DigestHashFunction.SHA256);
+
+    Credentials credentials = RemoteModule.newCredentialsFromNetrc(clientEnv, fileSystem);
+
+    assertThat(credentials).isNull();
+  }
+
+  @Test
+  public void testNetrc_netrcExist_shouldUse() throws Exception {
+    String home = "/home/foo";
+    Map<String, String> clientEnv = ImmutableMap.of("HOME", home);
+    FileSystem fileSystem = new InMemoryFileSystem(DigestHashFunction.SHA256);
+    Scratch scratch = new Scratch(fileSystem);
+    scratch.file(home + "/.netrc", "machine foo.example.org login foouser password foopass");
+
+    Credentials credentials = RemoteModule.newCredentialsFromNetrc(clientEnv, fileSystem);
+
+    assertThat(credentials).isNotNull();
+    assertRequestMetadata(
+        credentials.getRequestMetadata(URI.create("https://foo.example.org")),
+        "foouser",
+        "foopass");
+  }
+
+  @Test
+  public void testNetrc_netrcFromNetrcEnvExist_shouldUse() throws Exception {
+    String home = "/home/foo";
+    String netrc = "/.netrc";
+    Map<String, String> clientEnv = ImmutableMap.of("HOME", home, "NETRC", netrc);
+    FileSystem fileSystem = new InMemoryFileSystem(DigestHashFunction.SHA256);
+    Scratch scratch = new Scratch(fileSystem);
+    scratch.file(home + "/.netrc", "machine foo.example.org login foouser password foopass");
+    scratch.file(netrc, "machine foo.example.org login baruser password barpass");
+
+    Credentials credentials = RemoteModule.newCredentialsFromNetrc(clientEnv, fileSystem);
+
+    assertThat(credentials).isNotNull();
+    assertRequestMetadata(
+        credentials.getRequestMetadata(URI.create("https://foo.example.org")),
+        "baruser",
+        "barpass");
+  }
+
+  @Test
+  public void testNetrc_netrcFromNetrcEnvNotExist_shouldIgnore() throws Exception {
+    String home = "/home/foo";
+    String netrc = "/.netrc";
+    Map<String, String> clientEnv = ImmutableMap.of("HOME", home, "NETRC", netrc);
+    FileSystem fileSystem = new InMemoryFileSystem(DigestHashFunction.SHA256);
+    Scratch scratch = new Scratch(fileSystem);
+    scratch.file(home + "/.netrc", "machine foo.example.org login foouser password foopass");
+
+    Credentials credentials = RemoteModule.newCredentialsFromNetrc(clientEnv, fileSystem);
+
+    assertThat(credentials).isNull();
+  }
+
+  private static void assertRequestMetadata(
+      Map<String, List<String>> requestMetadata, String username, String password) {
+    assertThat(requestMetadata.keySet()).containsExactly("Authorization");
+    assertThat(Iterables.getOnlyElement(requestMetadata.values()))
+        .containsExactly(BasicHttpAuthenticationEncoder.encode(username, password, UTF_8));
+  }
 }