## 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`: ```bash 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 �’" ``` ## 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) ```bash # 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 ```bash 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 ```bash 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 ```bash # 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.