Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add eks pod identity credentials support #252

Merged
merged 4 commits into from
May 21, 2024
Merged
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
38 changes: 38 additions & 0 deletions common/etc/nginx/include/awscredentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ const EC2_IMDS_TOKEN_ENDPOINT = 'http://169.254.169.254/latest/api/token';
*/
const EC2_IMDS_SECURITY_CREDENTIALS_ENDPOINT = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/';

/**
* URL to EKS Pod Identity Agent credentials endpoint
* @type {string}
*/
const EKS_POD_IDENTITY_AGENT_CREDENTIALS_ENDPOINT = 'http://169.254.170.23/v1/credentials'

/**
* Offset to the expiration of credentials, when they should be considered expired and refreshed. The maximum
* time here can be 5 minutes, the IMDS and ECS credentials endpoint will make sure that each returned set of credentials
Expand Down Expand Up @@ -293,6 +299,15 @@ async function fetchCredentials(r) {
r.return(500);
return;
}
}
else if (utils.areAllEnvVarsSet('AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE')) {
try {
credentials = await _fetchEKSPodIdentityCredentials(r)
} catch (e) {
utils.debug_log(r, 'Could not assume role using EKS pod identity: ' + JSON.stringify(e));
r.return(500);
return;
}
} else {
try {
credentials = await _fetchEC2RoleCredentials();
Expand Down Expand Up @@ -378,6 +393,29 @@ async function _fetchEC2RoleCredentials() {
};
}

/**
* Get the credentials needed to generate AWS signatures from the EKS Pod Identity Agent
* endpoint.
*
* @returns {Promise<Credentials>}
* @private
*/
async function _fetchEKSPodIdentityCredentials() {
const token = fs.readFileSync(process.env['AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE']);
let resp = await ngx.fetch(EKS_POD_IDENTITY_AGENT_CREDENTIALS_ENDPOINT, {
headers: {
'Authorization': token,
},
});
const creds = await resp.json();

return {
accessKeyId: creds.AccessKeyId,
secretAccessKey: creds.SecretAccessKey,
sessionToken: creds.Token,
expiration: creds.Expiration,
};
}
/**
* Get the credentials by assuming calling AssumeRoleWithWebIdentity with the environment variable
* values ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE and AWS_ROLE_SESSION_NAME
Expand Down
19 changes: 19 additions & 0 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
[Running as a Systemd Service](#running-as-a-systemd-service)
[Running in Containers](#running-in-containers)
[Running Using AWS Instance Profile Credentials](#running-using-aws-instance-profile-credentials)
[Running on EKS with IAM roles for service accounts](#running-on-eks-with-iam-roles-for-service-accounts)
[Running on EKS with EKS Pod Identities](#running-on-eks-with-eks-pod-identities)
[Troubleshooting](#troubleshooting)

## Configuration
Expand Down Expand Up @@ -470,6 +472,23 @@ spec:
path: /health
port: http
```
## Running on EKS with EKS Pod Identities

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).
- Installing the [Amazon EKS Pod Identity Agent](https://docs.aws.amazon.com/eks/latest/userguide/pod-id-agent-setup.html) on the cluster
- 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)
- [Configure your pods, Deployments, etc to use the Service Account](https://docs.aws.amazon.com/eks/latest/userguide/pod-configuration.html)
- As soon as the pods/deployments are updated, you will see the couple of Env Variables listed below in the pods.
- `AWS_CONTAINER_CREDENTIALS_FULL_URI` - Contains the Uri of the EKS Pod Identity Agent that will provide the credentials
- `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE` - Contains the token which will be used to create temporary credentials using the EKS Pod Identity Agent.

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:
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-s3-gateway
```

## Troubleshooting

Expand Down
73 changes: 71 additions & 2 deletions test/unit/awscredentials_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ async function testEc2CredentialRetrieval() {
delete process.env['AWS_ACCESS_KEY_ID'];
}
if ('AWS_CONTAINER_CREDENTIALS_RELATIVE_URI' in process.env) {
delete process.env['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'];
delete process.env['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'];
}
globalThis.ngx.fetch = function (url, options) {
if (url === 'http://169.254.169.254/latest/api/token' && options && options.method === 'PUT') {
Expand Down Expand Up @@ -300,13 +300,82 @@ async function testEc2CredentialRetrieval() {
await awscred.fetchCredentials(r);

if (!globalThis.credentialsIssued) {
throw 'Did not reach the point where EC2 credentials were issues.';
throw 'Did not reach the point where EC2 credentials were issued.';
}
}

async function testEKSPodIdentityCredentialRetrieval() {
printHeader('testEKSPodIdentityCredentialRetrieval');
if ('AWS_ACCESS_KEY_ID' in process.env) {
delete process.env['AWS_ACCESS_KEY_ID'];
}
if ('AWS_CONTAINER_CREDENTIALS_RELATIVE_URI' in process.env) {
delete process.env['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'];
}
if ('AWS_WEB_IDENTITY_TOKEN_FILE' in process.env) {
delete process.env['AWS_WEB_IDENTITY_TOKEN_FILE'];
}
var tempDir = (process.env['TMPDIR'] ? process.env['TMPDIR'] : '/tmp');
var uniqId = `${new Date().getTime()}-${Math.floor(Math.random()*101)}`;
var tempFile = `${tempDir}/credentials-unit-test-${uniqId}.json`;
var testToken = 'A_TOKEN';
fs.writeFileSync(tempFile, testToken);
process.env['AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE'] = tempFile;
globalThis.ngx.fetch = function(url, options) {
console.log(' fetching eks pod identity mock credentials');
if (url === 'http://169.254.170.23/v1/credentials') {
if (options && options.headers && options.headers['Authorization'].toString() === testToken) {
return Promise.resolve({
ok: true,
json: function() {
globalThis.credentialsIssued = true;
return Promise.resolve({
AccessKeyId: 'AN_ACCESS_KEY_ID',
Expiration: '2017-05-17T15:09:54Z',
AccountId: 'AN_ACCOUNT_ID',
SecretAccessKey: 'A_SECRET_ACCESS_KEY',
Token: 'A_SECURITY_TOKEN',
});
},
});
} else {
throw 'Invalid token passed: ' + options.headers['Authorization'];
}
} else {
throw 'Invalid request URL: ' + url;
}
};
var r = {
"headersOut": {
"Accept-Ranges": "bytes",
"Content-Length": 42,
"Content-Security-Policy": "block-all-mixed-content",
"Content-Type": "text/plain",
"X-Amz-Bucket-Region": "us-east-1",
"X-Amz-Request-Id": "166539E18A46500A",
"X-Xss-Protection": "1; mode=block"
},
log: function(msg) {
console.log(msg);
},
return: function(code) {
if (code !== 200) {
throw 'Expected 200 status code, got: ' + code;
}
},
};

await awscred.fetchCredentials(r);

if (!globalThis.credentialsIssued) {
throw 'Did not reach the point where EKS Pod Identity credentials were issued.';
}
}

async function test() {
await testEc2CredentialRetrieval();
await testEcsCredentialRetrieval();
await testEKSPodIdentityCredentialRetrieval();
testReadCredentialsWithAccessSecretKeyAndSessionTokenSet();
testReadCredentialsFromFilePath();
testReadCredentialsFromNonexistentPath();
Expand Down
Loading