docs: add AWS certification notes (labs 1-50)
This commit is contained in:
189
aws-41-50.md
Normal file
189
aws-41-50.md
Normal file
@@ -0,0 +1,189 @@
|
||||
## 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 <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)
|
||||
|
||||
```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.
|
||||
Reference in New Issue
Block a user