Skip to content

Commit 29b1ea5

Browse files
committed
Merge pull request #854 from mziccard/signing-auth-interface
Add ServiceAccountSigner interface, enable signing for AE credentials
2 parents b29df66 + 804d447 commit 29b1ea5

File tree

7 files changed

+268
-105
lines changed

7 files changed

+268
-105
lines changed

gcloud-java-core/src/main/java/com/google/gcloud/AuthCredentials.java

+94-10
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@
2525
import java.io.IOException;
2626
import java.io.InputStream;
2727
import java.io.Serializable;
28+
import java.lang.reflect.InvocationTargetException;
2829
import java.lang.reflect.Method;
30+
import java.security.InvalidKeyException;
31+
import java.security.NoSuchAlgorithmException;
2932
import java.security.PrivateKey;
33+
import java.security.Signature;
34+
import java.security.SignatureException;
3035
import java.util.Collection;
3136
import java.util.Objects;
3237

@@ -35,16 +40,26 @@
3540
*/
3641
public abstract class AuthCredentials implements Restorable<AuthCredentials> {
3742

38-
private static class AppEngineAuthCredentials extends AuthCredentials {
43+
/**
44+
* Represents built-in credentials when running in Google App Engine.
45+
*/
46+
public static class AppEngineAuthCredentials extends AuthCredentials
47+
implements ServiceAccountSigner {
3948

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

43-
private static class AppEngineCredentials extends GoogleCredentials {
52+
private AppEngineCredentials credentials;
53+
54+
private static class AppEngineCredentials extends GoogleCredentials
55+
implements ServiceAccountSigner {
4456

4557
private final Object appIdentityService;
58+
private final String account;
4659
private final Method getAccessToken;
4760
private final Method getAccessTokenResult;
61+
private final Method signForApp;
62+
private final Method getSignature;
4863
private final Collection<String> scopes;
4964

5065
AppEngineCredentials() {
@@ -59,6 +74,12 @@ private static class AppEngineCredentials extends GoogleCredentials {
5974
"com.google.appengine.api.appidentity.AppIdentityService$GetAccessTokenResult");
6075
this.getAccessTokenResult = serviceClass.getMethod("getAccessToken", Iterable.class);
6176
this.getAccessToken = tokenResultClass.getMethod("getAccessToken");
77+
this.account = (String) serviceClass.getMethod("getServiceAccountName")
78+
.invoke(appIdentityService);
79+
this.signForApp = serviceClass.getMethod("signForApp", byte[].class);
80+
Class<?> signingResultClass = Class.forName(
81+
"com.google.appengine.api.appidentity.AppIdentityService$SigningResult");
82+
this.getSignature = signingResultClass.getMethod("getSignature");
6283
this.scopes = null;
6384
} catch (Exception e) {
6485
throw new RuntimeException("Could not create AppEngineCredentials.", e);
@@ -69,11 +90,14 @@ private static class AppEngineCredentials extends GoogleCredentials {
6990
this.appIdentityService = unscoped.appIdentityService;
7091
this.getAccessToken = unscoped.getAccessToken;
7192
this.getAccessTokenResult = unscoped.getAccessTokenResult;
93+
this.account = unscoped.account;
94+
this.signForApp = unscoped.signForApp;
95+
this.getSignature = unscoped.getSignature;
7296
this.scopes = scopes;
7397
}
7498

7599
/**
76-
* Refresh the access token by getting it from the App Identity service
100+
* Refresh the access token by getting it from the App Identity service.
77101
*/
78102
@Override
79103
public AccessToken refreshAccessToken() throws IOException {
@@ -98,6 +122,21 @@ public boolean createScopedRequired() {
98122
public GoogleCredentials createScoped(Collection<String> scopes) {
99123
return new AppEngineCredentials(scopes, this);
100124
}
125+
126+
@Override
127+
public String account() {
128+
return account;
129+
}
130+
131+
@Override
132+
public byte[] sign(byte[] toSign) {
133+
try {
134+
Object signingResult = signForApp.invoke(appIdentityService, (Object) toSign);
135+
return (byte[]) getSignature.invoke(signingResult);
136+
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
137+
throw new SigningException("Failed to sign the provided bytes", ex);
138+
}
139+
}
101140
}
102141

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

124163
@Override
125-
public GoogleCredentials credentials() {
126-
return new AppEngineCredentials();
164+
public AppEngineCredentials credentials() {
165+
if (credentials == null) {
166+
credentials = new AppEngineCredentials();
167+
}
168+
return credentials;
127169
}
128170

129171
@Override
130172
public RestorableState<AuthCredentials> capture() {
131173
return STATE;
132174
}
175+
176+
@Override
177+
public String account() {
178+
return credentials().account();
179+
}
180+
181+
@Override
182+
public byte[] sign(byte[] toSign) {
183+
return credentials().sign(toSign);
184+
}
133185
}
134186

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

196+
private final ServiceAccountCredentials credentials;
143197
private final String account;
144198
private final PrivateKey privateKey;
145199

@@ -178,23 +232,44 @@ public boolean equals(Object obj) {
178232
}
179233

180234
ServiceAccountAuthCredentials(String account, PrivateKey privateKey) {
181-
this.account = checkNotNull(account);
182-
this.privateKey = checkNotNull(privateKey);
235+
this(new ServiceAccountCredentials(null, account, privateKey, null, null));
236+
}
237+
238+
ServiceAccountAuthCredentials(ServiceAccountCredentials credentials) {
239+
this.credentials = checkNotNull(credentials);
240+
this.account = checkNotNull(credentials.getClientEmail());
241+
this.privateKey = checkNotNull(credentials.getPrivateKey());
183242
}
184243

185244
@Override
186245
public ServiceAccountCredentials credentials() {
187-
return new ServiceAccountCredentials(null, account, privateKey, null, null);
246+
return credentials;
188247
}
189248

249+
@Override
190250
public String account() {
191251
return account;
192252
}
193253

254+
/**
255+
* Returns the private key associated with the service account credentials.
256+
*/
194257
public PrivateKey privateKey() {
195258
return privateKey;
196259
}
197260

261+
@Override
262+
public byte[] sign(byte[] toSign) {
263+
try {
264+
Signature signer = Signature.getInstance("SHA256withRSA");
265+
signer.initSign(privateKey());
266+
signer.update(toSign);
267+
return signer.sign();
268+
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException ex) {
269+
throw new SigningException("Failed to sign the provided bytes", ex);
270+
}
271+
}
272+
198273
@Override
199274
public RestorableState<AuthCredentials> capture() {
200275
return new ServiceAccountAuthCredentialsState(account, privateKey);
@@ -242,6 +317,10 @@ public boolean equals(Object obj) {
242317
}
243318
}
244319

320+
ApplicationDefaultAuthCredentials(GoogleCredentials credentials) {
321+
googleCredentials = credentials;
322+
}
323+
245324
ApplicationDefaultAuthCredentials() throws IOException {
246325
googleCredentials = GoogleCredentials.getApplicationDefault();
247326
}
@@ -320,7 +399,12 @@ public static AuthCredentials createForAppEngine() {
320399
* @throws IOException if the credentials cannot be created in the current environment
321400
*/
322401
public static AuthCredentials createApplicationDefaults() throws IOException {
323-
return new ApplicationDefaultAuthCredentials();
402+
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
403+
if (credentials instanceof ServiceAccountCredentials) {
404+
ServiceAccountCredentials serviceAccountCredentials = (ServiceAccountCredentials) credentials;
405+
return new ServiceAccountAuthCredentials(serviceAccountCredentials);
406+
}
407+
return new ApplicationDefaultAuthCredentials(credentials);
324408
}
325409

326410
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.gcloud;
18+
19+
import java.util.Objects;
20+
21+
/**
22+
* Interface for a service account signer. A signer for a service account is capable of signing
23+
* bytes using the private key associated with its service account.
24+
*/
25+
public interface ServiceAccountSigner {
26+
27+
class SigningException extends RuntimeException {
28+
29+
private static final long serialVersionUID = 8962780757822799255L;
30+
31+
SigningException(String message, Exception cause) {
32+
super(message, cause);
33+
}
34+
35+
@Override
36+
public boolean equals(Object obj) {
37+
if (obj == this) {
38+
return true;
39+
}
40+
if (!(obj instanceof SigningException)) {
41+
return false;
42+
}
43+
SigningException other = (SigningException) obj;
44+
return Objects.equals(getCause(), other.getCause())
45+
&& Objects.equals(getMessage(), other.getMessage());
46+
}
47+
48+
@Override
49+
public int hashCode() {
50+
return Objects.hash(getMessage(), getCause());
51+
}
52+
}
53+
54+
/**
55+
* Returns the service account associated with the signer.
56+
*/
57+
String account();
58+
59+
/**
60+
* Signs the provided bytes using the private key associated with the service account.
61+
*
62+
* @param toSign bytes to sign
63+
* @return signed bytes
64+
* @throws SigningException if the attempt to sign the provided bytes failed
65+
*/
66+
byte[] sign(byte[] toSign);
67+
}

gcloud-java-core/src/test/java/com/google/gcloud/SerializationTest.java

+26-23
Original file line numberDiff line numberDiff line change
@@ -17,41 +17,22 @@
1717
package com.google.gcloud;
1818

1919
import com.google.common.collect.ImmutableList;
20+
import com.google.gcloud.ServiceAccountSigner.SigningException;
2021

2122
import java.io.ByteArrayInputStream;
2223
import java.io.IOException;
2324
import java.io.Serializable;
2425

2526
public class SerializationTest extends BaseSerializationTest {
2627

27-
private static class SomeIamPolicy extends IamPolicy<String> {
28-
29-
private static final long serialVersionUID = 271243551016958285L;
30-
31-
private static class Builder extends IamPolicy.Builder<String, Builder> {
32-
33-
@Override
34-
public SomeIamPolicy build() {
35-
return new SomeIamPolicy(this);
36-
}
37-
}
38-
39-
protected SomeIamPolicy(Builder builder) {
40-
super(builder);
41-
}
42-
43-
@Override
44-
public Builder toBuilder() {
45-
return new Builder();
46-
}
47-
}
48-
4928
private static final BaseServiceException BASE_SERVICE_EXCEPTION =
5029
new BaseServiceException(42, "message", "reason", true);
5130
private static final ExceptionHandler EXCEPTION_HANDLER = ExceptionHandler.defaultInstance();
5231
private static final Identity IDENTITY = Identity.allAuthenticatedUsers();
5332
private static final PageImpl<String> PAGE =
5433
new PageImpl<>(null, "cursor", ImmutableList.of("string1", "string2"));
34+
private static final SigningException SIGNING_EXCEPTION =
35+
new SigningException("message", BASE_SERVICE_EXCEPTION);
5536
private static final RetryParams RETRY_PARAMS = RetryParams.defaultInstance();
5637
private static final SomeIamPolicy SOME_IAM_POLICY = new SomeIamPolicy.Builder().build();
5738
private static final String JSON_KEY = "{\n"
@@ -81,10 +62,32 @@ public Builder toBuilder() {
8162
+ " \"type\": \"service_account\"\n"
8263
+ "}";
8364

65+
private static class SomeIamPolicy extends IamPolicy<String> {
66+
67+
private static final long serialVersionUID = 271243551016958285L;
68+
69+
private static class Builder extends IamPolicy.Builder<String, Builder> {
70+
71+
@Override
72+
public SomeIamPolicy build() {
73+
return new SomeIamPolicy(this);
74+
}
75+
}
76+
77+
protected SomeIamPolicy(Builder builder) {
78+
super(builder);
79+
}
80+
81+
@Override
82+
public Builder toBuilder() {
83+
return new Builder();
84+
}
85+
}
86+
8487
@Override
8588
protected Serializable[] serializableObjects() {
8689
return new Serializable[]{BASE_SERVICE_EXCEPTION, EXCEPTION_HANDLER, IDENTITY, PAGE,
87-
RETRY_PARAMS, SOME_IAM_POLICY};
90+
RETRY_PARAMS, SOME_IAM_POLICY, SIGNING_EXCEPTION};
8891
}
8992

9093
@Override

gcloud-java-examples/src/main/java/com/google/gcloud/examples/storage/StorageExample.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ public void run(Storage storage, Tuple<ServiceAccountAuthCredentials, BlobInfo>
489489
private void run(Storage storage, ServiceAccountAuthCredentials cred, BlobInfo blobInfo) {
490490
Blob blob = storage.get(blobInfo.blobId());
491491
System.out.println("Signed URL: "
492-
+ blob.signUrl(1, TimeUnit.DAYS, SignUrlOption.serviceAccount(cred)));
492+
+ blob.signUrl(1, TimeUnit.DAYS, SignUrlOption.signWith(cred)));
493493
}
494494

495495
@Override

0 commit comments

Comments
 (0)