Files
kodekloud-engineer/aws-41-50.md

11 KiB
Raw Permalink Blame History

Task 41

The Nautilus DevOps team is focusing on improving their data security by using AWS KMS. Your task is to create a KMS key and manage the encryption and decryption of a pre-existing sensitive file using the KMS key.

Specific Requirements:

Create a symmetric KMS key named nautilus-KMS-Key to manage encryption and decryption. Encrypt the provided SensitiveData.txt file (located in /root/), base64 decode the ciphertext, and save the encrypted version as EncryptedData.bin in the /root/ directory. Try to decrypt the same and verify that the decrypted data matches the original file. Make sure that the KMS key is correctly configured. The validation script will test your configuration by decrypting the EncryptedData.bin file using the KMS key you created.

Solution

AWS KMS Encryption/Decryption Task (nautilus-KMS-Key)

The concept that matters: KMS naming works differently. A KMS key has no "name" attribute at all — what you set is an alias (alias/nautilus-KMS-Key), a friendly pointer to the key's real ID. And there's a hard 4KB size limit on direct kms encrypt, which is why this task is fine for a small file but you'd need envelope encryption for anything bigger. The base64-decode step exists because the CLI returns ciphertext base64-encoded in JSON — you decode it back to raw bytes for the .bin.

Run on aws-client:

REGION=us-east-1

# 1. Create the symmetric KMS key (symmetric + ENCRYPT_DECRYPT are the defaults)
KEY_ID=$(aws kms create-key \
  --description "nautilus KMS key for sensitive data" \
  --key-usage ENCRYPT_DECRYPT \
  --key-spec SYMMETRIC_DEFAULT \
  --region $REGION \
  --query 'KeyMetadata.KeyId' --output text)

# Give it the "name" — which in KMS means an alias
aws kms create-alias \
  --alias-name alias/nautilus-KMS-Key \
  --target-key-id $KEY_ID \
  --region $REGION

# 2. Encrypt SensitiveData.txt -> base64-decode -> EncryptedData.bin
aws kms encrypt \
  --key-id alias/nautilus-KMS-Key \
  --plaintext fileb:///root/SensitiveData.txt \
  --output text \
  --query CiphertextBlob \
  --region $REGION \
  | base64 --decode > /root/EncryptedData.bin

# 3. Decrypt EncryptedData.bin and compare to the original
aws kms decrypt \
  --ciphertext-blob fileb:///root/EncryptedData.bin \
  --output text \
  --query Plaintext \
  --region $REGION \
  | base64 --decode > /root/DecryptedData.txt

# Verify round-trip integrity
diff /root/SensitiveData.txt /root/DecryptedData.txt && echo "MATCH ✔" || echo "MISMATCH <20>"

Every non-obvious piece explained

  • Alias, not name. create-key returns only a KeyId (a UUID) and ARN — no name field exists. create-alias with the alias/ prefix (mandatory prefix) is how you give it the human label nautilus-KMS-Key. Everywhere else you can then reference alias/nautilus-KMS-Key instead of the UUID. The validation script decrypting "using the KMS key you created" works because the ciphertext blob has the key ID embedded in it — decrypt doesn't even need --key-id.
  • SYMMETRIC_DEFAULT + ENCRYPT_DECRYPT are both the defaults, so you could omit those flags — set explicitly since the task says "symmetric." Symmetric = same key encrypts and decrypts, AES-256-GCM under the hood, key material never leaves KMS.
  • fileb:// not file:// for --plaintext — the b = binary. This reads the file's raw bytes. file:// would try to interpret it as text/UTF-8 and can mangle binary or trailing content. Critical for a clean round-trip.
  • The base64 dance. kms encrypt returns CiphertextBlob as base64 text inside JSON. --query CiphertextBlob --output text extracts just that base64 string; base64 --decode converts it to the raw binary ciphertext that becomes EncryptedData.bin. This is exactly the task's "base64 decode the ciphertext" step — the .bin must be raw bytes, not base64 text, or the validation script's decrypt fails.
  • Decrypt needs no --key-id. This surprises people: kms decrypt for a symmetric key figures out which key to use because the key ID is baked into the ciphertext blob itself. You just hand it the blob. (Asymmetric keys would require --key-id.) Same base64-decode on the way out to recover the original plaintext bytes.
  • fileb:// on decrypt's --ciphertext-blob too — reading the raw .bin bytes.

Verify the key + alias config (what the validation script cares about)

# Alias points at an enabled key
aws kms list-aliases --region $REGION \
  --query "Aliases[?AliasName=='alias/nautilus-KMS-Key']"

aws kms describe-key --key-id alias/nautilus-KMS-Key --region $REGION \
  --query 'KeyMetadata.{Id:KeyId,State:KeyState,Usage:KeyUsage,Spec:KeySpec,Enabled:Enabled}'

# The round-trip already proved it, but confirm the .bin exists and is binary
file /root/EncryptedData.bin
ls -l /root/EncryptedData.bin

Want: the alias resolving to your key; describe-key showing State: Enabled, Usage: ENCRYPT_DECRYPT, Spec: SYMMETRIC_DEFAULT; the diff printing MATCH; and EncryptedData.bin existing as binary data. The successful decrypt + matching diff is the task's proof — it means the key encrypts and decrypts correctly and the .bin is in the raw format the validator expects.

Debug order if something's off

  1. diff shows MISMATCH → almost always a file:// vs fileb:// mix-up mangling bytes, or you skipped a base64 --decode. Both the write and read paths need the binary treatment.
  2. Validation script can't decrypt the .bin → the .bin is probably still base64-encoded text instead of raw bytes (missing the base64 --decode on encrypt). file /root/EncryptedData.bin should say "data," not "ASCII text."
  3. AccessDenied on encrypt/decrypt → your CLI identity lacks kms permissions, or (rare) the key policy is restrictive — a freshly created key grants the creating account root full access by default, so this is unusual in a lab.

Task 42

The Nautilus DevOps team is developing a simple 'To-Do' application using DynamoDB to store and manage tasks efficiently. The team needs to create a DynamoDB table to hold tasks, each identified by a unique task ID. Each task will have a description and a status, which indicates the progress of the task (e.g., 'completed' or 'in-progress').

Your task is to:

Create a DynamoDB table named nautilus-tasks with a primary key called taskId (string). Insert the following tasks into the table: Task 1: taskId: '1', description: 'Learn DynamoDB', status: 'completed' Task 2: taskId: '2', description: 'Build To-Do App', status: 'in-progress' Verify that Task 1 has a status of 'completed' and Task 2 has a status of 'in-progress'. Ensure the DynamoDB table is created successfully and that both tasks are inserted correctly with the appropriate statuses.

Solution

DynamoDB To-Do Table Task (nautilus-tasks)

DynamoDB — the concepts that matter here: DynamoDB is schemaless except for the key. You only declare taskId at table creation; description and status are not defined up front — they're just attributes you attach per-item at insert time. And every value carries a type descriptor (S for string, N for number, etc.) in the API, which is why the item JSON looks verbose. Also on-demand billing (PAY_PER_REQUEST) is the right pick for a lab — no capacity planning, no idle cost.

Run on aws-client:

1. Create the table

REGION=us-east-1

aws dynamodb create-table \
  --table-name nautilus-tasks \
  --attribute-definitions AttributeName=taskId,AttributeType=S \
  --key-schema AttributeName=taskId,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --region $REGION

# Table creation is async — wait until ACTIVE
aws dynamodb wait table-exists --table-name nautilus-tasks --region $REGION

2. Insert the two tasks

aws dynamodb put-item \
  --table-name nautilus-tasks \
  --item '{
    "taskId":      {"S": "1"},
    "description": {"S": "Learn DynamoDB"},
    "status":      {"S": "completed"}
  }' \
  --region $REGION

aws dynamodb put-item \
  --table-name nautilus-tasks \
  --item '{
    "taskId":      {"S": "2"},
    "description": {"S": "Build To-Do App"},
    "status":      {"S": "in-progress"}
  }' \
  --region $REGION

Every non-obvious piece explained

  • Only the key attribute is declared at creation. --attribute-definitions lists only taskId — you do NOT declare description or status. DynamoDB is schemaless beyond the primary key; non-key attributes materialize when you write items. Declaring extra attributes here would actually error unless they're part of a key or index. This trips people coming from relational DBs.
  • KeyType=HASH = partition key. taskId as HASH makes it the partition (primary) key — the single-attribute simple primary key the task wants. (A RANGE key would add a sort key for a composite key; not needed here.)
  • The {"S": "..."} type descriptors are mandatory in the low-level API. Every attribute value is wrapped with its type: S string, N number, BOOL, M map, L list, etc. Even though taskId "looks" numeric ("1"), the task says it's a string, so S — and the values are quoted strings. Get this wrong (e.g. N for taskId) and it mismatches the key schema.
  • PAY_PER_REQUEST = on-demand mode: no read/write capacity units to provision, you pay per request. For a lab with 2 items this is free-tier-friendly and zero-config. The alternative PROVISIONED mode needs --provisioned-throughput numbers.
  • wait table-exists — table creation goes CREATING → ACTIVE asynchronously. You can't put-item until it's ACTIVE, so the waiter prevents a race where inserts fire against a still-creating table.

3. Verify both items and their statuses

# Fetch each item by key, checking the status
aws dynamodb get-item \
  --table-name nautilus-tasks \
  --key '{"taskId": {"S": "1"}}' \
  --region $REGION \
  --query 'Item.status.S'      # expect: "completed"

aws dynamodb get-item \
  --table-name nautilus-tasks \
  --key '{"taskId": {"S": "2"}}' \
  --region $REGION \
  --query 'Item.status.S'      # expect: "in-progress"

# Or dump both items at once to eyeball everything
aws dynamodb scan --table-name nautilus-tasks --region $REGION \
  --query 'Items[].{ID:taskId.S, Desc:description.S, Status:status.S}' \
  --output table

Want the two get-item calls returning "completed" and "in-progress" respectively, and the scan table showing both rows with correct descriptions and statuses. That confirms the task: table ACTIVE, both items inserted, statuses correct.

get-item vs scan: get-item is a direct key lookup (fast, cheap, single item) — the right way to verify a specific task by its taskId. scan reads the whole table (fine for 2 items, but avoid on large tables — it's a full sweep). Used both here: get-item for the targeted status checks, scan for the at-a-glance dump.

Debug order if verification fails

  1. get-item returns nothing / null → wrong key type or value. The key in get-item must match exactly: {"taskId": {"S": "1"}}S type, string "1". An N type or unquoted value won't match.
  2. put-item errored on insert → usually a JSON quoting issue in the --item blob, or a type mismatch against the key schema (taskId declared S but sent as N).
  3. create-table errored → likely declared a non-key attribute in --attribute-definitions. Only taskId belongs there.