Skip to content

Commit

Permalink
test: adds integration tests for downscoping with credential access b…
Browse files Browse the repository at this point in the history
…oundaries (#698)

* feat: Adding functional tests for Service Account  (#685)

ServiceAccountCredentials tests for 4110

* feat: allow scopes for self signed jwt (#689)

* feat: self signed jwt support

* update

* address comments

* allow to use uri as audience

* address comments

* chore: release 0.27.0 (#678)

:robot: I have created a release \*beep\* \*boop\*
---
## [0.27.0](https://www.github.com/googleapis/google-auth-library-java/compare/v0.26.0...v0.27.0) (2021-07-14)


### Features

* add Id token support for UserCredentials ([#650](https://www.github.com/googleapis/google-auth-library-java/issues/650)) ([5a8f467](https://www.github.com/googleapis/google-auth-library-java/commit/5a8f4676630854c53aa708a9c8b960770067f858))
* add impersonation credentials to ADC  ([#613](https://www.github.com/googleapis/google-auth-library-java/issues/613)) ([b9823f7](https://www.github.com/googleapis/google-auth-library-java/commit/b9823f70d7f3f7461b7de40bee06f5e7ba0e797c))
* Adding functional tests for Service Account  ([#685](https://www.github.com/googleapis/google-auth-library-java/issues/685)) ([dfe118c](https://www.github.com/googleapis/google-auth-library-java/commit/dfe118c261aadf137a3cf47a7acb9892c7a6db4d))
* allow scopes for self signed jwt ([#689](https://www.github.com/googleapis/google-auth-library-java/issues/689)) ([f4980c7](https://www.github.com/googleapis/google-auth-library-java/commit/f4980c77566bbd5ef4c532acb199d7d484dbcd01))
---


This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).

* test: adds integration tests for downscoping with credential access boundaries

Co-authored-by: Timur Sadykov <stim@google.com>
Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com>
Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
  • Loading branch information
4 people committed Jul 22, 2021
1 parent 5077450 commit b26f8a4
Show file tree
Hide file tree
Showing 17 changed files with 691 additions and 108 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,15 @@
# Changelog

## [0.27.0](https://www.github.com/googleapis/google-auth-library-java/compare/v0.26.0...v0.27.0) (2021-07-14)


### Features

* add Id token support for UserCredentials ([#650](https://www.github.com/googleapis/google-auth-library-java/issues/650)) ([5a8f467](https://www.github.com/googleapis/google-auth-library-java/commit/5a8f4676630854c53aa708a9c8b960770067f858))
* add impersonation credentials to ADC ([#613](https://www.github.com/googleapis/google-auth-library-java/issues/613)) ([b9823f7](https://www.github.com/googleapis/google-auth-library-java/commit/b9823f70d7f3f7461b7de40bee06f5e7ba0e797c))
* Adding functional tests for Service Account ([#685](https://www.github.com/googleapis/google-auth-library-java/issues/685)) ([dfe118c](https://www.github.com/googleapis/google-auth-library-java/commit/dfe118c261aadf137a3cf47a7acb9892c7a6db4d))
* allow scopes for self signed jwt ([#689](https://www.github.com/googleapis/google-auth-library-java/issues/689)) ([f4980c7](https://www.github.com/googleapis/google-auth-library-java/commit/f4980c77566bbd5ef4c532acb199d7d484dbcd01))

## [0.26.0](https://www.github.com/googleapis/google-auth-library-java/compare/v0.25.5...v0.26.0) (2021-05-20)


Expand Down
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -31,7 +31,7 @@ If you are using Maven, add this to your pom.xml file (notice that you can repla
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>0.26.0</version>
<version>0.27.0</version>
</dependency>
```
[//]: # ({x-version-update-end})
Expand All @@ -41,15 +41,15 @@ If you are using Gradle, add this to your dependencies

[//]: # ({x-version-update-start:google-auth-library-oauth2-http:released})
```Groovy
compile 'com.google.auth:google-auth-library-oauth2-http:0.26.0'
compile 'com.google.auth:google-auth-library-oauth2-http:0.27.0'
```
[//]: # ({x-version-update-end})

If you are using SBT, add this to your dependencies

[//]: # ({x-version-update-start:google-auth-library-oauth2-http:released})
```Scala
libraryDependencies += "com.google.auth" % "google-auth-library-oauth2-http" % "0.26.0"
libraryDependencies += "com.google.auth" % "google-auth-library-oauth2-http" % "0.27.0"
```
[//]: # ({x-version-update-end})

Expand Down
2 changes: 1 addition & 1 deletion appengine/pom.xml
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-parent</artifactId>
<version>0.26.1-SNAPSHOT</version><!-- {x-version-update:google-auth-library-parent:current} -->
<version>0.27.0</version><!-- {x-version-update:google-auth-library-parent:current} -->
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion bom/pom.xml
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-bom</artifactId>
<version>0.26.1-SNAPSHOT</version><!-- {x-version-update:google-auth-library-bom:current} -->
<version>0.27.0</version><!-- {x-version-update:google-auth-library-bom:current} -->
<packaging>pom</packaging>
<name>Google Auth Library for Java BOM</name>
<description>
Expand Down
2 changes: 1 addition & 1 deletion credentials/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-parent</artifactId>
<version>0.26.1-SNAPSHOT</version><!-- {x-version-update:google-auth-library-parent:current} -->
<version>0.27.0</version><!-- {x-version-update:google-auth-library-parent:current} -->
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 3 additions & 1 deletion oauth2_http/java/com/google/auth/oauth2/JwtClaims.java
Expand Up @@ -106,7 +106,9 @@ public JwtClaims merge(JwtClaims other) {
* @return true if all required fields have been set; false otherwise
*/
public boolean isComplete() {
return getAudience() != null && getIssuer() != null && getSubject() != null;
boolean hasScopes =
getAdditionalClaims().containsKey("scope") && !getAdditionalClaims().get("scope").isEmpty();
return (getAudience() != null || hasScopes) && getIssuer() != null && getSubject() != null;
}

@AutoValue.Builder
Expand Down
143 changes: 114 additions & 29 deletions oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java
Expand Up @@ -77,6 +77,7 @@
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -109,9 +110,9 @@ public class ServiceAccountCredentials extends GoogleCredentials
private final Collection<String> defaultScopes;
private final String quotaProjectId;
private final int lifetime;
private final boolean useJwtAccessWithScope;

private transient HttpTransportFactory transportFactory;
private transient ServiceAccountJwtAccessCredentials jwtCredentials = null;

/**
* Constructor with minimum identifying information and custom HTTP transport.
Expand All @@ -133,6 +134,7 @@ public class ServiceAccountCredentials extends GoogleCredentials
* most 43200 (12 hours). If the token is used for calling a Google API, then the value should
* be at most 3600 (1 hour). If the given value is 0, then the default value 3600 will be used
* when creating the credentials.
* @param useJwtAccessWithScope whether self signed JWT with scopes should be always used.
*/
ServiceAccountCredentials(
String clientId,
Expand All @@ -146,7 +148,8 @@ public class ServiceAccountCredentials extends GoogleCredentials
String serviceAccountUser,
String projectId,
String quotaProjectId,
int lifetime) {
int lifetime,
boolean useJwtAccessWithScope) {
this.clientId = clientId;
this.clientEmail = Preconditions.checkNotNull(clientEmail);
this.privateKey = Preconditions.checkNotNull(privateKey);
Expand All @@ -167,18 +170,7 @@ public class ServiceAccountCredentials extends GoogleCredentials
throw new IllegalStateException("lifetime must be less than or equal to 43200");
}
this.lifetime = lifetime;

// Use self signed JWT if scopes is not set, see https://google.aip.dev/auth/4111.
if (this.scopes.isEmpty()) {
jwtCredentials =
new ServiceAccountJwtAccessCredentials.Builder()
.setClientEmail(clientEmail)
.setClientId(clientId)
.setPrivateKey(privateKey)
.setPrivateKeyId(privateKeyId)
.setQuotaProjectId(quotaProjectId)
.build();
}
this.useJwtAccessWithScope = useJwtAccessWithScope;
}

/**
Expand Down Expand Up @@ -492,7 +484,8 @@ static ServiceAccountCredentials fromPkcs8(
serviceAccountUser,
projectId,
quotaProject,
DEFAULT_LIFETIME_IN_SECONDS);
DEFAULT_LIFETIME_IN_SECONDS,
false);
}

/** Helper to convert from a PKCS#8 String to an RSA private key */
Expand Down Expand Up @@ -698,7 +691,8 @@ public GoogleCredentials createScoped(
serviceAccountUser,
projectId,
quotaProjectId,
lifetime);
lifetime,
useJwtAccessWithScope);
}

/**
Expand All @@ -714,6 +708,16 @@ public ServiceAccountCredentials createWithCustomLifetime(int lifetime) {
return this.toBuilder().setLifetime(lifetime).build();
}

/**
* Clones the service account with a new useJwtAccessWithScope value.
*
* @param useJwtAccessWithScope whether self signed JWT with scopes should be used
* @return the cloned service account credentials with the given useJwtAccessWithScope
*/
public ServiceAccountCredentials createWithUseJwtAccessWithScope(boolean useJwtAccessWithScope) {
return this.toBuilder().setUseJwtAccessWithScope(useJwtAccessWithScope).build();
}

@Override
public GoogleCredentials createDelegated(String user) {
return new ServiceAccountCredentials(
Expand All @@ -728,7 +732,8 @@ public GoogleCredentials createDelegated(String user) {
user,
projectId,
quotaProjectId,
lifetime);
lifetime,
useJwtAccessWithScope);
}

public final String getClientId() {
Expand Down Expand Up @@ -776,6 +781,10 @@ int getLifetime() {
return lifetime;
}

public boolean getUseJwtAccessWithScope() {
return useJwtAccessWithScope;
}

@Override
public String getAccount() {
return getClientEmail();
Expand Down Expand Up @@ -833,7 +842,8 @@ public int hashCode() {
scopes,
defaultScopes,
quotaProjectId,
lifetime);
lifetime,
useJwtAccessWithScope);
}

@Override
Expand All @@ -849,6 +859,7 @@ public String toString() {
.add("serviceAccountUser", serviceAccountUser)
.add("quotaProjectId", quotaProjectId)
.add("lifetime", lifetime)
.add("useJwtAccessWithScope", useJwtAccessWithScope)
.toString();
}

Expand All @@ -867,7 +878,8 @@ public boolean equals(Object obj) {
&& Objects.equals(this.scopes, other.scopes)
&& Objects.equals(this.defaultScopes, other.defaultScopes)
&& Objects.equals(this.quotaProjectId, other.quotaProjectId)
&& Objects.equals(this.lifetime, other.lifetime);
&& Objects.equals(this.lifetime, other.lifetime)
&& Objects.equals(this.useJwtAccessWithScope, other.useJwtAccessWithScope);
}

String createAssertion(JsonFactory jsonFactory, long currentTime, String audience)
Expand Down Expand Up @@ -937,11 +949,58 @@ String createAssertionForIdToken(
}
}

/**
* Self signed JWT uses uri as audience, which should have the "https://{host}/" format. For
* instance, if the uri is "https://compute.googleapis.com/compute/v1/projects/", then this
* function returns "https://compute.googleapis.com/".
*/
@VisibleForTesting
static URI getUriForSelfSignedJWT(URI uri) {
if (uri == null || uri.getScheme() == null || uri.getHost() == null) {
return uri;
}
try {
return new URI(uri.getScheme(), uri.getHost(), "/", null);
} catch (URISyntaxException unused) {
return uri;
}
}

@VisibleForTesting
JwtCredentials createSelfSignedJwtCredentials(final URI uri) {
// Create a JwtCredentials for self signed JWT. See https://google.aip.dev/auth/4111.
JwtClaims.Builder claimsBuilder =
JwtClaims.newBuilder().setIssuer(clientEmail).setSubject(clientEmail);

if (uri == null) {
// If uri is null, use scopes.
String scopeClaim = "";
if (!scopes.isEmpty()) {
scopeClaim = Joiner.on(' ').join(scopes);
} else {
scopeClaim = Joiner.on(' ').join(defaultScopes);
}
claimsBuilder.setAdditionalClaims(Collections.singletonMap("scope", scopeClaim));
} else {
// otherwise, use audience with the uri.
claimsBuilder.setAudience(getUriForSelfSignedJWT(uri).toString());
}
return JwtCredentials.newBuilder()
.setPrivateKey(privateKey)
.setPrivateKeyId(privateKeyId)
.setJwtClaims(claimsBuilder.build())
.setClock(clock)
.build();
}

@Override
public void getRequestMetadata(
final URI uri, Executor executor, final RequestMetadataCallback callback) {
if (jwtCredentials != null && uri != null) {
jwtCredentials.getRequestMetadata(uri, executor, callback);
if (useJwtAccessWithScope) {
// This will call getRequestMetadata(URI uri), which handles self signed JWT logic.
// Self signed JWT doesn't use network, so here we do a blocking call to improve
// efficiency. executor will be ignored since it is intended for async operation.
blockingGetToCallback(uri, callback);
} else {
super.getRequestMetadata(uri, executor, callback);
}
Expand All @@ -950,17 +1009,31 @@ public void getRequestMetadata(
/** Provide the request metadata by putting an access JWT directly in the metadata. */
@Override
public Map<String, List<String>> getRequestMetadata(URI uri) throws IOException {
if (scopes.isEmpty() && defaultScopes.isEmpty() && uri == null) {
if (createScopedRequired() && uri == null) {
throw new IOException(
"Scopes and uri are not configured for service account. Either pass uri"
+ " to getRequestMetadata to use self signed JWT, or specify the scopes"
+ " by calling createScoped or passing scopes to constructor.");
"Scopes and uri are not configured for service account. Specify the scopes"
+ " by calling createScoped or passing scopes to constructor or"
+ " providing uri to getRequestMetadata.");
}
if (jwtCredentials != null && uri != null) {
return jwtCredentials.getRequestMetadata(uri);
} else {

// If scopes are provided but we cannot use self signed JWT, then use scopes to get access
// token.
if (!createScopedRequired() && !useJwtAccessWithScope) {
return super.getRequestMetadata(uri);
}

// If scopes are provided and self signed JWT can be used, use self signed JWT with scopes.
// Otherwise, use self signed JWT with uri as the audience.
JwtCredentials jwtCredentials;
if (!createScopedRequired() && useJwtAccessWithScope) {
// Create JWT credentials with the scopes.
jwtCredentials = createSelfSignedJwtCredentials(null);
} else {
// Create JWT credentials with the uri as audience.
jwtCredentials = createSelfSignedJwtCredentials(uri);
}
Map<String, List<String>> requestMetadata = jwtCredentials.getRequestMetadata(null);
return addQuotaProjectIdToRequestMetadata(quotaProjectId, requestMetadata);
}

@SuppressWarnings("unused")
Expand Down Expand Up @@ -997,6 +1070,7 @@ public static class Builder extends GoogleCredentials.Builder {
private HttpTransportFactory transportFactory;
private String quotaProjectId;
private int lifetime = DEFAULT_LIFETIME_IN_SECONDS;
private boolean useJwtAccessWithScope = false;

protected Builder() {}

Expand All @@ -1013,6 +1087,7 @@ protected Builder(ServiceAccountCredentials credentials) {
this.projectId = credentials.projectId;
this.quotaProjectId = credentials.quotaProjectId;
this.lifetime = credentials.lifetime;
this.useJwtAccessWithScope = credentials.useJwtAccessWithScope;
}

public Builder setClientId(String clientId) {
Expand Down Expand Up @@ -1077,6 +1152,11 @@ public Builder setLifetime(int lifetime) {
return this;
}

public Builder setUseJwtAccessWithScope(boolean useJwtAccessWithScope) {
this.useJwtAccessWithScope = useJwtAccessWithScope;
return this;
}

public String getClientId() {
return clientId;
}
Expand Down Expand Up @@ -1125,6 +1205,10 @@ public int getLifetime() {
return lifetime;
}

public boolean getUseJwtAccessWithScope() {
return useJwtAccessWithScope;
}

public ServiceAccountCredentials build() {
return new ServiceAccountCredentials(
clientId,
Expand All @@ -1138,7 +1222,8 @@ public ServiceAccountCredentials build() {
serviceAccountUser,
projectId,
quotaProjectId,
lifetime);
lifetime,
useJwtAccessWithScope);
}
}
}

0 comments on commit b26f8a4

Please sign in to comment.