Skip to content

Commit

Permalink
fix(ContainerAuthenticator): add sa-token as default CR token filename (
Browse files Browse the repository at this point in the history
#241)

This commit adds support for a second default CR token filename.
If the user doesn't specify a CR token filename, the authenticator will
first try '/var/run/secrets/tokens/vault-token', and then
'/var/run/secrets/tokens/sa-token' in that order.

Signed-off-by: Phil Adams <phil_adams@us.ibm.com>
  • Loading branch information
padamstx authored May 22, 2023
1 parent 26a3cec commit 91f9932
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 75 deletions.
3 changes: 2 additions & 1 deletion Authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ The IAM access token is added to each outbound request in the `Authorization` he
### Properties

- crTokenFilename: (optional) the name of the file containing the injected CR token value.
If not specified, then `/var/run/secrets/tokens/vault-token` is used as the default value.
If not specified, then the authenticator will first try `/var/run/secrets/tokens/vault-token`
and then `/var/run/secrets/tokens/sa-token` as the default value (first file found is used).
The application must have `read` permissions on the file containing the CR token value.

- iamProfileName: (optional) the name of the linked trusted IAM profile to be used when obtaining the
Expand Down
47 changes: 35 additions & 12 deletions auth/token-managers/container-token-manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2021, 2022 IBM Corp. All Rights Reserved.
* Copyright 2021, 2023 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,11 +14,11 @@
* limitations under the License.
*/

import logger from '../../lib/logger';
import { atLeastOne, readCrTokenFile } from '../utils';
import { IamRequestBasedTokenManager, IamRequestOptions } from './iam-request-based-token-manager';

const DEFAULT_CR_TOKEN_FILEPATH = '/var/run/secrets/tokens/vault-token';
const DEFAULT_CR_TOKEN_FILEPATH1 = '/var/run/secrets/tokens/vault-token';
const DEFAULT_CR_TOKEN_FILEPATH2 = '/var/run/secrets/tokens/sa-token';

/** Configuration options for IAM token retrieval. */
interface Options extends IamRequestOptions {
Expand Down Expand Up @@ -69,7 +69,9 @@ export class ContainerTokenManager extends IamRequestBasedTokenManager {
throw new Error('At least one of `iamProfileName` or `iamProfileId` must be specified.');
}

this.crTokenFilename = options.crTokenFilename || DEFAULT_CR_TOKEN_FILEPATH;
if (options.crTokenFilename) {
this.crTokenFilename = options.crTokenFilename;
}

if (options.iamProfileName) {
this.iamProfileName = options.iamProfileName;
Expand Down Expand Up @@ -110,8 +112,7 @@ export class ContainerTokenManager extends IamRequestBasedTokenManager {
* Request an IAM token using a compute resource token.
*/
protected async requestToken(): Promise<any> {
const crToken = getCrToken(this.crTokenFilename);
this.formData.cr_token = crToken;
this.formData.cr_token = this.getCrToken();

// these member variables can be reset, set them in the form data right
// before making the request to ensure they're up to date
Expand All @@ -124,11 +125,33 @@ export class ContainerTokenManager extends IamRequestBasedTokenManager {

return super.requestToken();
}
}

function getCrToken(filename: string): string {
logger.debug(`Attempting to read CR token from file: ${filename}`);

// moving the actual read to another file to isolate usage of node-only packages like `fs`
return readCrTokenFile(filename);
/**
* Retrieves the CR token from a file using this search order:
* 1. User-specified filename (if specified)
* 2. Default file #1 (/var/run/secrets/tokens/vault-token)
* 3. Default file #2 (/var/run/secrets/tokens/sa-token)
* First one found wins.
*
* @returns the CR token value as a string
*/
protected getCrToken(): string {
try {
let crToken = null;
if (this.crTokenFilename) {
// If the user specified a filename, then try to read from that.
crToken = readCrTokenFile(this.crTokenFilename);
} else {
// If no filename was specified, then try our two default filenames.
try {
crToken = readCrTokenFile(DEFAULT_CR_TOKEN_FILEPATH1);
} catch (err) {
crToken = readCrTokenFile(DEFAULT_CR_TOKEN_FILEPATH2);
}
}
return crToken;
} catch (err) {
throw new Error(`Error reading CR token file: ${err.toString()}`);
}
}
}
24 changes: 9 additions & 15 deletions auth/utils/file-reading-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2021 IBM Corp. All Rights Reserved.
* Copyright 2021, 2023 IBM Corp. All Rights Reserved.
*
* 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 @@ -92,21 +92,15 @@ export function readCrTokenFile(filepath: string): string {
return '';
}

let token: string = '';
const fileExists = fileExistsAtPath(filepath);
if (fileExists) {
try {
let token: string = '';
logger.debug(`Attempting to read CR token from file: ${filepath}`);
token = readFileSync(filepath, 'utf8');
logger.debug(`Successfully read CR token from file: ${filepath}`);
return token;
} catch (err) {
const msg = `Error reading CR token: ${err.toString()}`;
logger.debug(msg);
throw new Error(msg);
}

if (token === '') {
if (fileExists) {
logger.error(`Expected to read CR token from file but the file is empty: ${filepath}`);
} else {
logger.error(`Expected to find CR token file but the file does not exist: ${filepath}`);
}
throw new Error(`Unable to retrieve the CR token value from file: ${filepath}`);
}

return token;
}
1 change: 1 addition & 0 deletions etc/ibm-cloud-sdk-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export class ContainerAuthenticator extends IamRequestBasedAuthenticator {
export class ContainerTokenManager extends IamRequestBasedTokenManager {
// Warning: (ae-forgotten-export) The symbol "Options_7" needs to be exported by the entry point index.d.ts
constructor(options: Options_7);
protected getCrToken(): string;
protected requestToken(): Promise<any>;
setCrTokenFilename(crTokenFilename: string): void;
setIamProfileId(iamProfileId: string): void;
Expand Down
6 changes: 2 additions & 4 deletions test/unit/container-authenticator.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2021 IBM Corp. All Rights Reserved.
* Copyright 2021, 2023 IBM Corp. All Rights Reserved.
*
* 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 @@ -67,9 +67,7 @@ describe('Container Authenticator', () => {
it('should re-set crTokenFilename using the setter', () => {
const authenticator = new ContainerAuthenticator({ iamProfileName: config.iamProfileName });
expect(authenticator.crTokenFilename).toBeUndefined();
// the default is set on the token manager
expect(authenticator.tokenManager.crTokenFilename).toBeDefined();
expect(authenticator.tokenManager.crTokenFilename).not.toBe(config.crTokenFilename);
expect(authenticator.tokenManager.crTokenFilename).toBeUndefined();

authenticator.setCrTokenFilename(config.crTokenFilename);
expect(authenticator.crTokenFilename).toEqual(config.crTokenFilename);
Expand Down
10 changes: 2 additions & 8 deletions test/unit/container-token-manager.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-alert, no-console */

/**
* Copyright 2021 IBM Corp. All Rights Reserved.
* Copyright 2021, 2023 IBM Corp. All Rights Reserved.
*
* 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 @@ -44,12 +44,6 @@ describe('Container Token Manager', () => {
);
});

// use default filename
it('should use default filename if none is given', () => {
const instance = new ContainerTokenManager({ iamProfileName: IAM_PROFILE_NAME });
expect(instance.crTokenFilename).toBe('/var/run/secrets/tokens/vault-token');
});

it('should set given values', () => {
const instance = new ContainerTokenManager({
crTokenFilename: CR_TOKEN_FILENAME,
Expand All @@ -74,7 +68,7 @@ describe('Container Token Manager', () => {
describe('setters', () => {
it('should set crTokenFilename with the setter', () => {
const instance = new ContainerTokenManager({ iamProfileName: IAM_PROFILE_NAME });
expect(instance.crTokenFilename).toBe('/var/run/secrets/tokens/vault-token');
expect(instance.crTokenFilename).toBeUndefined();

instance.setCrTokenFilename(CR_TOKEN_FILENAME);
expect(instance.crTokenFilename).toBe(CR_TOKEN_FILENAME);
Expand Down
38 changes: 3 additions & 35 deletions test/unit/file-reading-helpers.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2021 IBM Corp. All Rights Reserved.
* Copyright 2021, 2023 IBM Corp. All Rights Reserved.
*
* 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 @@ -139,49 +139,20 @@ describe('read ibm credentials file', () => {
});

describe('Read CR Token File', () => {
const loggerDebugMock = jest.spyOn(logger, 'debug').mockImplementation(() => {});
const loggerErrorMock = jest.spyOn(logger, 'error').mockImplementation(() => {});

afterEach(() => {
loggerDebugMock.mockClear();
loggerErrorMock.mockClear();
});

afterAll(() => {
loggerDebugMock.mockRestore();
loggerErrorMock.mockRestore();
});

it('should successfully return contents of file as a string', () => {
const filename = `${__dirname}/../resources/vault-token`;
const token = readCrTokenFile(filename);

expect(token).toBe('my-cr-token-123');
expect(loggerDebugMock).toHaveBeenCalledWith(
`Successfully read CR token from file: ${filename}`
);
});

it('should throw an error if given file does not exist', () => {
const filename = '/path/to/nowhere/';
expect(() => {
const token = readCrTokenFile(filename);
}).toThrow(`Unable to retrieve the CR token value from file: ${filename}`);

expect(loggerErrorMock).toHaveBeenCalledWith(
`Expected to find CR token file but the file does not exist: ${filename}`
);
});
const expectedMessage = new RegExp('Error reading CR token:.*ENOENT:.*/path/to/nowhere');

it('should throw an error if given file is empty', () => {
const filename = `${__dirname}/../resources/empty-file`;
expect(() => {
const token = readCrTokenFile(filename);
}).toThrow(`Unable to retrieve the CR token value from file: ${filename}`);

expect(loggerErrorMock).toHaveBeenCalledWith(
`Expected to read CR token from file but the file is empty: ${filename}`
);
}).toThrow(expectedMessage);
});

it('should throw an error if file read goes wrong', () => {
Expand All @@ -195,9 +166,6 @@ describe('Read CR Token File', () => {
const token = readCrTokenFile(filename);
}).toThrow(fileReadingError);

expect(loggerDebugMock).not.toHaveBeenCalled();
expect(loggerErrorMock).not.toHaveBeenCalled();

readFileMock.mockRestore();
});
});

0 comments on commit 91f9932

Please sign in to comment.