Organizations worldwide often find their critical business data scattered across multiple content repositories, complicating the ability to access this information seamlessly. The challenge of creating a unified and secure search experience is significant, particularly as each repository may utilize a variety of document formats and access control mechanisms.
Amazon Kendra is an intelligent enterprise search service designed to allow users to search across diverse content repositories. Clients are tasked with authenticating and authorizing users to access their search applications. Amazon Kendra ensures that the search results provided to users only include documents they are permitted to view. By incorporating secure search tokens, Amazon Kendra can effortlessly validate the identities of individual users and user groups performing searches. This enhancement simplifies and secures access-based filtering in Amazon Kendra, enabling organizations to pass user access information directly in the query payload instead of employing attribute filters. With this capability, Kendra can validate the token details and automatically apply them to search results, ensuring accurate and secure access-based filtering.
Amazon Kendra supports token-based user access control via the following token types:
- Open ID
- JWT with a shared secret
- JWT with a public key
- JSON
In a previous post, we explored token-based user access control in Amazon Kendra utilizing Open ID. In this article, we will demonstrate how to implement token-based user access control using JWT with a shared secret. JWT, or JSON Web Token, is an open standard for sharing security information between a client and a server. It contains encoded JSON objects with a set of claims and is signed using a cryptographic algorithm to prevent alterations after issuance. JWTs are particularly useful in scenarios involving authorization and information exchange.
A JWT consists of three segments, separated by periods (.):
- Header – This portion includes details such as the token type (JWT) and the signing algorithm (e.g., HMAC SHA256 or RSA), along with an optional key identifier.
- Payload – This contains various key-value pairs, known as claims, issued by the identity provider. Beyond claims related to the token’s issuance and expiration, the payload can also include information regarding the principal and tenant.
- Signature – To generate the signature part, you encode the header and payload, use a secret, apply the algorithm specified in the header, and sign it.
Thus, a JWT appears as follows:
Header.Payload.Signature
Here’s an example of a header:
{
"alg": "HS256",
"typ": "JWT",
"kid": "jwttest"
}
And a sample payload might look like this:
{
"groups": [
"AWS-SA"
],
"username": "Alice",
"iat": 1676078851,
"exp": 1676079151,
"iss": "0oa5yce4g2sQdftHV5d7",
"sub": "0oa5yce4g2sQdftHV5d7",
"jti": "e3d62304-6608-4b72-ac0a-cb1d7049df5b"
}
The JWT is generated using a secret key that should remain private and never be disclosed to the public or embedded within the JWT itself. Upon receiving a JWT from the client, the server can validate it using the stored secret key. Any changes made to the JWT will lead to a verification failure.
This article illustrates how to use a JWT with a shared access key to secure Amazon Kendra indexes through access controls. In production environments, utilize a secure authentication service provider that meets your needs for generating JWTs. For additional insights on JWTs, visit this Introduction to JSON Web Tokens blog post.
Solution Overview
This solution is intended for a specific set of users and groups that query a document repository, returning results exclusively from documents they are authorized to access. Below is a table detailing which documents each user can access in our scenario, which utilizes a subset of AWS public documents.
User | Group | Document Types Authorized for Access |
---|---|---|
Guest | . | Blogs |
Patricia | Customer | Blogs, user guides |
James | Sales | Blogs, user guides, case studies |
Alice | Marketing | Blogs, user guides, case studies, analyst reports |
Mary | Solutions Architect | Blogs, user guides, case studies, analyst reports, whitepapers |
The diagram below depicts the creation of a JWT with a shared access key to manage user access to specific documents in the Amazon Kendra index.
When an Amazon Kendra index receives a query API call accompanied by a user access token, it validates this token using a shared secret key (secured in AWS Secrets Manager) and retrieves parameters such as username and groups from the payload. The index then filters the search results based on the established Access Control List (ACL) and the information from the user’s JWT. These filtered results are sent back in response to the query API call made by the application.
Prerequisites
To follow the steps in this article, you should have:
- A basic understanding of AWS.
- An AWS account with access to Amazon Simple Storage Service (Amazon S3), Amazon Kendra, and Secrets Manager.
- An S3 bucket for storing your documents. For guidance, refer to the Creating a bucket and the Amazon S3 User Guide.
Generate a JWT with a Shared Secret Key
The following Java code demonstrates how to create a JWT with a shared secret key using the open-source jsonwebtoken package. In production, select a secure authentication service provider to generate your JWTs based on your requirements.
We pass the username and groups information as claims in the payload, sign the JWT using the shared secret, and generate a JWT specific to that user. Ensure you provide a 256-bit string as your secret and retain the value of the base64 URL encoded shared secret for later use.
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
// HS256 token generation
public class TokenGeneration {
public static void main(String[] args) {
String secret = "${yourSecret}";
String base64secret = Base64.getUrlEncoder().encodeToString(secret.getBytes(StandardCharsets.UTF_8));
System.out.println("base64secret " + base64secret);
SecretKey sharedSecret = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
Instant now = Instant.now();
String sub = "${yourSub}";
String username = "${yourUsername}";
List groups = Arrays.asList("${yourGroup}");
// set claims
Map claims = new HashMap<>();
claims.put("username", username);
}
For further reading, you can check out relevant insights from this source, as they are an authority on this topic.
Leave a Reply