In AWS SSM Parameter Store, what does storing a value as a SecureString change compared with a String parameter, and what does a caller need in order to get the plaintext back?
answer
- three types, one of them encrypted
- the read is silent when you forget
- two permissions, two services
- the denial names the wrong service
- aws/ssm unless you name your own key
basics
~20 sA SecureString parameter is encrypted with a KMS key instead of being stored in the clear. Reading the plaintext needs two things: the GetParameter call must ask for decryption via WithDecryption, and the caller's IAM policy must allow kms:Decrypt on the key as well as ssm:GetParameter.
solid answer
~40 sParameter Store has three types: `String`, `StringList` and `SecureString`. The first two are stored in the clear and anyone with `ssm:GetParameter` on the name reads them. A `SecureString` is encrypted with a KMS key — the AWS managed `aws/ssm` key by default, or a customer managed key you name at write time. Two things then change for readers. First, the API call must explicitly request decryption: `GetParameter` with `WithDecryption` set to true, or `--with-decryption` on the CLI. Without it the call succeeds and hands back the ciphertext, which is a confusing failure mode because nothing errors. Second, authorization is now two-sided: the caller needs `ssm:GetParameter` on the parameter ARN **and** `kms:Decrypt` on the key. A role that has only the SSM permission gets AccessDenied from KMS, and the message names KMS, not Parameter Store.
code
bash · 9 linesaws ssm put-parameter --name /app/prod/api-key --type SecureString \
--key-id alias/app-secrets --value 'abc123'
# succeeds, but Value is the ciphertext
aws ssm get-parameter --name /app/prod/api-key --query Parameter.Value --output text
# returns abc123; caller needs kms:Decrypt on alias/app-secrets
aws ssm get-parameter --name /app/prod/api-key --with-decryption \
--query Parameter.Value --output textgo deeper
Recall the three parameter types, that SecureString means KMS-encrypted, and that reading it requires --with-decryption on the CLI or WithDecryption in the SDK. Say that the caller also needs KMS permission.
Explain that the undecrypted read succeeds and returns ciphertext, and that authorization spans two services — ssm:GetParameter on the parameter ARN plus kms:Decrypt on the key — so the denial message names KMS.
Show how the split permission is used deliberately: broad parameter read access for configuration, a customer managed key policy narrowing who can decrypt the few sensitive values, and CloudTrail decrypt events as the audit trail.
Own the convention across the estate: which key encrypts what, how key policies are reviewed, and where the boundary sits between configuration anyone may read and credentials gated by a second authorization surface.
## The three parameter types AWS Systems Manager Parameter Store stores a value under a name, and every parameter has a type: - **`String`** — an opaque string, stored as given. - **`StringList`** — a comma-separated list, returned as a single string that the SDK does not split for you. - **`SecureString`** — the value is encrypted with AWS KMS before storage. The type is chosen at write time with `PutParameter --type`, and a parameter's type can be changed on overwrite. Only `SecureString` involves KMS. ```bash aws ssm put-parameter --name /app/prod/api-key --type SecureString --value 'abc123' aws ssm get-parameter --name /app/prod/api-key # returns ciphertext aws ssm get-parameter --name /app/prod/api-key --with-decryption # returns abc123 ``` ## What encryption changes at read time The surprise for most people is the first of those two `get-parameter` calls. It does **not** fail. It returns a successful response whose `Value` is the encrypted blob. If your application forgets `WithDecryption`, it will happily pass that blob to a database driver and you will spend an afternoon debugging a wrong-password error whose real cause is a missing flag. The same applies to `GetParameters` and `GetParametersByPath`, which take the same option and apply it to every `SecureString` in the result. ## What encryption changes for IAM The second change is authorization. Reading a plaintext `String` needs one permission: ```json { "Effect": "Allow", "Action": "ssm:GetParameter", "Resource": "arn:aws:ssm:eu-west-1:111122223333:parameter/app/prod/*" } ``` Reading a `SecureString` needs that **plus** `kms:Decrypt` on the key that encrypted it. Two cases behave differently: - With the **AWS managed key** `aws/ssm`, the key policy already delegates to IAM for callers in the same account, so adding `kms:Decrypt` to the caller's identity policy is usually enough. You cannot edit that key's policy, which is why it does not work for cross-account access. - With a **customer managed key**, both sides must line up: the caller's identity policy allows `kms:Decrypt`, and the key policy allows that principal. This is the configuration you want for anything sensitive, precisely because the key policy gives you a second, independent gate. When it goes wrong, the error is an AccessDenied that names the KMS action, not the SSM one — a useful diagnostic signal. A role that can list and read parameter *names* and *metadata* (`ssm:DescribeParameters` returns metadata, never values) but cannot decrypt will look half-broken. ## Why this matters beyond the mechanics Separating the two permissions is the actual security value. Storing a credential as a `SecureString` under a customer managed key means that granting someone broad `ssm:GetParameter*` does not by itself hand them your secrets — they still need to be named on the key. That lets you give a large group read access to configuration while keeping the handful of encrypted values behind a narrower gate, without splitting them into different services. The corollary, and a thing candidates get wrong: `SecureString` protects the value at rest and in transit, not after retrieval. Once decrypted it is an ordinary string in your process, and if you inject it into a container's environment or log the parameter response, the encryption bought you nothing at that point. ## Practical notes - **CloudTrail** records the `GetParameter` call and the KMS `Decrypt`, but not the value, so you get an audit trail of who read a secret without the secret itself leaking into logs. This is a real reason to prefer `SecureString` even for values you do not consider highly sensitive. - **Hierarchies still work.** `GetParametersByPath --path /app/prod/ --recursive --with-decryption` returns a mixed subtree of `String` and `SecureString` parameters in one call, decrypting the encrypted ones. - **There is no rotation.** `SecureString` is storage, not lifecycle. If the value needs scheduled rotation, that is what Secrets Manager is for. - **Size limits are unchanged** by the type; the standard tier's value cap applies to the plaintext you submit.
- An application reads a SecureString and the database rejects the password. What do you check first?Whether the call passed `WithDecryption`. Omitting it returns HTTP 200 with the ciphertext as the value, so the application forwards an encrypted blob as if it were the password and nothing in the SSM response signals a problem. Check the retrieved value's shape before hunting through database configuration.
- Why is a customer managed KMS key preferable to aws/ssm for a sensitive parameter?Because you can edit its key policy. That gives a second, independent authorization gate: a principal needs both `ssm:GetParameter` and to be permitted on the key, so a broad SSM grant does not silently expose the value. It is also the only route to cross-account decryption, since the AWS managed key's policy cannot be changed.
- Does storing a value as SecureString stop it from leaking?Only up to the point of retrieval. It protects the value at rest and gives you a CloudTrail record of every decrypt, but once your code holds the plaintext the usual exposures apply — logging the response, injecting it into a process environment that a diagnostic dump captures, or echoing it in an error. Encryption at rest is one control, not the whole story.
saying these in an interview costs you the question
- Assuming GetParameter errors when decryption is not requested
- Granting ssm:GetParameter and expecting SecureString reads to work
- Thinking SecureString parameters rotate automatically
- Believing String parameters are encrypted too, just less strongly
- Treating a decrypted value as still protected inside the application