Skip to content
Open
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 @@ -488,6 +488,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 when obtaining the IAM access token.

- url: (optional) The VPC Instance Metadata Service's base URL.
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 @@ -500,12 +502,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`, `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 from `iamProfileCrn`, `iamProfileId` or `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
21 changes: 20 additions & 1 deletion auth/authenticators/vpc-instance-authenticator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export interface Options extends BaseOptions {
iamProfileCrn?: string;
/** The ID of the linked trusted IAM profile to be used when obtaining the IAM access token */
iamProfileId?: string;
/** The name of the linked trusted IAM profile to be used when obtaining the IAM access token */
iamProfileName?: string;
/** The version of the Instance Metadata Service to be used obtaining tokens */
serviceVersion?: string;
/** The lifetime of the Instance Identity Token */
Expand All @@ -47,6 +49,8 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator {

private iamProfileId: string;

private iamProfileName: string;

private serviceVersion: string;

private tokenLifetime: number;
Expand All @@ -59,9 +63,10 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator {
* - url: (optional) the endpoint URL for the VPC Instance Metadata Service (default value: "http://169.254.169.254")
* - iamProfileCrn: (optional) the CRN of the linked IAM trusted profile to be used to obtain the IAM access token
* - iamProfileId: (optional) the ID of the linked IAM trusted profile to be used to obtain the IAM access token
* - iamProfileName: (optional) the name of the linked IAM trusted profile to be used to obtain the IAM access token
*
* @remarks
* At most one of "iamProfileCrn" or "iamProfileId" may be specified. If neither one is specified,
* At most one of "iamProfileCrn", "iamProfileId" or "iamProfileName" may be specified. If neither one is specified,
* then the default IAM profile defined for the compute resource will be used.
*/
constructor(options: Options) {
Expand All @@ -76,6 +81,9 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator {
if (options.iamProfileId) {
this.iamProfileId = options.iamProfileId;
}
if (options.iamProfileName) {
this.iamProfileName = options.iamProfileName;
}
if (options.serviceVersion) {
this.serviceVersion = options.serviceVersion;
}
Expand Down Expand Up @@ -111,6 +119,17 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator {
this.tokenManager.setIamProfileId(iamProfileId);
}

/**
* Sets the "iamProfileName" value to be used when obtaining an IAM access token
* @param iamProfileName - the name of the linked IAM trusted profile to use when obtaining an IAM access token
*/
public setIamProfileName(iamProfileName: string): void {
this.iamProfileName = iamProfileName;

// update properties in token manager
this.tokenManager.setIamProfileName(iamProfileName);
}

public setServiceVersion(serviceVersion: string): void {
this.serviceVersion = serviceVersion;

Expand Down
29 changes: 26 additions & 3 deletions auth/token-managers/vpc-instance-token-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ interface Options extends JwtTokenManagerOptions {
iamProfileCrn?: string;
/** The ID of the linked trusted IAM profile to be used when obtaining the IAM access token */
iamProfileId?: string;
/** The name of the linked trusted IAM profile to be used when obtaining the IAM access token */
iamProfileName?: string;
/** The version of the Instance Metadata Service to be used obtaining tokens */
serviceVersion?: string;
/** The lifetime of the Instance Identity Token */
Expand All @@ -54,6 +56,7 @@ interface VpcTokenResponse {
interface TrustedProfile {
id?: string;
crn?: string;
name?: string;
}

interface CreateIamTokenBody {
Expand All @@ -68,6 +71,8 @@ export class VpcInstanceTokenManager extends JwtTokenManager {

private iamProfileId: string;

private iamProfileName: string;

private serviceVersion: string;

private tokenLifetime: number;
Expand All @@ -80,9 +85,10 @@ export class VpcInstanceTokenManager extends JwtTokenManager {
* - url: (optional) the endpoint URL for the VPC Instance Metadata Service (default value: "http://169.254.169.254")
* - iamProfileCrn: (optional) the CRN of the linked IAM trusted profile to be used to obtain the IAM access token
* - iamProfileId: (optional) the ID of the linked IAM trusted profile to be used to obtain the IAM access token
* - iamProfileName: (optional) the name of the linked IAM trusted profile to be used to obtain the IAM access token
*
* @remarks
* At most one of "iamProfileCrn" or "iamProfileId" may be specified. If neither one is specified,
* At most one of "iamProfileCrn", "iamProfileId" or "iamProfileName" may be specified. If neither one is specified,
* then the default IAM profile defined for the compute resource will be used.
*/
constructor(options: Options) {
Expand All @@ -91,8 +97,10 @@ export class VpcInstanceTokenManager extends JwtTokenManager {

super(options);

if (!atMostOne(options.iamProfileId, options.iamProfileCrn)) {
throw new Error('At most one of `iamProfileId` or `iamProfileCrn` may be specified.');
if (!atMostOne(options.iamProfileId, options.iamProfileCrn, options.iamProfileName)) {
throw new Error(
'At most one of `iamProfileId`, `iamProfileCrn` or `iamProfileName` may be specified.'
);
}

this.url = options.url || DEFAULT_IMS_ENDPOINT;
Expand All @@ -119,6 +127,9 @@ export class VpcInstanceTokenManager extends JwtTokenManager {
if (options.iamProfileId) {
this.iamProfileId = options.iamProfileId;
}
if (options.iamProfileName) {
this.iamProfileName = options.iamProfileName;
}

this.userAgent = buildUserAgent('vpc-instance-authenticator');
}
Expand All @@ -139,6 +150,14 @@ export class VpcInstanceTokenManager extends JwtTokenManager {
this.iamProfileId = iamProfileId;
}

/**
* Sets the name of the IAm trusted profile to use when fetching access token from the IAM token server.
* @param iamProfileName - the name of the IAM trusted profile
*/
public setIamProfileName(iamProfileName: string): void {
this.iamProfileName = iamProfileName;
}

public setServiceVersion(serviceVersion: string): void {
if (!metadataServiceSupportedVersions.includes(serviceVersion)) {
throw new Error(
Expand Down Expand Up @@ -182,6 +201,10 @@ export class VpcInstanceTokenManager extends JwtTokenManager {
body = {
trusted_profile: { crn: this.iamProfileCrn },
};
} else if (this.iamProfileName) {
body = {
trusted_profile: { name: this.iamProfileName },
};
}

const parameters = {
Expand Down
2 changes: 2 additions & 0 deletions etc/ibm-cloud-sdk-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator {
authenticationType(): string;
setIamProfileCrn(iamProfileCrn: string): void;
setIamProfileId(iamProfileId: string): void;
setIamProfileName(iamProfileName: string): void;
// (undocumented)
setServiceVersion(serviceVersion: string): void;
// (undocumented)
Expand All @@ -530,6 +531,7 @@ export class VpcInstanceTokenManager extends JwtTokenManager {
protected requestToken(): Promise<any>;
setIamProfileCrn(iamProfileCrn: string): void;
setIamProfileId(iamProfileId: string): void;
setIamProfileName(iamProfileName: string): void;
// (undocumented)
setServiceVersion(serviceVersion: string): void;
// (undocumented)
Expand Down
61 changes: 60 additions & 1 deletion test/unit/vpc-instance-authenticator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const SERVICE_VERSION_2025 = '2025-08-26';
const DEFAULT_TOKEN_LIFETIME = 300;
const CUSTOM_TOKEN_LIFETIME = 600;
const INVALID_SERVICE_VERSION_ERROR = `Invalid serviceVersion. Must be one of: ${SERVICE_VERSION_2022}, ${SERVICE_VERSION_2025}`;
const IAM_PROFILE_NAME = 'some-name';

// mock the `getToken` method in the token manager - dont make any rest calls
const fakeToken = 'iam-acess-token';
Expand Down Expand Up @@ -56,7 +57,65 @@ describe('VPC Instance Authenticator', () => {
iamProfileCrn: 'crn',
iamProfileId: 'id',
});
}).toThrow('At most one of `iamProfileId` or `iamProfileCrn` may be specified.');
}).toThrow(
'At most one of `iamProfileId`, `iamProfileCrn` or `iamProfileName` may be specified.'
);
});

it('should throw an error when both iamProfileCrn and iamProfileName are provided', () => {
expect(() => {
const unused = new VpcInstanceAuthenticator({
iamProfileCrn: 'crn',
iamProfileName: IAM_PROFILE_NAME,
});
}).toThrow(
'At most one of `iamProfileId`, `iamProfileCrn` or `iamProfileName` may be specified.'
);
});

it('should throw an error when both iamProfileId and iamProfileName are provided', () => {
expect(() => {
const unused = new VpcInstanceAuthenticator({
iamProfileId: 'id',
iamProfileName: IAM_PROFILE_NAME,
});
}).toThrow(
'At most one of `iamProfileId`, `iamProfileCrn` or `iamProfileName` may be specified.'
);
});

it('should store iamProfileName when provided in config', () => {
const authenticator = new VpcInstanceAuthenticator({ iamProfileName: IAM_PROFILE_NAME });

expect(authenticator.iamProfileName).toBe(IAM_PROFILE_NAME);
expect(authenticator.iamProfileCrn).toBeUndefined();
expect(authenticator.iamProfileId).toBeUndefined();

// should also be set on the token manager
expect(authenticator.tokenManager.iamProfileName).toBe(IAM_PROFILE_NAME);
});

it('should re-set iamProfileName using the setter', () => {
const authenticator = new VpcInstanceAuthenticator({ iamProfileName: 'initial-name' });
expect(authenticator.iamProfileName).toBe('initial-name');

authenticator.setIamProfileName(IAM_PROFILE_NAME);
expect(authenticator.iamProfileName).toBe(IAM_PROFILE_NAME);

// also, verify that the underlying token manager has been updated
expect(authenticator.tokenManager.iamProfileName).toBe(IAM_PROFILE_NAME);
});

it('should set iamProfileName using the setter when not declared in constructor', () => {
const authenticator = new VpcInstanceAuthenticator();
expect(authenticator.iamProfileName).toBeUndefined();
expect(authenticator.tokenManager.iamProfileName).toBeUndefined();

authenticator.setIamProfileName(IAM_PROFILE_NAME);
expect(authenticator.iamProfileName).toBe(IAM_PROFILE_NAME);

// also, verify that the underlying token manager has been updated
expect(authenticator.tokenManager.iamProfileName).toBe(IAM_PROFILE_NAME);
});

it('should re-set iamProfileCrn using the setter', () => {
Expand Down
66 changes: 65 additions & 1 deletion test/unit/vpc-instance-token-manager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const debugLogSpy = jest.spyOn(logger, 'debug').mockImplementation(() => {});

const IAM_PROFILE_CRN = 'some-crn';
const IAM_PROFILE_ID = 'some-id';
const IAM_PROFILE_NAME = 'some-name';
const EXPIRATION_WINDOW = 10;
const SERVICE_VERSION_2022 = '2022-03-01';
const SERVICE_VERSION_2025 = '2025-08-26';
Expand Down Expand Up @@ -70,7 +71,43 @@ describe('VPC Instance Token Manager', () => {
iamProfileCrn: IAM_PROFILE_CRN,
iamProfileId: IAM_PROFILE_ID,
})
).toThrow('At most one of `iamProfileId` or `iamProfileCrn` may be specified.');
).toThrow(
'At most one of `iamProfileId`, `iamProfileCrn` or `iamProfileName` may be specified.'
);
});

it('should throw an error when both `iamProfileCrn` and `iamProfileName` are provided', () => {
expect(
() =>
new VpcInstanceTokenManager({
iamProfileCrn: IAM_PROFILE_CRN,
iamProfileName: IAM_PROFILE_NAME,
})
).toThrow(
'At most one of `iamProfileId`, `iamProfileCrn` or `iamProfileName` may be specified.'
);
});

it('should throw an error when both `iamProfileId` and `iamProfileName` are provided', () => {
expect(
() =>
new VpcInstanceTokenManager({
iamProfileId: IAM_PROFILE_ID,
iamProfileName: IAM_PROFILE_NAME,
})
).toThrow(
'At most one of `iamProfileId`, `iamProfileCrn` or `iamProfileName` may be specified.'
);
});

it('should set given profile name', () => {
const instance = new VpcInstanceTokenManager({
iamProfileName: IAM_PROFILE_NAME,
});

expect(instance.iamProfileName).toBe(IAM_PROFILE_NAME);
expect(instance.iamProfileCrn).toBeUndefined();
expect(instance.iamProfileId).toBeUndefined();
});

it('should use default url if none is given', () => {
Expand Down Expand Up @@ -112,6 +149,22 @@ describe('VPC Instance Token Manager', () => {
instance.setIamProfileId(IAM_PROFILE_ID);
expect(instance.iamProfileId).toBe(IAM_PROFILE_ID);
});

it('should set iamProfileName with the setter', () => {
const instance = new VpcInstanceTokenManager();
expect(instance.iamProfileName).toBeUndefined();

instance.setIamProfileName(IAM_PROFILE_NAME);
expect(instance.iamProfileName).toBe(IAM_PROFILE_NAME);
});

it('should update iamProfileName with the setter when already set in constructor', () => {
const instance = new VpcInstanceTokenManager({ iamProfileName: 'initial-name' });
expect(instance.iamProfileName).toBe('initial-name');

instance.setIamProfileName(IAM_PROFILE_NAME);
expect(instance.iamProfileName).toBe(IAM_PROFILE_NAME);
});
});

describe('getInstanceIdentityToken', () => {
Expand Down Expand Up @@ -225,6 +278,17 @@ describe('VPC Instance Token Manager', () => {
expect(requestOptions.body.trusted_profile.id).toBe('some-id');
});

it('should set trusted profile to iam profile name, if set', async () => {
const instance = new VpcInstanceTokenManager({ iamProfileName: IAM_PROFILE_NAME });
await instance.requestToken();

const requestOptions = getRequestOptions(sendRequestMock, 1);
expect(requestOptions).toBeDefined();
expect(requestOptions.body).toBeDefined();
expect(requestOptions.body.trusted_profile).toBeDefined();
expect(requestOptions.body.trusted_profile.name).toBe(IAM_PROFILE_NAME);
});

it('should set User-Agent header', async () => {
const instance = new VpcInstanceTokenManager({ iamProfileId: 'some-id' });

Expand Down
Loading