Skip to content

Commit 1e0e5e7

Browse files
tieum4141done
andauthored
feat: add EKS pod identity credentials support (#252)
Uses the `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE` environment variable to provide support for EKS pod identity credentials. Ref: https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html --------- Co-authored-by: Javier Evans <[email protected]>
1 parent a2f1540 commit 1e0e5e7

File tree

3 files changed

+128
-2
lines changed

3 files changed

+128
-2
lines changed

common/etc/nginx/include/awscredentials.js

+38
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ const EC2_IMDS_TOKEN_ENDPOINT = 'http://169.254.169.254/latest/api/token';
5858
*/
5959
const EC2_IMDS_SECURITY_CREDENTIALS_ENDPOINT = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/';
6060

61+
/**
62+
* URL to EKS Pod Identity Agent credentials endpoint
63+
* @type {string}
64+
*/
65+
const EKS_POD_IDENTITY_AGENT_CREDENTIALS_ENDPOINT = 'http://169.254.170.23/v1/credentials'
66+
6167
/**
6268
* Offset to the expiration of credentials, when they should be considered expired and refreshed. The maximum
6369
* time here can be 5 minutes, the IMDS and ECS credentials endpoint will make sure that each returned set of credentials
@@ -293,6 +299,15 @@ async function fetchCredentials(r) {
293299
r.return(500);
294300
return;
295301
}
302+
}
303+
else if (utils.areAllEnvVarsSet('AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE')) {
304+
try {
305+
credentials = await _fetchEKSPodIdentityCredentials(r)
306+
} catch (e) {
307+
utils.debug_log(r, 'Could not assume role using EKS pod identity: ' + JSON.stringify(e));
308+
r.return(500);
309+
return;
310+
}
296311
} else {
297312
try {
298313
credentials = await _fetchEC2RoleCredentials();
@@ -378,6 +393,29 @@ async function _fetchEC2RoleCredentials() {
378393
};
379394
}
380395

396+
/**
397+
* Get the credentials needed to generate AWS signatures from the EKS Pod Identity Agent
398+
* endpoint.
399+
*
400+
* @returns {Promise<Credentials>}
401+
* @private
402+
*/
403+
async function _fetchEKSPodIdentityCredentials() {
404+
const token = fs.readFileSync(process.env['AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE']);
405+
let resp = await ngx.fetch(EKS_POD_IDENTITY_AGENT_CREDENTIALS_ENDPOINT, {
406+
headers: {
407+
'Authorization': token,
408+
},
409+
});
410+
const creds = await resp.json();
411+
412+
return {
413+
accessKeyId: creds.AccessKeyId,
414+
secretAccessKey: creds.SecretAccessKey,
415+
sessionToken: creds.Token,
416+
expiration: creds.Expiration,
417+
};
418+
}
381419
/**
382420
* Get the credentials by assuming calling AssumeRoleWithWebIdentity with the environment variable
383421
* values ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE and AWS_ROLE_SESSION_NAME

docs/getting_started.md

+19
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
[Running as a Systemd Service](#running-as-a-systemd-service)
77
[Running in Containers](#running-in-containers)
88
[Running Using AWS Instance Profile Credentials](#running-using-aws-instance-profile-credentials)
9+
[Running on EKS with IAM roles for service accounts](#running-on-eks-with-iam-roles-for-service-accounts)
10+
[Running on EKS with EKS Pod Identities](#running-on-eks-with-eks-pod-identities)
911
[Troubleshooting](#troubleshooting)
1012

1113
## Configuration
@@ -470,6 +472,23 @@ spec:
470472
path: /health
471473
port: http
472474
```
475+
## Running on EKS with EKS Pod Identities
476+
477+
An alternative way to use the container image on an EKS cluster is to use a service account which can assume a role using [Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html).
478+
- Installing the [Amazon EKS Pod Identity Agent](https://docs.aws.amazon.com/eks/latest/userguide/pod-id-agent-setup.html) on the cluster
479+
- Configuring a [Kubernetes service account to assume an IAM role with EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-id-association.html)
480+
- [Configure your pods, Deployments, etc to use the Service Account](https://docs.aws.amazon.com/eks/latest/userguide/pod-configuration.html)
481+
- As soon as the pods/deployments are updated, you will see the couple of Env Variables listed below in the pods.
482+
- `AWS_CONTAINER_CREDENTIALS_FULL_URI` - Contains the Uri of the EKS Pod Identity Agent that will provide the credentials
483+
- `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE` - Contains the token which will be used to create temporary credentials using the EKS Pod Identity Agent.
484+
485+
The minimal set of resources to deploy is the same than for [Running on EKS with IAM roles for service accounts](#running-on-eks-with-iam-roles-for-service-accounts), except there is no need to annotate the service account:
486+
```yaml
487+
apiVersion: v1
488+
kind: ServiceAccount
489+
metadata:
490+
name: nginx-s3-gateway
491+
```
473492

474493
## Troubleshooting
475494

test/unit/awscredentials_test.js

+71-2
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ async function testEc2CredentialRetrieval() {
234234
delete process.env['AWS_ACCESS_KEY_ID'];
235235
}
236236
if ('AWS_CONTAINER_CREDENTIALS_RELATIVE_URI' in process.env) {
237-
delete process.env['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'];
237+
delete process.env['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'];
238238
}
239239
globalThis.ngx.fetch = function (url, options) {
240240
if (url === 'http://169.254.169.254/latest/api/token' && options && options.method === 'PUT') {
@@ -300,13 +300,82 @@ async function testEc2CredentialRetrieval() {
300300
await awscred.fetchCredentials(r);
301301

302302
if (!globalThis.credentialsIssued) {
303-
throw 'Did not reach the point where EC2 credentials were issues.';
303+
throw 'Did not reach the point where EC2 credentials were issued.';
304+
}
305+
}
306+
307+
async function testEKSPodIdentityCredentialRetrieval() {
308+
printHeader('testEKSPodIdentityCredentialRetrieval');
309+
if ('AWS_ACCESS_KEY_ID' in process.env) {
310+
delete process.env['AWS_ACCESS_KEY_ID'];
311+
}
312+
if ('AWS_CONTAINER_CREDENTIALS_RELATIVE_URI' in process.env) {
313+
delete process.env['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'];
314+
}
315+
if ('AWS_WEB_IDENTITY_TOKEN_FILE' in process.env) {
316+
delete process.env['AWS_WEB_IDENTITY_TOKEN_FILE'];
317+
}
318+
var tempDir = (process.env['TMPDIR'] ? process.env['TMPDIR'] : '/tmp');
319+
var uniqId = `${new Date().getTime()}-${Math.floor(Math.random()*101)}`;
320+
var tempFile = `${tempDir}/credentials-unit-test-${uniqId}.json`;
321+
var testToken = 'A_TOKEN';
322+
fs.writeFileSync(tempFile, testToken);
323+
process.env['AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE'] = tempFile;
324+
globalThis.ngx.fetch = function(url, options) {
325+
console.log(' fetching eks pod identity mock credentials');
326+
if (url === 'http://169.254.170.23/v1/credentials') {
327+
if (options && options.headers && options.headers['Authorization'].toString() === testToken) {
328+
return Promise.resolve({
329+
ok: true,
330+
json: function() {
331+
globalThis.credentialsIssued = true;
332+
return Promise.resolve({
333+
AccessKeyId: 'AN_ACCESS_KEY_ID',
334+
Expiration: '2017-05-17T15:09:54Z',
335+
AccountId: 'AN_ACCOUNT_ID',
336+
SecretAccessKey: 'A_SECRET_ACCESS_KEY',
337+
Token: 'A_SECURITY_TOKEN',
338+
});
339+
},
340+
});
341+
} else {
342+
throw 'Invalid token passed: ' + options.headers['Authorization'];
343+
}
344+
} else {
345+
throw 'Invalid request URL: ' + url;
346+
}
347+
};
348+
var r = {
349+
"headersOut": {
350+
"Accept-Ranges": "bytes",
351+
"Content-Length": 42,
352+
"Content-Security-Policy": "block-all-mixed-content",
353+
"Content-Type": "text/plain",
354+
"X-Amz-Bucket-Region": "us-east-1",
355+
"X-Amz-Request-Id": "166539E18A46500A",
356+
"X-Xss-Protection": "1; mode=block"
357+
},
358+
log: function(msg) {
359+
console.log(msg);
360+
},
361+
return: function(code) {
362+
if (code !== 200) {
363+
throw 'Expected 200 status code, got: ' + code;
364+
}
365+
},
366+
};
367+
368+
await awscred.fetchCredentials(r);
369+
370+
if (!globalThis.credentialsIssued) {
371+
throw 'Did not reach the point where EKS Pod Identity credentials were issued.';
304372
}
305373
}
306374

307375
async function test() {
308376
await testEc2CredentialRetrieval();
309377
await testEcsCredentialRetrieval();
378+
await testEKSPodIdentityCredentialRetrieval();
310379
testReadCredentialsWithAccessSecretKeyAndSessionTokenSet();
311380
testReadCredentialsFromFilePath();
312381
testReadCredentialsFromNonexistentPath();

0 commit comments

Comments
 (0)