Multi-tenant applications should be architected so the sources of each tenant are isolated and can’t be accessed by other tenants in the machine. AWS Identity and Access Management (IAM) is usually a important element in achieving this goal. Among the challenges with using IAM, however, is that the quantity and complexity of IAM policies you will need to aid your tenants can grow rapidly and impact the scale and manageability of one’s isolation model. The attribute-based access control (ABAC) mechanism of IAM provides developers with ways to address this challenge.
<p>In this website post, we describe and offer detailed examples of ways to use ABAC in IAM to implement tenant isolation in a multi-tenant environment.</p>
Choose an IAM isolation method
IAM can help you implement tenant isolation and scope down permissions in a manner that is integrated with other AWS services. By counting on IAM, it is possible to create strong isolation foundations in one’s body, and reduce the threat of developers introducing code leading to a violation of tenant boundaries unintentionally. IAM has an AWS native, non-invasive way to achieve isolation for all those cases where IAM supports policies that align together with your overall isolation model.
There are many methods in IAM which you can use for isolating tenants and restricting usage of resources. Deciding on the best method for the application depends upon several parameters. The amount of tenants and the amount of role definitions are two important dimensions that you ought to consider.
Most applications require multiple role definitions for different user functions. A job definition refers to a minor group of privileges that users or programmatic components need to carry out their job. For instance, business users and data analysts could have different group of permissions to allow& typically;nbsp;minimum necessary usage of resources they use.
In software-as-a-service (SaaS) applications, along with functional boundaries, you can find boundaries between tenant resources also. As a result, the complete group of role definitions exists for every individual tenant. In highly dynamic environments (e.g., collaboration scenarios with cross-tenant access), new role definitions could be added ad-hoc. In that full case, the amount of role definitions and their complexity can grow because the system evolves significantly.
You can find three main tenant isolation methods in IAM. Let’s briefly review them before concentrating on the ABAC in the next sections.
Figure 1: IAM tenant isolation methods
RBAC – Each tenant includes a dedicated IAM role or static group of IAM roles that it uses for usage of tenant resources. The amount of IAM roles in RBAC equals to the amount of role definitions multiplied by the amount of tenants. RBAC is effective when you have a small amount of tenants and relatively static policies. You might find it difficult to control multiple IAM roles because the amount of tenants and the complexity of the attached policies grows.
Dynamically generated IAM policies – This technique dynamically generates an IAM policy for a tenant in accordance with user identity. Choose this technique in highly dynamic environments with changing or frequently added role definitions (e.g., tenant collaboration scenario). You may even choose dynamically generated policies when you have a preference for generating and managing IAM policies through the use of your code instead of counting on built-in IAM service features. You’ll find more details concerning this method in your blog post Isolating SaaS Tenants with Dynamically Generated IAM Policies.
ABAC – This technique is suitable for an array of SaaS applications, unless your use case requires support for changed or added role definitions frequently, which are simpler to manage with dynamically generated IAM policies. Unlike Dynamically generated IAM policies, where you manage and apply policies by way of a self-managed mechanism, ABAC enables you to rely more on IAM directly.
ABAC for tenant isolation
ABAC is attained by using parameters (attributes) to regulate tenant usage of resources. Using ABAC for tenant isolation results in temporary usage of resources, that is restricted based on the caller’s attributes and identity.
Among the key benefits of the ABAC model is that it scales to a variety of tenants with an individual role. That is attained by using tags (like the tenant ID) in IAM polices and a temporary session specifically made for accessing tenant data. The session encapsulates the attributes of the requesting entity (for instance, a tenant user). At policy evaluation time, IAM replaces these tags with session attributes.
Another element of ABAC may be the assignation of attributes to tenant resources through the use of special naming conventions or resource tags. The usage of a resource is granted when session and resource attributes match (for instance, a session with the TenantID: yellow attribute can access a resource that’s tagged as TenantID: yellow).
To find out more about ABAC in IAM, see What’s ABAC for AWS?
ABAC in an average SaaS architecture
To show ways to use ABAC in IAM for tenant isolation, we will walk you via an example of an average microservices-based application. More specifically, we shall concentrate on two microservices that implement a shipment tracking flow in a multi-tenant ecommerce application.
Our sample tenant, Yellow, which includes many users in lots of roles, has exclusive usage of shipment data that belongs to the particular tenant. To do this, all microservices in the decision chain operate in a restricted context that prevents cross-tenant access.
Figure 2: Sample shipment tracking flow in a SaaS application
Let’s have a closer go through the sequence of events and discuss the implementation at length.
A shipment tracking request is set up by an authenticated Tenant Yellow user. The authentication process is overlooked of the scope of the discussion with regard to brevity. An individual identity expressed in the JSON Web Token (JWT) includes custom claims, among which really is a TenantID. In this example, TenantID equals yellow.
The JWT is delivered from the user’s browser in the HTTP header of the Get Shipment request to the shipment service. The shipment service then authenticates the request and collects the mandatory parameters so you can get the shipment estimated time of arrival (ETA). A GetShippingETA is manufactured by the shipment service request utilizing the parameters to the tracking service combined with the JWT.
The tracking service manages shipment tracking data in a data repository. The repository stores data for several of the tenants, but each shipment record comes with an attached TenantID resource tag, for example yellow, as inside our example.
An IAM role mounted on the tracking service, called TrackingServiceRole in this example, determines the AWS resources that the microservice can access and what it could perform.
Remember that TrackingServiceRole itself doesn’t have permissions to gain access to tracking data in the info store. To get usage of tracking records, the tracking service assumes another role called TrackingAccessRole. This role remains valid for a restricted time frame, until credentials representing this temporary session expire.
To comprehend how this ongoing works, we need to discuss the trust relationship between < first;strong>TrackingAccessRole and TrackingServiceRole. The next trust policy lists TrackingServiceRole as a reliable entity.
“Version”: “2012-10-17”,
“Statement”: [
"Effect": "Allow",
"Principal":
"AWS": "arn:aws:iam:: <account-id> :role/TrackingServiceRole"
,
"Action": "sts:AssumeRole"
,
"Effect": "Allow",
"Principal":
"AWS": "arn:aws:iam:: <account-id> :role/TrackingServiceRole"
,
"Action": "sts:TagSession",
"Condition":
"StringLike":
"aws:RequestTag/TenantID": "*"
]
This policy must be connected with <strong>TrackingAccessRole</strong>. You can certainly do that on the <a href="https://docs.aws.amazon.com/directoryservice/latest/admin-guide/edit_trust.html" target="_blank" rel="noopener noreferrer"><strong>Trust relationships tab</strong></a> of the <strong>Role Details</strong> page in the IAM console or via the AWS CLI <a href="https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/update-assume-role-policy.html" target="_blank" rel="noopener noreferrer"><strong>update-assume-role-policy</strong></a> method. That association is what allows the tracking service with the attached <strong>TrackingServiceRole </strong>role to assume<strong> TrackingAccessRole</strong>. The policy allows <strong>TrackingServiceRole</strong> to add the <span>TenantID</span> session tag to the temporary sessions it generates.</p>
Session tags are principal tags that you specify once you request a session. This is one way you inject variables in to the request context for API calls executed through the session. This is exactly what allows IAM policies evaluated in subsequent API calls to reference TenantID with the aws:PrincipalTag context key.
Let’s discuss < now;strong>TrackingAccessPolicy. It’s an identity policy mounted on TrackingAccessRole. This policy employs the aws:PrincipalTag/TenantID key to scope usage of a particular tenant dynamically.
In this post later, you can see types of such data access policies for three different data storage services.
Now the stage is defined to observe how the tracking service creates a temporary injects and session TenantID in to the request context. The next Python function does that through the use of AWS SDK for Python (Boto3). The < is got by the event;span>TenantID (extracted from the JWT) and the TrackingAccessRole Amazon Resource Name (ARN) as parameters and returns a scoped Boto3 session object.
import boto3
def create_temp_tenant_session(access_role_arn, session_name, tenant_id, duration_sec):
“””
Develop a temporary session
:param access_role_arn: The ARN of the role that the caller is assuming
:param session_name: An identifier for the assumed session
:param tenant_id: The tenant identifier the session is established for
:param duration_sec: The duration, in seconds, of the temporary session
:return: The session object which allows one to create service clients and resources
“””
sts = boto3.client(‘sts’)
assume_role_response = sts.assume_role(
RoleArn=access_role_arn,
DurationSeconds=duration_sec,
RoleSessionName=session_name,
Tags=[
'Key': 'TenantID',
'Value': tenant_id
]
)
session = boto3.Session(aws_access_key_id=assume_role_response['Credentials']['AccessKeyId'],
aws_secret_access_key=assume_role_response['Credentials']['SecretAccessKey'],
aws_session_token=assume_role_response['Credentials']['SessionToken'])
return session
Use these parameters to generate temporary sessions for a particular tenant with a duration that meets your preferences.
access_role_arn – The assumed role having an attached templated policy. The IAM policy must are the aws:PrincipalTag/TenantID tag key.
session_name – The name of the session. Utilize the role session name to recognize a session. The role session name can be used in the ARN of the assumed role principal and contained in the AWS CloudTrail logs.
tenant_id – The tenant identifier that describes which tenant the session is established for. For better compatibility with resource names in IAM policies, it’s recommended to generate non-guessable alphanumeric lowercase tenant identifiers.
duration_sec – The duration of one’s temporary session.
Note: The facts of token management could be abstracted away from the application form by extracting the token generation right into a separate module, as described in your blog post Isolating SaaS Tenants with Dynamically Generated IAM Policies . For the reason that post, the reusable application code for acquiring temporary session tokens is named a Token Vending Machine.
The returned session may be used to instantiate IAM-scoped objects like a storage service. Following the session is returned, any API call performed with the temporary session credentials provides the aws:PrincipalTag/TenantID key-value pair in the request context.
Once the tracking service attempts to gain access to tracking data, IAM completes several evaluation steps to find out whether to permit or deny the request. Included in these are evaluation of the principal’s identity-based policy, that is, in this example, represented by TrackingAccessPolicy . It really is at this time that the aws:PrincipalTag/TenantID tag key is replaced with the specific value, policy conditions are resolved, and access is granted to the tenant data.
Common ABAC scenarios
Let’s have a look at some typically common scenarios with different data storage services. For every example, a diagram is roofed that illustrates the allowed usage of tenant data and the way the data is partitioned in the service.
These examples depend on the architecture described earlier and assume that the temporary session context contains a TenantID parameter. We shall demonstrate different versions of TrackingAccessPolicy which are applicable to different services. The way aws:PrincipalTag/TenantID can be used depends upon service-specific IAM features, such as for example tagging support, policy ability and conditions to parameterize resource ARN with session tags. Examples below illustrate these techniques put on different services.
Pooled storage isolation with DynamoDB
Many SaaS applications depend on a pooled data partitioning model where data from all tenants is combined right into a single table. The tenant identifier is then introduced into each table to recognize the items which are connected with each tenant. Figure 3 has an exemplory case of this model.
Figure 3: DynamoDB index-based partitioning
In this example, we’ve used Amazon DynamoDB , storing each tenant identifier in the table’s partition key. Now, we are able to use IAM  and ABAC; fine-grained access control to implement tenant isolation for the things in this table.
The next TrackingAccessPolicy uses the dynamodb:LeadingKeys condition key to restrict permissions to only the things whose partition key matches the tenant’s identifier as passed in a session tag.
“Version”: “2012-10-17”,
“Statement”: [
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:BatchGetItem",
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb: <region> : <account-id> :table/TrackingData"
],
"Condition":
"ForAllValues:StringEquals":
"dynamodb:LeadingKeys": [
"$aws:PrincipalTag/TenantID"
]
]
This example uses the dynamodb:LeadingKeys condition type in the policy to spell it out ways to control usage of tenant resources. You’ll observe that we haven’t bound this policy to any specific tenant. Instead, the policy depends on the aws:PrincipalTag tag to solve the TenantID parameter at runtime.
This approach implies that you can include new tenants without having to create any new IAM constructs. This reduces the maintenance limits and burden your chances that any IAM quotas will undoubtedly be exceeded.
Siloed storage isolation with Amazon Elasticsearch Service
Let’s look at another example that illustrates the way you might implement tenant isolation of Amazon Elasticsearch Service resources. Figure 4 illustrates a silo data partitioning model , where each tenant of one’s system includes a separate Elasticsearch index for every tenant.
Figure 4: Elasticsearch index-per-tenant strategy
It is possible to isolate these tenant resources by developing a parameterized identity policy with the principal TenantID tag as a variable (like the one we designed for DynamoDB). In the next example, the main tag is really a right area of the index name in the policy resource element. At access time, the main tag is replaced with the tenant identifier from the request context, yielding the Elasticsearch index ARN as a complete result.
“Version”: “2012-10-17”,
“Statement”: [
"Effect": "Allow",
"Action": [
"es:ESHttpGet",
"es:ESHttpPut"
],
"Resource": [
"arn:aws:es: <region> : <account-id> :domain/test/$aws:PrincipalTag/TenantID / "
]
]
In the event where you have multiple indices that participate in the same tenant, it is possible to allow usage of them with a wildcard. The preceding policy allows es:ESHttpGet and es:ESHttpPut actions to be studied on documents if the documents participate in an index with a name that matches the pattern.
Important: For this to work, the tenant identifier must follow the same naming restrictions as indices .
Although this process scales the tenant isolation strategy, you will need to bear in mind that solution is constrained by the amount of indices your Elasticsearch cluster can support.
Amazon S3 prefix-per-tenant strategy
Amazon Simple Storage Service (Amazon S3) buckets are generally used as shared object stores with dedicated prefixes for different tenants. For enhanced security, it is possible to optionally work with a dedicated customer master key (CMK) per tenant. Should you choose so, attach a corresponding TenantID resource tag to a CMK.
Through the use of IAM and ABAC, you can make sure each tenant can only just get and decrypt objects in a shared S3 bucket which have the prefixes that match that tenant.
Figure 5: S3 prefix-per-tenant strategy
In the next policy, the initial statement uses the TenantID principal tag in the resource element. The policy grants s3:GetObject permission, but only when the requested object key begins with the tenant’s prefix.
The next statement allows the kms:Decrypt operation on a KMS key that the requested object is encrypted with. The KMS key will need to have a TenantID resource tag mounted on it with a corresponding tenant ID as a value.
“Version”: “2012-10-17”,
“Statement”: [
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::sample-bucket-12345678/$aws:PrincipalTag/TenantID/ "
,
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms: <region> : <account-id> :key/ ",
"Condition":
"StringEquals":
"aws:PrincipalTag/TenantID": "$aws:ResourceTag/TenantID"
]
Important: For this policy to work, the tenant identifier must follow the S3 object key name guidelines .
With the prefix-per-tenant approach, it is possible to support any true amount of tenants. However, if you opt to work with a dedicated customer managed KMS key per tenant, you will be bounded by the amount of KMS keys per AWS Region .
Conclusion
The ABAC method coupled with IAM provides teams who build SaaS platforms with a compelling model for implementing tenant isolation. Employing this dynamic, attribute-driven model, it is possible to scale your IAM isolation policies to any practical amount of tenants. This process also allows for you to depend on IAM to control, scale, and enforce isolation in ways that’s integrated into your current tenant identity scheme. You could start tinkering with IAM ABAC through the use of either the examples in this website post, or this resource: IAM Tutorial: Define permissions to gain access to AWS resources predicated on tags.
When you have feedback concerning this post, submit comments in the Comments section below. When you have questions concerning this post, take up a new thread on the AWS IAM forum or contact AWS Support .
Want more AWS Security how-to content, news, and show announcements? Follow us on Twitter .
Like this:
Like Loading...
You must be logged in to post a comment.