From d7d904219104bd2223049dd54f9d253de326d251 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 27 Aug 2018 16:32:18 +0200 Subject: [PATCH] Encapsulate Vault lease endpoint differences in LeaseEndpoints. We now provide a LeaseEndpoints enum that reflects differences between Vault versions regarding their lease endpoints. Related pull request: gh-282. Closes gh-262. --- .../vault/core/lease/LeaseEndpoints.java | 141 ++++++++++++++++++ .../core/lease/SecretLeaseContainer.java | 57 ++++--- .../lease/SecretLeaseContainerUnitTests.java | 28 +--- 3 files changed, 171 insertions(+), 55 deletions(-) create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java new file mode 100644 index 00000000..20e588ec --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java @@ -0,0 +1,141 @@ +/* + * 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.core.lease; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; +import org.springframework.vault.core.lease.domain.Lease; +import org.springframework.web.client.RestOperations; + +/** + * Version-specific endpoint implementations that use either legacy or sys/leases + * endpoints. + * + * @author Mark Paluch + * @since 2.1 + * @see SecretLeaseContainer + */ +public enum LeaseEndpoints { + + /** + * Legacy endpoints prior to Vault 0.8 ({@literal /sys/renew},{@literal /sys/revoke}). + */ + Legacy { + + @Override + public void revoke(Lease lease, RestOperations operations) { + + operations.exchange("sys/revoke", HttpMethod.PUT, + LeaseEndpoints.getLeaseRevocationBody(lease), Map.class, + lease.getLeaseId()); + } + + @SuppressWarnings("unchecked") + @Override + public Lease renew(Lease lease, RestOperations operations) { + + HttpEntity leaseRenewalEntity = getLeaseRenewalBody(lease); + + ResponseEntity> entity = (ResponseEntity) operations + .exchange("sys/renew", HttpMethod.PUT, leaseRenewalEntity, Map.class); + + Assert.state(entity != null && entity.getBody() != null, + "Renew response must not be null"); + + return toLease(entity.getBody()); + } + }, + + /** + * Sys/lease endpoints for Vault 0.8 ans higher ({@literal /sys/leases/…}). + */ + SysLeases { + + @Override + public void revoke(Lease lease, RestOperations operations) { + + operations.exchange("sys/leases/revoke", HttpMethod.PUT, + LeaseEndpoints.getLeaseRevocationBody(lease), Map.class, + lease.getLeaseId()); + } + + @Override + @SuppressWarnings("unchecked") + public Lease renew(Lease lease, RestOperations operations) { + + HttpEntity leaseRenewalEntity = getLeaseRenewalBody(lease); + + ResponseEntity> entity = (ResponseEntity) operations + .exchange("sys/leases/renew", HttpMethod.PUT, leaseRenewalEntity, + Map.class); + + Assert.state(entity != null && entity.getBody() != null, + "Renew response must not be null"); + + return toLease(entity.getBody()); + } + }; + + /** + * Revoke a {@link Lease}. + * + * @param lease must not be {@literal null}. + * @param operations must not be {@literal null}. + */ + abstract void revoke(Lease lease, RestOperations operations); + + /** + * Renew a {@link Lease} and return the renewed {@link Lease}. + * + * @param lease must not be {@literal null}. + * @param operations must not be {@literal null}. + * @return the renewed {@link Lease}. + */ + abstract Lease renew(Lease lease, RestOperations operations); + + private static Lease toLease(Map body) { + + String leaseId = (String) body.get("lease_id"); + Number leaseDuration = (Number) body.get("lease_duration"); + boolean renewable = (Boolean) body.get("renewable"); + + return Lease.of(leaseId, leaseDuration != null ? leaseDuration.longValue() : 0, + renewable); + } + + private static HttpEntity getLeaseRenewalBody(Lease lease) { + + Map leaseRenewalData = new HashMap<>(); + leaseRenewalData.put("lease_id", lease.getLeaseId()); + leaseRenewalData.put("increment", + Long.toString(lease.getLeaseDuration().getSeconds())); + + return new HttpEntity<>(leaseRenewalData); + } + + private static HttpEntity getLeaseRevocationBody(Lease lease) { + + Map leaseRenewalData = new HashMap<>(); + leaseRenewalData.put("lease_id", lease.getLeaseId()); + + return new HttpEntity<>(leaseRenewalData); + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/SecretLeaseContainer.java b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/SecretLeaseContainer.java index 6784d2c9..f0236384 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/SecretLeaseContainer.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/SecretLeaseContainer.java @@ -35,10 +35,7 @@ import lombok.extern.apachecommons.CommonsLog; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.lang.Nullable; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.Trigger; @@ -48,7 +45,6 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.vault.VaultException; import org.springframework.vault.client.VaultResponses; -import org.springframework.vault.core.RestOperationsCallback; import org.springframework.vault.core.VaultOperations; import org.springframework.vault.core.lease.domain.Lease; import org.springframework.vault.core.lease.domain.RequestedSecret; @@ -117,6 +113,7 @@ import org.springframework.web.client.HttpStatusCodeException; * @see RequestedSecret * @see SecretLeaseEventPublisher * @see Lease + * @see LeaseEndpoints */ @CommonsLog public class SecretLeaseContainer extends SecretLeaseEventPublisher implements @@ -137,6 +134,8 @@ public class SecretLeaseContainer extends SecretLeaseEventPublisher implements private final VaultOperations operations; + private LeaseEndpoints leaseEndpoints = LeaseEndpoints.Legacy; + private Duration minRenewal = Duration.ofSeconds(10); private Duration expiryThreshold = Duration.ofSeconds(60); @@ -178,6 +177,22 @@ public class SecretLeaseContainer extends SecretLeaseEventPublisher implements setTaskScheduler(taskScheduler); } + /** + * Set the {@link LeaseEndpoints} to delegate renewal/revocation calls to. + * {@link LeaseEndpoints} encapsulates differences between Vault versions that affect + * the location of renewal/revocation endpoints. + * + * @param leaseEndpoints must not be {@literal null}. + * @since 2.1 + * @see LeaseEndpoints + */ + public void setLeaseEndpoints(LeaseEndpoints leaseEndpoints) { + + Assert.notNull(leaseEndpoints, "LeaseEndpoints must not be null"); + + this.leaseEndpoints = leaseEndpoints; + } + /** * Sets the amount of seconds that is at least required before renewing a lease. * {@code minRenewalSeconds} prevents renewals to happen too often. @@ -595,33 +610,10 @@ public class SecretLeaseContainer extends SecretLeaseEventPublisher implements @SuppressWarnings("unchecked") private Lease renew(Lease lease) { - HttpEntity leaseRenewalEntity = getLeaseRenewalBody(lease); - - ResponseEntity> entity = operations - .doWithSession(restOperations -> (ResponseEntity) restOperations - .exchange("sys/renew", HttpMethod.PUT, leaseRenewalEntity, Map.class)); - - Assert.state(entity != null && entity.getBody() != null, - "Renew response must not be null"); - - Map body = entity.getBody(); - String leaseId = (String) body.get("lease_id"); - Number leaseDuration = (Number) body.get("lease_duration"); - boolean renewable = (Boolean) body.get("renewable"); - - return Lease.of(leaseId, leaseDuration != null ? leaseDuration.longValue() : 0, - renewable); + return operations.doWithSession(restOperations -> leaseEndpoints.renew(lease, + restOperations)); } - private static HttpEntity getLeaseRenewalBody(Lease lease) { - - Map leaseRenewalData = new HashMap<>(); - leaseRenewalData.put("lease_id", lease.getLeaseId()); - leaseRenewalData.put("increment", - Long.toString(lease.getLeaseDuration().getSeconds())); - - return new HttpEntity<>(leaseRenewalData); - } /** * Hook method called when a {@link Lease} expires. The default implementation is to @@ -654,9 +646,10 @@ public class SecretLeaseContainer extends SecretLeaseEventPublisher implements onBeforeLeaseRevocation(requestedSecret, lease); operations - .doWithSession((RestOperationsCallback>>) restOperations -> (ResponseEntity) restOperations - .exchange("sys/revoke/{leaseId}", HttpMethod.PUT, null, - Map.class, lease.getLeaseId())); +.doWithSession(restOperations -> { + leaseEndpoints.revoke(lease, restOperations); + return null; + }); onAfterLeaseRevocation(requestedSecret, lease); } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/lease/SecretLeaseContainerUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/lease/SecretLeaseContainerUnitTests.java index 4145cfab..66a7c3a1 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/core/lease/SecretLeaseContainerUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/lease/SecretLeaseContainerUnitTests.java @@ -19,7 +19,6 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledFuture; @@ -34,7 +33,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.Trigger; import org.springframework.vault.VaultException; @@ -216,8 +214,9 @@ public class SecretLeaseContainerUnitTests { public void shouldRenewLease() { prepareRenewal(); + when(vaultOperations.doWithSession(any(RestOperationsCallback.class))) - .thenReturn(getResponseEntity("new_lease", true, 70, HttpStatus.OK)); + .thenReturn(Lease.of("new_lease", Duration.ofSeconds(70), true)); secretLeaseContainer.start(); @@ -308,7 +307,7 @@ public class SecretLeaseContainerUnitTests { prepareRenewal(); when(vaultOperations.doWithSession(any(RestOperationsCallback.class))) - .thenReturn(getResponseEntity("new_lease", true, 5, HttpStatus.OK)); + .thenReturn(Lease.of("new_lease", Duration.ofSeconds(5), true)); secretLeaseContainer.start(); @@ -334,7 +333,7 @@ public class SecretLeaseContainerUnitTests { when(vaultOperations.read(requestedSecret.getPath())).thenReturn(first, second); when(vaultOperations.doWithSession(any(RestOperationsCallback.class))) - .thenReturn(getResponseEntity("new_lease", true, 5, HttpStatus.OK)); + .thenReturn(Lease.of("new_lease", Duration.ofSeconds(5), true)); secretLeaseContainer.requestRotatingSecret("my-secret"); @@ -410,7 +409,7 @@ public class SecretLeaseContainerUnitTests { prepareRenewal(); when(vaultOperations.doWithSession(any(RestOperationsCallback.class))) - .thenReturn(getResponseEntity("new_lease", true, 70, HttpStatus.OK)); + .thenReturn(Lease.of("new_lease", Duration.ofSeconds(70), true)); secretLeaseContainer.start(); @@ -554,23 +553,6 @@ public class SecretLeaseContainerUnitTests { secretLeaseContainer.addRequestedSecret(requestedSecret); } - private ResponseEntity> getResponseEntity(String leaseId, - Boolean renewable, Integer leaseDuration, HttpStatus httpStatus) { - - Map body = new HashMap(); - body.put("lease_id", leaseId); - body.put("renewable", renewable); - body.put("lease_duration", leaseDuration); - - return getEntity(body, httpStatus); - } - - private ResponseEntity> getEntity(Map body, - HttpStatus status) { - - return new ResponseEntity>(body, status); - } - private VaultResponse createSecrets() { return createSecrets("key", "value", true); }