Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,8 @@ The IAM access token is added to each outbound request in the `Authorization` he

- iamProfileId: (optional) the id of the linked trusted IAM profile to be used when obtaining the IAM access token.

- iamProfileName: (optional) the name of the linked trusted IAM profile to be used as the identity of the compute resource.

- url: (optional) The base endpoint URL of the VPC Instance Metadata Service.
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`.
Expand All @@ -472,12 +474,12 @@ The default value is `2022-03-01`. When set to `2025-08-26`, the authenticator w
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
1. At most one of `iamProfileCrn` or `iamProfileId` or `iamProfileName` may be specified. The specified value must map
to a trusted IAM profile that has been linked to the compute resource (virtual server instance).

2. If both `iamProfileCrn` and `iamProfileId` are specified, then an error occurs.
2. If more than one of `iamProfileCrn`, `iamProfileId` and `iamProfileName` are specified, then an error occurs.

3. If neither `iamProfileCrn` nor `iamProfileId` are specified, then the default trusted profile linked to the
3. If neither `iamProfileCrn`, `iamProfileId` nor `iamProfileName` are specified, then the default trusted profile linked to the
compute resource will be used to perform the IAM token exchange.
If no default trusted profile is defined for the compute resource, then an error occurs.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class AuthenticatorBase {
public static final String ERRORMSG_REQ_FAILED = "Error while fetching access token from token service: ";
public static final String ERRORMSG_EXCLUSIVE_PROP_ERROR = "Exactly one of %s or %s must be specified.";
public static final String ERRORMSG_ATLEAST_ONE_PROP_ERROR = "At least one of %s or %s must be specified.";
public static final String ERRORMSG_ATMOST_ONE_PROP_ERROR = "At most one of %s or %s may be specified.";
public static final String ERRORMSG_ATMOST_ONE_PROP_ERROR = "At most one of %s, %s or %s may be specified.";
public static final String ERRORMSG_PROP_INVALID_INTEGER_VALUE =
"The %s property must be a valid integer but was %s.";
public static final String ERRORMSG_ACCOUNTID_PROP_ERROR =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class VpcInstanceAuthenticator
// Properties specific to a VpcInstanceAuthenticator.
private String iamProfileCrn;
private String iamProfileId;
private String iamProfileName;
private String url;
private String serviceVersion;
private int tokenLifetime;
Expand All @@ -68,6 +69,7 @@ public class VpcInstanceAuthenticator
public static class Builder {
private String iamProfileCrn;
private String iamProfileId;
private String iamProfileName;
private String url;
private String serviceVersion;
private int tokenLifetime;
Expand All @@ -80,6 +82,7 @@ public Builder() {
private Builder(VpcInstanceAuthenticator obj) {
this.iamProfileCrn = obj.iamProfileCrn;
this.iamProfileId = obj.iamProfileId;
this.iamProfileName = obj.iamProfileName;
this.url = obj.url;
this.serviceVersion = obj.serviceVersion;
this.tokenLifetime = obj.tokenLifetime;
Expand All @@ -100,7 +103,7 @@ public VpcInstanceAuthenticator build() {
*
* @param iamProfileCrn the CRN of the linked trusted IAM profile to be used as
* the identity of the compute resource. At most one of
* iamProfileCrn or iamProfileId may be specified. If
* iamProfileCrn or iamProfileId or iamProfileName may be specified. If
* neither one is specified, then the default IAM profile
* defined for the compute resource will be used.
* @return the Builder
Expand All @@ -115,7 +118,7 @@ public Builder iamProfileCrn(String iamProfileCrn) {
*
* @param iamProfileId the id of the linked trusted IAM profile to be used as
* the identity of the compute resource. At most one of
* iamProfileCrn or iamProfileId may be specified. If
* iamProfileCrn or iamProfileId or iamProfileName may be specified. If
* neither one is specified, then the default IAM profile
* defined for the compute resource will be used.
* @return the Builder
Expand All @@ -125,6 +128,21 @@ public Builder iamProfileId(String iamProfileId) {
return this;
}

/**
* Sets the iamProfileName property.
*
* @param iamProfileName the name of the linked trusted IAM profile to be used as
* the identity of the compute resource. At most one of
* iamProfileCrn or iamProfileId or iamProfileName may be specified. If
* neither one is specified, then the default IAM profile
* defined for the compute resource will be used.
* @return the Builder
*/
public Builder iamProfileName(String iamProfileName) {
this.iamProfileName = iamProfileName;
return this;
}

/**
* Sets the url property.
*
Expand Down Expand Up @@ -174,6 +192,7 @@ protected VpcInstanceAuthenticator(Builder builder) {
this();
this.iamProfileCrn = builder.iamProfileCrn;
this.iamProfileId = builder.iamProfileId;
this.iamProfileName = builder.iamProfileName;
this.url = builder.url;
this.serviceVersion = StringUtils.isEmpty(builder.serviceVersion) ? metadataServiceVersion : builder.serviceVersion;
this.tokenLifetime = builder.tokenLifetime == 0 ? instanceIdentityTokenLifetime : builder.tokenLifetime;
Expand All @@ -199,7 +218,8 @@ public Builder newBuilder() {
*/
public static VpcInstanceAuthenticator fromConfiguration(Map<String, String> config) {
return new Builder().iamProfileCrn(config.get(PROPNAME_IAM_PROFILE_CRN))
.iamProfileId(config.get(PROPNAME_IAM_PROFILE_ID)).url(config.get(PROPNAME_URL))
.iamProfileId(config.get(PROPNAME_IAM_PROFILE_ID)).iamProfileName(config.get(PROPNAME_IAM_PROFILE_NAME))
.url(config.get(PROPNAME_URL))
.serviceVersion(config.get(PROPNAME_VPC_IMS_VERSION)).build();
}

Expand All @@ -208,10 +228,21 @@ public static VpcInstanceAuthenticator fromConfiguration(Map<String, String> con
*/
@Override
public void validate() {
// At most one of iamProfileCrn or iamProfileId may be specified.
if (StringUtils.isNotEmpty(getIamProfileCrn()) && StringUtils.isNotEmpty(getIamProfileId())) {
// At most one of iamProfileCrn or iamProfileId or iamProfileName may be specified.
int counter = 0;
if (StringUtils.isNotEmpty(getIamProfileCrn())) {
counter++;
}
if (StringUtils.isNotEmpty(getIamProfileId())) {
counter++;
}
if (StringUtils.isNotEmpty(getIamProfileName())) {
counter++;
}

if (counter > 1) {
throw new IllegalArgumentException(
String.format(ERRORMSG_ATMOST_ONE_PROP_ERROR, "iamProfileCrn", "iamProfileId"));
String.format(ERRORMSG_ATMOST_ONE_PROP_ERROR, "iamProfileCrn", "iamProfileId", "iamProfileName"));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing: the ERRORMSG_ATMOST_ONE_PROP_ERROR constant should be updated to handle 3 arguments. Previously it was just 2.

}

if (!this.defaultServiceSupportedVersions.contains(this.serviceVersion)) {
Expand Down Expand Up @@ -261,6 +292,22 @@ protected void setIamProfileId(String iamProfileId) {
this.iamProfileId = iamProfileId;
}

/**
* @return the iamProfileName configured on this Authenticator.
*/
public String getIamProfileName() {
return this.iamProfileName;
}

/**
* Sets the iamProfileName property on this Authenticator.
*
* @param iamProfileName the value to set
*/
protected void setIamProfileName(String iamProfileName) {
this.iamProfileName = iamProfileName;
}

/**
* @return the URL configured on this Authenticator.
*/
Expand Down Expand Up @@ -432,6 +479,9 @@ public IamToken retrieveIamAccessToken(String instanceIdentityToken) {
if (!StringUtils.isEmpty(getIamProfileId())) {
requestBody = String.format("{\"trusted_profile\": {\"id\": \"%s\"}}", getIamProfileId());
}
if (!StringUtils.isEmpty(getIamProfileName())) {
requestBody = String.format("{\"trusted_profile\": {\"name\": \"%s\"}}", getIamProfileName());
}

// If we created a request body above, then set it on the request now.
if (!StringUtils.isEmpty(requestBody)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ 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 mockIamProfileName = "iam-profile-name-123";

private static final String operationPathCreateAccessToken = "/instance_identity/v1/token";
private static final String operationPathCreateIamToken = "/instance_identity/v1/iam_token";
Expand Down Expand Up @@ -105,6 +106,31 @@ public void testBuilderErrorProfileCrnAndId() {
.build();
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void testBuilderErrorProfileCrnAndName() {
new VpcInstanceAuthenticator.Builder()
.iamProfileCrn(mockIamProfileCrn)
.iamProfileName(mockIamProfileName)
.build();
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void testBuilderErrorProfileIdAndName() {
new VpcInstanceAuthenticator.Builder()
.iamProfileId(mockIamProfileId)
.iamProfileName(mockIamProfileName)
.build();
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void testBuilderErrorAllThreeProfiles() {
new VpcInstanceAuthenticator.Builder()
.iamProfileCrn(mockIamProfileCrn)
.iamProfileId(mockIamProfileId)
.iamProfileName(mockIamProfileName)
.build();
}

@Test
public void testBuilderDefaultConfig() {
VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder()
Expand All @@ -113,6 +139,7 @@ public void testBuilderDefaultConfig() {
assertNull(authenticator.getURL());
assertNull(authenticator.getIamProfileCrn());
assertNull(authenticator.getIamProfileId());
assertNull(authenticator.getIamProfileName());

VpcInstanceAuthenticator auth2 = authenticator.newBuilder().build();
assertNotNull(auth2);
Expand Down Expand Up @@ -142,6 +169,19 @@ public void testBuilderCorrectConfig2() {
assertEquals(authenticator.getURL(), "url");
}

@Test
public void testBuilderCorrectConfig3() {
VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder()
.iamProfileName(mockIamProfileName)
.url("url")
.build();
assertEquals(authenticator.authenticationType(), Authenticator.AUTHTYPE_VPC);
assertNull(authenticator.getIamProfileCrn());
assertNull(authenticator.getIamProfileId());
assertEquals(authenticator.getIamProfileName(), mockIamProfileName);
assertEquals(authenticator.getURL(), "url");
}

@Test
public void testConfigCorrectConfig1() {
Map<String, String> props = new HashMap<>();
Expand All @@ -168,6 +208,20 @@ public void testConfigCorrectConfig2() {
assertEquals(authenticator.getURL(), "url");
}

@Test
public void testConfigCorrectConfig3() {
Map<String, String> props = new HashMap<>();
props.put(Authenticator.PROPNAME_IAM_PROFILE_NAME, mockIamProfileName);
props.put(Authenticator.PROPNAME_URL, "url");

VpcInstanceAuthenticator authenticator = VpcInstanceAuthenticator.fromConfiguration(props);
assertEquals(authenticator.authenticationType(), Authenticator.AUTHTYPE_VPC);
assertNull(authenticator.getIamProfileCrn());
assertNull(authenticator.getIamProfileId());
assertEquals(authenticator.getIamProfileName(), mockIamProfileName);
assertEquals(authenticator.getURL(), "url");
}

//
// Tests involving the "retrieveInstanceIdentityToken()" method.
//
Expand Down Expand Up @@ -239,6 +293,29 @@ public void testRetrieveIamAccessTokenSuccess() throws Throwable {
assertTrue(actualHeaders.get(HttpHeaders.USER_AGENT).startsWith("ibm-java-sdk-core/vpc-instance-authenticator"));
}

@Test
public void testRetrieveIamAccessTokenWithProfileName() throws Throwable {
VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder()
.iamProfileName(mockIamProfileName)
.url(url)
.build();

// Set mock server to send back a good response.
server.enqueue(jsonResponse(vpcIamAccessTokenResponse1));

String instanceIdentityToken = "vpc-token";
IamToken iamToken = authenticator.retrieveIamAccessToken(instanceIdentityToken);
assertNotNull(iamToken);
assertEquals(iamToken.getAccessToken(), vpcIamAccessTokenResponse1.getAccessToken());

// Verify the request body contained the expected trusted_profile.name field.
RecordedRequest vpcIamTokenRequest = server.takeRequest();
assertNotNull(vpcIamTokenRequest);
String requestBody = vpcIamTokenRequest.getBody().readUtf8();
assertTrue(requestBody.contains("\"name\": \"" + mockIamProfileName + "\""),
"Expected request body to contain trusted_profile name, but was: " + requestBody);
}

@Test
public void testRetrieveIamAccessTokenFailure() throws Throwable {
VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder()
Expand Down Expand Up @@ -282,6 +359,31 @@ public void testRequestTokenSuccess() throws Throwable {
assertEquals(iamToken.getAccessToken(), vpcIamAccessTokenResponse1.getAccessToken());
}

@Test
public void testRequestTokenSuccessWithProfileName() throws Throwable {
VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder()
.iamProfileName(mockIamProfileName)
.url(url)
.build();

// Set mock server to send back good responses.
server.enqueue(jsonResponse(vpcInstanceIdentityTokenResponse));
server.enqueue(jsonResponse(vpcIamAccessTokenResponse1));

IamToken iamToken = authenticator.requestToken();
assertNotNull(iamToken);
assertEquals(iamToken.getAccessToken(), vpcIamAccessTokenResponse1.getAccessToken());

// Consume the first request (instance identity token) and verify the second
// (IAM token) request body contained trusted_profile.name.
server.takeRequest(); // create_access_token
RecordedRequest iamTokenRequest = server.takeRequest(); // create_iam_token
assertNotNull(iamTokenRequest);
String requestBody = iamTokenRequest.getBody().readUtf8();
assertTrue(requestBody.contains("\"name\": \"" + mockIamProfileName + "\""),
"Expected request body to contain trusted_profile name, but was: " + requestBody);
}

@Test
public void testRequestTokenFailure1() throws Throwable {
VpcInstanceAuthenticator authenticator = new VpcInstanceAuthenticator.Builder()
Expand Down
Loading