Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
andreasgebauer committed Jan 27, 2021
1 parent 1e453eb commit ffa6aab
Show file tree
Hide file tree
Showing 11 changed files with 1,007 additions and 0 deletions.
19 changes: 19 additions & 0 deletions spring-vault-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,25 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-iamcredentials</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.google.auth</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2018-2020 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
*
* https://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 org.springframework.util.StringUtils;

import com.google.auth.oauth2.ServiceAccountCredentials;

/**
* Default implementation of{@link GcpServiceAccountCredentialsProjectIdAccessor} and
* {@link GcpServiceAccountCredentialsAccountIdAccessor}. Used by {@link GcpIamCredentialsAuthentication}.
*
* @author Andreas Gebauer
* @since 2.4
* @see GcpIamCredentialsAuthentication
*/
enum DefaultGcpServiceAccountCredentialsAccessors implements GcpServiceAccountCredentialsProjectIdAccessor,
GcpServiceAccountCredentialsAccountIdAccessor {

INSTANCE;

/**
* Get a the service account id (email) to be placed in the signed JWT.
* @param credentials credentials object to obtain the service account id from.
* @return the service account id to use.
*/
@Override
public String getServiceAccountId(ServiceAccountCredentials credentials) {

Assert.notNull(credentials, "GoogleCredential must not be null");
Assert.notNull(credentials.getAccount(),
"The configured GoogleCredential does not represent a service account. Configure the service account id with GcpIamAuthenticationOptionsBuilder#serviceAccountId(String).");

return credentials.getAccount();
}

/**
* Get a the GCP project id to used in Google Cloud IAM credentials API calls.
* @param credentials the credentials object to obtain the project id from.
* @return the service account id to use.
*/
@Override
public String getProjectId(ServiceAccountCredentials credentials) {

Assert.notNull(credentials, "GoogleCredential must not be null");
Assert.isInstanceOf(ServiceAccountCredentials.class, credentials,
"GoogleCredential must be instance of ServiceAccountCredentials");

return StringUtils.isEmpty(credentials.getProjectId()) ? "-" : credentials.getProjectId();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@
* @see <a href=
* "https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signJwt">GCP:
* projects.serviceAccounts.signJwt</a>
* @deprecated Use {@link GcpIamCredentialsAuthentication} instead.
*/
@Deprecated
public class GcpIamAuthentication extends GcpJwtAuthenticationSupport implements ClientAuthentication {

private static final JsonFactory JSON_FACTORY = new JacksonFactory();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright 2021 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
*
* https://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 java.io.IOException;
import java.time.Instant;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.util.Assert;
import org.springframework.vault.VaultException;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.RestOperations;

import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
import com.google.api.gax.rpc.TransportChannelProvider;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.cloud.iam.credentials.v1.IamCredentialsClient;
import com.google.cloud.iam.credentials.v1.IamCredentialsSettings;
import com.google.cloud.iam.credentials.v1.ServiceAccountName;
import com.google.cloud.iam.credentials.v1.SignJwtResponse;
import com.google.cloud.iam.credentials.v1.stub.IamCredentialsStubSettings;

/**
* GCP IAM login implementation using GCP IAM service accounts to legitimate its
* authenticity via JSON Web Token.
* <p/>
* This authentication method uses Googles IAM Credentials API to obtain a signed token for
* a specific {@link com.google.api.client.auth.oauth2.Credential}. Project and service
* account details are obtained from a {@link GoogleCredentials} that can be retrieved
* either from a JSON file or the runtime environment (GAE, GCE).
* <p/>
* {@link GcpIamCredentialsAuthentication} uses Google Java API that uses synchronous API.
*
* @author Andreas Gebauer
* @since 2.4
* @see GcpIamCredentialsAuthenticationOptions
* @see HttpTransport
* @see GoogleCredentials
* @see GoogleCredentials#getApplicationDefault()
* @see RestOperations
* @see <a href="https://www.vaultproject.io/docs/auth/gcp.html">Auth Backend: gcp
* (IAM)</a>
* @see <a href=
* "https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signJwt">GCP:
* projects.serviceAccounts.signJwt</a>
*/
public class GcpIamCredentialsAuthentication extends GcpJwtAuthenticationSupport implements ClientAuthentication {

private static final JsonFactory JSON_FACTORY = new JacksonFactory();

private final GcpIamCredentialsAuthenticationOptions options;

private final TransportChannelProvider transportChannelProvider;

private final ServiceAccountCredentials credentials;

/**
* Create a new instance of {@link GcpIamCredentialsAuthentication} given
* {@link GcpIamCredentialsAuthenticationOptions} and {@link RestOperations}. This constructor
* initializes {@link InstantiatingGrpcChannelProvider} for Google API usage.
* @param options must not be {@literal null}.
* @param restOperations HTTP client for for Vault login, must not be {@literal null}.
*/
public GcpIamCredentialsAuthentication(GcpIamCredentialsAuthenticationOptions options,
RestOperations restOperations) {
this(options, restOperations, IamCredentialsStubSettings.defaultGrpcTransportProviderBuilder().build());
}

/**
* Create a new instance of {@link GcpIamCredentialsAuthentication} given
* {@link GcpIamCredentialsAuthenticationOptions}, {@link RestOperations} and {@link TransportChannelProvider}.
* @param options must not be {@literal null}.
* @param restOperations HTTP client for for Vault login, must not be {@literal null}.
* @param transportChannelProvider Provider for transport channel Google API use, must
* not be {@literal null}.
*/
public GcpIamCredentialsAuthentication(GcpIamCredentialsAuthenticationOptions options,
RestOperations restOperations, TransportChannelProvider transportChannelProvider) {

super(restOperations);

Assert.notNull(options, "GcpAuthenticationOptions must not be null");
Assert.notNull(restOperations, "RestOperations must not be null");
Assert.notNull(transportChannelProvider, "TransportChannelProvider must not be null");

this.options = options;
this.transportChannelProvider = transportChannelProvider;
this.credentials = options.getCredentialSupplier().get();
}

@Override
public VaultToken login() throws VaultException {

String signedJwt = signJwt();

return doLogin("GCP-IAM", signedJwt, this.options.getPath(), this.options.getRole());
}

protected String signJwt() {

String projectId = getProjectId();
String serviceAccount = getServiceAccountId();
Map<String, Object> jwtPayload = getJwtPayload(this.options, serviceAccount);

try {
IamCredentialsSettings credentialsSettings = IamCredentialsSettings.newBuilder()
.setCredentialsProvider(() -> this.credentials)
.setTransportChannelProvider(this.transportChannelProvider).build();
try (IamCredentialsClient iamCredentialsClient = IamCredentialsClient.create(credentialsSettings)) {
String payload = JSON_FACTORY.toString(jwtPayload);
ServiceAccountName serviceAccountName = ServiceAccountName.of(projectId, serviceAccount);
SignJwtResponse response = iamCredentialsClient.signJwt(serviceAccountName, Collections.emptyList(),
payload);
return response.getSignedJwt();
}
}
catch (IOException e) {
throw new VaultLoginException("Cannot sign JWT", e);
}
}

private String getServiceAccountId() {
return this.options.getServiceAccountIdAccessor().getServiceAccountId(this.credentials);
}

private String getProjectId() {
return this.options.getProjectIdAccessor().getProjectId(this.credentials);
}

private static Map<String, Object> getJwtPayload(GcpIamCredentialsAuthenticationOptions options,
String serviceAccount) {

Instant validUntil = options.getClock().instant().plus(options.getJwtValidity());

Map<String, Object> payload = new LinkedHashMap<>();

payload.put("sub", serviceAccount);
payload.put("aud", "vault/" + options.getRole());
payload.put("exp", validUntil.getEpochSecond());

return payload;
}

}
Loading

0 comments on commit ffa6aab

Please sign in to comment.