Skip to content

Commit

Permalink
Implementation of aws-sig-v4 HTTP auth for S3 caching
Browse files Browse the repository at this point in the history
  • Loading branch information
GregBowyer authored and borkaehw committed Aug 6, 2019
1 parent 9ac5dfa commit 1967b90
Show file tree
Hide file tree
Showing 18 changed files with 1,313 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ site/* linguist-documentation

# Files that should not use CRLF line endings, even on Windows.
tools/genrule/genrule-setup.sh -text
third_party/aws-sig-v4-test-suite/**/*.authz -text
third_party/aws-sig-v4-test-suite/**/*.creq -text
third_party/aws-sig-v4-test-suite/**/*.req -text
third_party/aws-sig-v4-test-suite/**/*.sreq -text
third_party/aws-sig-v4-test-suite/**/*.sts -text
2 changes: 1 addition & 1 deletion scripts/bootstrap/compile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ if [ "$ERROR_PRONE_INDEX" -lt "$GUAVA_INDEX" ]; then
LIBRARY_JARS="${LIBRARY_JARS_ARRAY[*]}"
fi

DIRS=$(echo src/{java_tools/singlejar/java/com/google/devtools/build/zip,main/java} tools/java/runfiles third_party/java/dd_plist/java ${OUTPUT_DIR}/src)
DIRS=$(echo src/{java_tools/singlejar/java/com/google/devtools/build/zip,main/java} tools/java/runfiles third_party/java/dd_plist/java third_party/aws-sdk-auth-lite ${OUTPUT_DIR}/src)
EXCLUDE_FILES="src/main/java/com/google/devtools/build/lib/server/GrpcServerImpl.java src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/testing/*"
# Exclude whole directories under the bazel src tree that bazel itself
# doesn't depend on.
Expand Down
52 changes: 51 additions & 1 deletion site/docs/remote-caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ make builds significantly faster.
* [nginx](#nginx)
* [Bazel Remote Cache](#bazel-remote-cache)
* [Google Cloud Storage](#google-cloud-storage)
* [AWS S3 Storage](#aws-s3-storage)
* [Other servers](#other-servers)
* [Authentication](#authentication)
* [HTTP Caching Protocol](#http-caching-protocol)
Expand Down Expand Up @@ -98,6 +99,7 @@ include:
* [nginx](#nginx)
* [Bazel Remote Cache](#bazel-remote-cache)
* [Google Cloud Storage](#google-cloud-storage)
* [AWS S3 Storage](#aws-s3-storage)

### nginx

Expand Down Expand Up @@ -173,11 +175,59 @@ to/from your GCS bucket.
5. You can configure Cloud Storage to automatically delete old files. To do so, see
[Managing Object Lifecycles](https://cloud.google.com/storage/docs/managing-lifecycles).

### AWS S3 Storage

[AWS S3] is a fully managed object store which provides an
HTTP API that is compatible with Bazel's remote caching protocol. It requires
that you have an AWS account.

To use S3 as the cache:

1. [Create a bucket](https://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html).
Ensure that you select a bucket location that's closest to you, as network bandwidth
is important for the remote cache.

2. Generate the relevant auth token and pass it to Bazel for authentication.
Store the key securely, as anyone with the key can read and write arbitrary data
to/from your S3 bucket.

3. Connect to S3 by adding the following flags to your Bazel command:
* Pass the following URL to Bazel by using the flag:
`--remote_http_cache=https://bucket-name.s3-website-region.amazonaws.com`
where `bucket-name` is the name of your storage bucket, and `region` is the
name of the chosen region. You will need to use the HTTP/HTTPS url that
points to your bucket. Presently `s3://` style urls are _not_ supported due
to a lack of any consistent standard.
* Pass the authentication key using the flags:
`--aws_access_key_id=$ACCESS_KEY`
`--aws_secret_access_key=$SECRET_KEY`
Alternatively, AWS credentials can be configured using standard methods
such as instance roles and environment variables. Use this flag to
authenticate with instance credentials: `--aws_default_credentials`
A specific credentials profile can be specified using `--aws_profile=$PROFILE`

4. You can configure S3 to automatically delete old files. To do so, see
[Object Lifecycle Management](https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lifecycle-mgmt.html)

#### Virtual hosted and accelerated buckets

Buckets that have been configured under a virtual host name, or are using the s3
cloudfront acceleration proxy require some additional configuration.

These buckets cannot determine the region that should be used from the URL alone.
to specify the region for these buckets directly use the
`--aws_region=$AWS_REGION` flag.

For those using s3 accelerated buckets, the remote http cache url will be of the
following form:
`--remote_http_cache=https://bucket-name.s3-accelerate.amazonaws.com` where
`bucket-name` is the name of the bucket to use.

### Other servers

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].
[AWS S3] and [Apache httpd].

## Authentication

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/google/devtools/build/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ filegroup(
"//src/main/java/com/google/devtools/build/lib/analysis/skylark/annotations:srcs",
"//src/main/java/com/google/devtools/build/lib/analysis/skylark/annotations/processor:srcs",
"//src/main/java/com/google/devtools/build/lib/authentication:srcs",
"//src/main/java/com/google/devtools/build/lib/authentication/aws:srcs",
"//src/main/java/com/google/devtools/build/lib/authentication/google:srcs",
"//src/main/java/com/google/devtools/build/lib/grpc:srcs",
"//src/main/java/com/google/devtools/build/lib/bazel/repository/cache:srcs",
Expand Down Expand Up @@ -842,6 +843,7 @@ java_library(
":bazel-rules",
":bazel/BazelRepositoryModule",
":build-base",
"//src/main/java/com/google/devtools/build/lib/authentication/aws",
"//src/main/java/com/google/devtools/build/lib/authentication/google",
"//src/main/java/com/google/devtools/build/lib/bazel/debug:workspace-rule-module",
"//src/main/java/com/google/devtools/build/lib/buildeventservice",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.google.devtools.build.lib.authentication.aws;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSCredentialsProviderChain;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.remote.options.RemoteOptions;
import com.google.devtools.build.lib.runtime.AuthHeaderRequest;
import com.google.devtools.build.lib.runtime.AuthHeadersProvider;
import com.google.devtools.build.lib.runtime.BlazeModule;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.runtime.ServerBuilder;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParsingResult;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

public class AwsAuthModule extends BlazeModule {

private static final String SERVICE = "s3";
private final AuthHeadersProviderDelegate delegate = new AuthHeadersProviderDelegate();

@Override
public void serverInit(OptionsParsingResult startupOptions, ServerBuilder builder)
throws AbruptExitException {
super.serverInit(startupOptions, builder);
builder.addAuthHeadersProvider("aws", delegate);
}

@Override
public void beforeCommand(CommandEnvironment env) throws AbruptExitException {
delegate.setDelegate(null);

final AwsAuthOptions opts = env.getOptions().getOptions(AwsAuthOptions.class);
final RemoteOptions remoteOpts = env.getOptions().getOptions(RemoteOptions.class);
if (remoteOpts == null || opts == null) {
return;
}

final AwsRegion region = AwsRegion.determineRegion(opts.awsRegion, remoteOpts.remoteCache);
if (region == null) {
return;
}

final AWSCredentialsProvider credsProvider = newCredsProvider(opts);
if (credsProvider != null) {
this.delegate.setDelegate(new AwsV4AuthHeadersProvider(region, SERVICE, credsProvider, true));
}
}

@Override
public Iterable<Class<? extends OptionsBase>> getCommandOptions(final Command command) {
return "build".equals(command.name())
? ImmutableList.of(AwsAuthOptions.class)
: ImmutableList.of();
}

@Nullable
private static AWSCredentialsProvider newCredsProvider(final AwsAuthOptions opts)
throws AbruptExitException {
final ImmutableList.Builder<AWSCredentialsProvider> creds = ImmutableList.builder();

if (opts.awsAccessKeyId != null || opts.awsSecretAccessKey != null) {
ensure(opts.awsAccessKeyId != null, "AWS Access key provided, but missing Secret Key");
ensure(opts.awsSecretAccessKey != null, "AWS Secret key provided, but missing Access Key");

final BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(
opts.awsAccessKeyId, opts.awsSecretAccessKey);
creds.add(new AWSStaticCredentialsProvider(basicAWSCredentials));
}

if (opts.awsProfile != null) {
creds.add(new ProfileCredentialsProvider(opts.awsProfile));
}

if (opts.useAwsDefaultCredentials) {
creds.add(DefaultAWSCredentialsProviderChain.getInstance());
}

final List<AWSCredentialsProvider> providers = creds.build();
return providers.isEmpty() ? null : new AWSCredentialsProviderChain(providers);
}

private static void ensure(final boolean condition, final String msg) throws AbruptExitException {
if (!condition) {
throw new AbruptExitException(msg, ExitCode.COMMAND_LINE_ERROR);
}
}

private static class AuthHeadersProviderDelegate implements AuthHeadersProvider {

private volatile AuthHeadersProvider delegate;

public void setDelegate(AuthHeadersProvider delegate) {
this.delegate = delegate;
}

@Override
public String getType() {
return delegate.getType();
}

@Override
public Map<String, List<String>> getRequestHeaders(AuthHeaderRequest request) throws IOException {
Preconditions.checkState(delegate != null, "delegate has not been initialized");
return delegate.getRequestHeaders(request);
}

@Override
public void refresh() throws IOException {
Preconditions.checkState(delegate != null, "delegate has not been initialized");
delegate.refresh();
}

@Override
public boolean isEnabled() {
return delegate != null && delegate.isEnabled();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.google.devtools.build.lib.authentication.aws;

import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
import com.google.devtools.common.options.OptionsBase;

public class AwsAuthOptions extends OptionsBase {

@Option(
name = "aws_default_credentials",
defaultValue = "false",
category = "remote",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.UNKNOWN},
help =
"Whether to use 'AWS Default Credentials' for authentication."
+ "See https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html"
+ " for details. Disabled by default."
)
public boolean useAwsDefaultCredentials;

@Option(
name = "aws_access_key_id",
defaultValue = "null",
category = "remote",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.UNKNOWN},
help = "Use a specific AWS_ACCESS_KEY_ID for authentication"
)
public String awsAccessKeyId;

@Option(
name = "aws_secret_access_key",
defaultValue = "null",
category = "remote",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.UNKNOWN},
help = "Use a specific AWS_SECRET_ACCESS_KEY for authentication"
)
public String awsSecretAccessKey;

@Option(
name = "aws_profile",
defaultValue = "null",
category = "remote",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.UNKNOWN},
help = "Use a specific profile for credentials"
)
public String awsProfile;

@Option(
name = "aws_region",
defaultValue = "null",
category = "remote",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.UNKNOWN},
help = "Override AWS region detection and force a specific bucket region"
)
public String awsRegion;


}
Loading

0 comments on commit 1967b90

Please sign in to comment.