Hedronite · Cert Lesson · AWS DOP-C02 · Task Statements 1.1 and 2.1 · Sun 2026-10-25

AWS DOP-C02: Parameter Store hierarchies, versions, labels and tiers, and what a local emulator will not tell you

The path chooses what you read, the label chooses which version, and the tier chooses what you are allowed to store.

Lesson Class: Cert (AWS DOP-C02 · Task 1.1 build and deployment secrets · Task 2.1 configuration management)
Tested with: aws-cli 2.38.5 · moto 5.2.4.dev0 (local emulator)
Lab: lab/run.sh · out/cert-lab.txt
Source: DOP-C02 exam guide · Systems Manager User Guide (hierarchies, labels, KMS, tiers) · API Reference (GetParametersByPath, PutParameter, GetParameter)
Recursive or Nothing
Without Recursive, a path read stops one level down, silently.
Labels Move, Pipelines Don't
Read name:label and move the label to promote or roll back.
Check InvalidLabels
A bad label exits 0 and attaches nothing.
The emulator said yes three times. The real service would have said no.

The problem

An online shop runs in two environments, dev and prod. Each needs a database host and a database password, and the values differ between them. A deployment pipeline has to read every setting for prod in one call, because one call per value means the pipeline carries a list of names that drifts out of date. It must also roll a changed value forward and back without anyone editing the pipeline definition. The password has to stay encrypted, and only callers allowed to decrypt it should see it. Now and then a value, such as a long certificate, is larger than usual.

AWS Systems Manager Parameter Store, a managed key-value store for configuration data and secrets, covers all four needs. The DOP-C02 exam guide names it under Task Statement 1.1, "Implement CI/CD pipelines" (CI/CD is continuous integration and continuous delivery), as part of the skill "Managing build and deployment secrets", and under Task Statement 2.1, where Systems Manager is one of the configuration management services a candidate must choose between. AWS Secrets Manager, named next to it in the guide, is a separate service not covered here. Exam questions describe a pipeline like this one and ask which call, flag, tier or label makes it work.

Every command below is AWS CLI (command line interface) 2.38.5 run against a local AWS emulator (moto 5.2.4.dev0) on 127.0.0.1 with throwaway credentials and no AWS account. The emulator diverged from the documented service in three places, and each one is pointed out where it appears.

The idea, in plain words

A parameter is a named value, and slashes in the name make a hierarchy that one call can read as a subtree. Every change creates a new numbered version, and a label is a movable name for one version, so a pipeline can read name:label while an operator moves the label. A SecureString encrypts its value through KMS (AWS Key Management Service) and decrypts it only on request. Tiers, called Standard and Advanced, decide the maximum value size, the number of parameters allowed, and whether parameter policies (rules such as an expiration time) are permitted. In one line: the path chooses what you read, the label chooses which version, and the tier chooses what you are allowed to store.

Hierarchies and GetParametersByPath

A hierarchy is, in the documentation's words, "a parameter name that includes a path that you define by using forward slashes (/)". A name can have at most 15 levels. Paths matter because /shop/prod/... and /shop/dev/... share a shape, so a pipeline needs to know only the environment prefix; otherwise it must carry every individual name.

The operation that reads a subtree is GetParametersByPath, which the CLI exposes as get-parameters-by-path. It takes a Path, a Recursive flag, MaxResults and NextToken. MaxResults accepts 1 to 10, and results are best effort: a page can hold anywhere from zero to MaxResults items, and a NextToken in the response tells the caller to ask again. The path is the parameter name without its last part, because "the last part of the parameter name can't be in the path".

The lab created four parameters with put-parameter: /shop/prod/db/host, /shop/prod/db/password (a SecureString), /shop/prod/log-level and /shop/dev/db/host. A small shell function printed each response's Version and Tier, and all four reported Version,1,Tier,Standard. The lab then read /shop/prod twice.

$ aws ssm get-parameters-by-path --path /shop/prod --query 'Parameters[].Name' --output text
/shop/prod/log-level
[exit 0]

$ aws ssm get-parameters-by-path --path /shop/prod --recursive --query 'Parameters[].[Name,Type,Value]' --output text
/shop/prod/db/host	String	db.prod.internal
/shop/prod/db/password	SecureString	kms:alias/aws/ssm:pr0d-s3cret
/shop/prod/log-level	String	warn
[exit 0]

Without --recursive, the call returned only /shop/prod/log-level, the one parameter directly under the path; the two under /shop/prod/db sit a level deeper. The second call, with --recursive, returned all three parameters in the prod subtree and nothing from /shop/dev, which is the separation the naming scheme is meant to give. The [exit 0] lines, echoed by the lab script, show that the shortfall in the first call is silent.

The last-part rule also showed up in the lab:

$ aws ssm get-parameters-by-path --path /shop/prod/db/host --query 'length(Parameters)' --output text
0
[exit 0]

Passing a full parameter name as the path returned 0 parameters and exit status 0, not an error. A single known name is read with get-parameter instead.

On the exam: a stem that says one call must return all settings for an environment points to GetParametersByPath with a path and the recursive option. Watch the documentation's Important note on access: a user allowed on /a can read /a/b through a recursive call on /a, even with an explicit IAM (Identity and Access Management) deny on /a/b. A deny on one deep parameter does not hide it from someone who holds the parent path.

Symptom: the pipeline reads /shop/prod and gets only log-level, without the database settings.

Cause: GetParametersByPath reads one level unless Recursive is set, as steps 2a and 2b of the lab showed.

Fix: pass --recursive and handle NextToken, because the service returns at most 10 parameters per call.

SecureString parameters and KMS

Parameter Store has three parameter types: String, StringList (items separated by commas) and SecureString. A SecureString is a parameter with a plaintext name and an encrypted value, and Parameter Store uses KMS to encrypt and decrypt it. If no KeyId is given, the system uses the AWS managed key with the alias aws/ssm. A customer managed key can be named through KeyId instead; only symmetric keys are supported.

A standard SecureString (at most 4096 bytes) is encrypted directly under the KMS key, while an advanced one uses envelope encryption with a data key generated per value. The permissions follow: kms:Encrypt to write a standard SecureString, kms:GenerateDataKey to write an advanced one, and kms:Decrypt to read either. The encryption context is a key named PARAMETER_ARN whose value is the parameter's ARN (Amazon Resource Name). Access control policies cannot be set on the default aws/ssm key, so key-level control needs a customer managed key.

A read returns the value encrypted unless the caller sets WithDecryption, which is --with-decryption in the CLI. The flag is ignored for String and StringList parameters. The lab read the same SecureString both ways. The first line below is from the read without the flag (step 2b), and the second is from the read with it (step 2c):

/shop/prod/db/password	SecureString	kms:alias/aws/ssm:pr0d-s3cret
/shop/prod/db/password	pr0d-s3cret

The first line is where the emulator parts from the documented service. The documented response for a SecureString read without decryption is ciphertext, such as "Value": "AQECAHgnOkMROh5LaLXkA4j0+vYi6tmM17Lg". The emulator returned the plaintext with a prefix naming the key alias, kms:alias/aws/ssm:pr0d-s3cret. The emulator therefore demonstrates the flag, since the decrypted read returned pr0d-s3cret, but it does not demonstrate encryption. The lab defined no IAM or key policies, so everything said here about KMS permissions comes from the documentation alone.

On the exam: a build role that must read a SecureString needs two grants. The documentation's example policy allows ssm:GetParameter* on arn:aws:ssm:us-east-1:111122223333:parameter/ITParameters/* and kms:Decrypt on one customer managed key. Because decrypting needs kms:Decrypt, an SSM-only grant cannot return plaintext, and scoping the SSM action to a path keeps the role from reading unrelated parameters.

Symptom: a build step receives a long base64-looking string instead of the password.

Cause: the value is a SecureString and the call omitted WithDecryption, so the encrypted value came back as documented.

Fix: pass --with-decryption and grant kms:Decrypt on the key to that role, with ssm:GetParameter* scoped to the path.

Versions and labels

A version is a numbered snapshot of a parameter's value. PutParameter has an Overwrite option that defaults to false, so putting to an existing name fails with ParameterAlreadyExists. With overwrite, Parameter Store creates a new version and increments the number. It keeps the 100 most recent versions and deletes the oldest when another arrives. If the oldest version carries a label, it refuses and returns ParameterMaxVersionLimitExceeded instead.

A label is a user-defined alias for one version, and GetParameter accepts name:version or name:label as the name. Labels exist so that a pipeline reading name:known-good never changes while the operator moves the label from one version to another. A pipeline that hard-codes name:2 has to be edited for every promotion. The rules for labels are strict:

A label that breaks a rule is not attached, and the system lists it under InvalidLabels. A request for a missing version "fails. It doesn't fall back to the latest or default value".

The lab first tried to overwrite without the flag, then with it, then read the parameter through four selectors (lines trimmed, none altered):

### 3a overwrite without --overwrite
aws: [ERROR]: An error occurred (ParameterAlreadyExists) when calling the PutParameter operation: The parameter already exists. To overwrite this value, set the overwrite option in the request to true.
[exit 254]

### 3b with --overwrite: a new version
Version,2,Tier,Standard
[exit 0]

### 3c read by version selector
None	2	db2.prod.internal
[/shop/prod/db/host exit 0]
None	1	db.prod.internal
[/shop/prod/db/host:1 exit 0]
None	2	db2.prod.internal
[/shop/prod/db/host:2 exit 0]
aws: [ERROR]: An error occurred (ParameterVersionNotFound) when calling the GetParameter operation: Systems Manager could not find version 7 of /shop/prod/db/host. Verify the version and try again.
[/shop/prod/db/host:7 exit 254]

The put without --overwrite failed with ParameterAlreadyExists and exit status 254, and the put with it created version 2. Selector :1 returned the old host and :2 the new one. Selector :7 failed with ParameterVersionNotFound, with no fallback to the latest value. The None column is the second emulator divergence: the documented Selector field echoes the requested version or label.

The labels came next: attach known-good to version 1, read by label, move it to version 2, then print the history as version and labels:

### 4a label version 1 as known-good, read by label
{
    "InvalidLabels": [],
    "ParameterVersion": 1
}
None	1	db.prod.internal

### 4b move the label to version 2
{
    "InvalidLabels": [],
    "ParameterVersion": 2
}
1	
2	known-good

The empty InvalidLabels list means the label was applied, and reading /shop/prod/db/host:known-good returned version 1. After a second label-parameter-version call naming version 2, the history shows the label on version 2 only; no separate removal step was needed. Rolling back is the same call with --parameter-version 1, and the pipeline's read of name:known-good never changes, which is the whole reason to use a label rather than a version number.

Three bad labels came back like this:

{
    "InvalidLabels": [
        "2026-q4",
        "aws-prod",
        "ssm-x"
    ],
    "ParameterVersion": 2
}
[exit 0]

The call succeeded with exit status 0 while attaching none of the three labels. Finally, get-parameters-by-path --path /shop --recursive with a Label filter of known-good returned /shop/prod/db/host 2, so the filter finds which parameters under a path carry a label.

On the exam: name:label is the answer when a pipeline must follow a promotion without edits, and ParameterVersionNotFound is the answer when a stem names a version that does not exist. A stem that attaches a label while creating the parameter is a trap, because labels attach only to an existing version.

Symptom: label-parameter-version exits 0, but reads of name:label fail and the label is missing from the history.

Cause: the label broke a naming rule, such as starting with a number or with "aws" or "ssm", and came back in InvalidLabels (lab step 4c).

Fix: check InvalidLabels in the response, not only the exit code, and rename the label.

Tiers and parameter policies

A tier is the class of a parameter, and it sets storage attributes. Standard is the default and suits most configuration data.

StandardAdvanced
Maximum parameters per account and Region10,000100,000
Maximum value size4 KB8 KB
Parameter policiesNot supportedSupported
Sharing across AWS accountsNot supportedSupported
Changing tierUpgradeableNot downgradeable
CostNo additional chargeCharges apply

Standard can be upgraded to Advanced at any time, but Advanced cannot revert: the value would be truncated from 8 KB to 4 KB, policies would be lost, and the tiers encrypt differently. To return to Standard, delete and recreate the parameter. The account's default tier setting can be Standard, Advanced, or Intelligent-Tiering, which picks Advanced only when a request needs it: the value is over 4 KB, the request uses a parameter policy, or more than 10,000 parameters already exist in the Region. Tiers are also separate from throughput, which controls request rate.

Policies come in three types. Expiration deletes the parameter after it expires. ExpirationNotification starts an Amazon CloudWatch Events event about the expiration. NoChangeNotification starts an event if a parameter has not been modified for a set period. A parameter can carry at most 10 policies, and because policies need Advanced, a policy request on a Standard parameter requires the upgrade.

The lab tried the limits. Step 5a stored 5,000 x characters as a Standard String, 5b stored the same value with --tier Advanced, and 5c put a small value to the Advanced parameter with --tier Standard --overwrite:

Version,1,Tier,Standard
[exit 0]
Version,1,Tier,Advanced
[exit 0]
Version,2,Tier,Standard
[exit 0]
/shop/prod/big-adv	Standard	2

This is the third divergence. A 5,000-byte value exceeds the documented 4 KB Standard limit, yet 5a succeeded, and 5c moved an Advanced parameter back to Standard, confirmed by the describe call. The documented service allows neither, so this step shows only that the emulator does not enforce the rules; every tier rule above comes from the documentation.

On the exam: a value over 4 KB, a need for parameter policies, or more than 10,000 parameters each point to Advanced. Intelligent-Tiering as the default tier setting is the answer when a team wants Advanced chosen automatically only for requests that need it.

Symptom: an emulator-based pipeline test passes, then the real put of a large certificate file fails.

Cause: the value exceeds the Standard tier's 4 KB, and the emulator did not enforce the limit (lab step 5a).

Fix: set Tier Advanced for that parameter, or set the default tier to Intelligent-Tiering, and test size and tier rules against the real service.

Putting it together

The pipeline from the problem comes together in this order:

  1. Name parameters under /shop/<env>/... so each environment is a subtree.
  2. Read an environment with get-parameters-by-path --recursive --with-decryption, and follow NextToken until it is absent.
  3. Promote by moving a label such as known-good to the new version, with the pipeline reading name:label.
  4. Roll back by moving the label back to the earlier version.
  5. Keep secrets as SecureString, and give each role kms:Decrypt on the key plus ssm:GetParameter* scoped to its path.
  6. Choose Advanced only where size or policies require it.
Exam cueAnswer
Read all parameters for one environment in a single callGetParametersByPath with Path and Recursive
Value comes back as unreadable ciphertextSet WithDecryption and grant kms:Decrypt
Promote or roll back without editing the pipelineRead name:label and move the label
Create the parameter and label it in one stepNot possible, labels attach to an existing version
Value larger than 4 KB, or a parameter policyAdvanced tier, or Intelligent-Tiering as the default
Return an Advanced parameter to StandardDelete it and recreate it
Deny one deep parameter but allow its parent pathDoes not hide it from recursive reads of the parent

The lab verified the hierarchy reads, the overwrite rule, version and label selectors, InvalidLabels and the Label filter. It did not verify real KMS encryption or permissions, tier limits, the downgrade refusal, policies, NextToken paging or throughput.

Check yourself

Question 1
A pipeline calls get-parameters-by-path with the path /orders/prod and receives only two of the eight parameters stored under that prefix. Which option is missing, and what must the caller also handle if the subtree has more than 10 parameters?
tap to reveal
The Recursive option is missing, because without it only one level is read. The caller must also follow NextToken, since each response holds at most 10 parameters.
Question 2
A deployment role can call ssm:GetParameter* on /orders/prod/* but receives an encrypted string for the database password. What two changes fix it?
tap to reveal
The call needs WithDecryption set to true, and the role needs kms:Decrypt on the KMS key protecting the SecureString. Without the permission, the decryption call cannot succeed.
Question 3
An operator runs label-parameter-version with the label 2026-q4. The command exits 0, but the pipeline's read of name:2026-q4 fails. What happened, and how would the operator have caught it?
tap to reveal
Labels cannot begin with a number, so the label came back in InvalidLabels and was never attached. The operator should have checked the InvalidLabels list in the response, not only the exit code.
Question 4
A team stores a 6 KB certificate chain as an encrypted parameter. Which tier does it need, and could the team later switch that parameter to Standard to save cost?
tap to reveal
It needs the Advanced tier, because the Standard limit is 4 KB. An Advanced parameter cannot be downgraded, so the team must delete it and recreate it as a new Standard parameter, which only works if the value fits in 4 KB.
Question 5
A user holds permission on the path /a and has an explicit IAM deny on the parameter /a/b. Can the user still see /a/b?
tap to reveal
Yes. A recursive GetParametersByPath call on /a can return /a/b despite the explicit deny, so access should be granted on the narrowest path.

Sources