Guide to using AWS Tokens effectively

Introduction

Current issues

In previous articles, I have provided instructions on using AWS access tokens and secret tokens to utilize Amazon services, but there is an issue where these tokens do not have an expiration time. Thus, if your tokens are somehow leaked, an attacker can use them for as long as they want until you can detect and delete these tokens. This is a user-side issue, but there are still ways to limit the impact of this by granting temporary tokens during use. Naturally, these temporary tokens will have a short lifespan (about a few hours, or you can change the duration to suit your security level). Therefore, even if this token is leaked, an attacker only has a limited amount of time to use it before the token expires.

If you are a member created by IAM Identity Center, you are already supported with permission management and integrated security measures during use. However, if you are using a personal account or have the rights to create an IAM User, there are still ways to configure your own account to require active MFA before the tokens can be used.

Detail

First, use AWS CDK to create lib/user-stack.ts

import * as cdk from "aws-cdk-lib"
import { Construct } from "constructs"
import * as iam from "aws-cdk-lib/aws-iam"

export class UserWithMfaStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const userName = "s3-mfa-restricted-user"
    const groupName = "S3FullAccessWithMfaGroup"
    const s3MfaGroup = new iam.Group(this, "S3MfaGroup", { groupName })
    s3MfaGroup.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonS3FullAccess"),
    )

    const forceMfaPolicy = new iam.PolicyStatement({
      sid: "BlockMostAccessUnlessSignedInWithMFA",
      effect: iam.Effect.DENY,
      notActions: [
        "iam:CreateVirtualMFADevice",
        "iam:EnableMFADevice",
        "iam:ListMFADevices",
        "iam:ListUsers",
        "iam:ListVirtualMFADevices",
        "iam:ResyncMFADevice",
        "iam:GetAccountPasswordPolicy",
        "iam:ListAccountAliases",
      ],
      resources: ["*"],
      conditions: {
        BoolIfExists: {
          "aws:MultiFactorAuthPresent": "false",
        },
      },
    })
    s3MfaGroup.addToPolicy(forceMfaPolicy)

    const user = new iam.User(this, "S3User", {userName})
    user.addToGroup(s3MfaGroup)
    const accessKey = new iam.AccessKey(this, "S3UserAccessKey", {user})

    new cdk.CfnOutput(this, "AccessKeyId", {
      value: accessKey.accessKeyId,
    })
    new cdk.CfnOutput(this, "SecretAccessKey", {
      value: accessKey.secretAccessKey.unsafeUnwrap(),
    })
    new cdk.CfnOutput(this, "GroupName", {
      value: s3MfaGroup.groupName,
    })
  }
}

  • userName: you can change to a different username
  • The information created includes
    • Policy group: has full permissions for AWS S3 (AmazonS3FullAccess)
    • Policy statement: Deny every action if there is no MFA session
    • User: create AccessKeyId and SecretAccessKey (this information is only shown once, so please save it for use)
  • Then add the Policy Group to the Policy statement, and add the User to the Policy Group


Update file bin/aws-cdk.ts

#!/usr/bin/env node
import * as cdk from "aws-cdk-lib/core"
import { UserWithMfaStack } from "../lib/user-stack"

const app = new cdk.App()
new UserWithMfaStack(app, "UserWithMfaStack");


After deployment, the results are as follows:

 UserWithMfaStack
 Deployment time: 183.71s

Outputs:
UserWithMfaStack.AccessKeyId = {AccessKeyId}
UserWithMfaStack.SecretAccessKey = {SecretAccessKey}
UserWithMfaStack.GroupName = S3FullAccessWithMfaGroup

 Total time: 196.16s


The User has been created in IAM User


Then click Assign MFA device to add Multi-factor authentication



Once MFA is successfully created, get the Identifier information to add to the ~/.aws/config file as follows (please change the values accordingly):

[default]
region = ap-southeast-1
duration_seconds = 43200
output = json
mfa_serial = <Identifier>


Next, use this command to configure the token:

aws configure


After success, the ~/.aws/credentials file will have the following content:

[default]
aws_access_key_id = {AccessKeyId}
aws_secret_access_key = {SecretAccessKey}


You can check the user currently in use like this

aws sts get-caller-identity
{
    "UserId": "{UserId}",
    "Account": "{Account}",
    "Arn": "arn:aws:iam::{Account}:user/s3-mfa-restricted-user"
}


Next, install awsume, which is a popular tool for AWS Devs to automatically retrieve temporary tokens and set them into the environment for use

pip install awsume


At this point, the preparation steps are complete and you can begin use. You might wonder why we have the aws_access_key_id and aws_secret_access_key but do not use them directly and instead must add many other steps; this is because, in the AWS CDK config above, we added a policy that only works when the session has MFA. Therefore, if you use the aws_access_key_id and aws_secret_access_key directly, they will not work (this is the security mechanism I mentioned at the beginning of the article; even if these tokens are leaked, there is no impact)


To verify, trying to use an AWS service will report the following error:

$ aws s3 ls
An error occurred (AccessDenied) when calling the ListBuckets operation: User: arn:aws:iam::{Account}:user/s3-mfa-restricted-user is not authorized to perform: s3:ListAllMyBuckets with an explicit deny in an identity-based policy


Next, you must use awsume to retrieve a temporary token to be able to use it

awsume default
Enter MFA token: 277940
Session token will expire at 2025-02-19 07:14:54


After successfully getting the session, the token information will be stored here ~/.awsume/cache/aws-credentials

{
  "AccessKeyId": "{AccessKeyId}",
  "SecretAccessKey": "{SecretAccessKey}",
  "SessionToken": "{SessionToken}",
  "Expiration": "at 2025-02-19 07:14:54",
  "Region": "ap-southeast-1"
}


Then you can use it as normal

$ aws s3 ls
2025-01-10 12:06:06 cdk-hnb659fds-assets


To exit the current session, use the unset command as follows:

awsume -u


Happy coding!

See more articles here.

Comments

Popular posts from this blog

All practice series

Deploying a NodeJS Server on Google Kubernetes Engine

Setting up Kubernetes Dashboard with Kind

Using Kafka with Docker and NodeJS

Monitoring with cAdvisor, Prometheus and Grafana on Docker

Kubernetes Deployment for Zero Downtime

Kubernetes Practice Series

Practicing with Google Cloud Platform - Google Kubernetes Engine to deploy nginx

NodeJS Practice Series

Helm for beginer - Deploy nginx to Google Kubernetes Engine