https://github.com/peterkneale/multi-tenant-grpc-postgres-row-level-security
Demo of a multi-tenant application using Grpc, Dapper and Postgres with Row level security
https://github.com/peterkneale/multi-tenant-grpc-postgres-row-level-security
dapper grpc multi-tenant postgresql row-level-security
Last synced: 11 months ago
JSON representation
Demo of a multi-tenant application using Grpc, Dapper and Postgres with Row level security
- Host: GitHub
- URL: https://github.com/peterkneale/multi-tenant-grpc-postgres-row-level-security
- Owner: PeterKneale
- Created: 2022-10-22T05:47:57.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2022-10-24T21:54:00.000Z (over 3 years ago)
- Last Synced: 2025-03-13T03:31:13.799Z (over 1 year ago)
- Topics: dapper, grpc, multi-tenant, postgresql, row-level-security
- Language: C#
- Homepage:
- Size: 54.7 KB
- Stars: 5
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
Awesome Lists containing this project
README
# Demo of a multi-tenant application using Grpc, Dapper and Postgres with Row level security
- Featuring both tenant and admin access
## API
### Admin API
- Executes use cases in the context of an administrator on the platform
- The security policy defined below allows read-only acccess to all tenant data
### Tenant API
- Executes use cases in the context of a specific tenant on the platform
- The security policy defined below allows full acccess to the specified tenants data
## GRPC Request Pipeline
### ExceptionInterceptor
- Trap for exceptions and translate them to GRPC response status codes
- Applied to both the `Admin API` and `Tenant API`
### ValidationInterceptor
- Finds a validator for the GRPC request and uses it to validate the request or throw a validation exception
- Applied to both the `Admin API` and `Tenant API`
### TenantContextInterceptor
- Extracts the tenant identifier from the GRPC request and stores it in the tenant context.
- Only applied to the `Tenant API`
## Mediatr Request Pipeline
### LoggingBehaviour
- Log the request being executed
- Applies to all requests
### TenantTransactionBehaviour
- Open a database connection and begin a transaction then retrieves the tenant identity from the tenant context and sets the tenant context for the connection
- Only applies when a request is annotated with the `IRequireTenantContext` marker interface
## Database schema
Create a table for use by multiple tenants
```cs
Create.Table("cars")
.WithColumn("id").AsGuid().NotNullable().PrimaryKey()
.WithColumn("tenant").AsString().NotNullable() // This column indicates which tenant a row belongs to
.WithColumn("registration").AsString().Nullable().Unique()
.WithColumn("data").AsCustom("jsonb").NotNullable();
```
## Row Level Security Policies
### Admin Security Policy
All rows can be accessed
```csharp
// Create a separate account for administrators to login with
Execute.Sql($"CREATE USER {Username} LOGIN PASSWORD '{Password}';");
// Give this administrators account access to the table
Execute.Sql($"GRANT {Permissions} ON {Table} TO {Username};");
// Define the policy that will be applied
Execute.Sql($"CREATE POLICY {Policy} ON {Table} FOR ALL TO {Username} USING (true);");
```
### Tenant Security Policy
Only those rows where the `tenant identifier` stored in the `app.tenant` context matches the `tenant` column can be
accessed
```csharp
// Create a separate account for tenants to login with
Execute.Sql($"CREATE USER {Username} LOGIN PASSWORD '{Password}';");
// Give this tenant account access to the table
Execute.Sql($"GRANT {Permissions} ON {Table} TO {Username};");
// Define the policy that will be applied
Execute.Sql($"CREATE POLICY {Policy} ON {Table} FOR ALL TO {Username} USING ({Column} = current_setting('app.tenant')::VARCHAR);");
```