diff options
author | Bjørn Christian Seime <bjorncs@yahooinc.com> | 2023-07-12 13:32:20 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@yahooinc.com> | 2023-07-12 13:37:30 +0200 |
commit | 876ca0b666b01fd88be644a1e31065c5707254f0 (patch) | |
tree | 8f9a3d6e08248e990698ce263358b9c27118baf1 /controller-server | |
parent | 5df78771f0ff297c7d83eaaaa71df067896d5520 (diff) |
Add expiration concept to data plane tokens
Diffstat (limited to 'controller-server')
4 files changed, 32 insertions, 12 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DataplaneTokenSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DataplaneTokenSerializer.java index 90be14fd349..5df183d9abb 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DataplaneTokenSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DataplaneTokenSerializer.java @@ -10,6 +10,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId; import java.time.Instant; import java.util.List; +import java.util.Optional; /** * @author mortent @@ -23,6 +24,7 @@ public class DataplaneTokenSerializer { private static final String checkAccessHashField = "checkAccessHash"; private static final String creationTimeField = "creationTime"; private static final String authorField = "author"; + private static final String expirationField = "expiration"; public static Slime toSlime(List<DataplaneTokenVersions> dataplaneTokenVersions) { Slime slime = new Slime(); @@ -39,6 +41,7 @@ public class DataplaneTokenSerializer { versionCursor.setLong(creationTimeField, version.creationTime().toEpochMilli()); versionCursor.setString(creationTimeField, version.creationTime().toString()); versionCursor.setString(authorField, version.author()); + versionCursor.setString(expirationField, version.expiration().map(Instant::toString).orElse("<none>")); }); }); return slime; @@ -55,7 +58,9 @@ public class DataplaneTokenSerializer { String checkAccessHash = versionCursor.field(checkAccessHashField).asString(); Instant creationTime = SlimeUtils.instant(versionCursor.field(creationTimeField)); String author = versionCursor.field(authorField).asString(); - return new DataplaneTokenVersions.Version(fingerPrint, checkAccessHash, creationTime, author); + String expirationStr = versionCursor.field(expirationField).asString(); + Optional<Instant> expiration = expirationStr.equals("<none>") ? Optional.empty() : Optional.of(Instant.parse(expirationStr)); + return new DataplaneTokenVersions.Version(fingerPrint, checkAccessHash, creationTime, expiration, author); }) .toList(); return new DataplaneTokenVersions(id, versions); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index daa64ddb07f..99ad75d0ec4 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -111,6 +111,7 @@ import com.yahoo.vespa.hosted.controller.notification.Notification; import com.yahoo.vespa.hosted.controller.notification.NotificationSource; import com.yahoo.vespa.hosted.controller.persistence.SupportAccessSerializer; import com.yahoo.vespa.hosted.controller.restapi.ErrorResponses; +import com.yahoo.vespa.hosted.controller.restapi.dataplanetoken.DataplaneTokenService; import com.yahoo.vespa.hosted.controller.routing.RoutingStatus; import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext; import com.yahoo.vespa.hosted.controller.routing.rotation.RotationId; @@ -985,12 +986,17 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private HttpResponse generateToken(String tenant, String tokenid, HttpRequest request) { - DataplaneToken token = controller.dataplaneTokenService().generateToken(TenantName.from(tenant), TokenId.of(tokenid), request.getJDiscRequest().getUserPrincipal()); + // 'expiration=PT0S' for no expiration, no 'expiration' for default TTL. + Duration expiration = Optional.ofNullable(request.getProperty("expiration")) + .map(Duration::parse).orElse(DataplaneTokenService.DEFAULT_TTL); + DataplaneToken token = controller.dataplaneTokenService().generateToken( + TenantName.from(tenant), TokenId.of(tokenid), expiration, request.getJDiscRequest().getUserPrincipal()); Slime slime = new Slime(); Cursor tokenObject = slime.setObject(); tokenObject.setString("id", token.tokenId().value()); tokenObject.setString("token", token.tokenValue()); tokenObject.setString("fingerprint", token.fingerPrint().value()); + tokenObject.setString("expiration", token.expiration().map(Instant::toString).orElse("<none>")); return new SlimeJsonResponse(slime); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenService.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenService.java index b3e5f663317..32872a01bce 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenService.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenService.java @@ -15,6 +15,8 @@ import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import java.security.Principal; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -30,6 +32,7 @@ public class DataplaneTokenService { private static final String TOKEN_PREFIX = "vespa_cloud_"; private static final int TOKEN_BYTES = 32; private static final int CHECK_HASH_BYTES = 32; + public static final Duration DEFAULT_TTL = Duration.ofDays(30); private final Controller controller; @@ -51,10 +54,12 @@ public class DataplaneTokenService { * * @param tenantName name of the tenant to connect the token to * @param tokenId The user generated name/id of the token + * @param ttl The time to live of the token. Use {@link Duration#ZERO} for no TTL. * @param principal The principal making the request * @return a DataplaneToken containing the secret generated token */ - public DataplaneToken generateToken(TenantName tenantName, TokenId tokenId, Principal principal) { + public DataplaneToken generateToken(TenantName tenantName, TokenId tokenId, Duration ttl, Principal principal) { + Optional<Instant> expiration = ttl.isZero() ? Optional.empty() : Optional.ofNullable(controller.clock().instant().plus(ttl)); TokenDomain tokenDomain = TokenDomain.of("Vespa Cloud tenant data plane:%s".formatted(tenantName.value())); Token token = TokenGenerator.generateToken(tokenDomain, TOKEN_PREFIX, TOKEN_BYTES); TokenCheckHash checkHash = TokenCheckHash.of(token, CHECK_HASH_BYTES); @@ -62,6 +67,7 @@ public class DataplaneTokenService { FingerPrint.of(token.fingerprint().toDelimitedHexString()), checkHash.toHexString(), controller.clock().instant(), + expiration, principal.getName()); CuratorDb curator = controller.curator(); @@ -85,7 +91,8 @@ public class DataplaneTokenService { curator.writeDataplaneTokens(tenantName, dataplaneTokenVersions); // Return the data plane token including the secret token. - return new DataplaneToken(tokenId, FingerPrint.of(token.fingerprint().toDelimitedHexString()), token.secretTokenString()); + return new DataplaneToken(tokenId, FingerPrint.of(token.fingerprint().toDelimitedHexString()), + token.secretTokenString(), expiration); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenServiceTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenServiceTest.java index 066eecc2c95..9a8e43d1597 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenServiceTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenServiceTest.java @@ -12,6 +12,7 @@ import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal; import org.junit.jupiter.api.Test; import java.security.Principal; +import java.time.Duration; import java.util.Collection; import java.util.List; @@ -29,15 +30,16 @@ public class DataplaneTokenServiceTest { @Test void generates_and_persists_token() { - DataplaneToken dataplaneToken = dataplaneTokenService.generateToken(tenantName, tokenId, principal); + DataplaneToken dataplaneToken = dataplaneTokenService.generateToken(tenantName, tokenId, Duration.ofDays(100), principal); List<DataplaneTokenVersions> dataplaneTokenVersions = dataplaneTokenService.listTokens(tenantName); assertEquals(dataplaneToken.fingerPrint(), dataplaneTokenVersions.get(0).tokenVersions().get(0).fingerPrint()); + assertEquals(dataplaneToken.expiration(), dataplaneTokenVersions.get(0).tokenVersions().get(0).expiration()); } @Test void generating_new_token_appends() { - DataplaneToken dataplaneToken1 = dataplaneTokenService.generateToken(tenantName, tokenId, principal); - DataplaneToken dataplaneToken2 = dataplaneTokenService.generateToken(tenantName, tokenId, principal); + DataplaneToken dataplaneToken1 = dataplaneTokenService.generateToken(tenantName, tokenId, Duration.ofDays(1), principal); + DataplaneToken dataplaneToken2 = dataplaneTokenService.generateToken(tenantName, tokenId, Duration.ZERO, principal); assertNotEquals(dataplaneToken1.fingerPrint(), dataplaneToken2.fingerPrint()); List<DataplaneTokenVersions> dataplaneTokenVersions = dataplaneTokenService.listTokens(tenantName); @@ -52,8 +54,8 @@ public class DataplaneTokenServiceTest { @Test void delete_last_fingerprint_deletes_token() { - DataplaneToken dataplaneToken1 = dataplaneTokenService.generateToken(tenantName, tokenId, principal); - DataplaneToken dataplaneToken2 = dataplaneTokenService.generateToken(tenantName, tokenId, principal); + DataplaneToken dataplaneToken1 = dataplaneTokenService.generateToken(tenantName, tokenId, Duration.ZERO, principal); + DataplaneToken dataplaneToken2 = dataplaneTokenService.generateToken(tenantName, tokenId, Duration.ZERO, principal); dataplaneTokenService.deleteToken(tenantName, tokenId, dataplaneToken1.fingerPrint()); dataplaneTokenService.deleteToken(tenantName, tokenId, dataplaneToken2.fingerPrint()); assertEquals(List.of(), dataplaneTokenService.listTokens(tenantName)); @@ -61,8 +63,8 @@ public class DataplaneTokenServiceTest { @Test void deleting_nonexistent_fingerprint_throws() { - DataplaneToken dataplaneToken = dataplaneTokenService.generateToken(tenantName, tokenId, principal); - DataplaneToken dataplaneToken2 = dataplaneTokenService.generateToken(tenantName, tokenId, principal); + DataplaneToken dataplaneToken = dataplaneTokenService.generateToken(tenantName, tokenId, Duration.ZERO, principal); + DataplaneToken dataplaneToken2 = dataplaneTokenService.generateToken(tenantName, tokenId, Duration.ZERO, principal); dataplaneTokenService.deleteToken(tenantName, tokenId, dataplaneToken.fingerPrint()); // Token currently contains value of "dataplaneToken2" @@ -72,7 +74,7 @@ public class DataplaneTokenServiceTest { @Test void deleting_nonexistent_token_throws() { - DataplaneToken dataplaneToken = dataplaneTokenService.generateToken(tenantName, tokenId, principal); + DataplaneToken dataplaneToken = dataplaneTokenService.generateToken(tenantName, tokenId, Duration.ZERO, principal); dataplaneTokenService.deleteToken(tenantName, tokenId, dataplaneToken.fingerPrint()); // Token is created and deleted above, no longer exists |