IAM
Identity and Access Management (IAM) service enables cloud administrators to define, manage, and audit an AWS user’s level of access to AWS APIs and resources within an account.
Identity and Access Management (IAM) service enables cloud administrators to define, manage, and audit an AWS user’s level of access to AWS APIs and resources within an account.
IAM, which stands for Identity Access Management, is an AWS service used to manage API and web console access to cloud resources.
When a user attempts to access an AWS resource through the API or web console, an HTTP request is sent to the endpoint for the target AWS service. After verifying the request signature, it is evaluated against all relevant IAM access policies, which dictate the allowed actions for the caller. If there exists at least one rule granting access and no explicit deny rule in any policies or permission boundaries, then the request is granted. Otherwise, it is rejected.
IAM primarily handles all aspects of authentication and authorization when accessing cloud services.1 Authentication determines the validity of the identity of a user while authorization determines whether a given identity is permitted to perform a given action.
Identity is an entity that enables access to an AWS account’s cloud resources.1 IAM provides the following entities for managing identity information:
Once the identity of the caller has been established, the next step is to determine whether they are authorized to perform the requested action. To accomplish this, IAM evaluates the identity against all relevant policies, which specifies the allowed and restricted actions for a given user. These IAM policies can be directly assigned to users or groups or through Attribute Access Control (ABAC), which associates a policy with an identity based on the identity’s tags.2
In addition to providing services to define identity information and access, IAM also provides services to ensure best practices are enforced regarding access. Some of this tooling includes analyzing user’s access patterns to refine permissions, identifying unused permissions, and other auditing capabilities.
IAM handles authentication and authorization when accessing resources via the AWS APIs. It is not intended for the following use-cases:
An IAM identity refers to some entity that provides access to AWS resources. There are several types of identities provided by IAM for this purpose, including users, user groups, and roles. By default, newly created identities have no permissions. These entities must be explicitly assigned permissions to successfully perform API calls, which will be discussed in the following Access section.
IAM users represent a human user or programmatic workload and provides access to AWS resources or services. Users belong to a single parent account and consists of the following properties and credentials:
Command
aws iam get-user --user-name $USERNAME
Output
{
"User": {
"Path": "/",
"UserName": "username",
"UserId": "ADSAYFF72444NEI6MNIOP",
"Arn": "arn:aws:iam::012345678910:user/username",
"CreateDate": "2019-07-10T23:13:11+00:00",
"Tags": [
{
"Key": "TagName",
"Value": "TagValue"
}
]
}
}
Upon creating a new AWS account, a root user is automatically created, which has complete access to all cloud resources with limited exceptions. For this reason, it is recommended that you create an administrator user for everyday use and restrict access to root user credentials.
There are two possible login flows depending on the type of user attempting to sign in.1
To sign in as a root user, simply navigate to the sign-in page, select “Root User”, and enter the root user credentials and optional MFA code.
To sign in to the AWS console as a non-root user, you can either use standard sign-in webpage and provide the target AWS account number or specify the account alias in the URL as such: https://<account_id_or_alias>.signin.aws.amazon.com/console/.
To provision an IAM user, the only required property is the username, which is unique within the AWS account.
pulumi up -y
pulumi destroy -y
// iam/identities/users/create_user/ts/index.ts
import * as aws from "@pulumi/aws";
// Create IAM user with long-lived access credentials
const user = new aws.iam.User("techsquawks-user", {
name: "techsquawks-user"
});
// Export user information
export const userArn = user.arn;
export const userName = user.name;
pulumi up -y
pulumi destroy -y
// iam/identities/users/create_user/js/index.js
"use strict";
const aws = require("@pulumi/aws");
// Create IAM user with long-lived access credentials
const user = new aws.iam.User("techsquawks-user", {
name: "techsquawks-user"
});
// Export user information
exports.userArn = user.arn;
exports.userName = user.name;
pulumi up -y
pulumi destroy -y
# iam/identities/users/create_user/py/__main__.py
import pulumi
from pulumi_aws import iam
# Create IAM user with long-lived access credentials
user = iam.User("techsquawks-user", name="techsquawks-user")
# Export user information
pulumi.export('userArn', user.arn)
pulumi.export('userName', user.name)
pulumi up -y
pulumi destroy -y
// iam/identities/users/create_user/go/main.go
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/iam"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create IAM user with long-lived access credentials
user, err := iam.NewUser(ctx, "techsquawks-user", &iam.UserArgs{
Name: pulumi.String("techsquawks-user"),
})
if err != nil {
return err
}
// Export user information
ctx.Export("userArn", user.Arn)
ctx.Export("userName", user.Name)
return nil
})
}
Stack Outputs
Outputs:
userArn : "arn:aws:iam::012345678910:user/techsquawks-user"
userName: "techsquawks-user"
To further verify that the user was provisioned correctly, the identity can be fetched via the get-user
IAM API call.
Command
aws iam get-user --user-name techsquawks-user
Output
{
"User": {
"Path": "/",
"UserName": "techsquawks-user",
"UserId": "DBSAYFF32444NEI6MNIOP",
"Arn": "arn:aws:iam::012345678910:user/techsquawks-user",
"CreateDate": "2023-07-10T23:13:11+00:00",
}
}
Paths are analogous to namespaces and help in organizing IAM users both for reporting and associating permissions.
pulumi up -y
pulumi destroy -y
// iam/identities/users/paths/ts/index.ts
import * as aws from "@pulumi/aws";
// Create IAM user with long-lived access credentials
const user1 = new aws.iam.User("techsquawks-user-1", {
name: "techsquawks-user",
path: "/example/path/1/"
});
const user2 = new aws.iam.User("techsquawks-user-2", {
name: "techsquawks-user-2",
path: "/example/path/2/"
});
const user2a = new aws.iam.User("techsquawks-user-2a", {
name: "techsquawks-user-2a",
path: "/example/path/2/"
});
pulumi up -y
pulumi destroy -y
// iam/identities/users/paths/js/index.js
"use strict";
const aws = require("@pulumi/aws");
// Create IAM user with long-lived access credentials
const user1 = new aws.iam.User("techsquawks-user-1", {
name: "techsquawks-user1",
path: "/example/path/1/"
});
const user2 = new aws.iam.User("techsquawks-user-2", {
name: "techsquawks-user-2",
path: "/example/path/2/"
});
const user2a = new aws.iam.User("techsquawks-user-2a", {
name: "techsquawks-user-2a",
path: "/example/path/2/"
});
pulumi up -y
pulumi destroy -y
# iam/identities/users/paths/py/__main__.py
import pulumi
from pulumi_aws import iam
# Create IAM user with long-lived access credentials
user1 = iam.User("techsquawks-user-1", name="techsquawks-user1", path="/example/path/1/")
user2 = iam.User("techsquawks-user-2a", name="techsquawks-user2", path="/example/path/2/")
user2a = iam.User("techsquawks-user-2b", name="techsquawks-user2a", path="/example/path/2/")
pulumi up -y
pulumi destroy -y
// iam/identities/users/paths/go/main.go
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/iam"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create IAM user with long-lived access credentials
_, err := iam.NewUser(ctx, "techsquawks-user1", &iam.UserArgs{
Name: pulumi.String("techsquawks-user1"),
Path: pulumi.String("/example/path/1/"),
})
if err != nil {
return err
}
_, err = iam.NewUser(ctx, "techsquawks-user2", &iam.UserArgs{
Name: pulumi.String("techsquawks-user2"),
Path: pulumi.String("/example/path/2/"),
})
if err != nil {
return err
}
_, err = iam.NewUser(ctx, "techsquawks-user2a", &iam.UserArgs{
Name: pulumi.String("techsquawks-user2a"),
Path: pulumi.String("/example/path/2/"),
})
if err != nil {
return err
}
return nil
})
}
Users who belong to the same path can be targeted in certain IAM operations, such as listing users:
Command
aws iam list-users --path "/example/path/2/"
Output
{
"Users": [
{
"Path": "/example/path/2/",
"UserName": "techsquawks-user-2",
"UserId": "AFCAYGH7AQ44FXTSPCX55",
"Arn": "arn:aws:iam::012345678910:user/example/path/2/techsquawks-user-2",
"CreateDate": "2023-07-24T22:39:32+00:00"
},
{
"Path": "/example/path/2/",
"UserName": "techsquawks-user-2a",
"UserId": "BEAAYHI7AQ45GXTQPCX44",
"Arn": "arn:aws:iam::012345678910:user/example/path/2/techsquawks-user-2a",
"CreateDate": "2023-07-24T22:39:32+00:00"
}
]
}
To access cloud resources through the API, users may be associated with a set of long-lived access credential key pair, consisting of an access key ID and secret key for signing requests.
pulumi up -y
pulumi destroy -y
// iam/identities/users/access_credentials/ts/index.ts
import * as aws from "@pulumi/aws";
// Create IAM user with long-lived access credentials
const user = new aws.iam.User("techsquawks-user", {
name: "techsquawks-user"
});
const credentials = new aws.iam.AccessKey("techsquawks-user-credentials", {user: user.name});
// Export user information
export const userArn = user.arn;
export const userName = user.name;
export const userAccessKeyId = credentials.id;
export const userAccessSecret = credentials.secret;
pulumi up -y
pulumi destroy -y
// iam/identities/users/access_credentials/js/index.js
"use strict";
const aws = require("@pulumi/aws");
// Create IAM user with long-lived access credentials
const user = new aws.iam.User("techsquawks-user", {
name: "techsquawks-user"
});
const credentials = new aws.iam.AccessKey("techsquawks-user-credentials", {user: user.name});
// Export user information
exports.userArn = user.arn;
exports.userName = user.name;
exports.userAccessKeyId = credentials.id;
exports.userAccessSecret = credentials.secret;
pulumi up -y
pulumi destroy -y
# iam/identities/users/access_credentials/py/__main__.py
import pulumi
from pulumi_aws import iam
# Create IAM user with long-lived access credentials
user = iam.User("techsquawks-user", name="techsquawks-user")
credentials = iam.AccessKey("techsquawks-user-credentials", user=user.name)
# Export user information
pulumi.export('userArn', user.arn)
pulumi.export('userName', user.name)
pulumi.export('userAccessKeyId', credentials.id)
pulumi.export('userSecretKey', credentials.secret)
pulumi up -y
pulumi destroy -y
// iam/identities/users/access_credentials/go/main.go
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/iam"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create IAM user with long-lived access credentials
user, err := iam.NewUser(ctx, "techsquawks-user", &iam.UserArgs{
Name: pulumi.String("techsquawks-user"),
})
if err != nil {
return err
}
credentials, err := iam.NewAccessKey(ctx, "techsquawks-user-credentials", &iam.AccessKeyArgs{
User: user.Name,
})
if err != nil {
return err
}
// Export user information
ctx.Export("userArn", user.Arn)
ctx.Export("userName", user.Name)
ctx.Export("userAccessKeyId", credentials.ID())
ctx.Export("userSecretKey", credentials.Secret)
return nil
})
}
Stack Outputs
Outputs:
userAccessKeyId: "BKINYAJ78Q44D3CT4NER"
userArn : "arn:aws:iam::012345678910:user/techsquawks-user"
userName: "techsquawks-user"
userSecretKey: [secret]
Sensitive values are hidden by default in the Pulumi stack output. To get the secret key value, execute pulumi stack output userSecretKey --show-secrets --stack dev
To validate that the access credentials work as expected, invoke the caller identity API using the newly provisioned credentials:
Command
AWS_ACCESS_KEY_ID=$(pulumi stack output userAccessKeyId --stack dev)
AWS_SECRET_ACCESS_KEY=$(pulumi stack output userSecretKey --stack dev --show-secrets)
aws sts get-caller-identity
Output
{
"UserId": "BKXNYAA78Q56W3CT4NTJ",
"Account": "012345678910,
"Arn": "arn:aws:iam::012345678910:user/techsquawks-user"
}
While API access requires user access keys, password credentials are required for accessing the AWS account resources via the web console. IAM provides various API methods and resources for managing user passwords. For instance, login profiles define the password constraints for a given user.
pulumi up -y
pulumi destroy -y
// iam/identities/users/user_login_profile/ts/index.ts
import * as aws from "@pulumi/aws";
// Create IAM user with password/console credentials
const user = new aws.iam.User("techsquawks-user", {
name: "techsquawks-user",
forceDestroy: true
});
const loginProfile = new aws.iam.UserLoginProfile("techsquawks-user-login-profile", {
user: user.name,
passwordLength: 15,
passwordResetRequired: true
});
export const password = loginProfile.password;
pulumi up -y
pulumi destroy -y
// iam/identities/users/user_login_profile/js/index.js
"use strict";
const aws = require("@pulumi/aws");
// Create IAM user with password/console credentials
const user = new aws.iam.User("techsquawks-user", {
name: "techsquawks-user",
forceDestroy: true
});
const loginProfile = new aws.iam.UserLoginProfile("techsquawks-user-login-profile", {
user: user.name,
passwordLength: 15,
passwordResetRequired: true
});
exports.password = loginProfile.password;
pulumi up -y
pulumi destroy -y
# iam/identities/users/user_login_profile/py/__main__.py
import pulumi
from pulumi_aws import iam
# Create IAM user with long-lived access credentials
user = iam.User("techsquawks-user", name="techsquawks-user")
login_profile = iam.UserLoginProfile("techsquawks-user-login-profile",
user=user.name,
password_length=15,
password_reset_required=True
);
pulumi.export('password', login_profile.password)
pulumi up -y
pulumi destroy -y
// iam/identities/users/user_login_profile/go/main.go
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/iam"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create IAM user with password/console credentials
user, err := iam.NewUser(ctx, "techsquawks-user", &iam.UserArgs{
Name: pulumi.String("techsquawks-user"),
})
if err != nil {
return err
}
loginProfile, err := iam.NewUserLoginProfile(ctx, "loginProfile", &iam.UserLoginProfileArgs{
User: user.Name,
PasswordLength: pulumi.Int(15),
PasswordResetRequired: pulumi.Bool(true),
})
if err != nil {
return err
}
ctx.Export("password", loginProfile.Password)
return nil
})
}
IAM groups act as containers for multiple IAM users. Groups can have IAM policies associated with them, which are automatically inherited by child users when evaluating access. This allows administrators to more easily manage access for multiple users simultaneously. User may belong to several groups, but groups cannot contain other groups.
There is no default group for newly created users. Therefore, any groups must be explicitly created and poclicies must be explicitly assigned to the groups.1
Figure 1: Permissions may be assigned to groups which are automatically applied to assigned users.
Groups are rather simply entities, with the main component required being the group name. Similar to users, groups also may have a path specified which can be used to further distinguish groups while querying.
Command
aws iam list-groups
Output
{
"Groups": [
{
"Path": "/",
"GroupName": "Admins",
"GroupId": "AGPAYGH7AQ44DKI7UGME4",
"Arn": "arn:aws:iam::012345678910:group/Administrators",
"CreateDate": "2019-10-01T22:50:30+00:00"
},
{
"Path": "/",
"GroupName": "Developers",
"GroupId": "AGPAYGH7AQ44BVG3THNH7",
"Arn": "arn:aws:iam::012345678910:group/Developers",
"CreateDate": "2019-07-10T23:10:47+00:00"
},
{
"Path": "/",
"GroupName": "Devops",
"GroupId": "ADSAYFF72444NEI6MNIOP",
"Arn": "arn:aws:iam::012345678910:group/Devops",
"CreateDate": "2019-07-10T23:10:47+00:00"
}
]
}
Similar to users, roles are IAM identities intended to grant access to AWS resources. However, unlike users, roles lack any long-lived credentials such as console passwords and access key pairs. Rather, they are assumed by other authorized identities temporarily to obtain a certain level of access. 1 Once assumed, the user is given temporary short-lived credentials via the AWS STS service, which are associated with the role permissions.
In addition to authorizing other human users to perform certain actions, roles may also be associated with AWS services to perform certain API calls. These are known as service-linked roles. 2