Add updateTransitionalVersion method to certificate operations.

Fixes #42
This commit is contained in:
Scott Frederick
2018-10-24 14:33:12 -05:00
parent 926de97c91
commit 9af009a5d2
9 changed files with 244 additions and 42 deletions

View File

@@ -17,9 +17,8 @@
package org.springframework.credhub.core.certificate;
import org.springframework.credhub.support.CertificateSummary;
import org.springframework.credhub.support.CredentialDetails;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.certificate.CertificateCredential;
import org.springframework.credhub.support.certificate.CertificateCredentialDetails;
import java.util.List;
@@ -48,9 +47,25 @@ public interface CredHubCertificateOperations {
/**
* Regenerate a certificate.
*
* @param id the CredHub-generated ID of the certificate credential; must not be {@literal null}
* @param setAsTransitional make the certificate version transitional or not
* @param id the CredHub-generated ID of the certificate credential; must not be {@literal null}
* and must be an ID returned by {@link #getAll()}
* or {@link #getByName(CredentialName)}
* @param setAsTransitional {@code true} to mark the certificate version transitional;
* {@code false} otherwise
* @return the details of the certificate credential
*/
CredentialDetails<CertificateCredential> regenerate(final String id, final boolean setAsTransitional);
CertificateCredentialDetails regenerate(final String id, final boolean setAsTransitional);
/**
* Make the specified version of a certificate the {@literal transitional} version.
*
* @param id the CredHub-generated ID of the certificate credential; must not be {@literal null}
* and must be an ID returned by {@link #getAll()}
* or {@link #getByName(CredentialName)}
* @param versionId the CredHub-generated ID of the version of the certificate credential that should be
* marked {@literal transitional}, or {@literal null} to indicate that no version
* is {@literal transitional}
* @return the details of the certificate credential, including all versions
*/
List<CertificateCredentialDetails> updateTransitionalVersion(final String id, final String versionId);
}

View File

@@ -22,10 +22,10 @@ import org.springframework.credhub.core.ExceptionUtils;
import org.springframework.credhub.core.RestOperationsCallback;
import org.springframework.credhub.support.CertificateSummary;
import org.springframework.credhub.support.CertificateSummaryData;
import org.springframework.credhub.support.CredentialDetails;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.certificate.CertificateCredential;
import org.springframework.credhub.support.certificate.CertificateCredentialDetails;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.RestOperations;
@@ -34,8 +34,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.springframework.http.HttpMethod.POST;
/**
* Implements the interactions with CredHub to retrieve, regenerate, and update
* * certificates.
@@ -46,7 +44,10 @@ public class CredHubCertificateTemplate implements CredHubCertificateOperations
static final String BASE_URL_PATH = "/api/v1/certificates";
static final String NAME_URL_QUERY = BASE_URL_PATH + "?name={name}";
static final String REGENERATE_URL_PATH = BASE_URL_PATH + "/{id}/regenerate";
static final String UPDATE_TRANSITIONAL_URL_PATH = BASE_URL_PATH + "/{id}/update_transitional_version";
static final String TRANSITIONAL_REQUEST_FIELD = "set_as_transitional";
static final String VERSION_REQUEST_FIELD = "version";
private CredHubOperations credHubOperations;
@@ -108,23 +109,59 @@ public class CredHubCertificateTemplate implements CredHubCertificateOperations
* Regenerate a certificate.
*
* @param id the CredHub-generated ID of the certificate credential; must not be {@literal null}
* @param setAsTransitional {@code true} to mark the certificate version transitional;
* {@code false} otherwise
* @return the details of the certificate credential
*/
@Override
public CredentialDetails<CertificateCredential> regenerate(final String id, final boolean setAsTransitional) {
public CertificateCredentialDetails regenerate(final String id, final boolean setAsTransitional) {
Assert.notNull(id, "credential ID must not be null");
final ParameterizedTypeReference<CredentialDetails<CertificateCredential>> ref =
new ParameterizedTypeReference<CredentialDetails<CertificateCredential>>() {};
final ParameterizedTypeReference<CertificateCredentialDetails> ref =
new ParameterizedTypeReference<CertificateCredentialDetails>() {};
return credHubOperations.doWithRest(new RestOperationsCallback<CredentialDetails<CertificateCredential>>() {
return credHubOperations.doWithRest(new RestOperationsCallback<CertificateCredentialDetails>() {
@Override
public CredentialDetails<CertificateCredential> doWithRestOperations(RestOperations restOperations) {
public CertificateCredentialDetails doWithRestOperations(RestOperations restOperations) {
Map<String, Boolean> request = new HashMap<>();
request.put(TRANSITIONAL_REQUEST_FIELD, setAsTransitional);
ResponseEntity<CredentialDetails<CertificateCredential>> response =
restOperations.exchange(REGENERATE_URL_PATH, POST,
ResponseEntity<CertificateCredentialDetails> response =
restOperations.exchange(REGENERATE_URL_PATH, HttpMethod.POST,
new HttpEntity<Object>(request), ref, id);
ExceptionUtils.throwExceptionOnError(response);
return response.getBody();
}
});
}
/**
* Make the specified version of a certificate the {@literal transitional} version.
*
* @param id the CredHub-generated ID of the certificate credential; must not be {@literal null}
* and must be an ID returned by {@link #getAll()}
* or {@link #getByName(CredentialName)}
* @param versionId the CredHub-generated ID of the version of the certificate credential that should be
* marked {@literal transitional}
* @return the details of the certificate credential, including all versions
*/
public List<CertificateCredentialDetails> updateTransitionalVersion(final String id,
final String versionId) {
Assert.notNull(id, "credential ID must not be null");
final ParameterizedTypeReference<List<CertificateCredentialDetails>> ref =
new ParameterizedTypeReference<List<CertificateCredentialDetails>>() {};
return credHubOperations.doWithRest(new RestOperationsCallback<List<CertificateCredentialDetails>>() {
@Override
public List<CertificateCredentialDetails> doWithRestOperations(RestOperations restOperations) {
Map<String, String> request = new HashMap<>();
request.put(VERSION_REQUEST_FIELD, versionId);
ResponseEntity<List<CertificateCredentialDetails>> response =
restOperations.exchange(UPDATE_TRANSITIONAL_URL_PATH, HttpMethod.PUT,
new HttpEntity<Object>(request), ref, id);
ExceptionUtils.throwExceptionOnError(response);

View File

@@ -30,6 +30,7 @@ import org.springframework.credhub.support.CredentialSummary;
import org.springframework.credhub.support.CredentialSummaryData;
import org.springframework.credhub.support.ParametersRequest;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.RestOperations;
@@ -38,10 +39,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpMethod.PUT;
/**
* Implements the interactions with CredHub to save, retrieve,
* and delete credentials.
@@ -81,7 +78,7 @@ public class CredHubCredentialTemplate implements CredHubCredentialOperations {
@Override
public CredentialDetails<T> doWithRestOperations(RestOperations restOperations) {
ResponseEntity<CredentialDetails<T>> response =
restOperations.exchange(BASE_URL_PATH, PUT,
restOperations.exchange(BASE_URL_PATH, HttpMethod.PUT,
new HttpEntity<>(credentialRequest), ref);
ExceptionUtils.throwExceptionOnError(response);
@@ -102,7 +99,7 @@ public class CredHubCredentialTemplate implements CredHubCredentialOperations {
@Override
public CredentialDetails<T> doWithRestOperations(RestOperations restOperations) {
ResponseEntity<CredentialDetails<T>> response =
restOperations.exchange(BASE_URL_PATH, POST,
restOperations.exchange(BASE_URL_PATH, HttpMethod.POST,
new HttpEntity<>(parametersRequest), ref);
ExceptionUtils.throwExceptionOnError(response);
@@ -127,7 +124,7 @@ public class CredHubCredentialTemplate implements CredHubCredentialOperations {
request.put("name", name.getName());
ResponseEntity<CredentialDetails<T>> response =
restOperations.exchange(REGENERATE_URL_PATH, POST,
restOperations.exchange(REGENERATE_URL_PATH, HttpMethod.POST,
new HttpEntity<>(request), ref);
ExceptionUtils.throwExceptionOnError(response);
@@ -149,7 +146,7 @@ public class CredHubCredentialTemplate implements CredHubCredentialOperations {
@Override
public CredentialDetails<T> doWithRestOperations(RestOperations restOperations) {
ResponseEntity<CredentialDetails<T>> response =
restOperations.exchange(ID_URL_PATH, GET, null, ref, id);
restOperations.exchange(ID_URL_PATH, HttpMethod.GET, null, ref, id);
ExceptionUtils.throwExceptionOnError(response);
@@ -170,7 +167,8 @@ public class CredHubCredentialTemplate implements CredHubCredentialOperations {
@Override
public CredentialDetails<T> doWithRestOperations(RestOperations restOperations) {
ResponseEntity<CredentialDetailsData<T>> response =
restOperations.exchange(NAME_URL_QUERY_CURRENT, GET, null, ref, name.getName());
restOperations.exchange(NAME_URL_QUERY_CURRENT, HttpMethod.GET,
null, ref, name.getName());
ExceptionUtils.throwExceptionOnError(response);
@@ -191,7 +189,7 @@ public class CredHubCredentialTemplate implements CredHubCredentialOperations {
@Override
public List<CredentialDetails<T>> doWithRestOperations(RestOperations restOperations) {
ResponseEntity<CredentialDetailsData<T>> response =
restOperations.exchange(NAME_URL_QUERY, GET, null, ref, name.getName());
restOperations.exchange(NAME_URL_QUERY, HttpMethod.GET, null, ref, name.getName());
ExceptionUtils.throwExceptionOnError(response);
@@ -213,7 +211,7 @@ public class CredHubCredentialTemplate implements CredHubCredentialOperations {
@Override
public List<CredentialDetails<T>> doWithRestOperations(RestOperations restOperations) {
ResponseEntity<CredentialDetailsData<T>> response =
restOperations.exchange(NAME_URL_QUERY_VERSIONS, GET, null, ref,
restOperations.exchange(NAME_URL_QUERY_VERSIONS, HttpMethod.GET, null, ref,
name.getName(), versions);
ExceptionUtils.throwExceptionOnError(response);

View File

@@ -21,12 +21,11 @@ import org.springframework.credhub.core.ExceptionUtils;
import org.springframework.credhub.core.RestOperationsCallback;
import org.springframework.credhub.support.ServicesData;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.RestOperations;
import static org.springframework.http.HttpMethod.POST;
/**
* Implements the main interaction with CredHub to interpolate service binding credentials.
*
@@ -54,7 +53,7 @@ public class CredHubInterpolationTemplate implements CredHubInterpolationOperati
@Override
public ServicesData doWithRestOperations(RestOperations restOperations) {
ResponseEntity<ServicesData> response = restOperations
.exchange(INTERPOLATE_URL_PATH, POST,
.exchange(INTERPOLATE_URL_PATH, HttpMethod.POST,
new HttpEntity<>(serviceData), ServicesData.class);
ExceptionUtils.throwExceptionOnError(response);

View File

@@ -23,14 +23,13 @@ import org.springframework.credhub.support.CredentialPermissions;
import org.springframework.credhub.support.permissions.Actor;
import org.springframework.credhub.support.permissions.CredentialPermission;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.RestOperations;
import java.util.List;
import static org.springframework.http.HttpMethod.POST;
/**
* Implements the main interaction with CredHub to add, retrieve,
* and delete permissions.
@@ -78,7 +77,7 @@ public class CredHubPermissionTemplate implements CredHubPermissionOperations {
credHubOperations.doWithRest(new RestOperationsCallback<Void>() {
@Override
public Void doWithRestOperations(RestOperations restOperations) {
restOperations.exchange(PERMISSIONS_URL_PATH, POST,
restOperations.exchange(PERMISSIONS_URL_PATH, HttpMethod.POST,
new HttpEntity<>(credentialPermissions),
CredentialPermissions.class);
return null;

View File

@@ -22,8 +22,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.util.Objects;
/**
* The details of a credential that has been written to CredHub. Clients don't
* typically instantiate objects of this type, but will receive them in response
* The details of a credential that has been written to CredHub.
*
* Clients don't typically instantiate objects of this type, but will receive them in response
* to write and retrieve requests. The {@literal id} and {@literal name} fields
* can be used in subsequent requests.
*

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2016-2017 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.credhub.support.certificate;
import org.springframework.credhub.support.CredentialDetails;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.CredentialType;
/**
* The details of a certificate credential that has been written to CredHub. This is a specialization
* of {@link CredentialDetails} that adds certificate-specific fields.
*
* Clients don't typically instantiate objects of this type, but will receive them in response
* to credential operation requests. The {@literal id} and {@literal name} fields
* can be used in subsequent requests.
*
* @author Scott Frederick
*/
public class CertificateCredentialDetails extends CredentialDetails<CertificateCredential> {
private final boolean transitional;
/**
* Create a {@link CertificateCredentialDetails}.
*/
@SuppressWarnings("unused")
private CertificateCredentialDetails() {
super();
this.transitional = false;
}
/**
* Create a {@link CertificateCredentialDetails} from the provided parameters. Intended for
* internal use. Clients will get {@link CertificateCredentialDetails} objects populated from
* CredHub responses.
*
* @param id the CredHub-generated unique ID of the credential
* @param name the client-provided name of the credential
* @param credentialType the {@link CredentialType} of the credential
* @param transitional a flag indicating whether the certificate will be used for signing
* @param value the client-provided value for the credential
*/
public CertificateCredentialDetails(String id, CredentialName name, CredentialType credentialType,
boolean transitional, CertificateCredential value) {
super(id, name, credentialType, value);
this.transitional = transitional;
}
/**
* Get the value of the flag indicating whether the certificate is currently being used for signing
* or if it is being staged.
*
* @return the transitional flag
*/
public boolean isTransitional() {
return transitional;
}
}

View File

@@ -25,15 +25,16 @@ import org.springframework.core.ParameterizedTypeReference;
import org.springframework.credhub.core.CredHubTemplate;
import org.springframework.credhub.support.CertificateSummary;
import org.springframework.credhub.support.CertificateSummaryData;
import org.springframework.credhub.support.CredentialDetails;
import org.springframework.credhub.support.CredentialType;
import org.springframework.credhub.support.SimpleCredentialName;
import org.springframework.credhub.support.certificate.CertificateCredential;
import org.springframework.credhub.support.certificate.CertificateCredentialDetails;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -46,6 +47,8 @@ import static org.springframework.credhub.core.certificate.CredHubCertificateTem
import static org.springframework.credhub.core.certificate.CredHubCertificateTemplate.NAME_URL_QUERY;
import static org.springframework.credhub.core.certificate.CredHubCertificateTemplate.REGENERATE_URL_PATH;
import static org.springframework.credhub.core.certificate.CredHubCertificateTemplate.TRANSITIONAL_REQUEST_FIELD;
import static org.springframework.credhub.core.certificate.CredHubCertificateTemplate.UPDATE_TRANSITIONAL_URL_PATH;
import static org.springframework.credhub.core.certificate.CredHubCertificateTemplate.VERSION_REQUEST_FIELD;
import static org.springframework.http.HttpStatus.OK;
@RunWith(MockitoJUnitRunner.class)
@@ -97,25 +100,57 @@ public class CredHubCertificateTemplateUnitTests {
}
@Test
@SuppressWarnings("unchecked")
public void regenerate() {
CredentialDetails<CertificateCredential> expectedCertificates =
new CredentialDetails<>("id", NAME, CredentialType.CERTIFICATE,
CertificateCredentialDetails expectedCertificate =
new CertificateCredentialDetails("id", NAME, CredentialType.CERTIFICATE, true,
new CertificateCredential("cert", "authority", "key"));
Map<String, Boolean> request = new HashMap<>();
request.put(TRANSITIONAL_REQUEST_FIELD, true);
when(restTemplate.exchange(eq(REGENERATE_URL_PATH), eq(HttpMethod.POST),
eq(new HttpEntity<Object>(request)), isA(ParameterizedTypeReference.class), eq("id")))
.thenReturn(new ResponseEntity<>(expectedCertificates, OK));
eq(new HttpEntity<>(request)), isA(ParameterizedTypeReference.class), eq("id")))
.thenReturn(new ResponseEntity<>(expectedCertificate, OK));
CredentialDetails<CertificateCredential> response = credHubTemplate.regenerate("id", true);
CertificateCredentialDetails response = credHubTemplate.regenerate("id", true);
assertThat(response).isNotNull();
assertThat(response.getId()).isEqualTo("id");
assertThat(response.getCredentialType()).isEqualTo(CredentialType.CERTIFICATE);
assertThat(response.isTransitional()).isTrue();
assertThat(response.getValue().getCertificate()).isEqualTo("cert");
assertThat(response.getValue().getCertificateAuthority()).isEqualTo("authority");
assertThat(response.getValue().getPrivateKey()).isEqualTo("key");
}
@Test
@SuppressWarnings("unchecked")
public void updateTransitionalVersion() {
List<CertificateCredentialDetails> expectedCertificates = Arrays.asList(
new CertificateCredentialDetails("id1", NAME, CredentialType.CERTIFICATE, false,
new CertificateCredential("cert1", "authority1", "key1")),
new CertificateCredentialDetails("id2", NAME, CredentialType.CERTIFICATE, true,
new CertificateCredential("cert2", "authority2", "key2"))
);
Map<String, String> request = new HashMap<>();
request.put(VERSION_REQUEST_FIELD, "id2");
when(restTemplate.exchange(eq(UPDATE_TRANSITIONAL_URL_PATH), eq(HttpMethod.PUT),
eq(new HttpEntity<>(request)), isA(ParameterizedTypeReference.class), eq("id1")))
.thenReturn(new ResponseEntity<>(expectedCertificates, OK));
List<CertificateCredentialDetails> response =
credHubTemplate.updateTransitionalVersion("id1", "id2");
assertThat(response).hasSize(2);
assertThat(response).extracting("id").contains("id1", "id2");
assertThat(response).extracting("credentialType").contains(CredentialType.CERTIFICATE,
CredentialType.CERTIFICATE);
assertThat(response).extracting("transitional").contains(false, true);
assertThat(response).extracting("value.certificate").contains("cert1", "cert2");
assertThat(response).extracting("value.certificateAuthority").contains("authority1", "authority2");
assertThat(response).extracting("value.privateKey").contains("key1", "key2");
}
}

View File

@@ -28,6 +28,7 @@ import org.springframework.credhub.support.CredentialSummary;
import org.springframework.credhub.support.CredentialType;
import org.springframework.credhub.support.SimpleCredentialName;
import org.springframework.credhub.support.certificate.CertificateCredential;
import org.springframework.credhub.support.certificate.CertificateCredentialDetails;
import org.springframework.credhub.support.certificate.CertificateParameters;
import org.springframework.credhub.support.certificate.CertificateParametersRequest;
@@ -101,10 +102,56 @@ public class CertificateIntegrationTests extends CredHubIntegrationTests {
CertificateSummary byName = certificates.getByName(CREDENTIAL_NAME);
CredentialDetails<CertificateCredential> regenerated = certificates.regenerate(byName.getId(), true);
CertificateCredentialDetails regenerated = certificates.regenerate(byName.getId(), true);
assertThat(regenerated.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(regenerated.isTransitional()).isTrue();
assertThat(regenerated.getValue().getCertificate()).isNotEqualTo(certificate.getValue().getCertificate());
assertThat(regenerated.getValue().getCertificateAuthority()).isNotEqualTo(certificate.getValue().getCertificateAuthority());
assertThat(regenerated.getValue().getPrivateKey()).isNotEqualTo(certificate.getValue().getPrivateKey());
}
@Test
public void rotateCertificate() {
CredentialDetails<CertificateCredential> certificate = credentials.generate(CertificateParametersRequest.builder()
.name(CREDENTIAL_NAME)
.parameters(CertificateParameters.builder()
.commonName("example.com")
.selfSign(true)
.build())
.build());
assertThat(certificate.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
String credentialVersion0Id = certificate.getId();
List<CredentialDetails<CertificateCredential>> allVersions =
credentials.getByNameWithHistory(CREDENTIAL_NAME, CertificateCredential.class);
assertThat(allVersions).hasSize(1);
assertThat(allVersions.get(0).getId()).isEqualTo(credentialVersion0Id);
CertificateSummary byName = certificates.getByName(CREDENTIAL_NAME);
String certificateId = byName.getId();
CertificateCredentialDetails regenerated = certificates.regenerate(certificateId, true);
assertThat(regenerated.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(regenerated.getValue().getCertificate()).isNotEqualTo(certificate.getValue().getCertificate());
assertThat(regenerated.getValue().getCertificateAuthority()).isNotEqualTo(certificate.getValue().getCertificateAuthority());
assertThat(regenerated.getValue().getPrivateKey()).isNotEqualTo(certificate.getValue().getPrivateKey());
String credentialVersion1Id = regenerated.getId();
allVersions = credentials.getByNameWithHistory(CREDENTIAL_NAME, CertificateCredential.class);
assertThat(allVersions).hasSize(2);
assertThat(allVersions).extracting("id").contains(credentialVersion1Id, credentialVersion0Id);
List<CertificateCredentialDetails> updatedCertificate =
certificates.updateTransitionalVersion(certificateId, credentialVersion0Id);
assertThat(updatedCertificate).hasSize(2);
assertThat(updatedCertificate).extracting("id").contains(credentialVersion1Id, credentialVersion0Id);
assertThat(updatedCertificate).extracting("transitional").contains(false, true);
updatedCertificate = certificates.updateTransitionalVersion(certificateId, null);
assertThat(updatedCertificate).hasSize(1);
assertThat(updatedCertificate).extracting("id").contains(credentialVersion1Id);
assertThat(updatedCertificate).extracting("transitional").contains(false);
}
}