Skip to content

Commit 34b89f0

Browse files
authored
Common AWS credentials library (#108)
Move AWS signature auth to independent script
1 parent 360da65 commit 34b89f0

File tree

10 files changed

+642
-500
lines changed

10 files changed

+642
-500
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ and run the gateway.
5454

5555
```
5656
common/ contains files used by both NGINX OSS and Plus configurations
57+
etc/nginx/include/
58+
awscredentials.js common library to read and write credentials
59+
s3gateway.js common library to integrate the s3 storage from NGINX OSS and Plus
60+
utils.js common library to be reused by all of NJS codebases
5761
deployments/ contains files used for deployment technologies such as
5862
CloudFormation
5963
docs/ contains documentation about the project
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Copyright 2023 F5, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import utils from "./utils.js";
18+
19+
const fs = require('fs');
20+
21+
/**
22+
* Get the current session token from either the instance profile credential
23+
* cache or environment variables.
24+
*
25+
* @param r {Request} HTTP request object (not used, but required for NGINX configuration)
26+
* @returns {string} current session token or empty string
27+
*/
28+
function sessionToken(r) {
29+
const credentials = readCredentials(r);
30+
if (credentials.sessionToken) {
31+
return credentials.sessionToken;
32+
}
33+
return '';
34+
}
35+
36+
/**
37+
* Get the instance profile credentials needed to authenticated against S3 from
38+
* a backend cache. If the credentials cannot be found, then return undefined.
39+
* @param r {Request} HTTP request object (not used, but required for NGINX configuration)
40+
* @returns {undefined|{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string|null), expiration: (string|null)}} AWS instance profile credentials or undefined
41+
*/
42+
function readCredentials(r) {
43+
// TODO: Change the generic constants naming for multiple AWS services.
44+
if ('S3_ACCESS_KEY_ID' in process.env && 'S3_SECRET_KEY' in process.env) {
45+
const sessionToken = 'S3_SESSION_TOKEN' in process.env ?
46+
process.env['S3_SESSION_TOKEN'] : null;
47+
return {
48+
accessKeyId: process.env['S3_ACCESS_KEY_ID'],
49+
secretAccessKey: process.env['S3_SECRET_KEY'],
50+
sessionToken: sessionToken,
51+
expiration: null
52+
};
53+
}
54+
55+
if ("variables" in r && r.variables.cache_instance_credentials_enabled == 1) {
56+
return _readCredentialsFromKeyValStore(r);
57+
} else {
58+
return _readCredentialsFromFile();
59+
}
60+
}
61+
62+
/**
63+
* Read credentials from the NGINX Keyval store. If it is not found, then
64+
* return undefined.
65+
*
66+
* @param r {Request} HTTP request object (not used, but required for NGINX configuration)
67+
* @returns {undefined|{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string), expiration: (string)}} AWS instance profile credentials or undefined
68+
* @private
69+
*/
70+
function _readCredentialsFromKeyValStore(r) {
71+
const cached = r.variables.instance_credential_json;
72+
73+
if (!cached) {
74+
return undefined;
75+
}
76+
77+
try {
78+
return JSON.parse(cached);
79+
} catch (e) {
80+
utils.debug_log(r, `Error parsing JSON value from r.variables.instance_credential_json: ${e}`);
81+
return undefined;
82+
}
83+
}
84+
85+
/**
86+
* Read the contents of the credentials file into memory. If it is not
87+
* found, then return undefined.
88+
*
89+
* @returns {undefined|{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string), expiration: (string)}} AWS instance profile credentials or undefined
90+
* @private
91+
*/
92+
function _readCredentialsFromFile() {
93+
const credsFilePath = _credentialsTempFile();
94+
95+
try {
96+
const creds = fs.readFileSync(credsFilePath);
97+
return JSON.parse(creds);
98+
} catch (e) {
99+
/* Do not throw an exception in the case of when the
100+
credentials file path is invalid in order to signal to
101+
the caller that such a file has not been created yet. */
102+
if (e.code === 'ENOENT') {
103+
return undefined;
104+
}
105+
throw e;
106+
}
107+
}
108+
109+
/**
110+
* Returns the path to the credentials temporary cache file.
111+
*
112+
* @returns {string} path on the file system to credentials cache file
113+
* @private
114+
*/
115+
function _credentialsTempFile() {
116+
if (process.env['S3_CREDENTIALS_TEMP_FILE']) {
117+
return process.env['S3_CREDENTIALS_TEMP_FILE'];
118+
}
119+
if (process.env['TMPDIR']) {
120+
return `${process.env['TMPDIR']}/credentials.json`
121+
}
122+
123+
return '/tmp/credentials.json';
124+
}
125+
126+
/**
127+
* Write the instance profile credentials to a caching backend.
128+
*
129+
* @param r {Request} HTTP request object (not used, but required for NGINX configuration)
130+
* @param credentials {{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string), expiration: (string)}} AWS instance profile credentials
131+
*/
132+
function writeCredentials(r, credentials) {
133+
/* Do not bother writing credentials if we are running in a mode where we
134+
do not need instance credentials. */
135+
if (process.env['S3_ACCESS_KEY_ID'] && process.env['S3_SECRET_KEY']) {
136+
return;
137+
}
138+
139+
if (!credentials) {
140+
throw `Cannot write invalid credentials: ${JSON.stringify(credentials)}`;
141+
}
142+
143+
if ("variables" in r && r.variables.cache_instance_credentials_enabled == 1) {
144+
_writeCredentialsToKeyValStore(r, credentials);
145+
} else {
146+
_writeCredentialsToFile(credentials);
147+
}
148+
}
149+
150+
/**
151+
* Write the instance profile credentials to the NGINX Keyval store.
152+
*
153+
* @param r {Request} HTTP request object (not used, but required for NGINX configuration)
154+
* @param credentials {{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string), expiration: (string)}} AWS instance profile credentials
155+
* @private
156+
*/
157+
function _writeCredentialsToKeyValStore(r, credentials) {
158+
r.variables.instance_credential_json = JSON.stringify(credentials);
159+
}
160+
161+
/**
162+
* Write the instance profile credentials to a file on the file system. This
163+
* file will be quite small and should end up in the file cache relatively
164+
* quickly if it is repeatedly read.
165+
*
166+
* @param r {Request} HTTP request object (not used, but required for NGINX configuration)
167+
* @param credentials {{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string), expiration: (string)}} AWS instance profile credentials
168+
* @private
169+
*/
170+
function _writeCredentialsToFile(credentials) {
171+
fs.writeFileSync(_credentialsTempFile(), JSON.stringify(credentials));
172+
}
173+
174+
export default {
175+
readCredentials,
176+
sessionToken,
177+
writeCredentials
178+
}

0 commit comments

Comments
 (0)