Skip to content

Commit 14715a8

Browse files
committed
Add tests for token generation.
Change-Id: Ibe6ab966ca7f944a5b9f10324ec4c9071f742b3f
1 parent d590d61 commit 14715a8

File tree

6 files changed

+413
-21
lines changed

6 files changed

+413
-21
lines changed

cab-token-generator/java/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactory.java

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
import com.google.auth.Credentials;
3939
import com.google.auth.credentialaccessboundary.protobuf.ClientSideAccessBoundaryProto.ClientSideAccessBoundary;
40+
import com.google.auth.credentialaccessboundary.protobuf.ClientSideAccessBoundaryProto.ClientSideAccessBoundaryRule;
4041
import com.google.auth.http.HttpTransportFactory;
4142
import com.google.auth.oauth2.AccessToken;
4243
import com.google.auth.oauth2.CredentialAccessBoundary;
@@ -52,6 +53,7 @@
5253
import com.google.crypto.tink.KeysetHandle;
5354
import com.google.crypto.tink.RegistryConfiguration;
5455
import com.google.crypto.tink.TinkProtoKeysetFormat;
56+
import com.google.crypto.tink.aead.AeadConfig;
5557
import com.google.errorprone.annotations.CanIgnoreReturnValue;
5658
import dev.cel.common.CelAbstractSyntaxTree;
5759
import dev.cel.common.CelOptions;
@@ -79,6 +81,12 @@ private ClientSideCredentialAccessBoundaryFactory(Builder builder) {
7981
this.sourceCredential = builder.sourceCredential;
8082
this.tokenExchangeEndpoint = builder.tokenExchangeEndpoint;
8183

84+
try {
85+
AeadConfig.register();
86+
} catch (GeneralSecurityException e) {
87+
throw new IllegalStateException("Error occurred when registering Tink");
88+
}
89+
8290
CelOptions options = CelOptions.current().build();
8391
this.celCompiler = CelCompilerFactory
8492
.standardCelCompilerBuilder()
@@ -142,9 +150,9 @@ private void refreshCredentials() throws IOException {
142150
}
143151
}
144152

145-
private void refreshCredentialsIfRequired() {
153+
private void refreshCredentialsIfRequired() throws IOException {
146154
// TODO(negarb): Implement refreshCredentialsIfRequired
147-
throw new UnsupportedOperationException("refreshCredentialsIfRequired is not yet implemented.");
155+
refreshCredentials();
148156
}
149157

150158
public AccessToken generateToken(CredentialAccessBoundary accessBoundary) throws IOException {
@@ -181,16 +189,18 @@ private byte[] serializeCredentialAccessBoundary(
181189
ClientSideAccessBoundary.newBuilder();
182190

183191
for (AccessBoundaryRule rule : rules) {
184-
String availabilityCondition =
185-
rule.getAvailabilityCondition().getExpression();
192+
ClientSideAccessBoundaryRule.Builder ruleBuilder =
193+
accessBoundaryBuilder.addAccessBoundaryRulesBuilder()
194+
.addAllAvailablePermissions(rule.getAvailablePermissions())
195+
.setAvailableResource(rule.getAvailableResource());
186196

187-
Expr availabilityConditionExpr =
188-
this.compileCel(availabilityCondition);
197+
if (rule.getAvailabilityCondition() != null) {
198+
String availabilityCondition =
199+
rule.getAvailabilityCondition().getExpression();
189200

190-
accessBoundaryBuilder.addAccessBoundaryRulesBuilder()
191-
.addAllAvailablePermissions(rule.getAvailablePermissions())
192-
.setAvailableResource(rule.getAvailableResource())
193-
.setCompiledAvailabilityCondition(availabilityConditionExpr);
201+
Expr availabilityConditionExpr = this.compileCel(availabilityCondition);
202+
ruleBuilder.setCompiledAvailabilityCondition(availabilityConditionExpr);
203+
}
194204
}
195205

196206
return accessBoundaryBuilder.build().toByteArray();
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
package com.google.auth.credentialaccessboundary;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertThrows;
5+
import static org.junit.Assert.assertTrue;
6+
7+
import com.google.api.client.http.HttpTransport;
8+
import com.google.auth.credentialaccessboundary.protobuf.ClientSideAccessBoundaryProto.ClientSideAccessBoundary;
9+
import com.google.auth.credentialaccessboundary.protobuf.ClientSideAccessBoundaryProto.ClientSideAccessBoundaryRule;
10+
import com.google.auth.http.HttpTransportFactory;
11+
import com.google.auth.oauth2.AccessToken;
12+
import com.google.auth.oauth2.CredentialAccessBoundary;
13+
import com.google.auth.oauth2.GoogleCredentials;
14+
import com.google.auth.oauth2.MockStsTransport;
15+
import com.google.auth.oauth2.MockTokenServerTransportFactory;
16+
import com.google.auth.oauth2.OAuth2Utils;
17+
import com.google.auth.oauth2.ServiceAccountCredentials;
18+
import com.google.common.collect.ImmutableList;
19+
import com.google.crypto.tink.Aead;
20+
import com.google.crypto.tink.InsecureSecretKeyAccess;
21+
import com.google.crypto.tink.KeysetHandle;
22+
import com.google.crypto.tink.RegistryConfiguration;
23+
import com.google.crypto.tink.TinkProtoKeysetFormat;
24+
import dev.cel.expr.Expr;
25+
import java.io.IOException;
26+
import java.util.Base64;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.junit.runners.JUnit4;
30+
31+
/** Tests for {@link ClientSideCredentialAccessBoundaryFactory} **/
32+
@RunWith(JUnit4.class)
33+
public class ClientSideCredentialAccessBoundaryFactoryTest {
34+
private static final String SA_PRIVATE_KEY_PKCS8 =
35+
"-----BEGIN PRIVATE KEY-----\n"
36+
+ "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALX0PQoe1igW12i"
37+
+ "kv1bN/r9lN749y2ijmbc/"
38+
+ "mFHPyS3hNTyOCjDvBbXYbDhQJzWVUikh4mvGBA07qTj79Xc3yBDfKP2IeyYQIFe0t0"
39+
+ "zkd7R9Zdn98Y2rIQC47aAbDfubtkU1U72t4zL11kHvoa0/"
40+
+ "RuFZjncvlr42X7be7lYh4p3NAgMBAAECgYASk5wDw"
41+
+ "4Az2ZkmeuN6Fk/"
42+
+ "y9H+"
43+
+ "Lcb2pskJIXjrL533vrDWGOC48LrsThMQPv8cxBky8HFSEklPpkfTF95tpD43iVwJRB/Gr"
44+
+ "CtGTw65IfJ4/tI09h6zGc4yqvIo1cHX/LQ+SxKLGyir/dQM925rGt/"
45+
+ "VojxY5ryJR7GLbCzxPnJm/oQJBANwOCO6"
46+
+ "D2hy1LQYJhXh7O+RLtA/"
47+
+ "tSnT1xyMQsGT+uUCMiKS2bSKx2wxo9k7h3OegNJIu1q6nZ6AbxDK8H3+d0dUCQQDTrP"
48+
+ "SXagBxzp8PecbaCHjzNRSQE2in81qYnrAFNB4o3DpHyMMY6s5ALLeHKscEWnqP8Ur6X4"
49+
+ "PvzZecCWU9BKAZAkAut"
50+
+ "LPknAuxSCsUOvUfS1i87ex77Ot+w6POp34pEX+UWb+"
51+
+ "u5iFn2cQacDTHLV1LtE80L8jVLSbrbrlH43H0DjU5AkEA"
52+
+ "gidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/"
53+
+ "Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJ"
54+
+ "ADj3e1YhMVdjJW5jqwlD/"
55+
+ "VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ"
56+
+ "==\n-----END PRIVATE KEY-----\n";
57+
58+
static class MockStsTransportFactory implements HttpTransportFactory {
59+
60+
MockStsTransport transport = new MockStsTransport();
61+
62+
@Override
63+
public HttpTransport create() {
64+
return transport;
65+
}
66+
}
67+
68+
private static GoogleCredentials
69+
getServiceAccountSourceCredentials(boolean canRefresh) throws IOException {
70+
MockTokenServerTransportFactory transportFactory =
71+
new MockTokenServerTransportFactory();
72+
73+
String email = "service-account@google.com";
74+
75+
ServiceAccountCredentials sourceCredentials =
76+
ServiceAccountCredentials.newBuilder()
77+
.setClientEmail(email)
78+
.setPrivateKey(
79+
OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8))
80+
.setPrivateKeyId("privateKeyId")
81+
.setProjectId("projectId")
82+
.setHttpTransportFactory(transportFactory)
83+
.build();
84+
85+
transportFactory.transport.addServiceAccount(email, "accessToken");
86+
87+
if (!canRefresh) {
88+
transportFactory.transport.setError(new IOException());
89+
}
90+
91+
return sourceCredentials.createScoped(
92+
"https://www.googleapis.com/auth/cloud-platform");
93+
}
94+
95+
@Test
96+
public void generateToken() throws Exception {
97+
MockStsTransportFactory transportFactory = new MockStsTransportFactory();
98+
transportFactory.transport.setReturnAccessBoundarySessionKey(true);
99+
100+
ClientSideCredentialAccessBoundaryFactory.Builder builder =
101+
ClientSideCredentialAccessBoundaryFactory.newBuilder();
102+
103+
ClientSideCredentialAccessBoundaryFactory factory =
104+
builder.setSourceCredential(getServiceAccountSourceCredentials(true))
105+
.setHttpTransportFactory(transportFactory)
106+
.build();
107+
108+
CredentialAccessBoundary.Builder cabBuilder =
109+
CredentialAccessBoundary.newBuilder();
110+
CredentialAccessBoundary accessBoundary =
111+
cabBuilder
112+
.addRule(
113+
CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
114+
.setAvailableResource("//storage.googleapis.com/projects/"
115+
+ "_/buckets/example-bucket")
116+
.setAvailablePermissions(
117+
ImmutableList.of("inRole:roles/storage.objectViewer"))
118+
.setAvailabilityCondition(
119+
CredentialAccessBoundary.AccessBoundaryRule
120+
.AvailabilityCondition.newBuilder()
121+
.setExpression(
122+
"resource.name.startsWith('projects/_/"
123+
+ "buckets/example-bucket/objects/customer-a')")
124+
.build())
125+
.build())
126+
.build();
127+
128+
AccessToken token = factory.generateToken(accessBoundary);
129+
130+
String[] parts = token.getTokenValue().split("\\.");
131+
assertEquals(parts.length, 2);
132+
assertEquals(parts[0], "accessToken");
133+
134+
byte[] rawKey = Base64.getDecoder().decode(
135+
transportFactory.transport.getAccessBoundarySessionKey());
136+
137+
KeysetHandle keysetHandle = TinkProtoKeysetFormat.parseKeyset(
138+
rawKey, InsecureSecretKeyAccess.get());
139+
140+
Aead aead =
141+
keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);
142+
byte[] rawRestrictions =
143+
aead.decrypt(Base64.getUrlDecoder().decode(parts[1]), new byte[0]);
144+
ClientSideAccessBoundary clientSideAccessBoundary =
145+
ClientSideAccessBoundary.parseFrom(rawRestrictions);
146+
assertEquals(clientSideAccessBoundary.getAccessBoundaryRulesCount(), 1);
147+
ClientSideAccessBoundaryRule rule =
148+
clientSideAccessBoundary.getAccessBoundaryRules(0);
149+
assertEquals(rule.getAvailableResource(),
150+
"//storage.googleapis.com/projects/_/buckets/example-bucket");
151+
assertEquals(rule.getAvailablePermissions(0),
152+
"inRole:roles/storage.objectViewer");
153+
Expr expr = rule.getCompiledAvailabilityCondition();
154+
assertEquals(expr.getCallExpr()
155+
.getTarget()
156+
.getSelectExpr()
157+
.getOperand()
158+
.getIdentExpr()
159+
.getName(),
160+
"resource");
161+
assertEquals(expr.getCallExpr().getFunction(), "startsWith");
162+
assertEquals(expr.getCallExpr().getArgs(0).getConstExpr().getStringValue(),
163+
"projects/_/buckets/example-bucket/objects/customer-a");
164+
}
165+
166+
@Test
167+
public void generateToken_withoutAvailabilityCondition() throws Exception {
168+
MockStsTransportFactory transportFactory = new MockStsTransportFactory();
169+
transportFactory.transport.setReturnAccessBoundarySessionKey(true);
170+
171+
ClientSideCredentialAccessBoundaryFactory.Builder builder =
172+
ClientSideCredentialAccessBoundaryFactory.newBuilder();
173+
174+
ClientSideCredentialAccessBoundaryFactory factory =
175+
builder.setSourceCredential(getServiceAccountSourceCredentials(true))
176+
.setHttpTransportFactory(transportFactory)
177+
.build();
178+
179+
CredentialAccessBoundary.Builder cabBuilder =
180+
CredentialAccessBoundary.newBuilder();
181+
CredentialAccessBoundary accessBoundary =
182+
cabBuilder
183+
.addRule(
184+
CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
185+
.setAvailableResource("//storage.googleapis.com/projects/"
186+
+ "_/buckets/example-bucket")
187+
.setAvailablePermissions(
188+
ImmutableList.of("inRole:roles/storage.objectViewer"))
189+
.build())
190+
.build();
191+
192+
AccessToken token = factory.generateToken(accessBoundary);
193+
194+
String[] parts = token.getTokenValue().split("\\.");
195+
assertEquals(parts.length, 2);
196+
assertEquals(parts[0], "accessToken");
197+
198+
byte[] rawKey = Base64.getDecoder().decode(
199+
transportFactory.transport.getAccessBoundarySessionKey());
200+
201+
KeysetHandle keysetHandle = TinkProtoKeysetFormat.parseKeyset(
202+
rawKey, InsecureSecretKeyAccess.get());
203+
204+
Aead aead =
205+
keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);
206+
byte[] rawRestrictions =
207+
aead.decrypt(Base64.getUrlDecoder().decode(parts[1]), new byte[0]);
208+
ClientSideAccessBoundary clientSideAccessBoundary =
209+
ClientSideAccessBoundary.parseFrom(rawRestrictions);
210+
assertEquals(clientSideAccessBoundary.getAccessBoundaryRulesCount(), 1);
211+
ClientSideAccessBoundaryRule rule =
212+
clientSideAccessBoundary.getAccessBoundaryRules(0);
213+
assertEquals(rule.getAvailableResource(),
214+
"//storage.googleapis.com/projects/_/buckets/example-bucket");
215+
assertEquals(rule.getAvailablePermissions(0),
216+
"inRole:roles/storage.objectViewer");
217+
assertTrue(rule.getCompiledAvailabilityCondition().equals(
218+
Expr.getDefaultInstance()));
219+
}
220+
221+
@Test
222+
public void generateToken_withInvalidCelExpression() throws Exception {
223+
MockStsTransportFactory transportFactory = new MockStsTransportFactory();
224+
transportFactory.transport.setReturnAccessBoundarySessionKey(true);
225+
226+
ClientSideCredentialAccessBoundaryFactory.Builder builder =
227+
ClientSideCredentialAccessBoundaryFactory.newBuilder();
228+
229+
ClientSideCredentialAccessBoundaryFactory factory =
230+
builder.setSourceCredential(getServiceAccountSourceCredentials(true))
231+
.setHttpTransportFactory(transportFactory)
232+
.build();
233+
234+
CredentialAccessBoundary.Builder cabBuilder =
235+
CredentialAccessBoundary.newBuilder();
236+
CredentialAccessBoundary accessBoundary =
237+
cabBuilder
238+
.addRule(
239+
CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
240+
.setAvailableResource("//storage.googleapis.com/projects/"
241+
+ "_/buckets/example-bucket")
242+
.setAvailablePermissions(
243+
ImmutableList.of("inRole:roles/storage.objectViewer"))
244+
.setAvailabilityCondition(
245+
CredentialAccessBoundary.AccessBoundaryRule
246+
.AvailabilityCondition.newBuilder()
247+
.setExpression(
248+
"resource.name.startsWith('projects/_/"
249+
+ "buckets/example-bucket/objects/customer-a'")
250+
.build())
251+
.build())
252+
.build();
253+
254+
assertThrows(IOException.class,
255+
() -> { factory.generateToken(accessBoundary); });
256+
}
257+
}

0 commit comments

Comments
 (0)