Skip to content

Commit

Permalink
remote: Add support for HTTP Basic Auth
Browse files Browse the repository at this point in the history
Closes #4609.

PiperOrigin-RevId: 185032751
  • Loading branch information
buchgr authored and Copybara-Service committed Feb 8, 2018
1 parent 1c6d061 commit cf3f81a
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 2 deletions.
9 changes: 9 additions & 0 deletions site/docs/remote-caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ make builds significantly faster.
* [Bazel Remote Cache](#bazel-remote-cache)
* [Google Cloud Storage](#google-cloud-storage)
* [Other servers](#other-servers)
* [Authentication](#authentication)
* [HTTP Caching Protocol](#http-caching-protocol)
* [Run Bazel using the remote cache](#run-bazel-using-the-remote-cache)
* [Read from and write to the remote cache](#read-from-and-write-to-the-remote-cache)
Expand Down Expand Up @@ -174,6 +175,14 @@ You can set up any HTTP/1.1 server that supports PUT and GET as the cache's
backend. Users have reported success with caching backends such as [Hazelcast],
[Apache httpd], and [AWS S3].

## Authentication

As of version 0.11.0 support for HTTP Basic Authentication was added to Bazel.
You can pass a username and password to Bazel via the remote cache URL. The
syntax is `https://username:password@hostname.com:port/path`. Please note that
HTTP Basic Authentication transmits username and password in plaintext over the
network and it's thus critical to always use it with HTTPS.

## HTTP Caching Protocol

Bazel supports remote caching via HTTP/1.1. The protocol is conceptually simple:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
package com.google.devtools.build.lib.remote.blobstore.http;

import com.google.auth.Credentials;
import com.google.common.base.Charsets;
import com.google.common.io.BaseEncoding;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import java.io.IOException;
Expand Down Expand Up @@ -53,6 +56,12 @@ protected void succeedAndResetUserPromise() {
}

protected void addCredentialHeaders(HttpRequest request, URI uri) throws IOException {
String userInfo = uri.getUserInfo();
if (userInfo != null) {
String value = BaseEncoding.base64Url().encode(userInfo.getBytes(Charsets.UTF_8));
request.headers().set(HttpHeaderNames.AUTHORIZATION, "Basic " + value);
return;
}
if (credentials == null || !credentials.hasRequestMetadata()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2018 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.remote.blobstore.http;

import static com.google.common.truth.Truth.assertThat;

import io.netty.channel.ChannelPromise;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpRequest;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;

/** Tests for {@link AbstractHttpHandlerTest}. */
@RunWith(JUnit4.class)
public abstract class AbstractHttpHandlerTest {

@Test
public void basicAuthShouldWork() throws Exception {
URI uri = new URI("http://user:password@does.not.exist/foo");
EmbeddedChannel ch = new EmbeddedChannel(new HttpDownloadHandler(null));
ByteArrayOutputStream out = Mockito.spy(new ByteArrayOutputStream());
DownloadCommand cmd = new DownloadCommand(uri, true, "abcdef", new ByteArrayOutputStream());
ChannelPromise writePromise = ch.newPromise();
ch.writeOneOutbound(cmd, writePromise);

HttpRequest request = ch.readOutbound();
assertThat(request.headers().get(HttpHeaderNames.AUTHORIZATION))
.isEqualTo("Basic dXNlcjpwYXNzd29yZA==");
}

@Test
public void basicAuthShouldNotEnabled() throws Exception {
URI uri = new URI("http://does.not.exist/foo");
EmbeddedChannel ch = new EmbeddedChannel(new HttpDownloadHandler(null));
ByteArrayOutputStream out = Mockito.spy(new ByteArrayOutputStream());
DownloadCommand cmd = new DownloadCommand(uri, true, "abcdef", new ByteArrayOutputStream());
ChannelPromise writePromise = ch.newPromise();
ch.writeOneOutbound(cmd, writePromise);

HttpRequest request = ch.readOutbound();
assertThat(request.headers().contains(HttpHeaderNames.AUTHORIZATION)).isFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

/** Tests for {@link HttpDownloadHandler}. */
@RunWith(JUnit4.class)
public class HttpDownloadHandlerTest {
public class HttpDownloadHandlerTest extends AbstractHttpHandlerTest {

private static final URI CACHE_URI = URI.create("http://storage.googleapis.com:80/cache-bucket");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

/** Tests for {@link HttpUploadHandler}. */
@RunWith(JUnit4.class)
public class HttpUploadHandlerTest {
public class HttpUploadHandlerTest extends AbstractHttpHandlerTest {

private static final URI CACHE_URI = URI.create("http://storage.googleapis.com:80/cache-bucket");

Expand Down

0 comments on commit cf3f81a

Please sign in to comment.