luizmachado.dev

PT EN

Session 045 — SSM Parameter Store: Hierarchies, SecureString and Decision vs Secrets Manager

Prerequisite: session-044 (Secrets Manager, rotation, staging labels)


Session Objectives

  • Create and retrieve parameters in a hierarchy with get-parameters-by-path (recursive, filters)
  • Understand the three types: String, StringList, SecureString (with default key vs CMK)
  • Compare Standard vs Advanced vs Intelligent-Tiering tiers (cost, limits, policies)
  • Use Parameter Policies (Expiration, ExpirationNotification, NoChangeNotification) — Advanced only
  • Reference parameters in CDK/CloudFormation via dynamic references
  • Document the decision criteria between SSM Parameter Store and Secrets Manager

1. Parameter Types

[FACT] Parameter Store supports three types:

╔═════════════════╦══════════════════════════════════════════════════════╗
║ Tipo            ║ Características                                      ║
╠═════════════════╬══════════════════════════════════════════════════════╣
║ String          ║ Plaintext. Qualquer texto. Ex.: ARN, endpoint, flag  ║
╠═════════════════╬══════════════════════════════════════════════════════╣
║ StringList      ║ Lista de strings separadas por vírgula.              ║
║                 ║ Ex.: "sg-abc,sg-def" (usada com aws:ssm:type)        ║
╠═════════════════╬══════════════════════════════════════════════════════╣
║ SecureString    ║ Criptografado com KMS em repouso e em trânsito.      ║
║                 ║ Chave padrão aws/ssm ou CMK do cliente.              ║
╚═════════════════╩══════════════════════════════════════════════════════╝

1.1 SecureString — Default Key vs CMK

[FACT] SecureString with default key aws/ssm:
- Free (no additional KMS charges)
- AWS-managed key — no control over key policy
- Does not allow cross-account decrypt
- One key per region per account — no per-parameter isolation

[FACT] SecureString with CMK (Customer Managed Key):
- KMS charges per encryption operation
- Granular control via key policy (who can encrypt/decrypt)
- Allows cross-account: account B can decrypt if it has permission in account A's key policy
- Enables audit trail via CloudTrail per key

[FACT] SecureString throughput is limited by the region's KMS throughput (5,500–10,000 TPS depending on region and key type).


2. Tiers: Standard, Advanced, Intelligent-Tiering

[FACT] Complete comparison table (AWS documentation):

╔═══════════════════════════════╦═══════════════╦══════════════╗
║ Característica                ║ Standard      ║ Advanced     ║
╠═══════════════════════════════╬═══════════════╬══════════════╣
║ Parâmetros por região/conta   ║ 10.000        ║ 100.000      ║
║ Tamanho máximo do valor       ║ 4 KB          ║ 8 KB         ║
║ Parameter Policies            ║ Não           ║ Sim          ║
║ Compartilhamento cross-account║ Não           ║ Sim          ║
║ Custo de armazenamento        ║ Grátis        ║ $0,05/param/mês ║
╚═══════════════════════════════╩═══════════════╩══════════════╝

[FACT] Promotion from Standard → Advanced is irreversible: it is not possible to revert an Advanced parameter to Standard without deleting and recreating it (which would cause loss of data and version history). If you want to save costs, you must delete and recreate as Standard.

[FACT] Intelligent-Tiering: Parameter Store evaluates each PutParameter and creates Standard automatically, except when: value > 4 KB, account already has ≥ 10,000 parameters, or parameter uses policies. In that case, it promotes to Advanced. Recommended for teams with uncertain usage growth.

2.1 Throughput

[FACT] The default Parameter Store throughput is 40 TPS. With high-throughput enabled (paid), it increases to up to 10,000 TPS.

[FACT] The cost of high-throughput is $0.05 per 10,000 API calls (when enabled). If you only use 40 TPS or less, there is no reason to enable high-throughput.


3. Parameter Hierarchies

[FACT] Parameter Store supports hierarchical structure by path, with up to 15 levels of depth separated by /. The full name (path) has a limit of 2,048 characters.

3.1 Recommended hierarchy convention

/aplicação/ambiente/componente/chave

Exemplos práticos:
  /checkout/prod/database/host
  /checkout/prod/database/port
  /checkout/prod/database/name
  /checkout/prod/database/password          ← SecureString
  /checkout/staging/database/host
  /checkout/staging/database/password       ← SecureString
  /checkout/prod/redis/endpoint
  /checkout/prod/feature-flags/new-checkout-enabled
  /shared/prod/observability/datadog-api-key ← SecureString
  /shared/prod/certificates/tls-cert         ← String (PEM)

3.2 GetParametersByPath — Retrieve an entire hierarchy

[FACT] GetParametersByPath returns parameters under a path prefix. With --recursive, it includes all sublevels. With --with-decryption, it decrypts SecureString automatically.

/checkout/prod/database/host     ←─┐
/checkout/prod/database/port     ←─┤ GetParametersByPath("/checkout/prod/database", recursive=False)
/checkout/prod/database/password ←─┘

/checkout/prod/database/host     ←─┐
/checkout/prod/database/port     ←─┤ GetParametersByPath("/checkout/prod", recursive=True)
/checkout/prod/database/password ←─┤
/checkout/prod/redis/endpoint    ←─┘

3.3 IAM — Scope by hierarchy

[FACT] IAM permissions can be scoped by path prefix using the parameter ARN:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameter",
        "ssm:GetParametersByPath",
        "ssm:GetParameters"
      ],
      "Resource": [
        "arn:aws:ssm:us-east-1:123456789012:parameter/checkout/prod/*",
        "arn:aws:ssm:us-east-1:123456789012:parameter/shared/prod/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": ["kms:Decrypt"],
      "Resource": "arn:aws:kms:us-east-1:123456789012:key/abc-123"
    }
  ]
}

4. Parameter Policies (Advanced only)

[FACT] Parameter Policies allow associating automatic behaviors to an Advanced parameter. Three types:

╔═══════════════════════════════╦═════════════════════════════════════════╗
║ Tipo de Policy                ║ Comportamento                           ║
╠═══════════════════════════════╬═════════════════════════════════════════╣
║ Expiration                    ║ Deleta automaticamente o parâmetro em   ║
║                               ║ uma data/hora específica                ║
╠═══════════════════════════════╬═════════════════════════════════════════╣
║ ExpirationNotification        ║ Notifica via EventBridge N dias antes    ║
║                               ║ da data de expiração                    ║
╠═══════════════════════════════╬═════════════════════════════════════════╣
║ NoChangeNotification          ║ Notifica via EventBridge se o parâmetro ║
║                               ║ não foi modificado em N dias            ║
╚═══════════════════════════════╩═════════════════════════════════════════╝
// Exemplo de policy combinada em JSON (passada no --policies)
[
  {
    "Type": "Expiration",
    "Version": "1.0",
    "Attributes": {
      "Timestamp": "2026-12-31T23:59:59.000Z"
    }
  },
  {
    "Type": "ExpirationNotification",
    "Version": "1.0",
    "Attributes": {
      "Before": "15",
      "Unit": "Days"
    }
  },
  {
    "Type": "NoChangeNotification",
    "Version": "1.0",
    "Attributes": {
      "After": "30",
      "Unit": "Days"
    }
  }
]

[FACT] Policy events are emitted to EventBridge with source aws.ssm and detail-type Parameter Store Policy Action.


5. CDK Python — Complete Hierarchy

from aws_cdk import (
    Stack, SecretValue, aws_ssm as ssm,
    aws_kms as kms, aws_iam as iam,
    aws_lambda as _lambda, aws_ec2 as ec2,
)
from constructs import Construct

class ParameterStoreStack(Stack):
    def __init__(self, scope: Construct, construct_id: str,
                 app_name: str = "checkout", env: str = "prod",
                 **kwargs):
        super().__init__(scope, construct_id, **kwargs)

        prefix = f"/{app_name}/{env}"

        # ──────────────────────────────────────────────────────────────
        # CMK para SecureString (controle granular)
        # ──────────────────────────────────────────────────────────────
        param_key = kms.Key(self, "ParamKey",
            description=f"CMK para SSM Parameter Store — {app_name}/{env}",
            enable_key_rotation=True,
            alias=f"alias/ssm-{app_name}-{env}",
        )

        # ──────────────────────────────────────────────────────────────
        # Parâmetros String — configurações não-sensíveis
        # ──────────────────────────────────────────────────────────────
        ssm.StringParameter(self, "DBHost",
            parameter_name=f"{prefix}/database/host",
            string_value="my-cluster.cluster-xxxxx.us-east-1.rds.amazonaws.com",
            description="Endpoint do cluster RDS PostgreSQL",
            tier=ssm.ParameterTier.STANDARD,
        )

        ssm.StringParameter(self, "DBPort",
            parameter_name=f"{prefix}/database/port",
            string_value="5432",
            tier=ssm.ParameterTier.STANDARD,
        )

        ssm.StringParameter(self, "DBName",
            parameter_name=f"{prefix}/database/name",
            string_value="appdb",
            tier=ssm.ParameterTier.STANDARD,
        )

        ssm.StringParameter(self, "RedisEndpoint",
            parameter_name=f"{prefix}/redis/endpoint",
            string_value="my-redis.xxxxx.ng.0001.use1.cache.amazonaws.com:6379",
            tier=ssm.ParameterTier.STANDARD,
        )

        # ──────────────────────────────────────────────────────────────
        # StringList — lista de security groups permitidos
        # ──────────────────────────────────────────────────────────────
        ssm.StringListParameter(self, "AllowedSGs",
            parameter_name=f"{prefix}/network/allowed-security-groups",
            string_list_value=["sg-aaa111", "sg-bbb222", "sg-ccc333"],
            description="Security groups permitidos para o balanceador",
        )

        # ──────────────────────────────────────────────────────────────
        # SecureString — valores sensíveis (API keys, tokens)
        # Nota: CDK L2 ssm.StringParameter não suporta SecureString diretamente.
        # Use CfnParameter (L1) para SecureString com CMK.
        # ──────────────────────────────────────────────────────────────
        ssm.CfnParameter(self, "DatadogAPIKey",
            name=f"{prefix}/observability/datadog-api-key",
            type="SecureString",
            value="PLACEHOLDER_ROTATED_EXTERNALLY",   # será sobrescrito via CLI/pipeline
            description="Datadog API Key — SecureString com CMK",
            key_id=param_key.key_id,
            tier="Standard",
        )

        ssm.CfnParameter(self, "JWTSigningKey",
            name=f"{prefix}/auth/jwt-signing-key",
            type="SecureString",
            value="PLACEHOLDER",
            description="JWT signing key — SecureString com CMK",
            key_id=param_key.key_id,
            tier="Advanced",            # Advanced para poder usar Parameter Policies
            policies="""[
                {"Type":"NoChangeNotification","Version":"1.0","Attributes":{"After":"30","Unit":"Days"}},
                {"Type":"ExpirationNotification","Version":"1.0","Attributes":{"Before":"7","Unit":"Days"}}
            ]""",
        )

        # ──────────────────────────────────────────────────────────────
        # Lambda — leitura da hierarquia completa na inicialização
        # ──────────────────────────────────────────────────────────────
        app_fn = _lambda.Function(self, "AppFunction",
            runtime=_lambda.Runtime.PYTHON_3_12,
            handler="index.handler",
            code=_lambda.Code.from_inline("""
import boto3, os, json

ssm = boto3.client('ssm')
_config_cache = None

def load_config():
    global _config_cache
    if _config_cache:
        return _config_cache
    prefix = os.environ['PARAM_PREFIX']
    config = {}
    paginator = ssm.get_paginator('get_parameters_by_path')
    for page in paginator.paginate(
        Path=prefix,
        Recursive=True,
        WithDecryption=True,   # decripta SecureString automaticamente
    ):
        for param in page['Parameters']:
            # Extrai chave relativa ao prefix: /checkout/prod/database/host → database/host
            key = param['Name'].replace(prefix + '/', '').replace('/', '_')
            config[key] = param['Value']
    _config_cache = config
    return config

def handler(event, context):
    config = load_config()
    return {'statusCode': 200, 'body': json.dumps({'dbHost': config.get('database_host')})}
"""),
            environment={
                "PARAM_PREFIX": f"{prefix}",
            },
        )

        # Permissões mínimas — apenas leitura no prefix desta aplicação
        app_fn.add_to_role_policy(iam.PolicyStatement(
            actions=["ssm:GetParameter", "ssm:GetParametersByPath", "ssm:GetParameters"],
            resources=[
                f"arn:aws:ssm:{self.region}:{self.account}:parameter{prefix}/*"
            ],
        ))
        # Permissão KMS para decriptar SecureStrings com CMK
        param_key.grant_decrypt(app_fn)


class ParameterReferenceStack(Stack):
    """
    Demonstra como referenciar parâmetros SSM em outros recursos CDK.
    """
    def __init__(self, scope: Construct, construct_id: str, **kwargs):
        super().__init__(scope, construct_id, **kwargs)

        # Referência a parâmetro já existente (não cria novo)
        db_host_param = ssm.StringParameter.from_string_parameter_name(
            self, "DBHostRef",
            string_parameter_name="/checkout/prod/database/host",
        )

        # Leitura do valor em tempo de síntese (deploy-time resolution)
        db_host_value = ssm.StringParameter.value_for_string_parameter(
            self, "/checkout/prod/database/host"
        )

        # Versão específica
        db_host_v2 = ssm.StringParameter.value_for_string_parameter(
            self, "/checkout/prod/database/host",
            # version=2  # versão específica
        )

6. Python — Hierarchy Reading with Cache

"""
Carregador de configuração baseado em SSM Parameter Store.
Padrão: carrega hierarquia completa no cold start, cacheia em memória.
"""
import os
import boto3
import logging
from typing import Any, Optional
from functools import lru_cache
from dataclasses import dataclass

logger = logging.getLogger(__name__)
ssm = boto3.client("ssm")


@dataclass
class AppConfig:
    """Configuração tipada da aplicação."""
    db_host: str
    db_port: int
    db_name: str
    db_password: str         # SecureString — decriptado em runtime
    redis_endpoint: str
    jwt_signing_key: str     # SecureString
    feature_new_checkout: bool

    @classmethod
    def from_param_dict(cls, params: dict[str, str]) -> "AppConfig":
        return cls(
            db_host=params["database_host"],
            db_port=int(params.get("database_port", "5432")),
            db_name=params["database_name"],
            db_password=params["database_password"],
            redis_endpoint=params["redis_endpoint"],
            jwt_signing_key=params["auth_jwt-signing-key"],
            feature_new_checkout=params.get("feature-flags_new-checkout-enabled", "false").lower() == "true",
        )


def load_parameters_by_path(
    path_prefix: str,
    with_decryption: bool = True,
) -> dict[str, str]:
    """
    Carrega todos os parâmetros sob path_prefix recursivamente.
    Retorna dict com chave = path relativo (/ substituído por _).
    Ex.: /checkout/prod/database/host → "database_host"
    """
    params: dict[str, str] = {}
    paginator = ssm.get_paginator("get_parameters_by_path")

    pages = paginator.paginate(
        Path=path_prefix,
        Recursive=True,
        WithDecryption=with_decryption,
        PaginationConfig={"PageSize": 10},  # máximo por página
    )

    for page in pages:
        for param in page.get("Parameters", []):
            relative_key = (
                param["Name"]
                .removeprefix(path_prefix)   # remove /checkout/prod
                .lstrip("/")                  # remove / inicial
                .replace("/", "_")            # /database/host → database_host
            )
            params[relative_key] = param["Value"]
            logger.debug("Loaded param: %s (version %d)", param["Name"], param["Version"])

    logger.info("Loaded %d parameters from %s", len(params), path_prefix)
    return params


# Cache em nível de módulo — persiste entre invocações Lambda (warm start)
_config_cache: Optional[AppConfig] = None


def get_config(force_reload: bool = False) -> AppConfig:
    """
    Retorna configuração da aplicação.
    Cacheia entre invocações Lambda para evitar chamadas SSM repetidas.
    """
    global _config_cache
    if _config_cache is None or force_reload:
        prefix = os.environ.get("PARAM_PREFIX", "/checkout/prod")
        params = load_parameters_by_path(prefix)
        _config_cache = AppConfig.from_param_dict(params)
    return _config_cache


def get_single_param(name: str, with_decryption: bool = True) -> str:
    """Busca um único parâmetro pelo nome completo."""
    response = ssm.get_parameter(Name=name, WithDecryption=with_decryption)
    return response["Parameter"]["Value"]


def get_params_by_names(names: list[str], with_decryption: bool = True) -> dict[str, str]:
    """
    Busca múltiplos parâmetros por nome em uma única chamada.
    GetParameters aceita até 10 nomes por chamada.
    Invalida nomes inválidos reportados em InvalidParameters.
    """
    result: dict[str, str] = {}

    # Processar em lotes de 10 (limite da API)
    for i in range(0, len(names), 10):
        batch = names[i:i + 10]
        response = ssm.get_parameters(Names=batch, WithDecryption=with_decryption)

        for param in response.get("Parameters", []):
            result[param["Name"]] = param["Value"]

        invalid = response.get("InvalidParameters", [])
        if invalid:
            logger.warning("Invalid parameter names: %s", invalid)

    return result


# Lambda handler que usa configuração carregada do SSM
def lambda_handler(event: dict, context) -> dict:
    config = get_config()

    # Exemplo: nova feature flag acessível sem recarregar configuração
    # Para feature flags, recomenda-se TTL curto ou recarregar periodicamente
    if config.feature_new_checkout:
        return process_new_checkout(event, config)
    return process_legacy_checkout(event, config)


def process_new_checkout(event: dict, config: AppConfig) -> dict:
    return {"statusCode": 200, "flow": "new-checkout", "db": config.db_host}


def process_legacy_checkout(event: dict, config: AppConfig) -> dict:
    return {"statusCode": 200, "flow": "legacy", "db": config.db_host}

7. CLI — Essential Examples

# 1. Criar parâmetro String na hierarquia
aws ssm put-parameter \
  --name "/checkout/prod/database/host" \
  --value "my-cluster.cluster-xxx.us-east-1.rds.amazonaws.com" \
  --type String \
  --description "Endpoint RDS PostgreSQL prod" \
  --tier Standard \
  --tags Key=app,Value=checkout Key=env,Value=prod

# 2. Criar SecureString com CMK
aws ssm put-parameter \
  --name "/checkout/prod/database/password" \
  --value "$(openssl rand -base64 32)" \
  --type SecureString \
  --key-id "alias/ssm-checkout-prod" \
  --tier Standard

# 3. Criar parâmetro Advanced com Parameter Policies
aws ssm put-parameter \
  --name "/checkout/prod/auth/jwt-signing-key" \
  --value "$(openssl rand -base64 64)" \
  --type SecureString \
  --key-id "alias/ssm-checkout-prod" \
  --tier Advanced \
  --policies '[
    {"Type":"NoChangeNotification","Version":"1.0","Attributes":{"After":"30","Unit":"Days"}},
    {"Type":"ExpirationNotification","Version":"1.0","Attributes":{"Before":"7","Unit":"Days"}}
  ]'

# 4. GetParametersByPath — toda a hierarquia de prod (recursivo)
aws ssm get-parameters-by-path \
  --path "/checkout/prod" \
  --recursive \
  --with-decryption \
  --query 'Parameters[*].{Name:Name,Value:Value,Type:Type,Version:Version}' \
  --output table

# 5. GetParametersByPath — apenas /database (sem recursão)
aws ssm get-parameters-by-path \
  --path "/checkout/prod/database" \
  --with-decryption \
  --query 'Parameters[*].{Name:Name,Value:Value}' \
  --output table

# 6. GetParametersByPath — com filtro por tier
aws ssm get-parameters-by-path \
  --path "/checkout/prod" \
  --recursive \
  --parameter-filters "Key=tier,Values=Advanced"

# 7. Buscar múltiplos parâmetros de uma vez (máx. 10 por chamada)
aws ssm get-parameters \
  --names \
    "/checkout/prod/database/host" \
    "/checkout/prod/database/port" \
    "/checkout/prod/redis/endpoint" \
  --with-decryption \
  --query '{
    Parameters: Parameters[*].{Name:Name,Value:Value},
    Invalid: InvalidParameters
  }'

# 8. Histórico de versões de um parâmetro
aws ssm get-parameter-history \
  --name "/checkout/prod/database/password" \
  --with-decryption \
  --query 'Parameters[*].{Version:Version,Modified:LastModifiedDate,User:LastModifiedUser}' \
  --output table

# 9. Atualizar valor sem sobrescrever (--overwrite omitido = erro se existir)
#    Com --overwrite = incrementa versão automaticamente
aws ssm put-parameter \
  --name "/checkout/prod/feature-flags/new-checkout-enabled" \
  --value "true" \
  --type String \
  --overwrite

# 10. Listar parâmetros com filtro por nome (busca prefix)
aws ssm describe-parameters \
  --parameter-filters "Key=Name,Option=BeginsWith,Values=/checkout/prod/" \
  --query 'Parameters[*].{Name:Name,Type:Type,Tier:Tier,Modified:LastModifiedDate}' \
  --output table

# 11. Habilitar high-throughput (quando necessário, tem custo)
aws ssm update-service-setting \
  --setting-id "arn:aws:ssm:us-east-1:$(aws sts get-caller-identity --query Account --output text):servicesetting/ssm/parameter-store/high-throughput-enabled" \
  --setting-value true

# 12. Configurar Intelligent-Tiering como padrão
aws ssm update-service-setting \
  --setting-id "arn:aws:ssm:us-east-1:$(aws sts get-caller-identity --query Account --output text):servicesetting/ssm/parameter-store/default-parameter-tier" \
  --setting-value Intelligent-Tiering

# 13. Cross-account: compartilhar parâmetro Advanced com outra conta
aws ssm add-tags-to-resource \
  --resource-type Parameter \
  --resource-id "/checkout/prod/shared-config" \
  --tags Key=shareable,Value=true
# (o compartilhamento real usa Resource Share no RAM para parâmetros Advanced)

8. Dynamic References — CloudFormation / CDK

[FACT] CloudFormation and CDK support references to SSM parameters and Secrets Manager directly in resource properties via dynamic references, resolved at deploy-time.

Sintaxe das dynamic references:

  String/StringList:
    {{resolve:ssm:/checkout/prod/database/host}}
    {{resolve:ssm:/checkout/prod/database/host:2}}   ← versão específica

  SecureString (somente em propriedades que suportam):
    {{resolve:ssm-secure:/checkout/prod/database/password}}
    {{resolve:ssm-secure:/checkout/prod/database/password:3}}

  Secrets Manager:
    {{resolve:secretsmanager:MySecret:SecretString:username}}
    {{resolve:secretsmanager:MySecret:SecretString:password::AWSPENDING}}

[FACT] Dynamic references ssm-secure (SecureString) have limited support — they only work in specific CloudFormation resource properties (e.g., AWS::RDS::DBInstance MasterUserPassword). They do not work in arbitrary properties or in Outputs.

# CDK Python — usando StringParameter.value_for_string_parameter
# Resolve em deploy-time (CloudFormation token)
from aws_cdk import aws_ssm as ssm, aws_rds as rds

db_name_value = ssm.StringParameter.value_for_string_parameter(
    self, "/checkout/prod/database/name"
)

# StringParameter.value_from_lookup = resolve em SYNTH-time (útil para conditionals)
db_host_at_synth = ssm.StringParameter.value_from_lookup(
    self, "/checkout/prod/database/host"
)
# CUIDADO: value_from_lookup requer que o parâmetro já exista na conta/região
# Retorna dummy value durante primeiro synth (CDK bootstrap)

9. Decision: SSM Parameter Store vs Secrets Manager

[CONSENSUS] The central decision criterion is: do you need native automatic rotation? If yes, Secrets Manager. If not, SSM Parameter Store (Standard) is significantly cheaper.

╔══════════════════════════════════════╦═══════════════════╦═══════════════════════╗
║ Critério                             ║ SSM Standard/Adv  ║ Secrets Manager       ║
╠══════════════════════════════════════╬═══════════════════╬═══════════════════════╣
║ Custo por segredo/parâmetro          ║ Grátis (Std)      ║ $0,40/mês             ║
║                                      ║ $0,05/mês (Adv)   ║                       ║
╠══════════════════════════════════════╬═══════════════════╬═══════════════════════╣
║ Custo de API call                    ║ Grátis (≤40 TPS)  ║ $0,05 / 10k calls     ║
║                                      ║ $0,05/10k (HT)    ║                       ║
╠══════════════════════════════════════╬═══════════════════╬═══════════════════════╣
║ Rotação automática nativa            ║ Não               ║ Sim (4h a 365 dias)   ║
╠══════════════════════════════════════╬═══════════════════╬═══════════════════════╣
║ Staging labels (AWSCURRENT etc.)     ║ Versões simples   ║ Sim (completo)        ║
╠══════════════════════════════════════╬═══════════════════╬═══════════════════════╣
║ Throughput padrão                    ║ 40 TPS            ║ ~200 TPS (default)    ║
║ Throughput máximo                    ║ 10.000 TPS (pago) ║ ~10.000 TPS           ║
╠══════════════════════════════════════╬═══════════════════╬═══════════════════════╣
║ Tamanho máximo do valor              ║ 4 KB / 8 KB       ║ 64 KB                 ║
╠══════════════════════════════════════╬═══════════════════╬═══════════════════════╣
║ Cross-account sharing nativo         ║ Advanced apenas   ║ Sim (resource policy) ║
╠══════════════════════════════════════╬═══════════════════╬═══════════════════════╣
║ Parameter Policies (expiração etc.)  ║ Advanced apenas   ║ N/A                   ║
╠══════════════════════════════════════╬═══════════════════╬═══════════════════════╣
║ Integração CloudFormation/CDK        ║ Sim (todas props) ║ Parcial (via dynamic) ║
╚══════════════════════════════════════╩═══════════════════╩═══════════════════════╝

9.1 Decision Tree

Você precisa de rotação automática (RDS, Redis, API keys)?
  SIM → Secrets Manager

Você precisa armazenar mais de 10.000 parâmetros por região?
  SIM → SSM Advanced OU Secrets Manager

O valor tem mais de 64 KB?
  SIM → S3 (Parameter Store e Secrets Manager não suportam)

Você precisa compartilhar cross-account sem VPC Endpoint?
  SIM → Secrets Manager (resource policy) OU SSM Advanced (RAM)

Você tem >1.000 parâmetros com leitura intensa (>40 TPS)?
  SIM, frequente → SSM com high-throughput habilitado
  SIM, mas é só config → cache na aplicação, SSM Standard

Caso contrário:
  Configurações não-sensíveis    → SSM Standard (grátis, String)
  Configurações sensíveis simples → SSM Standard SecureString (grátis)
  Credenciais com rotação         → Secrets Manager
  Feature flags / configs         → SSM Standard (grátis, hierarquia limpa)

9.2 Cost example for 1,000 parameters

SSM Parameter Store Standard:
  1.000 parâmetros × $0,00 = $0,00/mês
  1.000.000 chamadas/mês × $0,00 = $0,00/mês (até 40 TPS)
  TOTAL: $0,00/mês

Secrets Manager:
  1.000 segredos × $0,40 = $400,00/mês
  1.000.000 chamadas × $0,005 = $5,00/mês
  TOTAL: ~$405,00/mês

→ Para configurações simples, SSM Standard é 100% mais barato.
→ Para 10 credenciais de banco com rotação, SM = $4,05/mês — justificado.

10. Pitfalls

[FACT] Standard → Advanced is irreversible: it is not possible to revert without deleting and recreating. If you delete and recreate, you lose the version history and any CloudFormation reference that used the ARN.

[FACT] value_from_lookup (synth-time) requires the parameter to exist: if the parameter does not exist in the account/region during cdk synth, CDK returns a dummy value. The created resource will use the wrong value on the first deploy. Prefer value_for_string_parameter (deploy-time) when possible.

[FACT] SecureString is not supported in all CloudFormation properties: the dynamic reference {{resolve:ssm-secure:...}} only works in specific properties. For most properties, use Secrets Manager with {{resolve:secretsmanager:...}}.

[FACT] GetParametersByPath has pagination of maximum 10 items per page: if using --max-results 10 (the maximum), large hierarchies need multiple pages. Always use paginator in the SDK or --query with NextToken in the CLI.

[CONSENSUS] Do not use SSM Parameter Store as a complete replacement for Secrets Manager for rotated credentials: SSM has no native rotation mechanism. Implementing manual rotation via Lambda + EventBridge is possible, but reproduces the work that Secrets Manager already does natively.

[FACT] Tags are not inherited by hierarchy: tags applied to /checkout/prod are NOT automatically applied to /checkout/prod/database/host. Each parameter needs its own tags for chargeback.

[FACT] SecureString with default key aws/ssm does not allow cross-account: if an application in another account needs to read the parameter, use a CMK with a key policy that authorizes the external account.


Reflection Exercise

A platform team needs to manage configurations for 5 applications, each in 3 environments (prod, staging, dev), totaling ≈ 2,000 parameters. Some configurations are sensitive (third-party API keys, tokens), others are not (endpoints, feature flags). There are also 8 database credentials that must be rotated monthly.

Propose the complete configuration management architecture, answering:

  1. Which parameters go to SSM Standard, SSM Advanced, and Secrets Manager? Why?
  2. How to structure the naming hierarchy so that each application can only read its own parameters via IAM?
  3. With 5 applications × 3 environments doing simultaneous cold starts (e.g., deploy), how to avoid SSM throttling (40 TPS default)?
  4. How do the 8 database credentials fit into this architecture alongside the other SSM parameters?
  5. What is the estimated total monthly cost of the proposed solution?

References