惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
V
V2EX
C
Check Point Blog
GbyAI
GbyAI
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
博客园 - Franky
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Security Blog
Microsoft Security Blog
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学
The Cloudflare Blog
S
SegmentFault 最新的问题
Latest news
Latest news
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
I
InfoQ
博客园 - 【当耐特】
NISL@THU
NISL@THU
A
About on SuperTechFans
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Scott Helme
Scott Helme
雷峰网
雷峰网
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
V
Vulnerabilities – Threatpost
Security Archives - TechRepublic
Security Archives - TechRepublic
A
Arctic Wolf
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
IT之家
IT之家
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
SecWiki News
SecWiki News
大猫的无限游戏
大猫的无限游戏
S
Security Affairs
The Register - Security
The Register - Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题
T
Tor Project blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Manage Sensitive Data In Application Code | 🏗️ Build A Secure Configuration Service
Ntombizakhona Mabaso · 2026-06-27 · via DEV Community

Exam Guide: Developer - Associate
🏗️ Domain 2: Security
📘 Task 3: Manage Sensitive Data In Application Code

Managing Sensitive Data In Application Code is about keeping secrets out of your code, classifying data properly, and building applications that handle sensitive data safely.

You need to know when to use Secrets Manager vs Parameter Store, how to mask PII in API responses and logs, and how to isolate data in multi-tenant applications.

The ability to choose the right secret management service, implement data sanitization, and enforce tenant-level data boundaries, is vital.


📘Concepts

Data Classification

Understand data sensitivity levels and how each should be handled:

Classification Examples Handling Requirements
PII (Personally Identifiable Information) Name, email, SSN, phone number, address Encrypt at rest and in transit, mask in logs and API responses, restrict access
PHI (Protected Health Information) Medical records, insurance IDs, lab results HIPAA compliance, encryption required, audit trail mandatory
Financial Credit card numbers, bank accounts, transaction data PCI DSS compliance, tokenization, never store full card numbers
Public Marketing content, public API docs No special handling needed

💡If a scenario mentions compliance or audit trail, think encryption with KMS (for CloudTrail logging) and Secrets Manager (for automatic rotation). If it mentions PII in logs, think data masking and sanitization.

Secrets Manager vs SSM Parameter Store

Both store configuration and secrets.

Feature Secrets Manager SSM Parameter Store
Automatic rotation Yes (built-in for RDS, Redshift, DocumentDB) No (you build it yourself with Lambda)
Cost $0.40/secret/month + $0.05 per 10,000 API calls Free (Standard tier), $0.05/advanced parameter/month
Cross-account access Yes (via resource policy) Yes (advanced parameters only)
Max size 64 KB 4 KB (Standard) / 8 KB (Advanced)
Versioning Automatic (AWSCURRENT, AWSPREVIOUS labels) Yes (version history)
Encryption Always encrypted (KMS required) Optional (SecureString type uses KMS)
CloudFormation resolve {{resolve:secretsmanager:...}} {{resolve:ssm:...}}
Best for Database credentials, API keys that need rotation App config, feature flags, non-rotating secrets

💡If it says automatic rotation or database credentials, the answer is Secrets Manager.
If it says application configuration, feature flags, or free, the answer is Parameter Store.
If it says encrypted configuration, Parameter Store with SecureString works and costs nothing.

Data Masking and Sanitization Patterns

Pattern What It Does When to Use
Field Masking Replace characters with asterisks (j***e@example.com) API responses containing PII
Log Sanitization Strip or redact sensitive fields before logging Any log output that might contain secrets
Tokenization Replace sensitive data with a non-reversible token Credit card numbers, SSNs in databases
Field-Level Encryption Encrypt individual fields within a record When only some fields in a record are sensitive

Multi-Tenant Data Isolation Patterns

Pattern How It Works Isolation Level
Partition Key Prefix Prefix DynamoDB keys with tenant ID (TENANT#123#ORDER#456) Logical (application-enforced)
IAM Condition Keys Use dynamodb:LeadingKeys condition to restrict access Policy-enforced
Cognito Claims Extract tenant ID from JWT and scope all queries Token-Enforced
Separate Tables/Accounts Each tenant gets their own table or AWS account Physical (strongest isolation)

💡 Partition key isolation with IAM condition keys is the most common pattern. Separate accounts is the strongest but most expensive.
dynamodb:LeadingKeys restricts which partition keys a principal can access.

When to Use Each Secret Management Approach

Scenario Service Why
RDS database password that rotates every 30 days Secrets Manager Built-in rotation for RDS
API endpoint URL that differs per environment Parameter Store (String) Simple config, no encryption needed, free
Third-party API key that doesn't rotate Parameter Store (SecureString) Encrypted, free, no rotation needed
OAuth client secret shared across accounts Secrets Manager Cross-account resource policies
Feature flag (enable/disable a feature) Parameter Store (String) or AppConfig Simple config value
Lambda environment variable with a password KMS encryption + runtime decryption Encrypted at rest, decrypted at cold start

🏗️ Build A Secure Configuration Service

Build a Secure Configuration Service that demonstrates secret and configuration management:

  • Database credentials stored in Secrets Manager with console-based setup
  • Application configuration stored in SSM Parameter Store
  • A Lambda function that retrieves secrets and config at runtime
  • Data masking applied to API responses containing PII
  • A multi-tenant data isolation pattern using DynamoDB with partition key prefixes and IAM conditions

Prerequisites

An AWS account


Part I

Store Database Credentials in Secrets Manager

Step 01: Open the Secrets Manager console

Step 02: Click Store a new secret

Step 03: Choose secret type

  • Secret type: Other type of secret
  • Key: username, Value: admin
  • Key: password, Value: SuperSecretPass123!
  • Key: host, Value: mydb.cluster-abc123.us-east-1.rds.amazonaws.com
  • Key: port, Value: 3306
  • Key: dbname, Value: orders

Step 04: Encryption key: aws/secretsmanager

Step 05: Click Next

Step 06: Configure secret

  • Secret name: prod/database/credentials
  • Description: Production database credentials for the orders service

Step 07: Click Next

Step 08: Configure rotation - optional

Leave disabled for this tutorial (in production, you'd enable automatic rotation for RDS)

Step 09: Click Next

Step 10: Review

Step 11: Click Store

You now have a secret stored in Secrets Manager.
Click into it and note the Secret ARN.

Explore the Secret

Step 12: Click on prod/database/credentials

In the Secret value section, click Retrieve secret value
You'll see your key/value pairs displayed.

Step 13: Click the Versions tab

Notice the AWSCURRENT staging label

💡Secrets Manager automatically versions secrets. When rotation happens, the new value gets AWSCURRENT and the old value gets AWSPREVIOUS. Your application always retrieves AWSCURRENT by default, so rotation is seamless.


Part II

Store Application Config in SSM Parameter Store

Create String Parameters

Step 01: Open the Systems Manager console

Step 02: In the left sidebar ▼ Application Tools, click Parameter Store

Step 03: Click Create parameter

  • Name: /app/config/api-url
  • Description: External API endpoint URL
  • Tier: Standard
  • Type: String
  • Data type: text
  • Value: https://api.example.com/v2

Step 04: Click Create parameter

Create a SecureString Parameter

Step 05: Click Create parameter again

  • Name: /app/secrets/api-key
  • Description: Third-party API key (encrypted)
  • Tier: Standard
  • Type: SecureString
  • KMS key source: My current account
  • KMS Key ID: alias/aws/ssm
  • Value: sk-abc123-this-is-a-secret-key

Step 06: Click Create parameter

Create a Hierarchical Config Set

Step 07: Create three more parameters following the same steps:

  • Name: /app/config/max-items | Type: String | Value: 50
  • Name: /app/config/feature-flags/new-checkout | Type: String | Value: true
  • Name: /app/config/feature-flags/dark-mode | Type: String | Value: false

You now have a parameter hierarchy. The /app/config/ prefix groups related configuration together.

💡 Parameter Store supports hierarchies with path-based names. You can retrieve all parameters under a path with GetParametersByPath. This is useful for loading all config for an environment at once. SecureString parameters are encrypted with KMS. Use WithDecryption=True when retrieving them.


Part III

Create a Lambda Function That Retrieves Both

Create the Lambda Function

Step 01: Open the Lambda console → Create function

  • Function name: SecureConfigService
  • Runtime: Python 3.12

Step 02: Click Create function

Step 03: Paste this code

import json
import boto3
from functools import lru_cache

secrets_client = boto3.client('secretsmanager')
ssm_client = boto3.client('ssm')

@lru_cache(maxsize=None)
def get_secret(secret_name):
    """
    Retrieve a secret from Secrets Manager.
    Cached at cold start to avoid repeated API calls.
    """
    response = secrets_client.get_secret_value(SecretId=secret_name)
    return json.loads(response['SecretString'])

@lru_cache(maxsize=None)
def get_parameter(name, decrypt=True):
    """
    Retrieve a parameter from SSM Parameter Store.
    Use WithDecryption=True for SecureString parameters.
    """
    response = ssm_client.get_parameter(Name=name, WithDecryption=decrypt)
    return response['Parameter']['Value']

def get_all_config(path):
    """Retrieve all parameters under a path hierarchy."""
    response = ssm_client.get_parameters_by_path(
        Path=path,
        Recursive=True,
        WithDecryption=True
    )
    return {p['Name']: p['Value'] for p in response['Parameters']}

def lambda_handler(event, context):
    """
    Demonstrates retrieving secrets and config from both services.

    Key patterns:
    - Cache secrets at cold start (lru_cache) to reduce API calls
    - Use Secrets Manager for credentials that rotate
    - Use Parameter Store for app config and non-rotating secrets
    - Use GetParametersByPath to load config hierarchies
    """
    action = event.get('action', 'get_all')

    if action == 'get_db_credentials':
        # From Secrets Manager
        creds = get_secret('prod/database/credentials')
        return {
            'statusCode': 200,
            'body': json.dumps({
                'message': 'Database credentials retrieved from Secrets Manager',
                'host': creds['host'],
                'port': creds['port'],
                'dbname': creds['dbname'],
                'username': creds['username'],
                'password': '***REDACTED***'  # Never return passwords in responses!
            })
        }

    elif action == 'get_app_config':
        # From Parameter Store
        api_url = get_parameter('/app/config/api-url')
        api_key = get_parameter('/app/secrets/api-key')
        max_items = get_parameter('/app/config/max-items')

        return {
            'statusCode': 200,
            'body': json.dumps({
                'message': 'App config retrieved from Parameter Store',
                'apiUrl': api_url,
                'apiKey': api_key[:8] + '***REDACTED***',  # Mask the key
                'maxItems': int(max_items)
            })
        }

    elif action == 'get_feature_flags':
        # Load all feature flags under a path
        flags = get_all_config('/app/config/feature-flags')
        return {
            'statusCode': 200,
            'body': json.dumps({
                'message': 'Feature flags loaded from Parameter Store',
                'flags': {k.split('/')[-1]: v == 'true' for k, v in flags.items()}
            })
        }

    else:
        return {
            'statusCode': 200,
            'body': json.dumps({
                'message': 'Secure Config Service',
                'availableActions': ['get_db_credentials', 'get_app_config', 'get_feature_flags']
            })
        }

Step 04: Paste Click Deploy

Add Permissions to the Lambda Role

Step 05: Go to ConfigurationPermissions → click the role name

Step 06: Click Add permissionsAttach policies

Step 07: Search for and attach:

  • SecretsManagerReadWrite
  • AmazonSSMReadOnlyAccess

Step 08: Click Add permissions

Test the Function

Step 09: Go to the Test tab

Step 10: Create test events

Test event 1: Get database credentials:

{"action": "get_db_credentials"}

Test event 2: Get app config:

{"action": "get_app_config"}

Test event 3: Get feature flags:

{"action": "get_feature_flags"}

Step 11: Run each test and verify the responses

💡Notice the caching pattern with @lru_cache. Secrets Manager and Parameter Store charge per API call.

Caching at cold start means you only pay for one call per Lambda instance, not one per invocation.

For secrets that rotate, use a TTL-based cache instead of lru_cache so the Lambda picks up new values.


Part IV

Implement Data Masking in API Responses

Create the Data Masking Lambda

Step 01: LambdaCreate function

  • Function name: DataMaskingDemo
  • Runtime: Python 3.12

Step 02: Click Create function

Step 03: Paste this code

import json
import re
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

# --- Data Masking Functions ---

def mask_email(email):
    """jane.doe@example.com → j***e@example.com"""
    local, domain = email.split('@')
    if len(local) <= 2:
        masked = local[0] + '***'
    else:
        masked = local[0] + '***' + local[-1]
    return f"{masked}@{domain}"

def mask_phone(phone):
    """+1-555-123-4567 → ***-***-**67"""
    digits = re.sub(r'\D', '', phone)
    return f"***-***-**{digits[-2:]}"

def mask_ssn(ssn):
    """123-45-6789 → ***-**-6789"""
    return f"***-**-{ssn[-4:]}"

def mask_credit_card(card):
    """4111-1111-1111-1234 → ****-****-****-1234"""
    digits = re.sub(r'\D', '', card)
    return f"****-****-****-{digits[-4:]}"

def sanitize_user(user):
    """Mask all PII fields before returning to the client."""
    return {
        'id': user['id'],
        'name': user['name'],
        'email': mask_email(user['email']),
        'phone': mask_phone(user['phone']),
        'ssn': mask_ssn(user['ssn']),
        'memberSince': user['memberSince']
    }

# --- Log Sanitization ---

SENSITIVE_PATTERNS = {
    'password': r'"password"\s*:\s*"[^"]*"',
    'ssn': r'\d{3}-\d{2}-\d{4}',
    'credit_card': r'\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}'
}

def sanitize_for_logging(data):
    """Remove sensitive data before logging."""
    text = json.dumps(data) if isinstance(data, dict) else str(data)
    for field, pattern in SENSITIVE_PATTERNS.items():
        text = re.sub(pattern, f'"***REDACTED-{field}***"', text)
    return text

def lambda_handler(event, context):
    """
    Demonstrates data masking in API responses and log sanitization.

    Key patterns:
    - Mask PII fields (email, phone, SSN) before returning to clients
    - Sanitize log output to prevent sensitive data from reaching CloudWatch
    - Never log passwords, full credit card numbers, or SSNs
    """
    # Simulated user data (in production, this comes from DynamoDB)
    users = [
        {
            'id': 'USR-001',
            'name': 'Jane Doe',
            'email': 'jane.doe@example.com',
            'phone': '+1-555-123-4567',
            'ssn': '123-45-6789',
            'memberSince': '2023-01-15'
        },
        {
            'id': 'USR-002',
            'name': 'John Smith',
            'email': 'john.smith@example.com',
            'phone': '+1-555-987-6543',
            'ssn': '987-65-4321',
            'memberSince': '2023-06-20'
        }
    ]

    # Sanitize before logging — never log raw PII
    logger.info(f"Processing request: {sanitize_for_logging(event)}")

    # Mask PII before returning in the response
    masked_users = [sanitize_user(u) for u in users]

    return {
        'statusCode': 200,
        'headers': {'Content-Type': 'application/json'},
        'body': json.dumps({
            'message': 'User data with PII masked',
            'users': masked_users
        })
    }

Step 04: Click Deploy

Test Data Masking

Step 05: Go to the Test tab → create a test event with {}

Step 06: Run the test

⚠️ Verify the response shows masked values:

  • Email: j***e@example.com
  • Phone: ***-***-**67
  • SSN: ***-**-6789

Step 07: Check CloudWatch Logs to confirm the log output is also sanitized

💡 Data masking happens at the application layer, not the database layer. Always mask PII before returning it in API responses.
Always sanitize data before logging.
CloudWatch Logs can be accessed by anyone with the right IAM permissions, so treat logs as potentially public.


Part V

Build a Multi-Tenant Data Isolation Pattern

Create the DynamoDB Table

Step 01: Open the DynamoDB console → Create table

  • Table name: MultiTenantApp
  • Partition key: PK (String)
  • Sort key: SK (String)
  • Table settings: Customize settings
  • Read/write capacity settings: On-demand

Step 02: Click Create table

Add Sample Data

Step 03: Click into the MultiTenantApp table

Step 04: Click Explore table itemsCreate item

Step 05: Switch to JSON view and add these items one at a time:

Tenant A: Order 1:

{
  "PK": {"S": "TENANT#tenant-a#USER#user-001"},
  "SK": {"S": "ORDER#2024-001"},
  "orderTotal": {"N": "149.99"},
  "status": {"S": "shipped"},
  "tenantId": {"S": "tenant-a"}
}

Tenant A: Order 2:

{
  "PK": {"S": "TENANT#tenant-a#USER#user-001"},
  "SK": {"S": "ORDER#2024-002"},
  "orderTotal": {"N": "89.50"},
  "status": {"S": "processing"},
  "tenantId": {"S": "tenant-a"}
}

Tenant B: Order 1:

{
  "PK": {"S": "TENANT#tenant-b#USER#user-002"},
  "SK": {"S": "ORDER#2024-003"},
  "orderTotal": {"N": "299.00"},
  "status": {"S": "delivered"},
  "tenantId": {"S": "tenant-b"}
}

Create the Multi-Tenant Lambda

Step 06: LambdaCreate function

  • Function name: MultiTenantQuery
  • Runtime: Python 3.12

Step 07: Paste this code:

import json
import boto3
from boto3.dynamodb.conditions import Key

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('MultiTenantApp')

def lambda_handler(event, context):
    """
    Multi-tenant data isolation using partition key prefixes.

    The tenant ID is extracted from the request context (simulating
    a Cognito JWT claim) and used to scope all database queries.
    A user in Tenant A can never see Tenant B's data.

    In production, the tenant ID comes from:
    - Cognito custom claims (custom:tenantId)
    - Lambda authorizer context
    - API Gateway request context
    """
    # In production: extract from JWT claims
    # tenant_id = event['requestContext']['authorizer']['claims']['custom:tenantId']
    tenant_id = event.get('tenantId', 'tenant-a')
    user_id = event.get('userId', 'user-001')

    # Query is automatically scoped to this tenant
    pk = f"TENANT#{tenant_id}#USER#{user_id}"

    response = table.query(
        KeyConditionExpression=Key('PK').eq(pk) & Key('SK').begins_with('ORDER#')
    )

    orders = []
    for item in response['Items']:
        orders.append({
            'orderId': item['SK'].replace('ORDER#', ''),
            'total': str(item['orderTotal']),
            'status': item['status']
        })

    return {
        'statusCode': 200,
        'body': json.dumps({
            'tenantId': tenant_id,
            'userId': user_id,
            'orderCount': len(orders),
            'orders': orders
        })
    }

Step 08: Click Deploy

Step 09: Add AmazonDynamoDBReadOnlyAccess to the Lambda execution role

Test Tenant Isolation

Step 10: Create test events

Tenant A query:

{"tenantId": "tenant-a", "userId": "user-001"}

Tenant B query:

{"tenantId": "tenant-b", "userId": "user-002"}

⚠️ Run each test and verify that Tenant A only sees their orders and Tenant B only sees theirs

💡 Partition key isolation is the most common multi-tenant pattern on the exam. For stronger isolation, combine it with IAM condition keys using dynamodb:LeadingKeys. This enforces isolation at the IAM policy level so even a bug in your application code can't leak data across tenants.


🏗️ What You Built | 📘 Exam Concepts Recap

What You Built Exam Concept
Stored database credentials in Secrets Manager Secret management for rotating credentials
Explored the AWSCURRENT staging label Secrets Manager versioning and seamless rotation
Created String and SecureString parameters Parameter Store types. Plaintext vs KMS-encrypted
Built a parameter hierarchy under /app/config/ Path-based config organization, GetParametersByPath
Cached secrets with @lru_cache at cold start Reducing API calls and cost for secret retrieval
Retrieved SecureString with WithDecryption=True KMS decryption of encrypted parameters
Masked email, phone, SSN in API responses Application-level PII data masking
Sanitized log output before writing to CloudWatch Preventing sensitive data leakage in logs
Used partition key prefixes (TENANT#...) Multi-tenant data isolation pattern
Scoped queries to a tenant from request context Token/claim-based tenant boundary enforcement

⚠️ Clean Up Protocol

  1. Secrets Manager → Select prod/database/credentialsActionsDelete secret (set minimum 7-day waiting period)
  2. Systems ManagerParameter Store → Select all parameters → Delete
  3. Lambda → Delete SecureConfigService, DataMaskingDemo, and MultiTenantQuery
  4. DynamoDB → Delete the MultiTenantApp table
  5. IAM → Delete Lambda execution roles
  6. CloudWatch → Delete log groups for all three functions

Key Takeaways

  1. Secrets Manager for credentials that need automatic rotation (RDS, Redshift, DocumentDB).
  2. Parameter Store for app config and non-rotating secrets.
  3. SecureString parameters are encrypted with KMS. Always use WithDecryption=True to read them.
  4. Don't resolve secrets at deploy time ({{resolve:...}}) if they rotate fetch at runtime with caching instead.
  5. Lambda environment variables are encrypted at rest by default. Use a CMK + in-code decryption for additional security.
  6. Mask PII in API responses and sanitize logs.
  7. Never log passwords, SSNs, or credit card numbers.
  8. Multi-tenant isolation: use partition key prefixes combined with IAM condition keys (dynamodb:LeadingKeys) for policy-enforced isolation.
  9. Secrets Manager costs $0.40/secret/month. Parameter Store Standard is free. Choose based on rotation needs, not just cost.
  10. Cache secrets at cold start to reduce API calls and cost. Use lru_cache for static secrets, TTL-based cache for rotating secrets.

Additional Resources


🏗️