AWS CLI
The real aws CLI reaches a served simulated environment over a local endpoint URL, and twenty of
Yulin’s twenty-five SDK-facing services answer it.
Serving on localhost is the reference for what each service serves. This page covers the way in from a shell.
An endpoint and a key to sign with
Section titled “An endpoint and a key to sign with”Serving binds a simulated environment to a port. A served request runs as whoever signed it, and the access key to sign the first one comes from simulated IAM in the process that built the environment:
/** * Serving a simulated environment for the aws CLI to reach. */
import { CreateAccessKeyCommand, CreateUserCommand, PutUserPolicyCommand,} from "@aws-sdk/client-iam";import { SimAws } from "@kensio/yulin";import { serveSimAws } from "@kensio/yulin/serve";
const simAws = new SimAws();const simIam = simAws.iam();
await simIam.createUser(new CreateUserCommand({ UserName: "Operator" }));await simIam.putUserPolicy( new PutUserPolicyCommand({ UserName: "Operator", PolicyName: "Everything", PolicyDocument: JSON.stringify({ Version: "2012-10-17", Statement: { Effect: "Allow", Action: "*", Resource: "*" }, }), }),);const created = await simIam.createAccessKey( new CreateAccessKeyCommand({ UserName: "Operator" }),);
const srv = await serveSimAws({ simAws, port: 8787 });
// Paste these into the shell the CLI runs in.console.log(`export AWS_ENDPOINT_URL=http://localhost:${srv.port}`);console.log(`export AWS_ACCESS_KEY_ID=${created.AccessKey.AccessKeyId}`);console.log( `export AWS_SECRET_ACCESS_KEY=${created.AccessKey.SecretAccessKey}`,);console.log(`export AWS_DEFAULT_REGION=${simAws.defaultRegionName}`);Pin the port when the URL has to stay the same between runs. Without one the server takes whatever is free.
Configuring the CLI
Section titled “Configuring the CLI”Four environment variables are the whole configuration:
export AWS_ENDPOINT_URL=http://localhost:8787export AWS_ACCESS_KEY_ID=AKIAVEXOWARWMKBOA0MPexport AWS_SECRET_ACCESS_KEY=RzIvKRp1sd5yXfEifA1twsUTd4GlHL5JpzvECpoxexport AWS_DEFAULT_REGION=us-east-1sts get-caller-identity is the call to check the wiring with. It reports the principal behind the
key that signed the request:
aws sts get-caller-identity{ "UserId": "AIDARNFLISEC7SCUNSDY", "Account": "888888888888", "Arn": "arn:aws:iam::888888888888:user/Operator"}The credentials have to come from simulated IAM. Any other key is refused with 403 Forbidden, and
an unsigned request reaches nothing.
A Region is required, as it is against real AWS. Changing it moves the CLI between simulated
Regions, and a Queue created under eu-west-2 is invisible to AWS_DEFAULT_REGION=us-east-1.
A named profile instead
Section titled “A named profile instead”A profile in the CLI’s own config file carries the same four values. The simulation then stays out of the ambient environment:
[profile sim]region = us-east-1endpoint_url = http://localhost:8787aws_access_key_id = AKIAVEXOWARWMKBOA0MPaws_secret_access_key = RzIvKRp1sd5yXfEifA1twsUTd4GlHL5JpzvECpoxaws --profile sim sts get-caller-identity then reaches the simulation while a bare aws still
goes to real AWS. The profile has to carry credentials of its own. Once --profile is given the CLI
stops reading AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY from the environment.
role_arn and source_profile work too. The CLI assumes the simulated Role for itself and signs
with the session it gets back:
[profile reader]region = us-east-1endpoint_url = http://localhost:8787role_arn = arn:aws:iam::888888888888:role/Readersource_profile = simBuilding the rest of the identities from the shell
Section titled “Building the rest of the identities from the shell”Only the first key has to come from the process that built the simulation. aws iam builds
everything after it:
aws iam create-user --user-name shopperaws iam put-user-policy --user-name shopper --policy-name read-buckets \ --policy-document '{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"s3:ListAllMyBuckets","Resource":"*"}}'aws iam create-access-key --user-name shoppercreate-access-key answers with the secret once. Signing with the new key reports the new User, and
simulated IAM authorizes every request against the policy it was given:
aws s3api create-bucket --bucket nope
aws: [ERROR]: An error occurred (AccessDenied) when calling the CreateBucket operation: User:arn:aws:iam::888888888888:user/shopper is not authorized to perform: s3:CreateBucket on resource:arn:aws:s3:::nopeaws sts assume-role answers with temporary credentials. Export the three values it returns and the
rest of the session runs as the Role:
aws sts assume-role --role-arn arn:aws:iam::888888888888:role/Reader --role-session-name probeexport AWS_ACCESS_KEY_ID=ASIAKBEUIHDMON9VBZXFexport AWS_SECRET_ACCESS_KEY=jm2N56vfVtLgJEo11OtbIXbgnJBgxpUMrszPrQdlexport AWS_SESSION_TOKEN=11568oBDksY9czECUMiAWk9tzmvG7zlQNjtLI1WmhZFS...The expiry is stamped from the simulation’s own clock, and advancing it past the expiry stops the session authenticating.
What answers
Section titled “What answers”Twenty simulated services answer the endpoint. S3, STS, IAM, ELBv2, SNS, CloudFormation and Lambda, along with the AWS JSON protocol services: DynamoDB, DynamoDB Streams, SQS, Cognito Identity Provider, EventBridge, ECS, SSM, ACM, CloudWatch, CloudWatch Logs, KMS, Secrets Manager and Rekognition.
A quick tour of the ones a shell reaches for most:
aws s3 cp ./index.html s3://widgets/index.htmlaws s3 sync ./site s3://widgets/site/aws dynamodb put-item --table-name widgets --item '{"id":{"S":"w1"}}'aws sqs create-queue --queue-name ordersaws ssm get-parameter --name /shop/url --query 'Parameter.Value' --output textaws secretsmanager get-secret-value --secret-id shop/db --query SecretString --output textaws logs describe-log-groups --query 'logGroups[].logGroupName' --output text--query, --output text and the rest of the CLI’s own machinery work throughout, because they run
client-side over an ordinary AWS response.
The serve docs list the operations each service
implements. Anything outside those lists is refused as NotImplemented.
CLI traps
Section titled “CLI traps”Each of these is CLI behaviour, and each catches people out against real AWS too. They are collected here because a simulated endpoint is often where someone meets them first.
--payload needs --cli-binary-format
Section titled “--payload needs --cli-binary-format”CLI v2 reads --payload as base64 by default:
aws lambda invoke --function-name orders --payload '{"id":1}' out.json
aws: [ERROR]: Invalid base64: "{"id":1}"Pass --cli-binary-format raw-in-base64-out and the same call runs the function:
aws lambda invoke --function-name orders --payload '{"id":1}' \ --cli-binary-format raw-in-base64-out out.jsoncat out.json--invocation-type Event answers 202 and runs the handler on the background scheduler. A script
reading what the function did waits on simAws.backgroundTasksComplete() first.
S3 addressing style
Section titled “S3 addressing style”The CLI needs nothing here. Its default auto style sends path-style requests to a custom endpoint,
which is what this endpoint routes. An SDK client is the one that needs forcePathStyle: true.
Forcing addressing_style = virtual in the config file breaks it. The Bucket moves into a hostname
the endpoint has no route for, and list-objects-v2 comes back empty while head-object comes back
404.
aws cloudformation deploy uses change sets
Section titled “aws cloudformation deploy uses change sets”deploy is a CLI-side wrapper over CreateChangeSet, and simulated CloudFormation implements four
operations that do not include it:
aws cloudformation deploy --stack-name site --template-file template.json
aws: [ERROR]: An error occurred (NotImplemented) when calling the CreateChangeSet operation:Simulated CloudFormation does not serve CreateChangeSetcreate-stack and describe-stacks do work. A deployment starts in the background and
create-stack is answered before the Resources exist, as real CloudFormation answers it.
Presigning needs the S3 service hostname
Section titled “Presigning needs the S3 service hostname”aws s3 presign signs whatever endpoint the CLI is configured with, and a URL built over the
general endpoint has no Bucket in it for the endpoint to route on. Point the one command at
simulated S3’s own hostname on the served port:
aws --endpoint-url http://s3.us-east-1.sim-aws.localhost:8787 s3 presign s3://widgets/one.txtThe URL that comes back is fetchable by anything, including curl and a browser. The same hostname
serves presigned URLs built by the SDK.
A bad key looks like an XML parse failure
Section titled “A bad key looks like an XML parse failure”STS, IAM and ELBv2 speak the AWS Query protocol and expect an XML body. The endpoint answers a rejected signature as JSON. The CLI reports the body it could not parse:
aws sts get-caller-identity
aws: [ERROR]: Unable to parse response (not well-formed (invalid token): line 1, column 0), invalidXML received. Further retries may succeed:b'{"Message":"Forbidden"}'Forbidden in the quoted body is the real answer. The other seventeen services report the same
rejection as a plain 403.
Limitations
Section titled “Limitations”-
Five services are refused with
501 Not Implemented. Route 53 and CloudFront speak REST-XML, and API Gateway v2, SES v2 and EventBridge Scheduler speak REST-JSON. Every one of them is reachable in process throughSimAwsand through SDK interception. Simulated ECR is refused the same way and has no AWS API surface at all, since its images are registered in process. -
An operation a served service has not implemented is refused as
NotImplemented. That is a separate answer from the protocol refusal above.aws iam list-usersreportsSimulated IAM does not serve ListUsers, andaws lambda list-functionsnames the path it arrived at. -
aws cloudwatch get-metric-statisticsandaws cloudwatch get-metric-datafail withTypeError: time.getTime is not a function. The JSON protocol carries a timestamp as epoch seconds and the endpoint hands that number to the simulation where aDateis expected. Both reads work in process and through SDK interception.put-metric-dataandlist-metricsare unaffected. -
aws s3 cpandaws s3 synccorrupt a download above the CLI’s 8MB threshold (#717). The CLI splits the download into ranged GETs, simulated S3 ignoresRangeand returns the whole Object for each one, and the parts land on top of each other. A 12MB Object arrives as a 20MB file. Uploads above the threshold are fine, and so is any download under it. Two ways round it, both verified.aws s3api get-objectissues one unranged GET. Raising the threshold in the config file keepsaws s3 cpon a single GET too.[profile sim]s3 =multipart_threshold = 5GB
Software Engineering by Kensio Software
This page as plain text: llms.txt
Documenting Yulin v1.20.2
