Skip to content

Commit

Permalink
Merge pull request #854 from mziccard/signing-auth-interface
Browse files Browse the repository at this point in the history
Add ServiceAccountSigner interface, enable signing for AE credentials
  • Loading branch information
mziccard committed Apr 6, 2016
2 parents b29df66 + 804d447 commit 29b1ea5
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 105 deletions.
104 changes: 94 additions & 10 deletions gcloud-java-core/src/main/java/com/google/gcloud/AuthCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Collection;
import java.util.Objects;

Expand All @@ -35,16 +40,26 @@
*/
public abstract class AuthCredentials implements Restorable<AuthCredentials> {

private static class AppEngineAuthCredentials extends AuthCredentials {
/**
* Represents built-in credentials when running in Google App Engine.
*/
public static class AppEngineAuthCredentials extends AuthCredentials
implements ServiceAccountSigner {

private static final AuthCredentials INSTANCE = new AppEngineAuthCredentials();
private static final AppEngineAuthCredentialsState STATE = new AppEngineAuthCredentialsState();

private static class AppEngineCredentials extends GoogleCredentials {
private AppEngineCredentials credentials;

private static class AppEngineCredentials extends GoogleCredentials
implements ServiceAccountSigner {

private final Object appIdentityService;
private final String account;
private final Method getAccessToken;
private final Method getAccessTokenResult;
private final Method signForApp;
private final Method getSignature;
private final Collection<String> scopes;

AppEngineCredentials() {
Expand All @@ -59,6 +74,12 @@ private static class AppEngineCredentials extends GoogleCredentials {
"com.google.appengine.api.appidentity.AppIdentityService$GetAccessTokenResult");
this.getAccessTokenResult = serviceClass.getMethod("getAccessToken", Iterable.class);
this.getAccessToken = tokenResultClass.getMethod("getAccessToken");
this.account = (String) serviceClass.getMethod("getServiceAccountName")
.invoke(appIdentityService);
this.signForApp = serviceClass.getMethod("signForApp", byte[].class);
Class<?> signingResultClass = Class.forName(
"com.google.appengine.api.appidentity.AppIdentityService$SigningResult");
this.getSignature = signingResultClass.getMethod("getSignature");
this.scopes = null;
} catch (Exception e) {
throw new RuntimeException("Could not create AppEngineCredentials.", e);
Expand All @@ -69,11 +90,14 @@ private static class AppEngineCredentials extends GoogleCredentials {
this.appIdentityService = unscoped.appIdentityService;
this.getAccessToken = unscoped.getAccessToken;
this.getAccessTokenResult = unscoped.getAccessTokenResult;
this.account = unscoped.account;
this.signForApp = unscoped.signForApp;
this.getSignature = unscoped.getSignature;
this.scopes = scopes;
}

/**
* Refresh the access token by getting it from the App Identity service
* Refresh the access token by getting it from the App Identity service.
*/
@Override
public AccessToken refreshAccessToken() throws IOException {
Expand All @@ -98,6 +122,21 @@ public boolean createScopedRequired() {
public GoogleCredentials createScoped(Collection<String> scopes) {
return new AppEngineCredentials(scopes, this);
}

@Override
public String account() {
return account;
}

@Override
public byte[] sign(byte[] toSign) {
try {
Object signingResult = signForApp.invoke(appIdentityService, (Object) toSign);
return (byte[]) getSignature.invoke(signingResult);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
throw new SigningException("Failed to sign the provided bytes", ex);
}
}
}

private static class AppEngineAuthCredentialsState
Expand All @@ -122,14 +161,27 @@ public boolean equals(Object obj) {
}

@Override
public GoogleCredentials credentials() {
return new AppEngineCredentials();
public AppEngineCredentials credentials() {
if (credentials == null) {
credentials = new AppEngineCredentials();
}
return credentials;
}

@Override
public RestorableState<AuthCredentials> capture() {
return STATE;
}

@Override
public String account() {
return credentials().account();
}

@Override
public byte[] sign(byte[] toSign) {
return credentials().sign(toSign);
}
}

/**
Expand All @@ -138,8 +190,10 @@ public RestorableState<AuthCredentials> capture() {
* @see <a href="https://cloud.google.com/docs/authentication#user_accounts_and_service_accounts">
* User accounts and service accounts</a>
*/
public static class ServiceAccountAuthCredentials extends AuthCredentials {
public static class ServiceAccountAuthCredentials extends AuthCredentials
implements ServiceAccountSigner {

private final ServiceAccountCredentials credentials;
private final String account;
private final PrivateKey privateKey;

Expand Down Expand Up @@ -178,23 +232,44 @@ public boolean equals(Object obj) {
}

ServiceAccountAuthCredentials(String account, PrivateKey privateKey) {
this.account = checkNotNull(account);
this.privateKey = checkNotNull(privateKey);
this(new ServiceAccountCredentials(null, account, privateKey, null, null));
}

ServiceAccountAuthCredentials(ServiceAccountCredentials credentials) {
this.credentials = checkNotNull(credentials);
this.account = checkNotNull(credentials.getClientEmail());
this.privateKey = checkNotNull(credentials.getPrivateKey());
}

@Override
public ServiceAccountCredentials credentials() {
return new ServiceAccountCredentials(null, account, privateKey, null, null);
return credentials;
}

@Override
public String account() {
return account;
}

/**
* Returns the private key associated with the service account credentials.
*/
public PrivateKey privateKey() {
return privateKey;
}

@Override
public byte[] sign(byte[] toSign) {
try {
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey());
signer.update(toSign);
return signer.sign();
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException ex) {
throw new SigningException("Failed to sign the provided bytes", ex);
}
}

@Override
public RestorableState<AuthCredentials> capture() {
return new ServiceAccountAuthCredentialsState(account, privateKey);
Expand Down Expand Up @@ -242,6 +317,10 @@ public boolean equals(Object obj) {
}
}

ApplicationDefaultAuthCredentials(GoogleCredentials credentials) {
googleCredentials = credentials;
}

ApplicationDefaultAuthCredentials() throws IOException {
googleCredentials = GoogleCredentials.getApplicationDefault();
}
Expand Down Expand Up @@ -320,7 +399,12 @@ public static AuthCredentials createForAppEngine() {
* @throws IOException if the credentials cannot be created in the current environment
*/
public static AuthCredentials createApplicationDefaults() throws IOException {
return new ApplicationDefaultAuthCredentials();
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
if (credentials instanceof ServiceAccountCredentials) {
ServiceAccountCredentials serviceAccountCredentials = (ServiceAccountCredentials) credentials;
return new ServiceAccountAuthCredentials(serviceAccountCredentials);
}
return new ApplicationDefaultAuthCredentials(credentials);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2016 Google Inc. 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.gcloud;

import java.util.Objects;

/**
* Interface for a service account signer. A signer for a service account is capable of signing
* bytes using the private key associated with its service account.
*/
public interface ServiceAccountSigner {

class SigningException extends RuntimeException {

private static final long serialVersionUID = 8962780757822799255L;

SigningException(String message, Exception cause) {
super(message, cause);
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof SigningException)) {
return false;
}
SigningException other = (SigningException) obj;
return Objects.equals(getCause(), other.getCause())
&& Objects.equals(getMessage(), other.getMessage());
}

@Override
public int hashCode() {
return Objects.hash(getMessage(), getCause());
}
}

/**
* Returns the service account associated with the signer.
*/
String account();

/**
* Signs the provided bytes using the private key associated with the service account.
*
* @param toSign bytes to sign
* @return signed bytes
* @throws SigningException if the attempt to sign the provided bytes failed
*/
byte[] sign(byte[] toSign);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,22 @@
package com.google.gcloud;

import com.google.common.collect.ImmutableList;
import com.google.gcloud.ServiceAccountSigner.SigningException;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Serializable;

public class SerializationTest extends BaseSerializationTest {

private static class SomeIamPolicy extends IamPolicy<String> {

private static final long serialVersionUID = 271243551016958285L;

private static class Builder extends IamPolicy.Builder<String, Builder> {

@Override
public SomeIamPolicy build() {
return new SomeIamPolicy(this);
}
}

protected SomeIamPolicy(Builder builder) {
super(builder);
}

@Override
public Builder toBuilder() {
return new Builder();
}
}

private static final BaseServiceException BASE_SERVICE_EXCEPTION =
new BaseServiceException(42, "message", "reason", true);
private static final ExceptionHandler EXCEPTION_HANDLER = ExceptionHandler.defaultInstance();
private static final Identity IDENTITY = Identity.allAuthenticatedUsers();
private static final PageImpl<String> PAGE =
new PageImpl<>(null, "cursor", ImmutableList.of("string1", "string2"));
private static final SigningException SIGNING_EXCEPTION =
new SigningException("message", BASE_SERVICE_EXCEPTION);
private static final RetryParams RETRY_PARAMS = RetryParams.defaultInstance();
private static final SomeIamPolicy SOME_IAM_POLICY = new SomeIamPolicy.Builder().build();
private static final String JSON_KEY = "{\n"
Expand Down Expand Up @@ -81,10 +62,32 @@ public Builder toBuilder() {
+ " \"type\": \"service_account\"\n"
+ "}";

private static class SomeIamPolicy extends IamPolicy<String> {

private static final long serialVersionUID = 271243551016958285L;

private static class Builder extends IamPolicy.Builder<String, Builder> {

@Override
public SomeIamPolicy build() {
return new SomeIamPolicy(this);
}
}

protected SomeIamPolicy(Builder builder) {
super(builder);
}

@Override
public Builder toBuilder() {
return new Builder();
}
}

@Override
protected Serializable[] serializableObjects() {
return new Serializable[]{BASE_SERVICE_EXCEPTION, EXCEPTION_HANDLER, IDENTITY, PAGE,
RETRY_PARAMS, SOME_IAM_POLICY};
RETRY_PARAMS, SOME_IAM_POLICY, SIGNING_EXCEPTION};
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ public void run(Storage storage, Tuple<ServiceAccountAuthCredentials, BlobInfo>
private void run(Storage storage, ServiceAccountAuthCredentials cred, BlobInfo blobInfo) {
Blob blob = storage.get(blobInfo.blobId());
System.out.println("Signed URL: "
+ blob.signUrl(1, TimeUnit.DAYS, SignUrlOption.serviceAccount(cred)));
+ blob.signUrl(1, TimeUnit.DAYS, SignUrlOption.signWith(cred)));
}

@Override
Expand Down
Loading

0 comments on commit 29b1ea5

Please sign in to comment.