Chapter 2. Authorization and Authentication Basics

In Chapter 1, I talked about how security professionals may employ certain measures called controls (also known as countermeasures) to reduce the aggregate risk of any application. Some controls are better than others at applying nuanced security logic, to block only the unauthorized requests without affecting the experience of authorized users. Controls that are effective at identifying unauthorized requests and denying them access are said to be precise or sharp. The objective of this chapter is to examine two of the most precise security controls that an organization can employ on AWS–authorization and authentication. The level of granularity at which these controls work allows them to be sharpened enough to incorporate organizational nuance, resulting in well-tuned precision. This sharpening makes these controls your strongest and most targeted defense against potential threats. Authentication and authorization controls attempt to identify the originator of each request to determine if it should be granted access.

Note

The concepts from this chapter are applicable to all applications running on AWS, not just microservices. Identity and access controls are the fundamental building blocks of every secure system, not just microservices. However, throughout the chapter, I will point at places where these controls benefit from the modularity that is provided by microservices.

To begin introducing these controls, let me define some of the terms used in access policies. Each interaction or request can be thought of as initiated by some calling entity, called a principal. A principal can be a user or a service within or external to the organization. On the receiving end of this request may be a data store or another service that the principal wishes to interact with, called a resource. Thus, principals perform various actions on cloud resources. I will now use these terms to define authorization and authentication in more detail:

Authorization (access control)
Authorization is the process through which access to particular resources is defined. In determining whether access may be granted to a request, factors such as the principal’s identity, the target resource, the circumstances under which the request is made, and the location of the request may be considered. In most cases, prior to evaluating requests, access control systems require that the principal’s identity be known to them.
Authentication (identity management)
Authentication is the mechanism by which principals are able to identify themselves and prove their identities to the target system, thus distinguishing themselves from any impostors. Cloud systems can map an end user’s identity to a predefined set of known identities after a user has been authenticated. This enables cloud systems to control access to all incoming requests from this user through the authorization process.

Authentication and authorization work together to provide you with granular controls that guard against potential threats. The authentication process ensures that legitimate principals can identify themselves to the access control (authorization) system. Upon identification, the access control system can determine if the request is authorized or needs to be blocked.

As one might imagine, in a microservice architecture there are many principals and many resources. Thus, there are many ways a malicious actor could exploit vulnerabilities to compromise the system. As a result, the access policy, and overall, the general security policy, of a microservice architecture may get quite complex. This is where identity and access management (IAM) comes into play. The IAM system within an organization is used to specify rules as prescribed by the organization’s access policy. Each request is evaluated against these rules to identify if the sender of the request is permitted access. Each IAM access policy evaluation boils down to deciding whether to allow or deny a request.

Tip

The way I think about this is, using IAM policies, you write business logic for AWS. AWS refers to this business logic to enforce access control and thus enhance the security posture of your organization. This way, you can help AWS in sharpening the controls you have on your assets.

Basics of AWS Identity and Access Management

While IAM does not apply specifically to microservices, it lays the foundation of any architecture that you may implement on AWS. As opposed to monoliths, where most communication happens in memory, microservices utilize external communication channels. AWS IAM can intercept each of these requests and evaluate them against the access policy of the organization. Hence, the role played by AWS IAM as an arbitrator of access control and authentication is crucial in securing a microservice architecture.

IAM was designed to be a global and fully managed service. Instead of scattering access control policies and authentication logic across the cloud, IAM allows cloud architects to centralize access policies in one place. AWS was also able to implement some of the best practices in security engineering by taking on the responsibility of protecting the access control mechanism. AWS IAM can be assumed to be highly available, highly scalable, and completely managed by AWS as part of the SRM.

Figure 2-1 illustrates a typical microservice application for an e-commerce company with AWS IAM providing centralized authorization services for each of their requests. AWS IAM is programmed to allow legitimate communication requests. As seen in the figure, requests 1 and 2 are allowed to proceed, while request 3 is denied since it is not allowed by IAM policy.

IAM also provides authentication services. So, when an imposter—“Hacked Microservice”—tries to communicate with the purchase service (request 4), IAM denies the request.

Figure 2-1. AWS IAM acts as a centralized service that provides authentication and authorization services for all of your microservice architecture.

As seen in Figure 2-1, there is no reason why the “Time display microservice” should ever be allowed to make a purchase. Microservices can run securely on the cloud when such rules are configured in AWS IAM.

To sum up, it is the responsibility of the security professionals to come up with the security policy (more specifically, the access policy) that dictates which users and microservices are allowed to interact with each other within the application. This policy needs to be designed in such a way that potential threat actors are not able to exploit vulnerabilities. Once created inside AWS IAM, AWS then assumes the responsibility of enforcing this access policy as part of the SRM.

Principals on AWS

Before I go into the details of securing cloud resources through access control, let me briefly explain the major types of principals that exist on AWS.

IAM users
These are your day-to-day users employees, contractors, guests) who log in to your account with their individual credentials and interact with your cloud infrastructure.
Root users
These are special types of accounts that exist on AWS. These are account owners who have full control and superuser privilege over an AWS account. Given the near universal permissions that these users get on your AWS accounts, these accounts should be secured and never used for day-to-day activities. Ideally, root user accounts should only be used to create other IAM users where permissions are more granularly defined.
IAM groups
An IAM group is a collection of users that can be attached to a common permission policy, making it easier to manage organizational growth.
IAM roles
IAM roles are very similar to IAM users. An IAM role maps to a set of tasks or job functions that each identity is expected to perform. IAM roles can be thought of as IAM users without access credentials. (I cover roles in detail later in this chapter.)

IAM Policies

Access control on AWS is governed by IAM policies, which are JSON documents that codify permissions regarding access control for each principal in an account. AWS consults these policies when determining whether to grant access to requests.

An access control policy involves two types of actors: principals and resources. A principal is an actor that requests access. This actor could be an employee or, at times, another cloud service. A resource represents the service being accessed. This resource could be a database, an S3 bucket, or any other cloud service that is being provided by AWS.

Figure 2-2 illustrates the difference between identity-based policies and resource-based policies:

Figure 2-2. Identity-based and resource-based policies can be used to control access.

There are two ways to implement an access control policy in AWS IAM:

  • Access can be limited by limiting what each principal can do within the cloud system. These policies are called principal-based policies (or identity-based policies).

  • A resource’s actions can be restricted based on the principal that is requesting to perform them. These policies are called resource-based policies.

IAM policies can be found in the Policies tab inside the Identity and Access Management tab in your AWS Management Console. IAM policies exist independent of the principal or the resource that they are applied to and can be applied to multiple principals to enable reuse.

Principle of Least Privilege (PoLP)

Now that I have introduced you to the tool that will aid you in defining controls to protect your microservices, I would like to introduce you to the process you can follow to build these controls. This process is called need to know. In complex environments, the security architect creates a list of all the principals in the organization and determines what resources each principal needs access to. The list is then used to determine which policies will be applied to each principal or resource.

“Need to know” is the first step to applying what is known as the principle of least privilege, or PoLP. The PoLP was originally formulated back in 1958 by the Association for Computing Machinery as the following: Every program and every privileged user of the system should operate using the least amount of privilege necessary to complete the job.

A great way of visualizing the PoLP can be to follow the four rules highlighted in Figure 2-3 as demonstrated by AWS.

Figure 2-3. A great quote for remembering the PoLP

PoLP helps ensure that vulnerabilities in one area of the system can’t affect other areas. Policies should be assigned using PoLP, which means a principal should be assigned the most restrictive policy possible while still allowing them to do their job.

PoLP and Blast Radius

In Chapter 1, I discussed the concept of blast radius and how modular applications are advantageous because they minimize the impact of security incidents by locking attackers into specific modules. It is through the use of PoLP and access control that this advantage can be achieved.

Figure 2-4 compares two applications for an e-commerce company. One is built using a monolithic architecture while the other using a microservice-based architecture. The application connects to three different databases: Marketing DB, Finance DB, and User Profile DB for different business needs.

If PoLP is applied, in case of a monolith, the application’s runtime identity (the user or role that the application runs as) will require access to all three of these databases. In case of microservices, the application’s identity will have access to the specific database that it needs to connect to.

Figure 2-4. A comparison of access control policies between a monolith and a microservice-based application. A microservice provides better incident isolation than a monolith due to its modular structure and the ability to grant more granular access to applications.

As you can see in Figure 2-4, in case of a monolith, the application runtime identity ends up requiring access to all three of the databases in order to perform its job properly. Hence, the access policy for this identity is wide. Now compare that to a microservice-based application. Each microservice has to connect to one specific database. Therefore, the PoLP dictates that each identity has access only to the specific database it needs in order to perform its job. Now if a malicious actor were to compromise any of these identities, the impact is different.

An attacker who compromised the monolith’s identity could access all the databases. However, for microservices, malicious actors would only gain access to the marketing database, as shown in Figure 2-4. Technically speaking, in case of an incident, the blast radius in a microservice application is smaller than that of a monolith. This is, however, only true as long as PoLP is applied while designing the permissions for all identities and resources.

Structure of AWS IAM Policies

Now that I have conceptually defined access control, it is time to look at how it is configured on AWS IAM. As a reminder, the purpose of every statement within an access control policy is to state which principal is allowed (or denied) access to which resource under which conditions and for which specific action. On AWS, each policy can make one or more such statements.

PARC-E is a handy acronym to remember the components of an access policy statement:

Principal
The principal that the statement is prescribing permissions for. This may be explicitly mentioned in the policy or implicitly assumed if the policy is attached to a principal.
Action
The action that the statement applies to. Whether it is the ability to read the data or write to a table or any other action that the resource is able to perform.
Resource
The resource or set of resources that the statement is applicable for.
Condition (optional)
The conditions under which this statement should even be evaluated. Or the conditions under which the effect prescribed in the policy should be applied to the action.
Effect
Whether the statement should allow or deny access.
Note

All principals and resources within AWS are clearly identified by a unique ID assigned to them by AWS called the Amazon Resource Name (ARN).

All policies are written in a JSON format. Here is an example of a customer-managed policy:

{
    "Version": "2012-10-17",
    "Id": "key-default-1",
    "Statement": [
        {
 "Principal": {
                "AWS": "arn:aws:iam::244255116257:root"
            },
            "Action": "kms:*",
            "Resource": "*"
            "Effect": "Allow",
            "Sid": "Enable IAM User Permissions",
        }
    ]
}

The statement within this policy says, the principal with ARN (arn:aws:iam::244255116257:root) is allowed to perform all the activities related to AWS KMS (kms:*) on all of the resources that this policy is applied to.

Note

I demonstrate how the policy statements can be used to apply PoLP in large organizations in Appendix D.

Principal-Based Policies

Principal-based policies are applied to a principal within an account, determining the level of access the principal in question has. A principal can be either a user, a group, or a role that lives within the account. The resource being accessed can be within the same account or a different account.

Tip

If you have the answer to the question, What resources is this user/role/group allowed to access?, principal-based policies are best suited for your needs.

IAM follows a default deny rule. By default, whenever a user or a role is added to an account, they will have no access to any resources within that account until an IAM policy is created by an administrator or a root user of the particular account to allow access.

Policies that are principal-based apply to the principal in question; you don’t need to specify the principal explicitly in a policy statement (and AWS won’t allow you to). The principal of the policy is always the entity to which the policy is attached.

Note

When the policy is attached to an IAM group, the principal is the IAM user in that group who is making the request.

Resource-Based Policies

As the name suggests, resource-based policies are applied to specific supported resources. AWS maintains a list of resources that support resource-based policies. These policies are inline only. They are not bound by AWS accounts, so they may include principals from any AWS account, not necessarily the one the resource belongs to.

Tip

If you have the answer to the question, Who (which principal) is allowed to access this resource?, resource-based policies are best suited for your needs.

Here is a sample resource-based policy. This policy allows a specific user (arn:aws:iam::AWS-account-ID:user/user-name) to invoke the Lambda function (my-function):

{
    "Version": "2012-10-17",
    "Id": "default",
    "Statement": [
        {
            "Sid": "sample-resource-based-policy",
            "Effect": "Allow",
       "Principal": { "AWS": "arn:aws:iam::AWS-account-ID:user/user-name" }
,
            "Action": "lambda:InvokeFunction",
            "Resource": "arn:aws:lambda:us-east-2:<account>:function:my-function”
        }
     ]
}

The Zone of Trust

In a single account setup, resource-based policies may provide an additional layer of protection on top of principal-based policies.

Principals from one account may be allowed to call resources in another. Consider a scenario where Principal B in Account B would like to access Resource B in Account B (resource and principal being in the same account). Under a single-account configuration, an IAM policy that permits access to Resource B will provide the right access to Principal B.

Essentially, Principal B can be granted access to any resource in its own account by simply attaching an identity-based IAM policy to it that grants this access without requiring a resource-based policy.

Let’s consider a slightly different situation. Resource B is still in Account B, but Principal A is in Account A (resource and principal being in different accounts). Principal A has an identity-based policy that allows it to access Resource B. However, you will realize, simply adding an identity-based policy is not enough for cross-account access. A resource-based policy is required in Account B on Resource B, in addition to any identity-based policy to enable this access. Figure 2-3 illustrates this evaluation logic.

In other words, even if Principal A is granted access to Resource B within Account A, AWS will not allow Principal A to access Resource B unless there is a resource-based policy within Account B that allows this access.

Figure 2-5. For Principal B to access Resource B, only IAM policy (1) is required. For Principal A to access Resource B, though, both resource-based policy (2) and IAM policy (3) are required.

Any internal request for a service from within an account is said to have come from within the zone of trust for that service. That’s not the case for external access. Resources have to explicitly mention the principals that they trust outside their zones of trust (in this case, their accounts). A zone of trust dictates whether a resource-based policy is required to access a particular resource.

Figure 2-4 shows a Venn diagram that sums up how the concept of a zone of trust fits in with the policy evaluation algorithm. Consider a scenario where Principal A, defined in an AWS Account A, requests access to Resource B.

Figure 2-6. If the request happens to lie in any of regions 1, 2, or 3, access to the resource will be granted. For all other requests, access will be denied.

In evaluating whether access should be allowed, we can ask three questions:

  • Is Principal A in the same account as Resource A within the zone of trust)?

  • Does the IAM policy in Account A allow Principal A to access Resource B?

  • Is there a resource policy on Resource B that allows Principal A to gain access?

The request will be granted only if it falls into one of the three scenarios labeled in Figure 2-6.

Warning

In AWS, principals from one account are allowed to assume roles in other accounts and make requests to resources in the latter account. In such cases, the policy evaluation logic will assume that the request is originating from the target account’s role, and thus assume that it comes from within the zone of trust. There is no requirement for such a request to be accompanied by a resource-based policy.

Evaluation of Policies

For any given request, there may be multiple policy statements that need to be evaluated in conjunction with each other. After evaluating a request, AWS must decide whether to allow it to proceed or deny it access to the resources or services to which the original request specified access.

In making its decision, AWS follows a standard evaluation algorithm:

  • Start with the intent of denying the request by default (implicit deny).

  • Go through all the policies and statements that are applicable. If there are conditions, only retain statements where conditions are satisfied.

  • If there is a statement in a policy which explicitly denies this request, deny the request. Explicit “denies” always override the “allows.”

  • As long as there is a policy allowing the request, the request should go on to the next step. In the absence of any identity-based policies allowing this request, deny the request and terminate the evaluation.

  • If the principal is from the same account as the target resource, you can assume that the request is from within the zone of trust. Resource-based policies are not needed in such a scenario. Let such a request access the target resource.

  • If the request comes from a principal from a different account, a resource-based policy is needed within the resource’s account allowing this request. Check to see whether such a resource-based policy exists.

Figure 2-5 illustrates this algorithm in a flowchart.

Figure 2-7. Each request is evaluated against a workflow to determine if access should be granted or denied to the underlying service or resource.

Advanced Concepts in AWS IAM Policies

Microservices generally have a large number of principals and resources. Because of this, security professionals often realize that they can benefit from using some of the advanced tools AWS provides in order to better design their security policies. In this section, I will highlight some of the commonly used policy tools that can help in writing policies for large organizations.

IAM Policy Conditions

Policies and statements within a policy can be evaluated on a conditional basis, adding more flexibility to the IAM process. AWS gathers context on each request it receives. This context includes information such as the following:

  • The action that the request wants to perform on AWS

  • The name and ARN of the resource that the request wants to interact with

  • The principal or the identity performing this request

  • Environment data that may be part of the request—This may include a variety of factors, such as the IP address of the requestor, the user-agent, whether the request comes using a secure transport, whether the principal is another AWS service, and so on.

  • More information associated with the target resource, such as any tags on a resource or any tables in a target database

A system administrator could then use all this information to formulate conditional logic to enforce one or more IAM policies. The conditional evaluation process relies on the ability to identify the policy context in which the policy applies and comparing this context with the actual request context when deciding eligibility.

For example, access may be granted to a resource only if the source of the request is from an IP belonging to your corporate office. This policy can be written like this:

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Action": "*",
        "Resource": "*",
        "Condition": {
            "NotIpAddress": {
                "aws:SourceIp": [
                    "1.2.3.4/32"
                ]
            }
        }
    }
}

This policy will deny any request to all resources within the account. However, since this policy is only applicable if the IP address is not 1.2.3.4/32, it will restrict external access to resources without interfering with any other AWS policies related to requests originating from the IP address 1.2.3.4/32.

Here is another example where conditions can be used. To ensure that a particular resource can only be accessed using an HTTPS endpoint (secure endpoint), you can use this condition:

  "Condition": {"Bool": {"aws:SecureTransport": "true"}}

This condition will evaluate to true if the incoming request to access a resource comes by way of a secure transport such as HTTPS. AWS maintains a list of all conditions that it supports within AWS policies here.

AWS Tags and Attribute-Based Access Control

When physical data centers were the primary mode of deploying applications, connecting wires and boxes used to be color coded in order to identify their purpose and the level of sensitivity of the data that was present in these boxes. Access was granted to employees based on the levels and the color codes. Each server had stickers on it that allowed them to record basic metadata (such as the sensitivity of the data that the server dealt with). This way, teams could identify their resources. This process of marking resources based on teams is called server tagging.

You can assign metadata to your cloud resources using tags on AWS. Tags are simple labels composed of a key and an optional value that can be used to manage, search for, and filter resources. You can categorize resources based on purposes, owners, environments, bounded contexts, subdomains, or other criteria. In the case of microservices, with the increased fragmentation of services, tags can prove to be a great resource managing all of your cloud resources and more importantly, controlling access to them.

Assigning attributes (tags) to various resources allows your administrators to specify the sensitivity level of the data and the minimum clearance level required for accessing the resource. You can also specify the tag that a principal should have in order to gain access to this resource. So, you may have conditional policies such as “only a manager can access resources that have a level-manager set on them.” Or “only a person from Team X can access a resource that has a tag team set to team x.” Such a system is called attribute-based access control (ABAC).

IAM policies have access to the tags in the request’s context and thus can be used to compare the tag present on the resource to determine whether the request should be allowed:

   "Condition": {
                "StringEquals": {"aws:ResourceTag/project": "${aws:PrincipalTag/project}"}
            }

As you can imagine, microservices are great environments for using ABAC.

“Not” Policy Elements: NotPrincipal and NotResource

Two commonly used exclusion policy statements are NotPrincipal and NotResource. Since both statements are similar in structure and have a somewhat similar effect, I have bundled them together.

They are both used to specify a list of items that the specified policy does not apply to. Let’s say you want to create a policy that denies access to S3 buckets to everyone except for a few users in the organization. You can use NotPrincipal to specify a deny policy that applies to everyone in the account except the users specified in the policy:

{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Deny",
        "NotPrincipal": {"AWS": [
            "arn:aws:iam::<accountid>:user/<username>",
        ]},
        "Action": "s3:*",
        "Resource": [
            "arn:aws:s3:::<BucketName|>"
        ]
    }]
}

NotResource can be used in a similar way; it can be used with a deny policy if you want to deny access for one user to all buckets except one S3 bucket.

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Deny",
    "Action": "s3:*",
    "NotResource": [
      "arn:aws:s3:::<theonlybuckettoaccess>"
    ]
  }
}

Wrapping Up IAM Policies

It is impossible to overstate the importance of IAM in any cloud application and, specifically, in a microservice application. Due to their nature, in a microservice environment, you may end up requiring significantly more IAM policies in order to control each and every service. This proliferation of policies may add to the complexity of the application.

However, what you lose in complexity, you may gain in configurability. Microservices afford better control due to their modular nature. With a DDD, it is possible to frame IAM policies that are targeted and hence can provide preventive controls against potential threats. In order to target attacks, AWS provides you with many different conditions that can help in tailoring these IAM policies to better suit the needs of the service. AWS provides great documentation on how you can tailor these policies further.

Even so, for large organizations, IAM policies may end up causing friction between developers and microservice security architects. In Chapter 8, I will attempt to introduce you to some other techniques that aim at reducing the burden of complexity that microservices bring with them.

Role-Based Access Control (RBAC)

Because microservices involve a lot of services running with different identities, IAM policies may get complicated very soon, mainly due to their sheer volume. In such a situation, AWS roles aim to simplify the development of these identities while keeping the security policy simple and scalable. The concept of roles has been borrowed from social sciences. A role is a set of permissions, behaviors, identities, and norms as defined by people in a specific circumstance.

For example, a student attending a class and the same student at a party are the same person, yet that person plays two different roles. In class, the student may not be allowed to play loud music. However, at a party, the same student may be able to blast their stereo. Using RBAC, it is possible to place different restrictions on the same individual depending on the role they currently play.

RBAC is an access control paradigm and a fascinating topic in the field of security. For readers interested in learning further, I recommend Role-Based Access Control by David Ferraiolo et al. (Artech House).

Tip

RBAC is not the only way of organizing access control across complex organizations. There are many others, such as ABAC, mandatory access control (MAC), graph-based access control (GBAC), and more. Every organization needs to find the access control paradigm that best suits its security needs; each has their advantages. Since RBAC is widely used and ties well with other microservice concepts, it’s covered in this chapter.

If I am working for an organization and would like to access a resource, RBAC dictates that my responsibilities be converted to a role that I am allowed to assume. The role that I will be assuming can have a lifecycle that may not be tied to my lifecycle in the company. In this way, if I get replaced in the organization, the person who replaces me can still use the same role and access the same resource.

Figure 2-8 shows how access to a resource modeled after a role instead of an individual user helps in enforcing a similar access control policy. In this case, user Gaurav Raje does not have access to a resource. However, the same user is able to access the resource when he assumes a system administrator role.

Figure 2-8. In RBAC, the same user can request access to a resource either by using their individual identity or by using a role that they are authorized to perform within the organization.
Tip

Organization-wide paradigm shifts like RBAC require top-down alignment when it comes to implementation. The transitions can be a challenge at times, especially in large organizations. But once implemented, RBAC leads to reduced complexity, improved access visibility, improved compliance, and an easy-to-manage security infrastructure.

RBAC Modeling

In RBAC modeling, the first step is to identify the various resources and the actions that are authorized to be performed on these resources:

  • An inventory is taken of each and every action that needs to be performed on these resources.

  • After that, the organization is analyzed to identify the different employees and the actions they perform on each of the resources within the organization. This may include grouping actions based on the employees, their positions, their titles, and their access privileges.

  • Roles are then created to reflect the individual access pattern that employees within the organization use to perform their jobs, as reflected in step 2.

  • Roles are then assigned IAM policies to control their access. A rule of thumb here is to follow the principle of least privilege by providing access only to the bare minimum resources and actions to each role.

  • Finally, individual users are allowed to assume these roles, whereas direct access to resources (without using roles) is disabled for all users.

Figure 2-9 illustrates this process of role modeling and compares the access patterns before and after an implementation of RBAC.

Figure 2-9. How an organization can transition from user-based access to role-based access by modeling the user access patterns into roles

After completing the RBAC model, access to resources for each user should be restricted through roles rather than direct access.

Tip

RBAC can only be useful if the principle of least privilege is applied within the organization. Overly permissive roles may compromise the security of the application. On the other hand, excessively restrictive roles may deter legitimate users from doing their job.

Securing Roles

Assuming your application has already implemented the roles required for every employee and microservice in the organization, the next step is to allow these principals to assume these roles. AWS has many ways that users or other services can assume the roles necessary to accomplish what they need to do. Under the hood, AWS uses the AWS Security Token Service (AWS STS) to support all these methods of switching roles. For a principal to be able to switch roles, there needs to be an IAM policy attached to it that allows the principal to call the AssumeRole action on STS.

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "sts:AssumeRole",
    "Resource": "arn:aws:iam::ACCOUNT-ID-WITHOUT-HYPHENS:role/<Role>"
  }
}

In addition, IAM roles can also have a special type of a resource-based policy attached to them, called an IAM role trust policy. An IAM role trust policy can be written just like any other resource-based policy, by specifying the principals that are allowed to assume the role in question:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<accountid>:user/<username>"
      },
      "Action": "sts:AssumeRole",
    }
  ]
}

Any principal that is allowed to access a role is called a trusted entity for that role. A trusted entity can also be added to a role through the AWS Management Console by specifying it in the Trust relationships tab on the Roles page within the Identity and Access Management setup, as seen in Figure 2-10.

Figure 2-10. New principals can be added into the resource policy for IAM roles by editing the trust relationships of the role.
Tip

IAM role trust policy is like any other resource-based policy and allows principals to be from external accounts. Assuming a role within the current account is a great way of sharing access to AWS resources across accounts in a controlled way.

Warning

As with any policy evaluation, trust relationships and identity-based IAM policies are evaluated simultaneously when a user attempts to assume a role with IAM. To succeed in assuming a role, both these policies should allow the action to proceed. The user will not be able to assume the role if either of these policies leads to a denial.

Assuming Roles

I have already mentioned that the process of assuming a role involves the use of AWS STS behind the scenes. Assuming a role involves requesting a temporary set of credentials from AWS that can be used in future requests. Figure 2-11 shows two users, Bob and Alice, attempting to assume a role (Role A) using AWS STS. Bob is not specified in the trust policy for Role A while Alice is.

Figure 2-11. AWS STS is used to issue temporary credentials to any user specified within the trust policy of the role that tries to assume that role.
Warning

Root users are not allowed to assume roles using AWS STS. AssumeRole must be called with credentials for an IAM user or a role.

Roles allow access to resources. This is how roles are used to access resources:

  • IAM checks whether a user is allowed to make an STS request to assume a role.

  • A user makes a request to AWS STS to assume a particular role and get temporary credentials.

  • AWS STS checks with IAM to ensure that the target role allows the calling principal to assume this role in the trust policy of the target role.

  • The calling principal exchanges its own identity in return for temporary credentials of the target role.

Figure 2-12 illustrates this evaluation flow.

Figure 2-12. AWS STS allows users from either the same or external accounts to exchange their identity for that of a role in a target account.

In the event that the assumed role has access to a particular resource, the calling identity can access the resource in question by using the temporary credentials that it has of the assumed role. In Figure 2-12, because Role A has access to Resource A, User B can access Resource A by assuming Role A.

Tip

Giving up original credentials and access while assuming roles may be problematic for certain use cases—such as where one role with access to one AWS S3 bucket would like to copy or compare files with another bucket that allows access only to a different role. In such situations, using resource-based policies to allow access to both buckets for such a role may be the best solution.

As the same user may access resources in different roles, it is sometimes difficult to remember the exact identity used to access a particular resource. You can find out which identity is being used when interacting with AWS through the AWS Command Line Interface (CLI).

If you run the following command, AWS will inform you of the user’s current identity, as you can see in Figure 2-13. It could be a user or a role that you may have assumed:

aws sts get-caller-identity
Figure 2-13. A call to STS get-caller-identity will return the current user ID, account ID, and the ARN of the identity of the current session.

Assume Roles Using the AWS Command Line Interface (CLI)

The AWS CLI can be used to assume a role using AWS STS by running the following command:

aws sts assume-role --role-arn arn:aws:iam::123456789012:role/targetrole --role-session-name assumed-role-session

AWS responds to such requests by issuing temporary credentials that the caller can use to access AWS resources:

{
    "AssumedRoleUser": {
        "AssumedRoleId": "AXAISKDJSALA:assumed-role-session",
        "Arn": "arn:aws:sts::123456789012:assumed-role/targetrole/assumed-role-session"
    },
    "Credentials": {
        "SecretAccessKey": "9drTJvcXLB89EXAMPLELB8923FB892xMFI",
        "SessionToken": "AQoXdzELDDY//////////wEaoAK1wvxJY12r2IrDFT2IvAzTCn3zHoZ7YNtpiQLF0MqZye/qwjzP2iEXAMPLEbw/m3hsj8VBTkPOR
Gvr9jM5sgP+w9IZWZnU+LWhmg+a5fDi2oTGUYcdg9uexQ4mtCHIHfi4citgqZTgco40Yqr4lIlo4V2b2Dyauk0eYFNebHtYlFVgAUj
+7Indz3LU0aTWk1WKIjHmmMCIoTkyYp/k7kUG7moeEYKSitwQIi6Gjn+nyzM+PtoA3685ixzv0R7i5rjQi0YE0lf1oeie3bDiNHncm
zosRM6SFiPzSvp6h/32xQuZsjcypmwsPSDtTPYcs0+YN/8BRi2/IcrxSpnWEXAMPLEXSDFTAQAM6Dl9zR0tXoybnlrZIwMLlMi1Kcg
o5OytwU=",
        "Expiration": "2016-03-15T00:05:07Z",
        "AccessKeyId": "ASIAJEXAMPLEXEG2JICEA"
    }
}

Temporary credentials are used for the same reasons as regular credentials, except that they expire after a certain date and time, and they are tied to a session. The credentials in the response can be used to access other resources.

Switching Roles Using AWS Management Console

AWS allows users to switch to roles that the current logged-in user has access to, right through the AWS Management Console, as shown in Figure 2-14 and Figure 2-15.

Figure 2-14. A new role can be added to the list of roles that the logged user can switch to by selecting Switch Roles.
Figure 2-15. To add a new role, you can add the role name and account ID as well as a convenient nickname to the form; AWS will switch the console to the newly assumed identity of the role in the target account.
Tip

Now that you can set up roles for everyone in your organization and ask users to assume roles in order to access resources, your IAM policies should be much cleaner, since each access can now be tied to a role instead of individual users. This also helps in decoupling user identities from their function within the application.

Service-Linked Role

In a microservice environment, least privilege should be applied to the roles that microservices assume in order to run on the cloud infrastructure. To make this process easier, AWS allows the creation of a service-linked role for certain supported services. A service-linked role has its policies and statements prewritten by AWS. All service-linked roles are predefined by the service that assumes these roles. They include all the permissions that the assuming service needs in order to make calls to other AWS services. Service-linked roles can be created from the command line using the following command:

 aws iam create-service-linked-role --aws-service-name <service name>
Tip

The service name in this case can be looked up in the AWS documentation.

Authentication and Identity Management

So far, it has been assumed that AWS knew the user’s identity, without explaining how it could be accomplished. Access control tried to limit the amount of damage a known identity could inflict on a system. As long as AWS is aware of the real identity of the user, access control works effectively. But, for an access control system, what makes you you? What should it base its decision on? Is it the way you look? Is it the identity card you possess that says who you are? This section shows the various ways in which this initial identity can be established, proven, and secured so that the rest of IAM is able to successfully apply controls around threats pertaining to unauthorized access.

Basics of Authentication

Maintaining the integrity of identities ensures that access control systems can prevent unauthorized access by using these identities. For example, to prevent minors from purchasing alcohol, a liquor store may require every customer to show their driver’s license (attribute). It is possible to have strong security guards to ensure that people without such a document cannot buy alcohol. However, if it were easy to fabricate a fake driver’s license, minors can easily circumvent the access control system by creating fake proof of age, thus making such a system useless. It is the job of an authentication system to ensure that a compromise of identities is prevented. In case of driver’s licenses, this may mean the introduction of 3D holograms or computerized scanners that ensure the integrity of the document.

Authentication is the process of establishing confidence in user identities presented to an electronic system. An authenticating user or service must perform certain actions that your AWS account believes can only be performed by that particular user in order to establish their identity. In case of AWS, these actions can be roughly classified into three buckets:

Authentication using knowledge factors
In this bucket, AWS requires the user to prove knowledge of a secret that only a legitimate user is supposed to know. Typing in a password, using a passphrase, answering a security question, or using any secret pin code are all examples of knowledge factors.
Authentication using a possession factor
In this one, AWS requires the user to prove the possession of an item that only that user may be in possession of. This might include the user’s cellphone or a physical hardware authentication device (such as a YubiKey or a Google Titan key).
Authentication using federated authentication
Instead of the AWS taking on the responsibility for authentication, a different system is designated to perform the checks. This enables an organization’s existing authentication process to be reused. With federated authentication, AWS can delegate authentication tasks to established systems within the organization, such as Active Directory or any other mechanisms that the organization may already be using for registering identities.
Tip

In most secure organizations, a possession factor is used in addition to any of the other mechanisms to add an extra layer of security. This is known as multifactor authentication (MFA). Throughout this book, I will keep insisting on the importance of using MFA throughout your infrastructure.

Identity Federation on AWS

A strong password requirement on the AWS Management Console is a good start to securing your cloud infrastructure, but the added liability of requiring users to keep their login credentials could result in friction within organizations. This may be further aggravated by having stricter password policies for IAM credentials. Employees may already have corporate identities within most companies, so requiring them to create new accounts on AWS (with strong password policies) could simply increase friction among employees.

AWS gives organizations the ability to integrate AWS Identity Services into their own identity management systems. This system of delegating the authentication aspect of AWS to a trusted third-party system is called identity federation. The key to federated identities is the fact that you have different identity providers—for example, your on-premises system and AWS IAM—and the two providers agree to link their own identities to each other (for more on this, see Chris Dotson’s book Practical Cloud Security (O’Reilly). The third-party system that performs the actual identity check on behalf of AWS is called an identity provider (IdP).

There are many ways to perform identity federation. Most involve the use of RBAC to map external third-party identities to established roles within the AWS organization and allowing these third-party identities to exchange their credentials for an AWS role.

There are many ways of achieving identity federation, but they all follow a common theme (highlighted in Figure 2-16). Let us assume a user wishes to access a resource within AWS called Target Resource using federated identity. The on-premises system already has all of the users’ identifiers. This system is the IdP and is capable of determining the levels of privilege or the role that this user should be allowed to assume in order to perform their task with the minimum privilege.

Figure 2-16. Most of the identity federation mechanisms follow some version of this abstract flow when it comes to authenticating requests from federated identity sources.

This is the flow shown in Figure 2-16:

  • The external identity management system will set up some sort of a trust relationship with an AWS account.

  • A user who wishes to access the target resource first authenticates with the on-premises system. (This on-premises system can use whatever combinations of factors it chooses to ensure the identity of the calling user.)

  • The on-premises authentication system responds with some form of an assurance that it believes will be able to convince AWS of a successful validation. AWS on its end is able to look at this token and determine its validity.

  • The user uses this token to make a request to AWS by saying, “Here is my request along with the assurance that I have been authenticated by the IdP that you agreed to trust.”

  • AWS verifies the identity of the token (or the assertion that is present in the token) to ensure that the user is indeed validated by the IdP.

  • Upon verification, the end user can now exchange their identity within their target account for an AWS role, much like what was discussed in the “Role-Based Access Control (RBAC)” section. As opposed to being an external AWS account user that is authenticated by AWS, in this case the user is authenticated by a third-party system that you have instructed AWS to trust. Since the target role has access to the resource, the end user can access the target resource using the identity of the role it just assumed.

Identity Federation Using SAML 2.0 and OpenID Connect

SAML 2.0 is one of the most common standards used in exchanging identities between security domains. Many third-party identity providers are fully compliant with SAML 2.0; hence, being able to integrate with SAML allows your organization a wide array of third-party authentication providers.

Tip

Appendix B shows a hands-on example of integrating with a third-party SAML provider. If you’re interested in setting up federated identity providers, you will find the exercise very worthwhile.

OpenId Connect (OIDC) is another tool you can use to add a trusted IdP to your AWS account. OIDC is built on top of OAuth 2.0 and allows AWS to verify the identity of the end user based on an HTTP-based exchange. Like SAML, OIDC also allows federated users to exchange their existing external identity for the purpose of assuming a role within their target AWS account. This role can be made to reflect the policy of least privilege and all the concepts discussed in the earlier section, “RBAC Modeling.”

This way, using either a SAML- or an OIDC-based identity provider, AWS allows for the delegation of authentication to your existing identity providers, thus reducing the number of login credentials that your employees may have to keep track of.

Tip

Because AWS trusts IdPs for authentication, it is important that the IdP that is used for authentication adhere to security best practices. This may include choosing strong password policies, enforcing MFA on users, and reviewing access regularly to eliminate users who no longer need access to AWS resources.

RBAC and Microservices

This chapter has laid the foundation for IAM for any cloud system on AWS. IAM roles, authentication, and policies allow you to build secure microservices. Nevertheless, given that microservice applications have a greater number of services running and interacting with each other, it’s likely that your IAM policies will grow complicated if you aren’t careful. I would like to talk a little about some of the cloud infrastructure’s architecture elements that you can leverage in order to preserve the complexity of a microservice application.

To start, I will assume that your application has a clean separation of services based on domain design. As a result, each microservice within your application is focused on a specific business responsibility. Given that the service is neatly aligned with business-level objectives, it may be easier to define the service in terms of a business role. RBAC can be a good approach for implementing access control for such use cases.

In most microservice applications, you can perform the exercise of role modeling on these services. You will have to document all the ways in which each of your services accesses AWS resources and assign roles according to the permissions required for such access, based on the contexts of these requests. With the exception of tying each role to a service instead of to an employee, role modeling done on microservices is very similar to it being done with employees.

So, after a successful implementation of RBAC, you can assume that each of your microservices can be mapped to a role that will have the right level of access within your infrastructure. By applying the principle of least privilege (PoLP) to this role, you can ensure that your microservices will never stray from their defined roles.

Execution Roles

Many supported AWS services run by assuming a certain default role called an execution role. These roles can be created identical to roles that users assume. Role-based access control brings in a huge advantage to microservices that I will demonstrate with an example. Let’s say you have a microservice that is running a certain application that requires access to a certain sensitive cloud resource (say a database). You may have multiple cloud environments where your service can be deployed. In a staging environment, you may have a database that has certain staged data, while in a production environment, you may have actual customer data.

Before execution roles and cloud deployments, access control used to be controlled primarily with passwords or other authentication mechanisms. These passwords either used to live alongside application code in the form of encrypted config files or needed access to other third-party configuration management services, adding to the complexity of the application.

This problem is mitigated by the implementation of execution roles. All you need to do is create a role that has access to the database and make this role the execution role of your deployed microservice. The application code will now attempt to access the cloud resource by assuming that the role it runs as has the required access.

If the code is deployed to the production environment, it will run by assuming the production role. Simply assuming this role gives this microservice the ability to access the production data without needing any separate authentication logic. In a similar vein, deploying the same service to a staging environment can give access to the staging database. Figure 2-21 illustrates.

Figure 2-17. Execution roles simplify the process of authenticating against cloud resources since you can use their identities (roles) in place of passwords.

In such a setup, developers focus on writing business logic while security professionals can sharpen the execution roles to apply least privilege, clearly separating the duties. Application logic is never made aware of any security or access rules, making the process extremely elegant. Hence, it is a best practice for applications running microservices to use roles to access cloud resources rather than passwords, which may bring in concerns with respect to maintainability. Based on the type of microservices you’re running for your organization, let’s look at how to get microservices to assume these roles.

RBAC with AWS Lambda

We’ll start off with AWS Lambda functions since they require the least amount of effort in setting up. For serverless microservices using AWS Lambda, it is possible to specify the execution role while creating an AWS Lambda function. As seen in Figure 2-22, you can either create a new role or use an existing role while creating the Lambda function.

Figure 2-18. You have the choice of either creating a role or specifying an existing role for Lambda execution.

Using an execution role, it is easy to design an entire microservice application running on AWS Lambda with clean access control logic that doesn’t violate the PoLP, since each Lambda function will have a role associated with it. As mentioned earlier in the section “Execution Roles,” any code deployed to a Lambda will already have assumed the execution role that was specified while creating the Lambda. To achieve clean access control, you merely need to grant this role access to any sensitive resource in the correct context. You can reuse your code across multiple environments by using different roles across different environments.

RBAC with EC2 and the Instance Metadata Service (IMDS)

Although AWS Lambdas come with an execution role, and if all of your microservices run on AWS Lambda, you may be lucky enough to get great RBAC without a lot of heavy lifting. But not everyone is as lucky, and chances are you may have different AWS compute services backing your microservice architecture. Let’s consider AWS EC2, since it’s one of the most popular compute engines.

Multiple applications may run on EC2 instances, unlike AWS Lambdas where functions run on their own. With AWS, your applications running on top of EC2 can use a slightly modified version of AWS Lambda’s execution roles for gaining access to cloud resources called the instance metadata service (IMDS).

On AWS, you can run an EC2 instance and attach a role to the running instance. Figure 2-23 shows an example of such an instance being created.

Figure 2-19. IAM roles can be assigned to EC2 instances during their creation in the Configure Instance Details step.

Every Amazon EC2 instance is equipped with the IMDS service, which allows applications running on top of these instances to access and find more information about their runtime environments. The role assigned to the EC2 instance is within this information. Through the IMDS, applications are able to access information such as their AWS access secret key and their AWS access key ID. The role-specific security credentials are used to grant the application access to the actions and resources you have assigned to the role. These security credentials are temporarily rotated automatically. So, when you write your microservices and deploy them onto EC2 instances, the following steps happen:

  • The application contains no information around its identity. However, you can configure it to query the IMDS to find out about its identity.

  • The IMDS can then respond with the identity of the execution role that the EC2 instance runs as.

  • Applications running on EC2 can use the temporary credentials that IMDS returns, thus assuming the execution role.

  • The applications can make requests to the resources using the temporary credentials returned in step 3.

This way, applications can run without the need to have any secrets or config files within their code and rely solely on the IMDS to provide identity and access, as seen in Figure 2-20.

Figure 2-20. IMDS allows applications to access the temporary credentials of the role that is attached to the EC2 instance.

You can query IMDS by making a simple HTTP curl request to the endpoint http://169.254.169.254/latest/.

So, if you have an application running on EC2, you can make the following request:

TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` 
&& curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/iam/security-credentials/s3access

This request returns an output similar to this:

{
  "Code" : "Success",
  "LastUpdated" : "2012-04-26T16:39:16Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIAIOSFODNN7EXAMPLE",
  "SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "Token" : "token",
  "Expiration" : "2017-05-17T15:09:54Z"
}

Now that your application has the AWS access key ID and the AWS secret access key, you can use AWS STS to assume any role it wishes to and thus get access to resources that it wants, very similar to the way AWS Lambda does.

This means your code can assume a role on any environment of your choice. Depending on where it is deployed, it can call the IMDS and assume the role that the environment wants it to assume. Through IMDS, your applications can securely make API requests from your instances without having to manage the security credentials within the application code.

RBAC with Amazon EKS Using IAM Roles for Service Accounts (IRSA)

IMDS works with EC2 instances, so any service running on EC2 can use RBAC to gain access to AWS resources. If each EC2 instance in your microservice application hosted only one service at a time, this design would work flawlessly for your use. However, if multiple applications run on top of the same EC2 instance, they will be tied to the same underlying instance’s IAM role, making it harder to implement PoLP.

This is especially true for services running containerized microservices applications such as those running on AWS Elastic Kubernetes Service (EKS) nodes on EC2 instances. Containerized applications are known to have deployment transparency. Having the IAM role of the underlying node tied to each microservice makes sharing instances insecure from a security standpoint because multiple different microservices share the same instance. This means the resulting role is a union of all the permissions required by the underlying services. Since each service will have extra access, this goes against PoLP.

To make matters worse, Kubernetes also has its own RBAC mechanism where users are granted access based on Kubernetes identities and roles. So, in such a system, you have two sets of identities. On the Kubernetes side you have the Kubernetes role bindings that may allow access to pods or users depending on your Kubernetes config. On the AWS side, you have IAM roles that you would like to utilize to control access to cloud resources such as AWS S3 buckets.

If this problem of having two separate identities in two different systems sounds familiar, you’re right. It’s very similar to the problem faced by organizations using multiple authentication mechanisms. I talked in depth about how identity federation helped resolve such a problem, and to synchronize identities between Kubernetes and AWS I recommend a similar approach.

In their words, AWS decided to make Kubernetes pods first-class citizens in AWS. With Kubernetes service account annotations and OpenID Connect (OIDC) identity providers, you can assign IAM roles to individual pods and use roles the same way you did with AWS Lambda.

Figure 2-21 shows how IRSA works when a microservice running on a pod would like to access an AWS resource by assuming an AWS role.

Figure 2-21. Using IRSA, Pods can use OIDC IdP and AWS STS to assume roles and access AWS resources without being tied to the underlying node.
  • Each pod starts with an annotation about a service account along with a token that it can use to exchange with AWS STS in return for a role within your organization. This is done by mutating the pod with information that EKS injects into it through the mutating webhook. It also injects the web identity token file that the pod can use to identify itself to an OIDC IdP in step 2.

  • To fetch the credentials, the pod makes a request to AWS STS to assume the identity of the role along with the service account token that was injected in step 1. This token will be exchanged in order to get temporary authentication credentials with AWS.

  • AWS STS makes a request to the OIDC IdP to verify the validity of the request.

  • OIDC IdP responds with an affirmative response.

  • AWS STS responds with temporary credentials that the pod can use to access AWS resources using the role that it wanted to use.

  • The pod accesses the AWS resource using the temporary credentials provided to it by AWS STS.

By creating roles and assigning those roles to pods, IRSA also provides RBAC for Kubernetes pods. Once assigned to pods, these roles can be used to design access control using RBAC similar to how execution roles were used with AWS Lambda.

Summary

Authorization and authentication are the two most important controls that security architects have for reducing the aggregate risk of any secure system. Getting a good grasp on both of these mechanisms will help every security architect design better systems. Since microservices are made up of modular services that have a very specific purpose for their existence, it is easier for security architects to frame the security policies around microservices using the principle of least privilege. The security policies can be added to the AWS IAM, a service which is specifically designed for providing identity management services. On AWS, authorization and authentication generally provide you with the most comprehensive and granular protection against potential threats when combined with encryption controls. This interplay between authorization, authentication, and encryption will be covered in great detail in Chapter 3.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
54.144.95.36