Skip to content

Commit

Permalink
Add credential loading based on Configuration (either metadata server…
Browse files Browse the repository at this point in the history
… or P12 keyfiles).

By default, integration tests will not use any credentials. The jenkins
instance has already been updated with new OAuth scopes and the system
property -Dgoogle.anviltop.auth.service.account.enable can be set to
true to get metadata server based credentials.
  • Loading branch information
Angus Davis committed Oct 29, 2014
1 parent 180ec5b commit 57aa880
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 4 deletions.
36 changes: 36 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<compileSource>1.7</compileSource>
<!-- For Netty HTTP2/TLS negotiation -->
<alpn.version>7.0.0.v20140317</alpn.version>
<google.api.client.version>1.19.0</google.api.client.version>
<google.anviltop.auth.service.account.enable>false</google.anviltop.auth.service.account.enable>
</properties>
<profiles>
<profile>
Expand Down Expand Up @@ -388,8 +390,42 @@
<systemPath>${stubby.driver.path}</systemPath>
<scope>system</scope>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client-jackson2</artifactId>
<version>${google.api.client.version}</version>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client-java6</artifactId>
<version>${google.api.client.version}</version>
<exclusions>
<exclusion>
<!-- this version hides too many things -->
<artifactId>guava-jdk5</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client</artifactId>
<version>${google.api.client.version}</version>
</dependency>
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client-java6</artifactId>
<version>${google.api.client.version}</version>
</dependency>
</dependencies>
<build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<!-- enable system property substitution. -->
<filtering>true</filtering>
</testResource>
</testResources>
<pluginManagement>
<plugins>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

import org.apache.hadoop.conf.Configuration;

import java.io.IOException;
import java.security.GeneralSecurityException;

/**
* Static methods to convert an instance of {@link Configuration}
* to a {@link AnviltopOptions} instance.
Expand All @@ -30,7 +33,37 @@ public class AnvilTopOptionsFactory {
public static final String ANVILTOP_HOST_KEY = "google.anviltop.endpoint.host";
public static final String PROJECT_ID_KEY = "google.anviltop.project.id";

public static AnviltopOptions fromConfiguration(Configuration configuration) {
/**
* Key to set to enable service accounts to be used, either metadata server-based or P12-based.
* Defaults to enabled.
*/
public static final String ANVILTOP_USE_SERVICE_ACCOUNTS_KEY =
"google.anviltop.auth.service.account.enable";
public static final boolean ANVILTOP_USE_COMPUTE_CREDENTIAL_DEFAULT = true;

/**
* Key to allow unit tests to proceed with an invalid credential configuration.
*/
public static final String ANVILTOP_NULL_CREDENTIAL_ENABLE_KEY =
"google.anviltop.auth.null.credential.enable";
public static final boolean ANVILTOP_NULL_CREDENTIAL_ENABLE_DEFAULT = false;

/**
* Key to set when using P12 keyfile authentication. The value should be the service account email
* address as displayed. If this value is not set and using service accounts is enabled, a
* metadata server account will be used.
*/
public static final String ANVILTOP_SERVICE_ACCOUNT_EMAIL_KEY =
"google.anviltop.auth.service.account.email";

/**
* Key to set to a location where a P12 keyfile can be found that corresponds to the provided
* service account email address.
*/
public static final String ANVILTOP_SERVICE_ACCOUNT_P12_KEYFILE_LOCATION_KEY =
"google.anviltop.auth.service.account.keyfile";

public static AnviltopOptions fromConfiguration(Configuration configuration) throws IOException {

AnviltopOptions.Builder optionsBuilder = new AnviltopOptions.Builder();

Expand All @@ -49,6 +82,35 @@ public static AnviltopOptions fromConfiguration(Configuration configuration) {
int port = configuration.getInt(ANVILTOP_PORT_KEY, DEFAULT_ANVILTOP_PORT);
optionsBuilder.setPort(port);

// TODO: Wire in logging once logging PR is merged.
try {
if (configuration.getBoolean(
ANVILTOP_USE_SERVICE_ACCOUNTS_KEY, ANVILTOP_USE_COMPUTE_CREDENTIAL_DEFAULT)) {

String serviceAccountEmail = configuration.get(ANVILTOP_SERVICE_ACCOUNT_EMAIL_KEY);

if (!Strings.isNullOrEmpty(serviceAccountEmail)) {
// Using P12 keyfile based OAuth:
String keyfileLocation =
configuration.get(ANVILTOP_SERVICE_ACCOUNT_P12_KEYFILE_LOCATION_KEY);

Preconditions.checkState(
!Strings.isNullOrEmpty(keyfileLocation),
"Key file location must be specified when setting service account email");

optionsBuilder.setCredential(
CredentialFactory.getCredentialFromPrivateKeyServiceAccount(
serviceAccountEmail, keyfileLocation));
} else {
optionsBuilder.setCredential(CredentialFactory.getCredentialFromMetadataServiceAccount());
}
} else if (configuration.getBoolean(
ANVILTOP_NULL_CREDENTIAL_ENABLE_KEY, ANVILTOP_NULL_CREDENTIAL_ENABLE_DEFAULT)) {
optionsBuilder.setCredential(null); // Intended for testing purposes only.
}
} catch (GeneralSecurityException gse) {
throw new IOException("Failed to acquire credential.", gse);
}
return optionsBuilder.build();
}
}
127 changes: 127 additions & 0 deletions src/main/java/com/google/cloud/anviltop/hbase/CredentialFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.google.cloud.anviltop.hbase;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.compute.ComputeCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.common.collect.ImmutableList;

import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;

/**
* Simple factory for creating OAuth Credential objects for use with anviltop.
*/
public class CredentialFactory {

/**
* The OAuth scope required to perform administrator actions such as creating tables.
*/
public static final String CLOUD_BIGTABLE_ADMIN_SCOPE =
"https://www.googleapis.com/auth/cloud-bigtable.admin";
/**
* The OAuth scope required to read data from tables.
*/
public static final String CLOUD_BIGTABLE_READER_SCOPE =
"https://www.googleapis.com/auth/cloud-bigtable.data.readonly";
/**
* The OAuth scope required to write data to tables.
*/
public static final String CLOUD_BIGTABLE_WRITER_SCOPE =
"https://www.googleapis.com/auth/cloud-bigtable.data";

/**
* Scopes required to read and write data from tables.
*/
public static final List<String> CLOUD_BIGTABLE_READ_WRITE_SCOPES =
ImmutableList.of(
CLOUD_BIGTABLE_READER_SCOPE,
CLOUD_BIGTABLE_WRITER_SCOPE);

/**
* Scopes required for full access to cloud bigtable.
*/
public static final List<String> CLOUD_BIGTABLE_ALL_SCOPES =
ImmutableList.of(
CLOUD_BIGTABLE_READER_SCOPE,
CLOUD_BIGTABLE_WRITER_SCOPE,
CLOUD_BIGTABLE_ADMIN_SCOPE);

// JSON factory used for formatting credential-handling payloads.
private static final JsonFactory JSON_FACTORY = new JacksonFactory();

// HTTP transport used for created credentials to perform token-refresh handshakes with remote
// credential servers. Initialized lazily to move the possibility of throwing
// GeneralSecurityException to the time a caller actually tries to get a credential.
private static HttpTransport httpTransport = null;

/**
* Returns shared httpTransport instance; initializes httpTransport if it hasn't already been
* initialized.
*/
private static synchronized HttpTransport getHttpTransport()
throws IOException, GeneralSecurityException {
if (httpTransport == null) {
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
}
return httpTransport;
}

/**
* Initializes OAuth2 credential using preconfigured ServiceAccount settings on the local
* GCE VM. See: <a href="https://developers.google.com/compute/docs/authentication"
* >Authenticating from Google Compute Engine</a>.
*/
public static Credential getCredentialFromMetadataServiceAccount()
throws IOException, GeneralSecurityException {
Credential cred = new ComputeCredential(getHttpTransport(), JSON_FACTORY);
try {
cred.refreshToken();
} catch (IOException e) {
throw new IOException("Error getting access token from metadata server at: " +
ComputeCredential.TOKEN_SERVER_ENCODED_URL, e);
}
return cred;
}

/**
* Initializes OAuth2 credential from a private keyfile, as described in
* <a href="https://code.google.com/p/google-api-java-client/wiki/OAuth2#Service_Accounts"
* > OAuth2 Service Accounts</a>.
*
* @param serviceAccountEmail Email address of the service account associated with the keyfile.
* @param privateKeyFile Full local path to private keyfile.
*/
public static Credential getCredentialFromPrivateKeyServiceAccount(
String serviceAccountEmail, String privateKeyFile)
throws IOException, GeneralSecurityException {
return getCredentialFromPrivateKeyServiceAccount(
serviceAccountEmail, privateKeyFile, CLOUD_BIGTABLE_ALL_SCOPES);
}

/**
* Initializes OAuth2 credential from a private keyfile, as described in
* <a href="https://code.google.com/p/google-api-java-client/wiki/OAuth2#Service_Accounts"
* > OAuth2 Service Accounts</a>.
*
* @param serviceAccountEmail Email address of the service account associated with the keyfile.
* @param privateKeyFile Full local path to private keyfile.
* @param scopes List of well-formed desired scopes to use with the credential.
*/
public static Credential getCredentialFromPrivateKeyServiceAccount(
String serviceAccountEmail, String privateKeyFile, List<String> scopes)
throws IOException, GeneralSecurityException {
return new GoogleCredential.Builder()
.setTransport(getHttpTransport())
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(serviceAccountEmail)
.setServiceAccountScopes(scopes)
.setServiceAccountPrivateKeyFromP12File(new File(privateKeyFile))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.io.IOException;

@RunWith(JUnit4.class)
public class TestAnviltopOptionsFactory {

Expand All @@ -20,7 +22,7 @@ public class TestAnviltopOptionsFactory {
public ExpectedException expectedException = ExpectedException.none();

@Test
public void testProjectIdIsRequired() {
public void testProjectIdIsRequired() throws IOException {
Configuration configuration = new Configuration();
configuration.set(AnvilTopOptionsFactory.ANVILTOP_HOST_KEY, TEST_HOST);

Expand All @@ -29,7 +31,7 @@ public void testProjectIdIsRequired() {
}

@Test
public void testHostIsRequired() {
public void testHostIsRequired() throws IOException {
Configuration configuration = new Configuration();
configuration.set(AnvilTopOptionsFactory.PROJECT_ID_KEY, TEST_PROJECT_ID);

Expand All @@ -38,10 +40,12 @@ public void testHostIsRequired() {
}

@Test
public void testOptionsAreConstructedWithValidInput() {
public void testOptionsAreConstructedWithValidInput() throws IOException {
Configuration configuration = new Configuration();
configuration.set(AnvilTopOptionsFactory.PROJECT_ID_KEY, TEST_PROJECT_ID);
configuration.set(AnvilTopOptionsFactory.ANVILTOP_HOST_KEY, TEST_HOST);
configuration.setBoolean(AnvilTopOptionsFactory.ANVILTOP_USE_SERVICE_ACCOUNTS_KEY, false);
configuration.setBoolean(AnvilTopOptionsFactory.ANVILTOP_NULL_CREDENTIAL_ENABLE_KEY, false);
AnviltopOptions options = AnvilTopOptionsFactory.fromConfiguration(configuration);
Assert.assertEquals(TEST_HOST, options.getTransportOptions().getHost());
Assert.assertEquals(TEST_PROJECT_ID, options.getProjectId());
Expand Down
8 changes: 8 additions & 0 deletions src/test/resources/anviltop-test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@
<name>google.anviltop.project.id</name>
<value>testproject</value>
</property>
<property>
<name>google.anviltop.auth.service.account.enable</name>
<value>${google.anviltop.auth.service.account.enable}</value>
</property>
<property>
<name>google.anviltop.auth.null.credential.enable</name>
<value>true</value>
</property>
</configuration>

0 comments on commit 57aa880

Please sign in to comment.