<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Infrastructure on sbgrl.me</title><link>https://sbgrl.me/tags/infrastructure/</link><description>Recent content in Infrastructure on sbgrl.me</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>sylvain.bougerel@gmail.com (Sylvain Bougerel)</managingEditor><webMaster>sylvain.bougerel@gmail.com (Sylvain Bougerel)</webMaster><copyright>© 2026 Sylvain Bougerel</copyright><lastBuildDate>Mon, 12 Jan 2026 12:39:00 +0800</lastBuildDate><atom:link href="https://sbgrl.me/tags/infrastructure/index.xml" rel="self" type="application/rss+xml"/><item><title>3 rules for simple tenancy in DynamoDB</title><link>https://sbgrl.me/posts/3-rules-for-simple-tenancy-in-dynamodb/</link><pubDate>Mon, 12 Jan 2026 12:39:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/3-rules-for-simple-tenancy-in-dynamodb/</guid><description>
&lt;h2 class="relative group"&gt;Access control in DynamoDB
 &lt;div id="access-control-in-dynamodb" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#access-control-in-dynamodb" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://aws.amazon.com/dynamodb/" target="_blank" rel="noreferrer"&gt;DynamoDB&lt;/a&gt; does not come with its own account management &amp;amp; fine-grained access control. Instead, it integrates with IAM, just like other AWS services.&lt;/p&gt;
&lt;p&gt;This is great when your users authenticate with IAM, but it&amp;rsquo;s not the case of the majority of serverless web applications that authenticate users via &lt;a href="https://oauth.net/2" target="_blank" rel="noreferrer"&gt;OAuth&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Serverless web applications on AWS are &lt;em&gt;usually&lt;/em&gt; structured this way: API Gateway receives a request, proxies this request to a Lambda, that constructs the response, optionally querying DynamoDB. In this architecture, the Lambda runs with a static IAM role. OAuth authentication is performed in the Lambda runtime using this static role; i.e. &lt;em&gt;no&lt;/em&gt; fine-grained IAM access control.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/3-rules-for-simple-tenancy-in-dynamodb-1.svg" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;This approach is usually preferred because it is simple, responsive, and yet scales. It leaves the responsibility of access control to the Lambda&amp;rsquo;s runtime, however&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re writing a SaaS application in particular, you need to make sure that your tenants are &lt;a href="https://www.saas-architecture.com/capabilities/tenant-isolation.html" target="_blank" rel="noreferrer"&gt;isolated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fortunately, by following 3 rules, DynamoDB can provide tenant-based access control.&lt;/p&gt;

&lt;h2 class="relative group"&gt;All partition keys have &lt;code&gt;tenant&lt;/code&gt;
 &lt;div id="all-partition-keys-have-tenant" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#all-partition-keys-have-tenant" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Make the &lt;code&gt;tenant&lt;/code&gt; identifier a constituent of all your items&amp;rsquo; partition key (a.k.a. hash key). For a &lt;code&gt;String&lt;/code&gt; partition key, with a &lt;a href="https://aws.amazon.com/blogs/database/amazon-dynamodb-single-table-design-using-dynamodbmapper-and-spring-boot/" target="_blank" rel="noreferrer"&gt;single-table design&lt;/a&gt;, the value of the partition key be:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;${tenant}#${entity_kind}#${item_partition_key}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;No &lt;code&gt;SCAN&lt;/code&gt;
 &lt;div id="no-scan" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#no-scan" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;All other &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html" target="_blank" rel="noreferrer"&gt;CRUD operations&lt;/a&gt; are allowed, however &lt;em&gt;you shall not use &lt;code&gt;scan&lt;/code&gt;&lt;/em&gt;. Scanning in DynamoDB reads any of your partitions &lt;em&gt;at random&lt;/em&gt;. Thus, it bypasses the first rule. Enforce this in your Lambda role permission: deny &lt;code&gt;dynamodb:Scan&lt;/code&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Get &lt;code&gt;tenant&lt;/code&gt; from the request only
 &lt;div id="get-tenant-from-the-request-only" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#get-tenant-from-the-request-only" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The final piece: the tenant identifier is always provided by the request. It shall not be a value you can retrieve from a query in the same table. If you do need to provide a list of tenants somewhere, this should be done separately, in a different table or preferably in a different cell.&lt;/p&gt;

&lt;h2 class="relative group"&gt;How does it work?
 &lt;div id="how-does-it-work" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-does-it-work" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;All CRUD operations in DynamoDB require a partition key. All except &lt;code&gt;scan&lt;/code&gt; which is why we disable it in rule 2.&lt;/p&gt;
&lt;p&gt;Rule 3 ensures that a client&amp;rsquo;s access to a tenant partition is handled separately; the &lt;code&gt;tenant&lt;/code&gt; identifier acts as a message passed between this step and the current requests; isolating each request to a single tenant. As a corollary, you should treat any request that require access to multiple tenant with higher restrictions, ideally running on a separate Lambda with the least privileges.&lt;/p&gt;
&lt;p&gt;Finally, rule 1 leverages rule 2 and 3 to ensure that your CRUD operations can only retrieve information from that &lt;code&gt;tenant&lt;/code&gt;&amp;rsquo;s partition, since it becomes impossible to access other tenants&amp;rsquo; data without their identifiers.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Limitations
 &lt;div id="limitations" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#limitations" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;While this works well to isolate requests to a single tenant, this remains a &amp;ldquo;coarse-grained&amp;rdquo; approach; you do not have access to the attribute based access control provided by IAM and will need to implement the equivalent permissions in your application.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html" target="_blank" rel="noreferrer"&gt;API Gateway&amp;rsquo;s Lambda authorizers&lt;/a&gt; allow you to provide context variables that can be used in dynamic fine-grained access control roles. This approach is more complex and not common for Next.js applications, in particular.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item></channel></rss>