Allow configuration of service account id and project id values GCP IAM Authentication.

Original pull request: gh-287.
Closes gh-261.
This commit is contained in:
Magnus Jungsbluth
2018-08-23 10:25:32 +02:00
committed by Mark Paluch
parent 722368d59e
commit 41645f1034
7 changed files with 339 additions and 6 deletions

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2018 the original author or authors.
*
* 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 org.springframework.vault.authentication;
import org.springframework.util.StringUtils;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
/**
* Default implementation to obtain a GCP project id for GCP IAM authentication.
* Used by {@link GcpIamAuthentication}.
*
* @author Magnus Jungsbluth
* @since 2.1
* @see GcpIamAuthentication
*/
public class DefaultGcpProjectIdProvider implements GcpProjectIdProvider {
@Override
public String getProjectId(GoogleCredential credential) {
if (StringUtils.isEmpty(credential.getServiceAccountProjectId())) {
return "-";
} else {
return credential.getServiceAccountProjectId();
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2018 the original author or authors.
*
* 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 org.springframework.vault.authentication;
import org.springframework.util.Assert;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
/**
* Default implementation to obtain a service account id for GCP IAM authentication.
* Used by {@link GcpIamAuthentication}.
*
* @author Magnus Jungsbluth
* @since 2.1
* @see GcpIamAuthentication
*/
public class DefaultGcpServiceAccountIdProvider implements GcpServiceAccountIdProvider {
@Override
public String getServiceAccountId(GoogleCredential credential) {
Assert.notNull(credential.getServiceAccountId(), "The configured GoogleCredential does not represent a service account. Configure the service account id manually");
return credential.getServiceAccountId();
}
}

View File

@@ -125,8 +125,8 @@ public class GcpIamAuthentication extends GcpJwtAuthenticationSupport implements
protected String signJwt() {
String projectId = credential.getServiceAccountProjectId();
String serviceAccount = credential.getServiceAccountId();
String projectId = options.getProjectIdProvider().getProjectId(credential);
String serviceAccount = options.getServiceAccountIdProvider().getServiceAccountId(credential);
Map<String, Object> jwtPayload = getJwtPayload(options, serviceAccount);
Iam iam = new Builder(httpTransport, JSON_FACTORY, credential)

View File

@@ -18,7 +18,6 @@ package org.springframework.vault.authentication;
import java.time.Clock;
import java.time.Duration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
@@ -65,15 +64,27 @@ public class GcpIamAuthenticationOptions {
*/
private final Clock clock;
/**
* Provide the service account id to use as sub/iss claims
*/
private final GcpServiceAccountIdProvider serviceAccountIdSupplier;
/**
* The GCP project id to use in GCP IAM API calls
*/
private final GcpProjectIdProvider projectIdSupplier;
private GcpIamAuthenticationOptions(String path,
GcpCredentialSupplier credentialSupplier, String role, Duration jwtValidity,
Clock clock) {
Clock clock, GcpServiceAccountIdProvider serviceAccountIdSupplier, GcpProjectIdProvider projectIdSupplier) {
this.path = path;
this.credentialSupplier = credentialSupplier;
this.role = role;
this.jwtValidity = jwtValidity;
this.clock = clock;
this.serviceAccountIdSupplier = serviceAccountIdSupplier;
this.projectIdSupplier = projectIdSupplier;
}
/**
@@ -118,6 +129,20 @@ public class GcpIamAuthenticationOptions {
return clock;
}
/**
* Provide the service account id to use as sub/iss claims
*/
public GcpServiceAccountIdProvider getServiceAccountIdProvider() {
return serviceAccountIdSupplier;
}
/**
* The GCP project id to use in GCP IAM API calls
*/
public GcpProjectIdProvider getProjectIdProvider() {
return projectIdSupplier;
}
/**
* Builder for {@link GcpIamAuthenticationOptions}.
*/
@@ -135,6 +160,10 @@ public class GcpIamAuthenticationOptions {
private Clock clock = Clock.systemDefaultZone();
private GcpServiceAccountIdProvider serviceAccountIdProvider = new DefaultGcpServiceAccountIdProvider();
private GcpProjectIdProvider projectIdProvider = new DefaultGcpProjectIdProvider();
GcpIamAuthenticationOptionsBuilder() {
}
@@ -169,7 +198,7 @@ public class GcpIamAuthenticationOptions {
}
/**
* Configure an {@link AWSCredentialsProvider}, required to create a signed JWT.
* Configure an {@link GcpCredentialSupplier}, required to create a signed JWT.
* Alternatively, configure static {@link #credential(GoogleCredential)
* credentials}.
*
@@ -186,6 +215,61 @@ public class GcpIamAuthenticationOptions {
return this;
}
/**
* Configure an explicit service account id to use in GCP IAM calls. If none is configured, falls back to using
* {@link GoogleCredential#getServiceAccountId()}.
*
* @param serviceAccountId the service account id (email) to use
* @return {@code this} {@link GcpIamAuthenticationOptionsBuilder}.
*/
public GcpIamAuthenticationOptionsBuilder serviceAccountId(String serviceAccountId) {
Assert.notNull(serviceAccountId, "Service account id may not be null");
return serviceAccountIdProvider((GoogleCredential credential) -> serviceAccountId);
}
/**
* Configure an {@link GcpServiceAccountIdProvider} to obtain the service account id used in GCP IAM calls.
* If none is configured, falls back to using {@link GoogleCredential#getServiceAccountId()}.
*
* @param serviceAccountIdProvider the service account id provider to use
* @return {@code this} {@link GcpIamAuthenticationOptionsBuilder}.
* @see GcpServiceAccountIdProvider
*/
public GcpIamAuthenticationOptionsBuilder serviceAccountIdProvider(GcpServiceAccountIdProvider serviceAccountIdProvider) {
Assert.notNull(serviceAccountIdProvider, "GcpServiceAccountIdProvider must not be null");
this.serviceAccountIdProvider = serviceAccountIdProvider;
return this;
}
/**
* Configure an explicit GCP project id to use in GCP IAM API calls. If none is configured, falls back using
* {@link GoogleCredential#getServiceAccountProjectId()}.
*
* @param projectId the GCP project id to use in GCP IAM API calls
* @return {@code this} {@link GcpIamAuthenticationOptionsBuilder}.
*/
public GcpIamAuthenticationOptionsBuilder projectId(String projectId) {
Assert.notNull(projectId, "GCP project id must not be null");
return projectIdProvider((GoogleCredential credential) -> projectId);
}
/**
* Configure an {@link GcpProjectIdProvider} to use in GCP IAM API calls. If none is configured, falls back using
* {@link GoogleCredential#getServiceAccountProjectId()}.
*
* @param projectIdProvider the GCP project id supplier to use in GCP IAM API calls
* @return {@code this} {@link GcpIamAuthenticationOptionsBuilder}.
*/
public GcpIamAuthenticationOptionsBuilder projectIdProvider(GcpProjectIdProvider projectIdProvider) {
Assert.notNull(projectIdProvider, "GcpProjectIdProvider must not be null");
this.projectIdProvider = projectIdProvider;
return this;
}
/**
* Configure the name of the role against which the login is being attempted.
*
@@ -241,7 +325,7 @@ public class GcpIamAuthenticationOptions {
Assert.notNull(role, "Role must not be null");
return new GcpIamAuthenticationOptions(path, credentialSupplier, role,
jwtValidity, clock);
jwtValidity, clock, serviceAccountIdProvider, projectIdProvider);
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2018 the original author or authors.
*
* 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 org.springframework.vault.authentication;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
/**
* Interface to obtain a GCP project id for GCP IAM authentication.
* Implementations are used by {@link GcpIamAuthentication}.
*
* @author Magnus Jungsbluth
* @since 2.1
* @see GcpIamAuthentication
*/
@FunctionalInterface
public interface GcpProjectIdProvider {
/**
* Get a the GCP project id to used in Google Cloud IAM API calls.
*
* @return the service account id to use.
*/
String getProjectId(GoogleCredential credential);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2018 the original author or authors.
*
* 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 org.springframework.vault.authentication;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
/**
* Interface to obtain a service account id for GCP IAM authentication.
* Implementations are used by {@link GcpIamAuthentication}.
*
* @author Magnus Jungsbluth
* @since 2.1
* @see GcpIamAuthentication
*/
@FunctionalInterface
public interface GcpServiceAccountIdProvider {
/**
* Get a the service account id (email) to be placed in the signed JWT.
*
* @return the service account id to use.
*/
String getServiceAccountId(GoogleCredential credential);
}

View File

@@ -0,0 +1,96 @@
package org.springframework.vault.authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import java.security.PrivateKey;
import org.junit.Test;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
/**
* Unit tests for {@link GcpIamAuthenticationOptions.GcpIamAuthenticationOptionsBuilder}
*/
public class GcpIamAuthenticationOptionsBuilderUnitTests {
private GoogleCredential createGoogleCredential() {
GoogleCredential credential = new GoogleCredential.Builder().setServiceAccountId("hello@world")
.setServiceAccountProjectId("foobar")
.setServiceAccountPrivateKey(mock(PrivateKey.class))
.setServiceAccountPrivateKeyId("key-id").build();
credential.setAccessToken("foobar");
return credential;
}
@Test
public void shouldDefaultToCredentialServiceAccountId() {
GoogleCredential credential = createGoogleCredential();
GcpIamAuthenticationOptions options = GcpIamAuthenticationOptions.builder()
.credential(credential)
.role("foo")
.build();
assertThat(options.getServiceAccountIdProvider().getServiceAccountId(credential)).isEqualTo("hello@world");
}
@Test
public void shouldAllowServiceAccountIdOverride() {
GoogleCredential credential = createGoogleCredential();
GcpIamAuthenticationOptions options = GcpIamAuthenticationOptions.builder()
.credential(credential)
.serviceAccountId("override@foo.com")
.role("foo")
.build();
assertThat(options.getServiceAccountIdProvider().getServiceAccountId(credential)).isEqualTo("override@foo.com");
}
@Test
public void shouldAllowServiceAccountIdProviderOverride() {
GoogleCredential credential = createGoogleCredential();
GcpIamAuthenticationOptions options = GcpIamAuthenticationOptions.builder()
.credential(credential)
.serviceAccountIdProvider((GoogleCredential googleCredential) -> "override@foo.com")
.role("foo")
.build();
assertThat(options.getServiceAccountIdProvider().getServiceAccountId(credential)).isEqualTo("override@foo.com");
}
@Test
public void shouldDefaultToCredentialProjectId() {
GoogleCredential credential = createGoogleCredential();
GcpIamAuthenticationOptions options = GcpIamAuthenticationOptions.builder()
.credential(credential)
.role("foo")
.build();
assertThat(options.getProjectIdProvider().getProjectId(credential)).isEqualTo("foobar");
}
@Test
public void shouldAllowProjectIdOverride() {
GoogleCredential credential = createGoogleCredential();
GcpIamAuthenticationOptions options = GcpIamAuthenticationOptions.builder()
.credential(credential)
.projectId("my-project")
.role("foo")
.build();
assertThat(options.getProjectIdProvider().getProjectId(credential)).isEqualTo("my-project");
}
@Test
public void shouldAllowProjectIdProviderOverride() {
GoogleCredential credential = createGoogleCredential();
GcpIamAuthenticationOptions options = GcpIamAuthenticationOptions.builder()
.credential(credential)
.projectIdProvider((GoogleCredential googleCredential) -> "my-project")
.role("foo")
.build();
assertThat(options.getProjectIdProvider().getProjectId(credential)).isEqualTo("my-project");
}
}