At the end of 04-01 we left a promise half kept. The policy pol-mercadofresco-tienda requires
every thumbnail to be uploaded with s3:x-amz-server-side-encryption: aws:kms, and
rol-lambda-miniaturas has been granted kms:Decrypt and kms:GenerateDataKey on a key identified
by a UUID. But that key, alias/mercadofresco-datos, is still little more than a name: nobody has
decided who administers it, who may use it, how often it rotates, or what exactly happens when S3
"encrypts" an object.
And there is a business reason behind it. MercadoFresco stores names, delivery addresses, phone numbers and order histories of Spanish customers. That is personal data under the GDPR, and article 32 of the regulation cites encryption explicitly as an appropriate technical measure. If a laptop holding a copy of the database ends up on the back seat of a taxi, the difference between "incident" and "breach notifiable within 72 hours" is usually whether the contents were encrypted.
AWS Key Management Service (KMS) is the service that guards the encryption keys, controls who
may use them and records every use. In this lesson Marta really understands it —including the
mechanism of envelope encryption, which explains everything else— and finally encrypts the
database backups, the volumes and the instance mercadofresco-pedidos.
Warning. The examples in this lesson are teaching material. Any configuration of encryption, key management or regulatory compliance (GDPR, PCI DSS, ENS) that is going to be applied to real customer data must be reviewed by a security or compliance professional before it reaches production. Badly implemented encryption gives a false sense of security, and a badly deleted key destroys data irreversibly. Every identifier and piece of data in this course is fictitious.
Contents
- Why encrypt: the GDPR and MercadoFresco's data
- At rest and in transit
- Symmetric and asymmetric cryptography, just enough
- What KMS is and what it is not
- Key types
- Multi-Region keys, imported material and CloudHSM
- Envelope encryption
- Decryption, step by step
- Client-side and server-side encryption
- SSE-S3, SSE-KMS and SSE-C
- S3 Bucket Keys and the cost of requests
- The key policy and its relationship with IAM
kms:ViaServiceand the encryption context- Grants
- Automatic rotation and what it really means
- Encrypting
mercadofresco-copias-basedatos - Encrypting EBS volumes and snapshots
- Encrypting
mercadofresco-pedidos, which already exists unencrypted - Sharing an encrypted snapshot with another account
- KMS from boto3
- Limits, quotas and the real cost for MercadoFresco
- ACM is not KMS
- Deleting a key: the 7 to 30 day wait
Why encrypt: the GDPR and MercadoFresco's data
Encryption does not protect against everything. It protects against a very specific set of threats, and it is worth being honest about which ones:
| Threat | Does encryption at rest help? |
|---|---|
| Somebody steals a physical disk from the data centre | Yes, completely |
| A snapshot or a backup is shared with another account by mistake | Yes, if they do not have the key |
| A bucket is left publicly exposed | Yes with SSE-KMS: on top of the S3 permission you need the KMS one |
| An audit's regulatory requirement is met | Yes, and with a record of every use as well |
| An attacker steals the application's credentials | No: the application has permission to decrypt |
| A SQL injection extracts the customer table | No: the database returns the data already in the clear |
That table matters: encryption at rest is a layer, not a magic shield. The last two rows are exactly the reasons why 04-01 exists and why 04-05 will exist.
For MercadoFresco there are three data sets that must be encrypted, no argument:
mercadofresco-copias-basedatos: full backups of thepedidosdatabase, with the names, addresses and phone numbers of every customer. It is the most dangerous asset in the account.- The instance
mercadofresco-pedidosand its automatic snapshots. - The EBS volumes of the shop instances and the volume
mercadofresco-fotos-01.
The public photo catalogue (productos/) contains no personal data, but it is encrypted anyway
because the marginal cost is zero and not having to think about what is encrypted and what is not is
in itself an operational advantage.
At rest and in transit
They are two different problems, solved by different mechanisms:
| At rest | In transit | |
|---|---|---|
| What it protects | Data stored on disk | Data travelling over the network |
| Mechanism | AES-256 over the volume, the object or the row | TLS 1.2/1.3 |
| Service | KMS | ACM for the certificates |
| In MercadoFresco | S3, EBS, RDS, snapshots | HTTPS on CloudFront and on the ALB (03-03, 03-05) |
| How it is enforced | Bucket default encryption, kms: in the policy |
Deny with aws:SecureTransport: false |
Encryption in transit has been solved since module 3: the ACM certificate on the ALB and on
CloudFront, and the Deny we wrote into pol-mercadofresco-tienda for when aws:SecureTransport is
false. This lesson is about the other one.
Symmetric and asymmetric cryptography, just enough
You do not need to know cryptography to use KMS, but you do need to tell two families apart:
Symmetric. The same key encrypts and decrypts. It is blazingly fast: AES-256 encrypts hundreds of megabytes per second on any modern CPU. Its problem is distribution: the key has to reach the other end without anybody intercepting it.
Asymmetric. Two mathematically related keys: the public one encrypts and the private one decrypts (or the other way round, for signing). It solves distribution —the public one can be published— but it is orders of magnitude slower and can only encrypt very small amounts of data (a 2048-bit RSA key encrypts at most 190 bytes).
| Symmetric | Asymmetric | |
|---|---|---|
| Keys | One | Public/private pair |
| Speed | Very fast | Slow |
| Data size | Unlimited | Hundreds of bytes |
| Typical algorithm | AES-256-GCM | RSA-2048, ECC |
| Use in KMS | Default, data encryption | Signing, encryption between parties with no shared secret |
In KMS, unless you ask for something else, every key is symmetric AES-256. And the combination of both ideas —using symmetric for the data and protecting that key with another key— is exactly the envelope encryption we look at two sections from here.
What KMS is and what it is not
KMS is a service that:
- Generates and guards master keys inside hardware modules certified to FIPS 140-3 level 3, from which the key material never leaves in the clear.
- Performs cryptographic operations (
Encrypt,Decrypt,GenerateDataKey,Sign,Verify) on small amounts of data. - Controls who may use each key, through policies.
- Records every call in CloudTrail (05-03), including who decrypted what and when.
KMS is not:
- A secrets store: for passwords and connection strings there is Secrets Manager (04-03).
- A service for encrypting large files: there is a hard limit of 4 KB per
Encryptcall. - A certificate authority: that is ACM, and we clear it up at the end.
- A place you can extract your key from and take it away. With KMS-managed keys, the material cannot be exported. That is a guarantee, not a limitation.
That last sentence deserves a pause: if the material never leaves, how is the data encrypted? The answer is envelope encryption.
Key types
In KMS the fundamental unit is the KMS key (formerly called the CMK, Customer Master Key). There are three categories by origin:
| Type | Who creates it | Policy | Rotation | Cost | When |
|---|---|---|---|---|---|
| AWS owned | AWS, shared across customers | You never see it | AWS decides | Free | S3 default encryption (SSE-S3) |
AWS managed (aws/s3, aws/rds, aws/ebs) |
AWS, one per service and account | Not editable | Annual, mandatory | Free (requests are not) | Getting started fast, no fine control |
| Customer managed (CMK) | You | Editable | Optional, configurable | 1 USD/month | When you need to control who uses the key |
The decisive difference between the last two is not the price, it is control:
- With an AWS managed key you cannot edit the key policy. Any principal in your account with S3 permissions will be able to decrypt. You cannot use it from another account nor deny its use to a specific role.
- With a customer managed key you decide exactly who administers and who uses it, you can deny a role access even if it has S3 permissions, you can share it between accounts and you can disable it, which makes everything encrypted with it unreadable instantly. That last one is a very powerful emergency button.
For mercadofresco-copias-basedatos, which holds personal data, the key must be customer managed.
That is the role of alias/mercadofresco-datos.
And a note on aliases: an alias is a mutable pointer to a key. Using alias/mercadofresco-datos
in the code instead of the UUID lets you change the underlying key without touching the application.
Each alias is regional and cannot be repeated within the region.
Multi-Region keys, imported material and CloudHSM
Three variants worth knowing about even though MercadoFresco does not use them today:
Multi-Region keys. A primary key in eu-west-1 and replicas in other regions that share the
same cryptographic material and the same identifier (with the mrk- prefix). What is encrypted
in one region decrypts in another with no cross-region calls. It is essential for cross-region
backups and for disaster recovery. It breaks regional isolation, so use it only when it is genuinely
needed.
Imported material (BYOK). You bring your own key material and KMS merely guards it. It is used when a regulation requires the organisation to generate the keys. It comes with a serious catch: if you lose your copy of the material and it expires in KMS, the data is unrecoverable. And it does not support automatic rotation.
Custom key store on CloudHSM. The material lives in a CloudHSM cluster you own, dedicated, single-tenant. It is what some financial or public-sector environments demand. It costs of the order of 1.50 USD per hour and HSM —over 1,000 USD a month per HSM, and two are recommended— so it is a compliance decision, not an engineering one. We only mention it.
Envelope encryption
This is the central concept of the lesson. If the key material never leaves KMS and Encrypt only
takes 4 KB, how is a 200 GB snapshot encrypted?
The answer is that large data never travels to KMS. Two levels of key are used:
- The master key (the KMS key) lives in KMS and never leaves.
- The data key is a single-use AES-256 key that KMS generates, hands over in two formats and then forgets.
sequenceDiagram
participant A as Application / S3
participant K as KMS<br/>alias/mercadofresco-datos
participant D as Disk
Note over A,D: ENCRYPTION
A->>K: GenerateDataKey(KeyId, KeySpec=AES_256)
K->>K: Generates a random AES-256 key
K-->>A: Plaintext (key in the clear)<br/>+ CiphertextBlob (the same key, encrypted<br/>with the master key)
A->>A: Encrypts the 200 GB with Plaintext (local AES, fast)
A->>A: WIPES Plaintext from memory
A->>D: Stores: encrypted data + CiphertextBlob together
The steps, in words:
- The application asks KMS for a data key. It does not send the data.
- KMS generates a random AES-256 key and returns two versions of the same key: in the clear
(
Plaintext) and encrypted with the master key (CiphertextBlob). - The application encrypts the 200 GB locally with the clear version. It runs at CPU speed, with no network in the way.
- The application wipes the clear version from memory. This is the step that makes the system secure.
- It stores the encrypted data and the
CiphertextBlobtogether. The blob is useless to anybody who cannot callkms:Decrypton the master key.
The advantages of this design, all of them consequences of the same trick:
| Advantage | Why |
|---|---|
| Performance | The data is encrypted locally at CPU speed, not at network speed |
| Cost | One KMS call per file (or fewer), not per byte |
| No size limit | The 4 KB limit only affects the data key, which takes 32 bytes |
| Cheap rotation | Rotating the master key does not force you to re-encrypt the data (we see this later) |
| Traceability | Every GenerateDataKey and every Decrypt lands in CloudTrail |
Decryption, step by step
sequenceDiagram
participant D as Disk
participant A as Application / S3
participant K as KMS
Note over D,K: DECRYPTION
D-->>A: encrypted data + CiphertextBlob
A->>K: Decrypt(CiphertextBlob)
K->>K: Checks the key policy<br/>and the caller's IAM permissions
alt Authorised
K-->>A: Plaintext (data key in the clear)
A->>A: Decrypts the data locally
A->>A: WIPES Plaintext
else Not authorised
K-->>A: AccessDeniedException
Note over A: The data is unreadable<br/>even if you have the file
end
Notice the critical point: to decrypt you do not have to say which key was used. The
CiphertextBlob carries the master key identifier inside it, and KMS finds it on its own. That is
why Decrypt does not need the KeyId parameter with symmetric keys.
And notice the else branch: whoever has the file but no permission on the key is left with noise.
That is why an encrypted snapshot shared by mistake is still safe.
Client-side and server-side encryption
| Server-side (SSE) | Client-side (CSE) | |
|---|---|---|
| Who encrypts | AWS, on receiving the data | Your application, before sending it |
| What AWS sees | The data in the clear while processing | Only encrypted data, never in the clear |
| Complexity | Almost none: a checkbox | High: you manage the process |
| Searches, indexes | Work | Do not work over what is encrypted |
| When | 95 % of cases | Extreme confidentiality against the provider |
MercadoFresco uses server-side everywhere. Client-side encryption makes sense when the threat model includes the cloud provider itself or when a regulation requires it; for a fresh produce shop, the extra complexity is not worth it. If it were ever needed, there is the AWS Encryption SDK and the S3 encryption client, which implement the envelope for you.
SSE-S3, SSE-KMS and SSE-C
S3 offers three forms of server-side encryption. The difference lies in who controls the key:
| SSE-S3 | SSE-KMS | SSE-C | |
|---|---|---|---|
| Master key | AWS owned | Yours, in KMS | Yours, sent on every request |
| Header | AES256 |
aws:kms |
not aws:kms; -customer- headers |
| Access policy | S3 IAM only | S3 IAM and key policy | You hold it |
| CloudTrail record | No | Yes, every use | No |
| Cost | Free | 1 USD/month + requests | Free in KMS |
| Rotation | Automatic, invisible | Configurable and auditable | Manual, yours |
| Double authorisation | No | Yes | No |
| When | Non-sensitive data | Personal data, compliance | Almost never |
The decisive row is double authorisation. With SSE-KMS, reading an object takes two
permissions: s3:GetObject and kms:Decrypt on the key. If a bucket with personal data were
accidentally exposed to the public, the attacker would get the encrypted object and could not open
it, because they have no permission on the key. It is a real safety net that SSE-S3 does not give.
SSE-C, where you send the key on every request and AWS uses it and forgets it, forces you to manage key distribution yourself and leaves no record. It is mentioned for completeness.
The decision for MercadoFresco:
| Bucket | Encryption | Reason |
|---|---|---|
mercadofresco-copias-basedatos |
SSE-KMS with alias/mercadofresco-datos |
Personal data, GDPR, auditing |
mercadofresco-catalogo-fotos |
SSE-KMS | Consistency; the shop policy already requires it |
mercadofresco-informes-analitica |
SSE-KMS | Aggregated business data |
mercadofresco-registros-web |
SSE-S3 | High volume, no direct personal data |
mercadofresco-tienda-web-desarrollo |
SSE-S3 | Public static content |
S3 Bucket Keys and the cost of requests
Here is an economic trap that sinks budgets. With nothing else in place, every object uploaded to
or downloaded from a bucket with SSE-KMS generates a KMS call. mercadofresco-catalogo-fotos serves
—before CloudFront— of the order of 2 million requests a month. At 0.03 USD per 10,000 requests, that
is 6 USD a month in KMS calls alone, more than the key itself.
The solution is the S3 Bucket Key: S3 asks KMS for one bucket-level key, keeps it cached for a few hours and derives the individual object keys from it.
| Without a bucket key | With a bucket key | |
|---|---|---|
| KMS calls | One per object | One every few hours |
| Cost reduction | — | Up to 99 % |
| CloudTrail record | One entry per object | One entry per bucket key |
| Added cost | — | None |
The trade-off is the third row: you lose the per-object trace in CloudTrail. For
mercadofresco-catalogo-fotos, with millions of public photos, it is an excellent trade. For
mercadofresco-copias-basedatos, where you want to know exactly who decrypted which backup and when,
you may want to leave it off: it is few requests a month and the trace is worth more than the saving.
Turn it on whenever you do not need the per-object trace. It is free and saves 99 %.
The key policy and its relationship with IAM
Here is the exception we announced in 04-01. In almost every service, within the same account it is enough for one of the two policies to grant. In KMS it is not. Every key has a key policy (a resource-based policy) and:
If the key policy does not allow it, nobody can use the key. Not the account administrator, not even root.
This has a consequence worth burning into memory: if you create a key from the CLI with a policy that includes nobody, the key is unusable forever and all you can do is schedule its deletion. AWS prevents this in the console, but not from the CLI.
There are two models:
- The key policy delegates to IAM. You include the statement
"Principal": {"AWS": "arn:aws:iam::111122223333:root"}with"Action": "kms:*", which means "this account's IAM policies may grant access to this key". It is what the console does by default. - The key policy grants directly, naming specific roles. More restrictive and more explicit.
MercadoFresco uses a mixed model: it delegates to IAM for administration but explicitly names
whoever uses the key. This is the complete policy for alias/mercadofresco-datos:
{
"Version": "2012-10-17",
"Id": "politica-mercadofresco-datos",
"Statement": [
{
"Sid": "PermitirQueIAMGobierneLaClave",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:root" },
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "MartaAdministraLaClavePeroNoLaUsa",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:user/marta" },
"Action": [
"kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*",
"kms:Update*", "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*",
"kms:TagResource", "kms:UntagResource", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion"
],
"Resource": "*"
},
{
"Sid": "LosRolesDeAplicacionUsanLaClave",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::111122223333:role/rol-mercadofresco-tienda",
"arn:aws:iam::111122223333:role/rol-lambda-miniaturas"
]
},
"Action": [
"kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*",
"kms:GenerateDataKey*", "kms:DescribeKey"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": ["s3.eu-west-1.amazonaws.com"]
}
}
},
{
"Sid": "PermitirQueRDSYEBSUsenLaClaveMedianteConcesiones",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:root" },
"Action": "kms:CreateGrant",
"Resource": "*",
"Condition": {
"Bool": { "kms:GrantIsForAWSResource": "true" },
"StringEquals": {
"kms:ViaService": ["rds.eu-west-1.amazonaws.com", "ec2.eu-west-1.amazonaws.com"]
}
}
},
{
"Sid": "ProhibirElUsoFueraDeEuWest1",
"Effect": "Deny",
"Principal": "*",
"Action": "kms:*",
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:RequestedRegion": "eu-west-1" }
}
}
]
}Statement by statement:
PermitirQueIAMGobierneLaClave: the "switch" that makes IAM policies count for this key. Without it, no identity-based policy would have any effect on it. It is what stops you locking yourself out of your own key.MartaAdministraLaClavePeroNoLaUsa: Marta can enable, disable, tag, change the policy and schedule deletion... butkms:Decryptandkms:Encryptdo not appear. That is separation of duties: whoever administers the key cannot read the data it protects. It is a standard audit requirement and here you can see it implemented in two lines.LosRolesDeAplicacionUsanLaClave: the two roles we formalised in 04-01 can encrypt and decrypt, but only through S3, thanks tokms:ViaService. If somebody steals the instance's credentials and callskms:Decryptdirectly against a blob they obtained some other way, KMS rejects it.PermitirQueRDSYEBSUsenLaClaveMedianteConcesiones: RDS and EBS do not call the key directly; they create grants on your behalf. Thekms:GrantIsForAWSResourcecondition limits that ability to AWS services, not to people.ProhibirElUsoFueraDeEuWest1: a data residency barrier. It fits the GDPR argument about keeping processing inside the EU.
And the shop role additionally needs the same KMS permissions in its own identity-based policy —remember: within the same account one grant is enough, unless the resource one denies, but in KMS the resource one must always allow. In practice you write both.
kms:ViaService and the encryption context
Two mechanisms that refine control and that come up constantly in real policies.
kms:ViaService. Restricts use of the key to requests arriving through a specific service,
and only when the service is acting on behalf of the principal. Typical values:
s3.eu-west-1.amazonaws.com, rds.eu-west-1.amazonaws.com, ec2.eu-west-1.amazonaws.com,
secretsmanager.eu-west-1.amazonaws.com. It is the most useful condition in KMS: it turns a generic
key into an "S3 only" key.
Encryption context. A dictionary of key-value pairs associated with an encryption operation. It is not secret —it appears in the CloudTrail logs in the clear— but it is authenticated: if you do not supply exactly the same context when decrypting, the operation fails.
It serves two purposes:
- Readable auditing. In CloudTrail you see "something from the copias component was decrypted" instead of an opaque UUID.
- Granular authorisation. The key policy can be conditioned on a specific context:
{
"Sid": "SoloDescifrarCopias",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/rol-restauracion" },
"Action": "kms:Decrypt",
"Resource": "*",
"Condition": {
"StringEquals": { "kms:EncryptionContext:componente": "copias" }
}
}That role can decrypt database backups and nothing else, even though everything is encrypted with the same key. S3 automatically uses the bucket or object ARN as the context, so you already benefit from this without doing anything.
Grants
A grant is a temporary, granular permission on a key, designed for services and processes, not for people:
| Key policy | Grant | |
|---|---|---|
| Format | JSON | API call |
| Duration | Permanent until edited | Removed with RetireGrant |
| Granularity | Principal + action + condition | Principal + specific operations + context |
| Who uses them | Humans and roles | AWS services, mostly |
When you create an encrypted RDS instance, RDS creates a grant on the key so that it can decrypt the
volume at every start-up, every failover and every snapshot restore. That is why the key policy had
to allow kms:CreateGrant with kms:GrantIsForAWSResource.
A very important practical consequence: if you retire that grant, the database stops starting. And if the grant is retired while the database is stopped, it will not come back. Do not touch the grants that services create.
aws kms list-grants --key-id alias/mercadofresco-datos \
--query 'Grants[].[GranteePrincipal,Operations[0],Name]' --output table \
--profile mercadofresco-devAutomatic rotation and what it really means
Annual rotation can be switched on with a single call:
aws kms enable-key-rotation --key-id alias/mercadofresco-datos --profile mercadofresco-dev
aws kms get-key-rotation-status --key-id alias/mercadofresco-datos --profile mercadofresco-devAnd now the part almost everybody gets wrong. When KMS rotates a key:
- It generates new cryptographic material and ties it to the same logical key and the same ARN.
- It keeps all the earlier material, indefinitely.
- The new material is used for new encryption operations.
- Data already encrypted is not re-encrypted. It keeps its old material, which KMS finds itself.
In other words: rotation is transparent and free, but it does not "rejuvenate" old data. If an
attacker had somehow obtained the old material, the data encrypted with it is still compromised. To
genuinely renew the data you have to re-encrypt it, copying the objects over themselves or using
ReEncrypt.
| KMS automatic rotation | Real re-encryption of the data | |
|---|---|---|
| What changes | The material for new operations | The data already stored |
| Cost | Free | KMS requests + time |
| Compliance | Covers "annual key rotation" | Covers "renewal after an incident" |
| Effort | One command | A batch process |
Since 2024 the rotation period is configurable between 90 and 2,560 days with
--rotation-period-in-days. For MercadoFresco the default annual rotation is enough and it is what
most audits expect to see.
Encrypting mercadofresco-copias-basedatos
First, create the key with the policy from the previous section:
aws kms create-key \
--description "MercadoFresco personal data key (GDPR)" \
--key-usage ENCRYPT_DECRYPT \
--key-spec SYMMETRIC_DEFAULT \
--policy file:///tmp/politica-clave.json \
--tags TagKey=Proyecto,TagValue=mercadofresco TagKey=Entorno,TagValue=produccion \
TagKey=Componente,TagValue=datos TagKey=Propietario,TagValue=marta \
TagKey=CentroCoste,TagValue=tecnologia \
--profile mercadofresco-dev
# Returns KeyId: 1234abcd-12ab-34cd-56ef-1234567890ab
aws kms create-alias \
--alias-name alias/mercadofresco-datos \
--target-key-id 1234abcd-12ab-34cd-56ef-1234567890ab \
--profile mercadofresco-dev
aws kms enable-key-rotation --key-id alias/mercadofresco-datos --profile mercadofresco-devNext, default encryption on the bucket. Note that here we do not turn on the bucket key, because we want the per-object trace:
aws s3api put-bucket-encryption \
--bucket mercadofresco-copias-basedatos \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos"
},
"BucketKeyEnabled": false
}]
}' \
--profile mercadofresco-devOn the photo catalogue, by contrast, we do turn it on:
aws s3api put-bucket-encryption \
--bucket mercadofresco-catalogo-fotos \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos"
},
"BucketKeyEnabled": true
}]
}' \
--profile mercadofresco-devAnd finally, the bucket policy that rejects any unencrypted upload, so that default encryption does not depend on nobody switching it off:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RechazarSubidasSinKMS",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mercadofresco-copias-basedatos/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
},
{
"Sid": "RechazarOtraClave",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mercadofresco-copias-basedatos/*",
"Condition": {
"StringNotEqualsIfExists": {
"s3:x-amz-server-side-encryption-aws-kms-key-id":
"arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
}
}
}
]
}Important: default encryption does not encrypt retroactively. The objects already in the bucket stay exactly as they were. To encrypt them you have to copy them over themselves:
aws s3 cp s3://mercadofresco-copias-basedatos/ s3://mercadofresco-copias-basedatos/ \
--recursive --sse aws:kms \
--sse-kms-key-id arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos \
--metadata-directive REPLACE \
--profile mercadofresco-devWith versioning enabled this creates a new encrypted version and leaves the old one unencrypted. You have to delete the old versions afterwards, or the work is worthless. It is a detail that is frequently forgotten and that an audit spots straight away.
Encrypting EBS volumes and snapshots
The simplest thing: turn on EBS default encryption in the region. From that moment on, every new volume is encrypted without anybody having to remember.
aws ec2 enable-ebs-encryption-by-default --region eu-west-1 --profile mercadofresco-dev
aws ec2 modify-ebs-default-kms-key-id \
--kms-key-id alias/mercadofresco-datos --region eu-west-1 --profile mercadofresco-dev
aws ec2 get-ebs-default-kms-key-id --region eu-west-1 --profile mercadofresco-devFor a volume that already exists unencrypted —like mercadofresco-fotos-01, created in 02-02—
you cannot encrypt it in place. The procedure is snapshot, encrypted copy, new volume:
# 1. Snapshot of the unencrypted volume
aws ec2 create-snapshot --volume-id vol-0abc123 \
--description "mercadofresco-fotos-01 before encryption" \
--profile mercadofresco-dev
# 2. Copy the snapshot, ENCRYPTING along the way
aws ec2 copy-snapshot \
--source-region eu-west-1 --source-snapshot-id snap-0abc123 \
--encrypted --kms-key-id alias/mercadofresco-datos \
--description "mercadofresco-fotos-01 encrypted" \
--profile mercadofresco-dev
# 3. Create the new volume from the encrypted snapshot
aws ec2 create-volume --snapshot-id snap-0def456 \
--availability-zone eu-west-1a --volume-type gp3 \
--tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=mercadofresco-fotos-01},
{Key=Proyecto,Value=mercadofresco},{Key=Entorno,Value=produccion}]' \
--profile mercadofresco-dev
# 4. Stop the instance, detach the old volume, attach the new one, startThe trick is in step 2: copy-snapshot --encrypted is the only EBS operation that turns something
unencrypted into something encrypted. Remember it: it is the piece missing from almost every attempt.
And a rule that saves you scares: an encrypted volume only produces encrypted snapshots, and an encrypted snapshot only produces encrypted volumes. The property is inherited and cannot be removed.
Encrypting mercadofresco-pedidos, which already exists unencrypted
This is the most delicate operation of the module, because it involves downtime for the production database. RDS does not allow encrypting an existing instance. The route is:
flowchart TD
A["mercadofresco-pedidos<br/>NOT encrypted (production)"] --> B["1. Manual snapshot<br/>mercadofresco-pedidos-precifrado"]
B --> C["2. copy-db-snapshot --kms-key-id<br/>alias/mercadofresco-datos"]
C --> D["3. restore-db-instance-from-db-snapshot<br/>mercadofresco-pedidos-cifrada"]
D --> E["4. Verify: data, parameters,<br/>subnet group, SG"]
E --> F["5. Maintenance window:<br/>stop writes"]
F --> G["6. Rename: the old one to -antigua,<br/>the new one to mercadofresco-pedidos"]
G --> H["7. Re-enable Multi-AZ and the replica<br/>mercadofresco-pedidos-lectura"]
H --> I["8. Keep the old one 7 days,<br/>then delete"]
# 1 and 2
aws rds create-db-snapshot \
--db-instance-identifier mercadofresco-pedidos \
--db-snapshot-identifier mercadofresco-pedidos-precifrado \
--profile mercadofresco-dev
aws rds copy-db-snapshot \
--source-db-snapshot-identifier mercadofresco-pedidos-precifrado \
--target-db-snapshot-identifier mercadofresco-pedidos-cifrado \
--kms-key-id alias/mercadofresco-datos \
--profile mercadofresco-dev
# 3
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier mercadofresco-pedidos-cifrada \
--db-snapshot-identifier mercadofresco-pedidos-cifrado \
--db-subnet-group-name sng-mercadofresco-datos \
--db-parameter-group-name pg16-mercadofresco \
--multi-az \
--no-publicly-accessible \
--profile mercadofresco-devFour warnings from the field:
- The restore does not preserve the parameter group, the security groups or the Multi-AZ configuration. You have to specify them explicitly, as in the command above, or the new instance will start with the default values and end up misconfigured.
- The endpoint name changes. If the application points at the RDS DNS name, it has to be
updated. That is why in 03-05 we created the private zone
interno.mercadofresco.example: if the shop connects topedidos.interno.mercadofresco.example, changing one CNAME is enough. - The replica
mercadofresco-pedidos-lecturadoes not migrate. It has to be recreated from the new instance. - Do the full rehearsal in development first. Marta does this migration on a Tuesday night, never a Thursday: the Friday peak is no time to discover surprises.
Sharing an encrypted snapshot with another account
A snapshot encrypted with the AWS managed key (aws/rds) cannot be shared. Only those
encrypted with a customer managed key can, and two permissions are needed:
# 1. Share the snapshot with the target account
aws rds modify-db-snapshot-attribute \
--db-snapshot-identifier mercadofresco-pedidos-cifrado \
--attribute-name restore --values-to-add 444455556666 \
--profile mercadofresco-dev{
"Sid": "PermitirQueLaCuentaDeAuditoriaDescifreElSnapshot",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::444455556666:root" },
"Action": ["kms:Decrypt", "kms:DescribeKey", "kms:CreateGrant"],
"Resource": "*",
"Condition": {
"StringEquals": { "kms:ViaService": "rds.eu-west-1.amazonaws.com" }
}
}In other words: share the snapshot and give access to the key. If you only do the first, the other account will see the snapshot and will not be able to restore it. And do not forget that the target account also has to allow it in its own identity-based policies.
This, incidentally, is the mechanism that makes accidental exposure safe: sharing a snapshot without sharing the key reveals absolutely nothing.
KMS from boto3
The typical use case —encrypting a file of arbitrary size with the envelope, by hand:
import boto3
import os
from cryptography.fernet import Fernet # local example only
import base64
session = boto3.Session(profile_name="mercadofresco-dev", region_name="eu-west-1")
kms = session.client("kms")
KEY = "alias/mercadofresco-datos"
CONTEXT = {"proyecto": "mercadofresco", "componente": "copias"}
def encrypt_file(source_path: str, target_path: str) -> None:
"""Encrypts a file of any size using envelope encryption."""
# 1. Ask for a data key: BOTH versions come back
response = kms.generate_data_key(
KeyId=KEY,
KeySpec="AES_256",
EncryptionContext=CONTEXT,
)
key_in_the_clear = response["Plaintext"] # 32 bytes, to encrypt here
encrypted_key = response["CiphertextBlob"] # stored next to the file
# 2. Encrypt locally. The data NEVER travels to KMS.
f = Fernet(base64.urlsafe_b64encode(key_in_the_clear))
with open(source_path, "rb") as source:
encrypted_data = f.encrypt(source.read())
# 3. Store the encrypted key + the encrypted data in the same file
with open(target_path, "wb") as target:
target.write(len(encrypted_key).to_bytes(4, "big"))
target.write(encrypted_key)
target.write(encrypted_data)
# 4. Wipe the clear version from memory
del key_in_the_clear
def decrypt_file(source_path: str, target_path: str) -> None:
with open(source_path, "rb") as source:
length = int.from_bytes(source.read(4), "big")
encrypted_key = source.read(length)
encrypted_data = source.read()
# KMS works out the master key from the blob itself: no KeyId needed
response = kms.decrypt(
CiphertextBlob=encrypted_key,
EncryptionContext=CONTEXT, # must match EXACTLY
)
f = Fernet(base64.urlsafe_b64encode(response["Plaintext"]))
with open(target_path, "wb") as target:
target.write(f.decrypt(encrypted_data))
# Direct encryption, only for data smaller than 4 KB
small = kms.encrypt(KeyId=KEY, Plaintext=b"short piece of data", EncryptionContext=CONTEXT)
print(kms.decrypt(CiphertextBlob=small["CiphertextBlob"],
EncryptionContext=CONTEXT)["Plaintext"])Points to take away from the example:
generate_data_keyreturns both versions of the same key. That is the essence of the envelope.- The encrypted file is self-contained: it carries the encrypted key inside. It can be moved anywhere
and only somebody with permission on
alias/mercadofresco-datoswill open it. decryptreceives noKeyId: the blob carries it inside.- The
EncryptionContextmust match character for character on decryption. If not,InvalidCiphertext. - In production you do not use
Fernetby hand: the AWS Encryption SDK does all this correctly, including secure memory wiping and the message format. The example is to show the mechanics.
Limits, quotas and the real cost for MercadoFresco
| Item | Price |
|---|---|
| Customer managed key | 1.00 USD per key per month (pro rata) |
| Symmetric requests | 0.03 USD per 10,000 |
| RSA-2048 requests | 0.03 USD per 10,000 |
| ECC / RSA >2048 requests | 0.15 USD per 10,000 |
| AWS managed and AWS owned keys | Free (requests are charged) |
| Free tier | 20,000 requests a month, always |
| Custom key store (CloudHSM) | ~1.50 USD/hour per HSM, separately |
Request-per-second quotas in eu-west-1: of the order of 50,000 for symmetric Decrypt,
GenerateDataKey and Encrypt, and far lower (a few hundred) for asymmetric operations. They are
shared across every key in the account, so an aggressive batch process can trigger a
ThrottlingException across the whole account. The SDKs retry with exponential backoff, but it is
worth knowing.
The calculation for MercadoFresco:
| Item | Quantity | Monthly cost |
|---|---|---|
Key alias/mercadofresco-datos |
1 | 1.00 USD |
mercadofresco-catalogo-fotos with a bucket key |
~2,000,000 requests → ~700 calls | 0.00 USD (free tier) |
mercadofresco-copias-basedatos without a bucket key |
60 backups × 2 (upload + verify) = 120 | 0.00 USD |
| EBS and RDS (grants and start-ups) | ~500 | 0.00 USD |
| Total | 1.00 USD/month |
One dollar a month to encrypt every piece of personal data in the company, with an audit trail of every use. It is by far the best cost-benefit ratio in the whole course.
The contrast is useful: without the bucket key, those 2 million requests would have generated 2 million KMS calls, some 6 USD a month. That is not real money, but at a scale of 100 million requests it would be 300 USD a month for not having ticked a box.
ACM is not KMS
A frequent confusion, because both talk about keys:
| AWS KMS | AWS Certificate Manager | |
|---|---|---|
| Protects | Data at rest | Data in transit |
| Object | Symmetric encryption keys | X.509 certificates |
| Cost | 1 USD/key/month | Free for public certificates |
| In MercadoFresco | alias/mercadofresco-datos |
ALB and CloudFront certificates (03-03, 03-05) |
| Renewal | Annual rotation of the material | Automatic renewal via DNS |
They are independent services solving different halves of the same problem. The
mercadofresco.example certificate we validated with a CNAME in 03-05 has nothing to do with
alias/mercadofresco-datos.
Deleting a key: the 7 to 30 day wait
A KMS key cannot be deleted immediately, and that is deliberate: deleting it would irreversibly destroy every piece of data encrypted with it. All you can do is schedule the deletion, with a waiting period of between 7 and 30 days.
# Before anything else: is anybody using it? Check CloudTrail (05-03)
aws kms schedule-key-deletion \
--key-id alias/mercadofresco-datos --pending-window-in-days 30 \
--profile mercadofresco-dev
# Cancel while it is still pending
aws kms cancel-key-deletion --key-id 1234abcd-12ab-34cd-56ef-1234567890ab \
--profile mercadofresco-devA far more sensible alternative for testing the impact is to disable the key: it is instantly reversible and produces exactly the same effect as deleting it —everything encrypted stops being readable— but without destroying anything.
aws kms disable-key --key-id alias/mercadofresco-datos --profile mercadofresco-dev
aws kms enable-key --key-id alias/mercadofresco-datos --profile mercadofresco-devDisable it for 30 days, check that nothing breaks and only then schedule the deletion: that is the correct procedure. And bear in mind that while the key is disabled you go on paying its monthly dollar.
Common Mistakes and Tips
Writing a key policy that locks everybody out. It is the irreversible mistake of KMS. If you
create the key from the CLI with a policy that does not include "Principal": {"AWS": "arn:aws:iam::111122223333:root"} with kms:*, the key is unusable and all you can do is schedule
its deletion. Always include that statement.
Believing that a bucket's default encryption encrypts what was already there. It does not. The existing objects stay unencrypted until you copy them over themselves, and with versioning enabled you also have to purge the old versions.
Forgetting kms:Decrypt on the role. The symptom is confusing: s3:GetObject is granted, the
object exists and AccessDenied still arrives. With SSE-KMS you need two permissions. The first
thing to check on an AccessDenied in an encrypted bucket is always KMS.
Not turning on the bucket key on high-traffic buckets. It multiplies the KMS cost by the number of requests. Turn it on unless you need the per-object trace.
Using Encrypt for large files. It fails with ValidationException past 4 KB. That is what
GenerateDataKey and the envelope are for.
Confusing disabling with deleting. Disabling is reversible and still costs 1 USD a month; deleting is irreversible and destroys the data. Always start by disabling.
Retiring a grant created by RDS or EBS. The database stops starting. The grants that services create are not to be touched.
Tip: use aliases, never UUIDs, in the code. alias/mercadofresco-datos is readable and lets you
change the underlying key without a deployment. In policies, on the other hand, use the key ARN:
it is unambiguous and does not depend on where the alias points.
Tip: apply separation of duties from day one. Whoever administers the key should not be able to
decrypt the data, as in the alias/mercadofresco-datos policy. It costs two lines and it is the
first thing an auditor asks about.
Tip: turn on EBS default encryption in the region. One command, free, and from then on nobody can create an unencrypted volume by accident.
Tip: put an alarm on kms:Decrypt calls. An anomalous spike in decryptions is one of the
earliest signals of data exfiltration. We will build it with CloudWatch in 05-01 on top of the
CloudTrail events of 05-03.
Exercises
Exercise 1: designing the policy for a new key
MercadoFresco creates a second key, alias/mercadofresco-analitica, to encrypt
mercadofresco-informes-analitica. Requirements:
- Marta administers the key but cannot decrypt.
- Sara (user
sara) can decrypt reports, but only through S3 and only if the encryption context includescomponente=informes. - A role
rol-mercadofresco-etlcan encrypt and decrypt with no context restriction, also only through S3. - Nobody can use the key outside
eu-west-1. - The account's IAM policies must be able to grant access.
Write the complete key policy.
Exercise 2: calculating the cost and deciding about the bucket key
MercadoFresco opens in Portugal and the catalogue starts serving 40 million requests a month, of
which CloudFront absorbs 90 % (only 10 % reaches S3). In addition, the bucket
mercadofresco-copias-basedatos receives 3 backups a day and mercadofresco-informes-analitica
generates 50,000 objects a month.
Work out the monthly KMS cost in two scenarios —with and without the bucket key on the catalogue— using 2 customer managed keys, and decide which configuration you would recommend for each bucket, justifying the trade-off between cost and traceability.
Exercise 3: diagnosing an encryption failure
Luis deploys a new version of the shop. The photos read fine, but when generating a thumbnail the
function mercadofresco-generar-miniaturas fails with:
An error occurred (AccessDenied) when calling the PutObject operation:
User: arn:aws:sts::111122223333:assumed-role/rol-lambda-miniaturas/mercadofresco-generar-miniaturas
is not authorized to perform: kms:GenerateDataKey on resource:
arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab
because no identity-based policy allows the kms:GenerateDataKey actionAnswer: (a) why does it fail on writing if reading worked?; (b) which two different places do
you have to look at, and which one has the problem according to the message?; (c) write the minimum
fix; (d) if the error had said with an explicit deny in a resource-based policy, what would you
suspect, knowing that the key policy includes kms:ViaService?
Solutions
Solution 1
{
"Version": "2012-10-17",
"Id": "politica-mercadofresco-analitica",
"Statement": [
{
"Sid": "PermitirQueIAMGobierneLaClave",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:root" },
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "MartaAdministra",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:user/marta" },
"Action": [
"kms:Describe*", "kms:Get*", "kms:List*", "kms:Enable*", "kms:Disable*",
"kms:Update*", "kms:Put*", "kms:Revoke*", "kms:TagResource", "kms:UntagResource",
"kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion"
],
"Resource": "*"
},
{
"Sid": "SaraDescifraSoloInformes",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:user/sara" },
"Action": ["kms:Decrypt", "kms:DescribeKey"],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": "s3.eu-west-1.amazonaws.com",
"kms:EncryptionContext:componente": "informes"
}
}
},
{
"Sid": "ElProcesoETLCifraYDescifra",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/rol-mercadofresco-etl" },
"Action": ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey*", "kms:DescribeKey"],
"Resource": "*",
"Condition": {
"StringEquals": { "kms:ViaService": "s3.eu-west-1.amazonaws.com" }
}
},
{
"Sid": "ProhibirFueraDeEuWest1",
"Effect": "Deny",
"Principal": "*",
"Action": "kms:*",
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:RequestedRegion": "eu-west-1" }
}
}
]
}Key points: in Marta's statement no kms:Encrypt, kms:Decrypt or kms:GenerateDataKey
appears —separation of duties—; Sara only has Decrypt, never Encrypt, and with a double
condition; and the final Deny applies to "Principal": "*", which means it applies to Marta and
to root as well.
Solution 2
KMS requests per bucket:
- Catalogue: 40,000,000 × 10 % = 4,000,000 S3 requests a month.
- Without a bucket key: 4,000,000 KMS calls.
- With a bucket key: roughly one every few hours, of the order of 250 a month.
- Backups: 3 a day × 30 = 90 objects → ~180 calls (write + verify).
- Reports: 50,000 objects → 50,000 calls without a bucket key, ~250 with it.
Scenario A, no bucket key anywhere:
- Requests: 4,000,000 + 180 + 50,000 = 4,050,180. Less the 20,000 free tier = 4,030,180.
- Cost: 4,030,180 ÷ 10,000 × 0.03 = 12.09 USD
- Keys: 2 × 1.00 = 2.00 USD
- Total: 14.09 USD/month
Scenario B, with a bucket key on catalogue and reports, without it on backups:
- Requests: 250 + 180 + 250 = 680, well below the free tier of 20,000.
- Request cost: 0.00 USD
- Keys: 2.00 USD
- Total: 2.00 USD/month
Recommendation. A saving of 12.09 USD a month, some 145 USD a year, and it grows linearly with scale. Proposed configuration:
| Bucket | Bucket key | Justification |
|---|---|---|
mercadofresco-catalogo-fotos |
Yes | Millions of requests, public photos, the per-object trace adds nothing |
mercadofresco-informes-analitica |
Yes | 50,000 objects a month, aggregated data with no personal identifiers |
mercadofresco-copias-basedatos |
No | 90 objects a month: the saving is zero and the trace of who decrypted which backup of personal data is exactly what a GDPR audit will ask for |
The general reasoning: turn the bucket key on unless the volume is low and per-object traceability has compliance value.
Solution 3
(a) Because reading and writing require different KMS permissions. Downloading an object
encrypted with SSE-KMS needs kms:Decrypt; uploading a new one needs kms:GenerateDataKey, because
S3 has to ask for a new data key to encrypt it. In 04-01 we granted both to rol-lambda-miniaturas,
so what has happened here is that somebody has deployed a version of the policy with only
kms:Decrypt, or the bucket has changed key.
(b) You have to look in two places: the role's identity-based policy and the key policy of
alias/mercadofresco-datos. The message settles it: because no identity-based policy allows points
unambiguously at the role's identity-based policy, and it also indicates an implicit deny —an
Allow is missing— not an explicit Deny.
(c) Add the missing action to rol-lambda-miniaturas:
{
"Sid": "UsarLaClaveDelBucket",
"Effect": "Allow",
"Action": ["kms:Decrypt", "kms:GenerateDataKey"],
"Resource": "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
"Condition": {
"StringEquals": { "kms:ViaService": "s3.eu-west-1.amazonaws.com" }
}
}The minimum is kms:GenerateDataKey; kms:Decrypt is included because the function also reads the
original. kms:Encrypt is not needed: with envelope encryption, S3 never uses it.
(d) I would suspect the kms:ViaService condition. If the function had started calling KMS
directly —because somebody changed the code to encrypt before uploading, for instance, instead of
letting S3 do it— the request would no longer arrive "through S3", the condition would not be met and
the key policy would reject it. It could also be the aws:RequestedRegion Deny if the boto3 client
had been created pointing at another region. In both cases the fix is not to widen permissions, but
to correct how the call is being made.
Conclusion
alias/mercadofresco-datos has stopped being a name. It is now a customer managed key, with a
policy of its own, annual rotation switched on, project tags and an explicit separation of duties
written into it: Marta administers the key but cannot decrypt with it; rol-mercadofresco-tienda
and rol-lambda-miniaturas use it but only through S3, thanks to kms:ViaService; RDS and EBS
can create grants because they are AWS services; and nobody, not even the root user, can use it
outside eu-west-1.
You understand the mechanism that holds it all up, envelope encryption: KMS generates a data key
and returns it in two formats —in the clear and encrypted with the master key—, the application
encrypts locally at CPU speed, wipes the clear version and stores the CiphertextBlob next to the
data. That is why large data never travels to KMS, why the 4 KB limit of Encrypt is not a real
problem, and why Decrypt does not need you to tell it which key to use. You also know what
automatic rotation does and does not do: it changes the material for new operations, keeps the
old material forever and does not re-encrypt what is already stored.
You can tell SSE-S3, SSE-KMS and SSE-C apart, and you know that the real advantage of SSE-KMS is
double authorisation —s3:GetObject and kms:Decrypt—, which turns an accidental exposure
into a scare rather than a breach. You know that S3 Bucket Keys cut KMS calls by up to 99 % at
the price of losing the per-object trace, and you have decided to enable them on
mercadofresco-catalogo-fotos and not on mercadofresco-copias-basedatos, where the trace is worth
more than the saving.
In practical terms, MercadoFresco has encrypted the backups bucket with a bucket policy that
rejects any upload without KMS, has enabled EBS default encryption in eu-west-1, knows how to
convert an unencrypted volume using copy-snapshot --encrypted —the only operation that makes that
jump— and has migrated mercadofresco-pedidos to an encrypted instance by the route of snapshot,
encrypted copy and restore, without forgetting that the restore preserves neither the parameter group
nor Multi-AZ and that the endpoint changes. All of it for 1.00 USD a month, the best
cost-benefit ratio in the course. And you know that deleting a key is irreversible, that the sensible
route is to disable it first and that the 7 to 30 day wait exists precisely to save you.
There remains, however, the most uncomfortable hole of all, and it is one of those that cryptography
does not fix. The password of the mfadmin user of mercadofresco-pedidos is still where it
should not be: it went through the user data of the template lt-mercadofresco-tienda, it lives
in a configuration file on the server and there is a copy in the .env Luis shared over chat when he
set up the development environment. It makes no difference that the database is encrypted with
AES-256: anybody who reads that file gets in with valid credentials, and encryption, as we saw in the
first table of this lesson, does not protect against that. In lesson 04-03, "Secrets Manager and
Parameter Store", that password will finally leave where it is: we will see the difference between
configuration and secret, Parameter Store for the former and Secrets Manager for the latter,
how it is consumed from the application without ever writing it into a file again, and above all the
automatic rotation every 30 days with a managed Lambda, with its createSecret / setSecret /
testSecret / finishSecret cycle — which rests, precisely, on the key you have just built.
AWS Course
Module 1: Introduction to AWS
- What Is AWS?
- Setting Up Your AWS Account
- AWS Global Infrastructure
- The AWS Management Console
- AWS CLI and SDKs
Module 2: Core AWS Services
Module 3: Networking and Content Delivery
Module 4: Security and Identity
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager and Parameter Store
- AWS Shield
- AWS WAF
Module 5: Monitoring and Management
Module 6: Databases
Module 7: Application Integration
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Integration Patterns: Idempotency, Retries and Dead-Letter Queues
