aws-sdk-js-v3 icon indicating copy to clipboard operation
aws-sdk-js-v3 copied to clipboard

Provide utilities for accessing EC2 Instance Metadata Service

Open nwalters512 opened this issue 2 years ago • 21 comments

Describe the feature

I'd like an easy way to access information from the EC2 Instance Metadata Service, similar to the MetadataService that's available in the v2 SDK: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/MetadataService.html.

Use Case

I'd rather not re-implement the logic for picking the appropriate endpoint, refreshing the token, refetching the token, etc.

Proposed Solution

No response

Other Information

It looks like credential-provider-imds has already implemented a lot of the token fetching, error handling, etc. Maybe that can be refactored into a shared place?

SDKs for other languages already implement this feature, including Go (https://docs.aws.amazon.com/sdk-for-go/api/aws/ec2metadata/).

Acknowledgements

  • [X] I may be able to implement this feature request
  • [ ] This feature might incur a breaking change

SDK version used

3.178.0

Environment details (OS name and version, etc.)

macOS 12.5.1

nwalters512 avatar Sep 29 '22 23:09 nwalters512

@nwalters512 thanks for opening this issue, I see it is implemented in go-v2 https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws/ec2metadata as well, I'll discuss the feature with the team and update you soon.

ajredniwja avatar Oct 03 '22 05:10 ajredniwja

+1

I have aborted the migration to v3 because of this -- it does not make sense to migrate all my clients but still keep aws-sdk v2 around solely for MetaDataService :(

albertodiazdorado avatar Jan 13 '23 10:01 albertodiazdorado

Similar, I have also aborted upgrade to v3, this is an extremely critical piece of our infrastructure as we use it to determine which of our instances in the elastic beanstalk environment is the 'first' one as it does the cron jobs.

Please, @RanVaknin escalate this as a priority for the team as there is already signs this part of your SDK has been neglected. See https://github.com/aws/aws-sdk-js/issues/3584

jpike88 avatar Feb 20 '23 04:02 jpike88

@ajredniwja It's been almost 5 months, can you share an update from your team? I'd be happy to work on PR for this if it's something that y'all would accept.

nwalters512 avatar Feb 20 '23 23:02 nwalters512

@kuhe pulling in another amazon employee here. Considering that these irritating 4-line warnings are now spamming my dev team's console on every run, yet we are literally unable to migrate due to an incomplete v3 offering, this kind of unresponsiveness is very concerning and now reaching levels of being unacceptable.

jpike88 avatar Feb 21 '23 05:02 jpike88

Hi all,

I apologize for the long wait. I'll try to get someone to take a look, but since we have some higher priority issues they will probably take precedence.

I'll do my best. Ran~

RanVaknin avatar Feb 21 '23 23:02 RanVaknin

Hi @nwalters512 ,

I've discussed it with the team. Each SDK has implemented this in a different fashion. This likely will not be transformed into its own IMDS Client like in the Golang SDK. If there are customizations needed for the provider can you please elaborate the use cases?

Additionally, if you'd like to take a stab at cutting a PR, I'll have someone review it.

Thanks! Ran~

RanVaknin avatar Feb 22 '23 19:02 RanVaknin

@RanVaknin I'm not looking to customize @aws-sdk/credential-provider-imds per se - I just want some abstraction for "make an arbitrary request to the IMDS" just like what was available in the v2 SDK. As I mentioned in the original issue, I don't want to have to duplicate code for handling token fetching, expiration, refreshing, and so on. Most of that logic is already implemented in @aws-sdk/credential-provider-imds, just not in a way that I can reuse it for generalized IMDS requests.

If you want a specific use case: I want to be able to discover the instance ID of the host on which my code is running. But in general, I want to be able to fetch any of the categories of information listed here: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-categories.html.

If you're not willing to implement this as a separate client package (e.g. @aws-sdk/client-ec2-metadata), I'm not sure where I'd start with a PR. Are you thinking this would be a function exposed from @aws-sdk/client-ec2? IIRC most of the clients are code-genned, so I'd appreciate some guidance on how to work with that.

nwalters512 avatar Feb 23 '23 17:02 nwalters512

As I'm becoming unsure of whether this will be providing similar functionality to v2, below is my use case in TypeScript:

The code is basically saying, 'is this current instance the first instance in the array of instances in my elastic beanstalk environment? if so, I consider this to be the 'master' instance, and I want this to be my Cron job runner.'. It is incomplete in the first 10 lines as I just cut and paste some bits together to tell the story.

Notice the casting of MetadataService to the any type in order to be able to access the fetchMetadataToken property. It was reported here (https://github.com/aws/aws-sdk-js/issues/3584), and was never resolved, just auto-closed. It would be nice for this typing issue to be fixed as part of this issue as well.

import AWS from 'aws-sdk';

const ElasticBeanstalk = new AWS.ElasticBeanstalk(opts);
const EC2 = new AWS.EC2(opts);
const MetadataService = new AWS.MetadataService(opts);

const elasticbeanstalk = ElasticBeanstalk;
const ec2 = EC2;
const metadata = MetadataService;
let masterInstanceInitialized = false;

async function cronChecker() {
	const token: string = await promisify(
		(metadata as any).fetchMetadataToken.bind(metadata)
	)();

	const currentInstanceId = await promisify(metadata.request.bind(metadata))(
		'/latest/meta-data/instance-id',
		{
			headers: { 'x-aws-ec2-metadata-token': token },
		}
	);
	console.info('InstanceId', currentInstanceId);
	const params = {
		Filters: [
			{
				Name: 'resource-id',
				Values: [currentInstanceId],
			},
		],
	};
	const data = await promisify(ec2.describeTags.bind(ec2))(params);
	const envIdTag = data.Tags.find(
		(t) => t.Key === 'elasticbeanstalk:environment-id'
	);
	if (envIdTag === null) {
		throw new Error(
			'Failed to find the value of "elasticbeanstalk:environment-id" tag.'
		);
	}
	const ebData = await elasticbeanstalk
		.describeEnvironmentResources({ EnvironmentId: envIdTag.Value })
		.promise();
	const isMaster = !(
		currentInstanceId !== ebData.EnvironmentResources.Instances[0].Id
	);
	const health = await elasticbeanstalk
		.describeEnvironmentHealth({
			AttributeNames: ['HealthStatus'],
			EnvironmentId: envIdTag.Value,
		})
		.promise();
	console.warn('Environment health is ' + health.HealthStatus + '.');
	if (isMaster) {
		initCronJobs();
	} else {
		killJobs();
	}
}

jpike88 avatar Feb 24 '23 03:02 jpike88

I'm now receiving warnings about the impending deprecation of the v2 SDK (https://github.com/aws/aws-sdk-js/issues/4354):

(node:50693) NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023.

Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy

AWS.MetadataService isn't mentioned anywhere on https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md, nor is it handled by https://github.com/awslabs/aws-sdk-js-codemod. It feels very irresponsible to be so insistently pushing for a migration to a new package if that package is lacking necessary features.

nwalters512 avatar Mar 01 '23 18:03 nwalters512

I'm using the below js v3 sdk version

"@aws-sdk/client-secrets-manager": "^3.14.0", "@aws-sdk/client-sts": "^3.282.0", "@aws-sdk/credential-provider-imds": "^3.272.0",

Environment : EKS Pod

EC2 - worker

aws ec2 modify-instance-metadata-options --instance-id <instance_id> --http-tokens required --http-endpoint enabled --http-put-response-hop-limit 1

Pod is getting the role from service account the complete process of assuming role to service account is followed from below link https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html

This part of the code is assuming the pod role

        const command = new GetCallerIdentityCommand({});
        const data = await client1.send(command);
        console.log(":get_caller_identity:", data)

Response

:get_caller_identity: {
  '$metadata': {
    httpStatusCode: 200,
    requestId: 'requestId',
    extendedRequestId: undefined,
    cfId: undefined,
    attempts: 1,
    totalRetryDelay: 0
  },
  UserId: 'EXAMPLEID:aws-sdk-js-session-EXAMPLE_SESSION',
  Account: 'ACCOUNT_ID',
  Arn: 'arn:aws:sts::<ACCOUNT_ID>:assumed-role/<role_name_from_service_account>/aws-sdk-js-session-EXAMPLE_SESSION'
}
const client = new SecretsManagerClient({
          region: process.env.REGION,
        });
        response = await client.send(
          new GetSecretValueCommand({
            SecretId: process.env.APP_SECRET_NAME,
            VersionStage: "AWSCURRENT",
          })
        );

This above code returns the following response

 [ProviderError: Error response received from instance metadata service] {
  tryNextLink: true,
  statusCode: 401
}

Complete code snippet

const {
    SecretsManagerClient,
    GetSecretValueCommand,
  } = require("@aws-sdk/client-secrets-manager");
  
  const { STSClient, GetCallerIdentityCommand } = require("@aws-sdk/client-sts");
  
  
  const getSecret = async () => {
    return new Promise(async (resolve, reject) => {
  
      try {
        const client1 = new STSClient({ region: process.env.REGION});
        const command = new GetCallerIdentityCommand({});
        const data = await client1.send(command);
        console.log(":get_caller_identity:", data)
      } catch (error) {
        console.log("getting sts failed ")
        return reject(error);
      }
  
      let response;
  
      try {
        const client = new SecretsManagerClient({
          region: process.env.REGION,
        });
        response = await client.send(
          new GetSecretValueCommand({
            SecretId: process.env.APP_SECRET_NAME,
            VersionStage: "AWSCURRENT",
          })
        );
      } catch (error) {
        console.log("Secret fetching failed ", error)
        return reject(error);
      }
  
      const secret_string = response.SecretString;
  
      const secrets = JSON.parse(secret_string);
  
      
      resolve(secrets);
    })
  }
  
  module.exports = { getSecret };

psk200 avatar Mar 05 '23 16:03 psk200

@psk200 that looks unrelated to this issue, which is specifically about making requests directly to the metadata service. As far as I can tell, your comment is just about obtaining credentials from the metadata service, which indeed works correctly. If you're experiencing any problems with that, please open a separate issue.

nwalters512 avatar Mar 05 '23 17:03 nwalters512

It's now over 2 months since the last comment. Is there an ETA on this?

What are the minimal changes I am supposed to make to the existing code above (https://github.com/aws/aws-sdk-js-v3/issues/4004#issuecomment-1442768967)?

jpike88 avatar May 12 '23 06:05 jpike88

+1

I have aborted the migration to v3 because of this -- it does not make sense to migrate all my clients but still keep aws-sdk v2 around solely for MetaDataService :(

Same here

e920528 avatar May 18 '23 02:05 e920528

I ultimately built and published a small package to make talking to IMDS easier: @prairielearn/aws-imds. Source and minimal documentation are available here: https://github.com/PrairieLearn/PrairieLearn/tree/master/packages/aws-imds

nwalters512 avatar Jun 23 '23 16:06 nwalters512

Hi this is also blocking our migration to v3.

sameer-hashflow avatar Jul 21 '23 02:07 sameer-hashflow

@RanVaknin I elaborated on the use case as requested back in February, and it's now almost 6 months later, in Q2 of 2023, when v2 is supposed to be dropped in favor of v3. Zero follow up, zero alternative method or code examples provided, just putting things off over and over. This is placing my team in an increasingly risky scenario as year end approaches, and I think that's it's fair that some sort of conclusion is brought to this issue.

If not, please tell me the appropriate person to escalate this issue to.

jpike88 avatar Aug 07 '23 01:08 jpike88

Hi there. I'd like to add on that the lack of replacement for MetadataService in the v3 SDK is blocking our team from migrating as well. Any guidance on alternative methods to get an instance's Id, Tags, Role, etc. would be helpful.

sdenardi avatar Sep 13 '23 22:09 sdenardi

Hi @nwalters512, could I ask you to clarify what exactly the "easy way to access information from the EC2 Instance Metadata Service" might look like for you? Are you specifically requesting a client that can access things like, say instanceID? Would using a (possibly external) HTTP client in conjunction with the instancedata URL (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html) be satisfactory?

You mention credential-provider-imds has a lot of functionality that you might require, do you just want a straightforward exposure to a functionality specifically like fetching (like in the package you published) or is the requirement "make an arbitrary request to the IMDS" mean the request() method from v2 be ported to v3 as is?

siddsriv avatar Oct 10 '23 17:10 siddsriv

I don't want to prescribe any particular solution here. At a high level, I just don't want to have to worry about IMDS tokens: fetching, refreshing, TTLs, error handling, retries, etc. If that manifests as an almost-direct port of v2's request(), hopefully with Promise support, great! If you want to copy what Golang did and provide specific functions with well-defined return types like EC2Metadata.GetInstanceIdentityDocument, even better (though the JS SDK's stubborn insistence on typing everything as potentially undefined would make this less-useful than in Golang).

nwalters512 avatar Oct 10 '23 17:10 nwalters512

Gone are the days when AWS focused on reducing undifferentiated heavy lifting. Today, AWS creates problems for us developers that we didn't have in the past.

andreaswittig avatar Jan 03 '24 11:01 andreaswittig

@siddsriv has created https://www.npmjs.com/package/@aws-sdk/ec2-metadata-service.

import { MetadataService } from "@aws-sdk/ec2-metadata-service";

const metadataService = new MetadataService({
  ec2MetadataV1Disabled: true,
});
const token = await metadataService.fetchMetadataToken();
const metadata = await metadataService.request("/latest/meta-data/", {});

console.log({
  metadata,
});

kuhe avatar Mar 20 '24 17:03 kuhe

Hi @nwalters512 and everyone else on the thread!

Please check out the last comment where we created the package that provides utils to access EC2 Instance Metadata Service (IMDS).

Let us know if you have any questions!

aBurmeseDev avatar Mar 20 '24 21:03 aBurmeseDev

@aBurmeseDev @kuhe this is great, thanks for getting this done! Unfortunately the package isn't being built/published correctly; the file dist-types/MetadataService.d.ts tries to read from dist-types/MetadataServiceOptions, which does not exist. This causes a build error for tsc in projects with "skipLibCheck": false in their tsconfig.json:

../../node_modules/@aws-sdk/ec2-metadata-service/dist-types/MetadataService.d.ts:1:40 - error TS2307: Cannot find module './MetadataServiceOptions' or its corresponding type declarations.

1 import { MetadataServiceOptions } from "./MetadataServiceOptions";
                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 1 error in ../../node_modules/@aws-sdk/ec2-metadata-service/dist-types/MetadataService.d.ts:1

nwalters512 avatar Mar 20 '24 23:03 nwalters512

thank you for reporting this, we just put a fix out that will be released later today. let us know if you encounter any other issues or have questions.

siddsriv avatar Mar 21 '24 17:03 siddsriv

@aBurmeseDev @siddsriv Thank you for pushing this out!

Can you point me to the SDK docs for the package? All good if they're not out yet, didn't know if I wasn't looking in the right place.

sdenardi avatar Mar 22 '24 05:03 sdenardi

https://www.npmjs.com/package/@aws-sdk/ec2-metadata-service has basic instructions on how to make a request, which is most of what the package does.

You can then use the general documentation for IMDS https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html and take the URLs in those and input them to the JavaScript package for the same effect.

kuhe avatar Mar 22 '24 14:03 kuhe

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.

github-actions[bot] avatar Apr 10 '24 00:04 github-actions[bot]