From d49ca7d06bcf387c94f8539a9f6f4975027ca0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADdia=20Tarcza?= <100163235+diatrcz@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:40:56 +0200 Subject: [PATCH 1/3] feat(auth): add support for new service version in VPC Instant Auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lídia Tarcza <100163235+diatrcz@users.noreply.github.com> --- .../sdk/core/security/Authenticator.java | 1 + .../security/VpcInstanceAuthenticator.java | 104 +++++++++++++++++- .../VpcInstanceAuthenticatorTest.java | 67 +++++++++++ 3 files changed, 166 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/ibm/cloud/sdk/core/security/Authenticator.java b/src/main/java/com/ibm/cloud/sdk/core/security/Authenticator.java index 68cfa2f2..9add2632 100644 --- a/src/main/java/com/ibm/cloud/sdk/core/security/Authenticator.java +++ b/src/main/java/com/ibm/cloud/sdk/core/security/Authenticator.java @@ -60,6 +60,7 @@ public interface Authenticator { String PROPNAME_IAM_PROFILE_ID = "IAM_PROFILE_ID"; String PROPNAME_IAM_PROFILE_NAME = "IAM_PROFILE_NAME"; String PROPNAME_IAM_ACCOUNT_ID = "IAM_ACCOUNT_ID"; + String PROPNAME_VPC_IMS_VERSION = "VPC_IMS_VERSION"; String PROPNAME_SCOPE_COLLECTION_TYPE = "SCOPE_COLLECTION_TYPE"; String PROPNAME_SCOPE_ID = "SCOPE_ID"; String PROPNAME_INCLUDE_BUILTIN_ACTIONS = "INCLUDE_BUILTIN_ACTIONS"; diff --git a/src/main/java/com/ibm/cloud/sdk/core/security/VpcInstanceAuthenticator.java b/src/main/java/com/ibm/cloud/sdk/core/security/VpcInstanceAuthenticator.java index 59e27852..6d21fb00 100644 --- a/src/main/java/com/ibm/cloud/sdk/core/security/VpcInstanceAuthenticator.java +++ b/src/main/java/com/ibm/cloud/sdk/core/security/VpcInstanceAuthenticator.java @@ -50,6 +50,8 @@ public class VpcInstanceAuthenticator private String iamProfileCrn; private String iamProfileId; private String url; + private String serviceVersion; + private int tokenLifetime; /** @@ -59,6 +61,8 @@ public static class Builder { private String iamProfileCrn; private String iamProfileId; private String url; + private String serviceVersion; + private int tokenLifetime; // Default ctor. public Builder() { @@ -69,6 +73,8 @@ private Builder(VpcInstanceAuthenticator obj) { this.iamProfileCrn = obj.iamProfileCrn; this.iamProfileId = obj.iamProfileId; this.url = obj.url; + this.serviceVersion = obj.serviceVersion; + this.tokenLifetime = obj.tokenLifetime; } /** @@ -121,6 +127,27 @@ public Builder url(String url) { this.url = url; return this; } + + /** + * Sets the serviceVersion Property. + * + * @param serviceVersion the base service version to use with the service. + * @return the Builder + */ + public Builder serviceVersion(String serviceVersion) { + this.serviceVersion = serviceVersion; + return this; + } + + /** + * Sets the tokenLifetime Property. + * @param tokenLifetime the base token lifetime to use. + * @return the Builder + */ + public Builder tokenLifetime(int tokenLifetime) { + this.tokenLifetime = tokenLifetime; + return this; + } } // The default ctor is hidden to force the use of the non-default ctors. @@ -139,6 +166,8 @@ protected VpcInstanceAuthenticator(Builder builder) { this.iamProfileCrn = builder.iamProfileCrn; this.iamProfileId = builder.iamProfileId; this.url = builder.url; + this.serviceVersion = StringUtils.isEmpty(builder.serviceVersion) ? metadataServiceVersion : builder.serviceVersion; + this.tokenLifetime = builder.tokenLifetime == 0 ? instanceIdentityTokenLifetime : builder.tokenLifetime; this.validate(); } @@ -161,7 +190,8 @@ public Builder newBuilder() { */ public static VpcInstanceAuthenticator fromConfiguration(Map config) { return new Builder().iamProfileCrn(config.get(PROPNAME_IAM_PROFILE_CRN)) - .iamProfileId(config.get(PROPNAME_IAM_PROFILE_ID)).url(config.get(PROPNAME_URL)).build(); + .iamProfileId(config.get(PROPNAME_IAM_PROFILE_ID)).url(config.get(PROPNAME_URL)) + .serviceVersion(config.get(PROPNAME_VPC_IMS_VERSION)).build(); } /** @@ -236,10 +266,71 @@ protected void setURL(String url) { this.url = url; } + /** + * @return the VPC ServiceVersion configured in this Authenticator. + */ + public String getServiceVersion() { + return this.serviceVersion; + } + + /** + * Sets the ServiceVersion in this Authenticator. + * + * @return the VPC ServiceVersion + */ + protected void setServiceVersion(String serviceVersion) { + if (StringUtils.isEmpty(serviceVersion)) { + serviceVersion = metadataServiceVersion; + } + this.serviceVersion = serviceVersion; + } + + /** + * @return the TokenLifetime configured on this Authenticator. + */ + public int getTokenLifetime() { + return this.tokenLifetime; + } + + /** + * Sets the TokenLifetime in this Authenticator. + * @param tokenLifetime + */ + protected void setTokenLifetime(int tokenLifetime) { + if (tokenLifetime == 0) { + tokenLifetime = instanceIdentityTokenLifetime; + } + this.tokenLifetime = tokenLifetime; + } + private String getImsEndpoint() { return (StringUtils.isEmpty(this.url) ? defaultIMSEndpoint : this.url); } + /** + * Gets the operation path for creating an access token based on the service version. + * + * @return the correct access token path + */ + public String getCreateAccessTokenPath() { + if (this.serviceVersion.equals("2025-08-26")) { + return "/identity/v1/token"; + } + return operationPathCreateAccessToken; + } + + /** + * Gets the operation path for creating an IAM token based on the service version. + * + * @return the correct IAM token path + */ + public String getCreateIamTokenPath() { + if (this.serviceVersion.equals("2025-08-26")) { + return "/identity/v1/iam_tokens"; + } + return operationPathCreateIamToken; + } + /** * Fetches an IAM access token using the authenticator's configuration. * @@ -271,15 +362,15 @@ public String retrieveInstanceIdentityToken() throws Throwable { try { // Create a PUT request to retrieve the instance identity token. RequestBuilder builder = RequestBuilder - .put(RequestBuilder.resolveRequestUrl(getImsEndpoint(), operationPathCreateAccessToken)); + .put(RequestBuilder.resolveRequestUrl(getImsEndpoint(), this.getCreateAccessTokenPath())); // Set the params and request body. - builder.query("version", metadataServiceVersion); + builder.query("version", this.getServiceVersion()); builder.header(HttpHeaders.ACCEPT, HttpMediaType.APPLICATION_JSON); builder.header(HttpHeaders.CONTENT_TYPE, HttpMediaType.APPLICATION_JSON); builder.header("Metadata-Flavor", metadataFlavor); - String requestBody = String.format("{\"expires_in\": %d}", instanceIdentityTokenLifetime); + String requestBody = String.format("{\"expires_in\": %d}", this.getTokenLifetime()); builder.bodyContent(requestBody, HttpMediaType.APPLICATION_JSON); // Invoke the VPC IMDS "create_access_token" operation. @@ -306,10 +397,10 @@ public IamToken retrieveIamAccessToken(String instanceIdentityToken) { try { // Create a POST request to retrieve the IAM access token. RequestBuilder builder = - RequestBuilder.post(RequestBuilder.resolveRequestUrl(getImsEndpoint(), operationPathCreateIamToken)); + RequestBuilder.post(RequestBuilder.resolveRequestUrl(getImsEndpoint(), this.getCreateIamTokenPath())); // Set the params and request body. - builder.query("version", metadataServiceVersion); + builder.query("version", this.getServiceVersion()); builder.header(HttpHeaders.ACCEPT, HttpMediaType.APPLICATION_JSON); builder.header(HttpHeaders.CONTENT_TYPE, HttpMediaType.APPLICATION_JSON); builder.header(HttpHeaders.AUTHORIZATION, "Bearer " + instanceIdentityToken); @@ -347,3 +438,4 @@ public IamToken retrieveIamAccessToken(String instanceIdentityToken) { return iamToken; } } + diff --git a/src/test/java/com/ibm/cloud/sdk/core/test/security/VpcInstanceAuthenticatorTest.java b/src/test/java/com/ibm/cloud/sdk/core/test/security/VpcInstanceAuthenticatorTest.java index 55fa4794..00238555 100644 --- a/src/test/java/com/ibm/cloud/sdk/core/test/security/VpcInstanceAuthenticatorTest.java +++ b/src/test/java/com/ibm/cloud/sdk/core/test/security/VpcInstanceAuthenticatorTest.java @@ -623,4 +623,71 @@ public void testAuthenticateResponseError2() throws Throwable { fail("Expected RuntimeException, not " + t.getClass().getSimpleName()); } } + + @Test + public void testVpcAuthServiceVersionDefaults() { + VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder() + .build(); + assertNotNull(authenticator); + + assertEquals(authenticator.getServiceVersion(), "2022-03-01"); + assertEquals(authenticator.getTokenLifetime(), 300); + + assertEquals("/instance_identity/v1/token", authenticator.getCreateAccessTokenPath()); + assertEquals("/instance_identity/v1/iam_token", authenticator.getCreateIamTokenPath()); + } + + @Test + public void testVpcAuthServiceVersionBuilder() { + VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder() + .serviceVersion("2025-08-26") + .tokenLifetime(600) + .build(); + assertNotNull(authenticator); + + assertEquals(authenticator.getServiceVersion(), "2025-08-26"); + assertEquals(authenticator.getTokenLifetime(), 600); + + assertEquals("/identity/v1/token", authenticator.getCreateAccessTokenPath()); + assertEquals("/identity/v1/iam_tokens", authenticator.getCreateIamTokenPath()); + } + + @Test + public void testVpcAuthServiceVersionFromMap() { + Map properties = new HashMap<>(); + properties.put(Authenticator.PROPNAME_VPC_IMS_VERSION, "2025-08-26"); + + VpcInstanceAuthenticator authenticator = VpcInstanceAuthenticator.fromConfiguration(properties); + assertNotNull(authenticator); + + assertEquals(authenticator.getServiceVersion(), "2025-08-26"); + + assertEquals("/identity/v1/token", authenticator.getCreateAccessTokenPath()); + assertEquals("/identity/v1/iam_tokens", authenticator.getCreateIamTokenPath()); + } + + @Test + public void testVpcAuthServiceVersionOldVersion() { + VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder() + .serviceVersion("2022-03-01") + .build(); + assertNotNull(authenticator); + + assertEquals(authenticator.getServiceVersion(), "2022-03-01"); + + assertEquals("/instance_identity/v1/token", authenticator.getCreateAccessTokenPath()); + assertEquals("/instance_identity/v1/iam_token", authenticator.getCreateIamTokenPath()); + } + + @Test + public void testVpcAuthServiceVersionCustomVersion() { + VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder() + .serviceVersion("2024-01-01") + .build(); + assertNotNull(authenticator); + + assertEquals(authenticator.getServiceVersion(), "2024-01-01"); + assertEquals("/instance_identity/v1/token", authenticator.getCreateAccessTokenPath()); + assertEquals("/instance_identity/v1/iam_token", authenticator.getCreateIamTokenPath()); + } } From 0d0bedfa87297eca47e52f21f89cf9f535652e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADdia=20Tarcza?= <100163235+diatrcz@users.noreply.github.com> Date: Fri, 5 Jun 2026 11:45:02 +0200 Subject: [PATCH 2/3] docs: add documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lídia Tarcza <100163235+diatrcz@users.noreply.github.com> --- Authentication.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Authentication.md b/Authentication.md index ea2df1dd..1240bf9a 100644 --- a/Authentication.md +++ b/Authentication.md @@ -464,6 +464,13 @@ The IAM access token is added to each outbound request in the `Authorization` he The default value of this property is `http://169.254.169.254`. However, if the VPC Instance Metadata Service is configured with the HTTP Secure Protocol setting (`https`), then you should configure this property to be `https://api.metadata.cloud.ibm.com`. +- serviceVersion: (optional) The VPC Instance Metadata Service version to use. +The default value is `2022-03-01`. When set to `2025-08-26`, the authenticator will use the new API paths +(`/identity/v1/token` and `/identity/v1/iam_tokens`) instead of the legacy paths. + +- tokenLifetime: (optional) The lifetime (in seconds) of the instance identity token. +The default value is `300` seconds. This property can only be configured programmatically (not via environment variables). + Usage Notes: 1. At most one of `iamProfileCrn` or `iamProfileId` may be specified. The specified value must map to a trusted IAM profile that has been linked to the compute resource (virtual server instance). @@ -491,12 +498,27 @@ ExampleService service = new ExampleService(ExampleService.DEFAULT_SERVICE_NAME, // 'service' can now be used to invoke operations. ``` +To use the new service version with custom token lifetime: +```java +// Create the authenticator with new service version. +VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder() + .serviceVersion("2025-08-26") + .tokenLifetime(600) + .build(); +``` + ### Configuration example External configuration: ``` export EXAMPLE_SERVICE_AUTH_TYPE=vpc export EXAMPLE_SERVICE_IAM_PROFILE_CRN=crn:iam-profile-123 ``` +To use the new service version: +``` +export EXAMPLE_SERVICE_AUTH_TYPE=vpc +export EXAMPLE_SERVICE_IAM_PROFILE_CRN=crn:iam-profile-123 +export EXAMPLE_SERVICE_VPC_IMS_VERSION=2025-08-26 +``` Application code: ```java import .ExampleService.v1.ExampleService; From 9e02a6b1a1f374a2b19aa03a1b7d4b2cc632fb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADdia=20Tarcza?= <100163235+diatrcz@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:01:19 +0200 Subject: [PATCH 3/3] feat: add support for new service version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lídia Tarcza <100163235+diatrcz@users.noreply.github.com> --- .../sdk/core/security/AuthenticatorBase.java | 1 + .../security/VpcInstanceAuthenticator.java | 19 ++++++++-- .../VpcInstanceAuthenticatorTest.java | 37 +++++++++++-------- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/ibm/cloud/sdk/core/security/AuthenticatorBase.java b/src/main/java/com/ibm/cloud/sdk/core/security/AuthenticatorBase.java index b8687c1e..c840fd49 100644 --- a/src/main/java/com/ibm/cloud/sdk/core/security/AuthenticatorBase.java +++ b/src/main/java/com/ibm/cloud/sdk/core/security/AuthenticatorBase.java @@ -37,6 +37,7 @@ public class AuthenticatorBase { "iamAccountId must be specified if and only if iamProfileName is specified"; public static final String ERRORMSG_PROP_INVALID_BOOL = "The %s property must be a valid boolean but was '%s'. Valid values are 'true' and 'false'."; + public static final String ERRORMSG_INVALID_SERVICE_VERSION = "Invalid service version. Supported values are: %s"; /** * Returns a "Basic" Authorization header value for the specified username and password. diff --git a/src/main/java/com/ibm/cloud/sdk/core/security/VpcInstanceAuthenticator.java b/src/main/java/com/ibm/cloud/sdk/core/security/VpcInstanceAuthenticator.java index 6d21fb00..bfb8d216 100644 --- a/src/main/java/com/ibm/cloud/sdk/core/security/VpcInstanceAuthenticator.java +++ b/src/main/java/com/ibm/cloud/sdk/core/security/VpcInstanceAuthenticator.java @@ -13,6 +13,8 @@ package com.ibm.cloud.sdk.core.security; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -42,10 +44,15 @@ public class VpcInstanceAuthenticator private static final String defaultIMSEndpoint = "http://169.254.169.254"; private static final String operationPathCreateAccessToken = "/instance_identity/v1/token"; private static final String operationPathCreateIamToken = "/instance_identity/v1/iam_token"; + private static final String operationPathCreateAccessToken2 = "/identity/v1/token"; + private static final String operationPathCreateIamToken2 = "/identity/v1/iam_tokens"; private static final String metadataFlavor = "ibm"; private static final String metadataServiceVersion = "2022-03-01"; private static final int instanceIdentityTokenLifetime = 300; + private ArrayList defaultServiceSupportedVersions = new ArrayList<>(List.of("2022-03-01", "2025-08-26")); + + // Properties specific to a VpcInstanceAuthenticator. private String iamProfileCrn; private String iamProfileId; @@ -53,7 +60,6 @@ public class VpcInstanceAuthenticator private String serviceVersion; private int tokenLifetime; - /** * This Builder class is used to construct IamAuthenticator instances. */ @@ -204,6 +210,11 @@ public void validate() { throw new IllegalArgumentException( String.format(ERRORMSG_ATMOST_ONE_PROP_ERROR, "iamProfileCrn", "iamProfileId")); } + + if (!this.defaultServiceSupportedVersions.contains(this.serviceVersion)) { + throw new IllegalArgumentException( + String.format(ERRORMSG_INVALID_SERVICE_VERSION, this.defaultServiceSupportedVersions)); + } } /** @@ -314,7 +325,7 @@ private String getImsEndpoint() { */ public String getCreateAccessTokenPath() { if (this.serviceVersion.equals("2025-08-26")) { - return "/identity/v1/token"; + return operationPathCreateAccessToken2; } return operationPathCreateAccessToken; } @@ -326,7 +337,7 @@ public String getCreateAccessTokenPath() { */ public String getCreateIamTokenPath() { if (this.serviceVersion.equals("2025-08-26")) { - return "/identity/v1/iam_tokens"; + return operationPathCreateIamToken2; } return operationPathCreateIamToken; } @@ -400,7 +411,7 @@ public IamToken retrieveIamAccessToken(String instanceIdentityToken) { RequestBuilder.post(RequestBuilder.resolveRequestUrl(getImsEndpoint(), this.getCreateIamTokenPath())); // Set the params and request body. - builder.query("version", this.getServiceVersion()); + builder.query("version", this.serviceVersion); builder.header(HttpHeaders.ACCEPT, HttpMediaType.APPLICATION_JSON); builder.header(HttpHeaders.CONTENT_TYPE, HttpMediaType.APPLICATION_JSON); builder.header(HttpHeaders.AUTHORIZATION, "Bearer " + instanceIdentityToken); diff --git a/src/test/java/com/ibm/cloud/sdk/core/test/security/VpcInstanceAuthenticatorTest.java b/src/test/java/com/ibm/cloud/sdk/core/test/security/VpcInstanceAuthenticatorTest.java index 00238555..0c26c20b 100644 --- a/src/test/java/com/ibm/cloud/sdk/core/test/security/VpcInstanceAuthenticatorTest.java +++ b/src/test/java/com/ibm/cloud/sdk/core/test/security/VpcInstanceAuthenticatorTest.java @@ -55,6 +55,11 @@ public class VpcInstanceAuthenticatorTest extends BaseServiceUnitTest { private static final String mockIamProfileCrn = "crn:iam-profile:123"; private static final String mockIamProfileId = "iam-id-123"; + private static final String operationPathCreateAccessToken = "/instance_identity/v1/token"; + private static final String operationPathCreateIamToken = "/instance_identity/v1/iam_token"; + private static final String operationPathCreateAccessToken2 = "/identity/v1/token"; + private static final String operationPathCreateIamToken2 = "/identity/v1/iam_tokens"; + private static final String mockErrorResponseJson1 = "{\"errors\": [{\"message\": \"Your create_access_token request was bad.\", \"code\": \"invalid_parameter_value\"}]}"; private static final String mockErrorResponseJson2 = @@ -633,8 +638,8 @@ public void testVpcAuthServiceVersionDefaults() { assertEquals(authenticator.getServiceVersion(), "2022-03-01"); assertEquals(authenticator.getTokenLifetime(), 300); - assertEquals("/instance_identity/v1/token", authenticator.getCreateAccessTokenPath()); - assertEquals("/instance_identity/v1/iam_token", authenticator.getCreateIamTokenPath()); + assertEquals(operationPathCreateAccessToken, authenticator.getCreateAccessTokenPath()); + assertEquals(operationPathCreateIamToken, authenticator.getCreateIamTokenPath()); } @Test @@ -648,8 +653,8 @@ public void testVpcAuthServiceVersionBuilder() { assertEquals(authenticator.getServiceVersion(), "2025-08-26"); assertEquals(authenticator.getTokenLifetime(), 600); - assertEquals("/identity/v1/token", authenticator.getCreateAccessTokenPath()); - assertEquals("/identity/v1/iam_tokens", authenticator.getCreateIamTokenPath()); + assertEquals(operationPathCreateAccessToken2, authenticator.getCreateAccessTokenPath()); + assertEquals(operationPathCreateIamToken2, authenticator.getCreateIamTokenPath()); } @Test @@ -662,8 +667,8 @@ public void testVpcAuthServiceVersionFromMap() { assertEquals(authenticator.getServiceVersion(), "2025-08-26"); - assertEquals("/identity/v1/token", authenticator.getCreateAccessTokenPath()); - assertEquals("/identity/v1/iam_tokens", authenticator.getCreateIamTokenPath()); + assertEquals(operationPathCreateAccessToken2, authenticator.getCreateAccessTokenPath()); + assertEquals(operationPathCreateIamToken2, authenticator.getCreateIamTokenPath()); } @Test @@ -675,19 +680,19 @@ public void testVpcAuthServiceVersionOldVersion() { assertEquals(authenticator.getServiceVersion(), "2022-03-01"); - assertEquals("/instance_identity/v1/token", authenticator.getCreateAccessTokenPath()); - assertEquals("/instance_identity/v1/iam_token", authenticator.getCreateIamTokenPath()); + assertEquals(operationPathCreateAccessToken, authenticator.getCreateAccessTokenPath()); + assertEquals(operationPathCreateIamToken, authenticator.getCreateIamTokenPath()); } @Test public void testVpcAuthServiceVersionCustomVersion() { - VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder() - .serviceVersion("2024-01-01") - .build(); - assertNotNull(authenticator); - - assertEquals(authenticator.getServiceVersion(), "2024-01-01"); - assertEquals("/instance_identity/v1/token", authenticator.getCreateAccessTokenPath()); - assertEquals("/instance_identity/v1/iam_token", authenticator.getCreateIamTokenPath()); + try { + new VpcInstanceAuthenticator.Builder() + .serviceVersion("2024-01-01") + .build(); + fail("Expected build() to throw an exception!"); + } catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Invalid service version. Supported values are: [2022-03-01, 2025-08-26]"); + } } }