Skip to content

Commit

Permalink
fix: adjust IAM token expiration time (#268)
Browse files Browse the repository at this point in the history
This commit changes the IAM, Container and VPC Instance
authenticators slightly so that an IAM access token
will be viewed as "expired" when the current time is
within 10 seconds of the official expiration time.
IOW, we'll expire the access token 10 secs earlier
than the IAM server-computed expiration time.
We're doing this to avoid a scenario where
an IBM Cloud service receives a request along
with an "almost expired" access token and then uses
that token to perform downstream requests in a
somewhat longer-running transaction and then the
access token expires while that transaction is
still active.

Signed-off-by: Phil Adams <phil_adams@us.ibm.com>
  • Loading branch information
padamstx authored Feb 28, 2024
1 parent 7f0036a commit 9b975e0
Show file tree
Hide file tree
Showing 9 changed files with 1,157 additions and 854 deletions.
16 changes: 8 additions & 8 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"files": "package-lock.json|^.secrets.baseline$",
"lines": null
},
"generated_at": "2024-01-04T12:07:27Z",
"generated_at": "2024-02-27T01:02:51Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
Expand Down Expand Up @@ -96,7 +96,7 @@
"hashed_secret": "bc2f74c22f98f7b6ffbc2f67453dbfa99bce9a32",
"is_secret": false,
"is_verified": false,
"line_number": 118,
"line_number": 132,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down Expand Up @@ -244,23 +244,23 @@
"hashed_secret": "f84f793e0af9ade37c8b927bc5091e98f35bf821",
"is_secret": false,
"is_verified": false,
"line_number": 84,
"line_number": 85,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "45c43fe97e3a06ab078b0eeff6fbe622cc417a25",
"is_secret": false,
"is_verified": false,
"line_number": 118,
"line_number": 119,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "99833a8b234b57b886a9aef1dba187fdd7ceece8",
"is_secret": false,
"is_verified": false,
"line_number": 120,
"line_number": 121,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down Expand Up @@ -334,7 +334,7 @@
"hashed_secret": "45c43fe97e3a06ab078b0eeff6fbe622cc417a25",
"is_secret": false,
"is_verified": false,
"line_number": 283,
"line_number": 284,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down Expand Up @@ -522,7 +522,7 @@
"hashed_secret": "a7ef1be18bb8d37af79f3d87761a203378bf26a2",
"is_secret": false,
"is_verified": false,
"line_number": 146,
"line_number": 151,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down Expand Up @@ -582,7 +582,7 @@
}
]
},
"version": "0.13.1+ibm.61.dss",
"version": "0.13.1+ibm.62.dss",
"word_list": {
"file": null,
"hash": null
Expand Down
25 changes: 23 additions & 2 deletions auth/token-managers/iam-request-based-token-manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* (C) Copyright IBM Corp. 2019, 2023.
* (C) Copyright IBM Corp. 2019, 2024.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,13 +17,14 @@
import extend from 'extend';
import { OutgoingHttpHeaders } from 'http';
import logger from '../../lib/logger';
import { computeBasicAuthHeader, onlyOne, removeSuffix } from '../utils/helpers';
import { computeBasicAuthHeader, getCurrentTime, onlyOne, removeSuffix } from '../utils/helpers';
import { JwtTokenManager, JwtTokenManagerOptions } from './jwt-token-manager';

const CLIENT_ID_SECRET_WARNING =
'Warning: Client ID and Secret must BOTH be given, or the header will not be included.';
const DEFAULT_IAM_URL = 'https://iam.cloud.ibm.com';
const OPERATION_PATH = '/identity/token';
const IAM_EXPIRATION_WINDOW = 10;

/** Configuration options for IAM token retrieval. */
export interface IamRequestOptions extends JwtTokenManagerOptions {
Expand Down Expand Up @@ -180,4 +181,24 @@ export class IamRequestBasedTokenManager extends JwtTokenManager {

return this.requestWrapperInstance.sendRequest(parameters);
}

/**
* Returns true iff the currently-cached IAM access token is expired.
* We'll consider an access token as expired when we reach its IAM server-reported
* expiration time minus our expiration window (10 secs).
* We do this to avoid using an access token that might expire in the middle of a long-running
* transaction within an IBM Cloud service.
*
* @returns true if the token has expired, false otherwise
*/
protected isTokenExpired(): boolean {
const { expireTime } = this;

if (!expireTime) {
return true;
}

const currentTime = getCurrentTime();
return currentTime >= expireTime - IAM_EXPIRATION_WINDOW;
}
}
4 changes: 2 additions & 2 deletions auth/token-managers/jwt-token-manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable class-methods-use-this */

/**
* (C) Copyright IBM Corp. 2019, 2022.
* (C) Copyright IBM Corp. 2019, 2024.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -82,7 +82,7 @@ export class JwtTokenManager extends TokenManager {

const decodedResponse = decode(this.accessToken);
if (!decodedResponse) {
const err = 'Access token recieved is not a valid JWT';
const err = 'Access token received is not a valid JWT';
logger.error(err);
throw new Error(err);
}
Expand Down
4 changes: 2 additions & 2 deletions auth/token-managers/token-manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars, class-methods-use-this */

/**
* (C) Copyright IBM Corp. 2020, 2023.
* (C) Copyright IBM Corp. 2020, 2024.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -214,7 +214,7 @@ export class TokenManager {
/**
* Checks if currently-stored token is expired
*/
private isTokenExpired(): boolean {
protected isTokenExpired(): boolean {
const { expireTime } = this;

if (!expireTime) {
Expand Down
25 changes: 23 additions & 2 deletions auth/token-managers/vpc-instance-token-manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* (C) Copyright IBM Corp. 2021, 2023.
* (C) Copyright IBM Corp. 2021, 2024.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,11 +15,12 @@
*/

import logger from '../../lib/logger';
import { atMostOne } from '../utils/helpers';
import { atMostOne, getCurrentTime } from '../utils/helpers';
import { JwtTokenManager, JwtTokenManagerOptions } from './jwt-token-manager';

const DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254';
const METADATA_SERVICE_VERSION = '2022-03-01';
const IAM_EXPIRATION_WINDOW = 10;

/** Configuration options for VPC token retrieval. */
interface Options extends JwtTokenManagerOptions {
Expand Down Expand Up @@ -174,4 +175,24 @@ export class VpcInstanceTokenManager extends JwtTokenManager {

return token;
}

/**
* Returns true iff the currently-cached IAM access token is expired.
* We'll consider an access token as expired when we reach its IAM server-reported
* expiration time minus our expiration window (10 secs).
* We do this to avoid using an access token that might expire in the middle of a long-running
* transaction within an IBM Cloud service.
*
* @returns true if the token has expired, false otherwise
*/
protected isTokenExpired(): boolean {
const { expireTime } = this;

if (!expireTime) {
return true;
}

const currentTime = getCurrentTime();
return currentTime >= expireTime - IAM_EXPIRATION_WINDOW;
}
}
3 changes: 3 additions & 0 deletions etc/ibm-cloud-sdk-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ export class IamRequestBasedTokenManager extends JwtTokenManager {
// (undocumented)
protected formData: any;
getRefreshToken(): string;
protected isTokenExpired(): boolean;
// (undocumented)
protected refreshToken: string;
protected requestToken(): Promise<any>;
Expand Down Expand Up @@ -411,6 +412,7 @@ export class TokenManager {
getToken(): Promise<any>;
// (undocumented)
protected headers: OutgoingHttpHeaders;
protected isTokenExpired(): boolean;
protected pacedRequestToken(): Promise<any>;
// (undocumented)
protected refreshTime: number;
Expand Down Expand Up @@ -501,6 +503,7 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator {
export class VpcInstanceTokenManager extends JwtTokenManager {
// Warning: (ae-forgotten-export) The symbol "Options_9" needs to be exported by the entry point index.d.ts
constructor(options: Options_9);
protected isTokenExpired(): boolean;
// (undocumented)
protected requestToken(): Promise<any>;
setIamProfileCrn(iamProfileCrn: string): void;
Expand Down
Loading

0 comments on commit 9b975e0

Please sign in to comment.