Using AWS KMS to Encrypt a Secret

In the previous section, you created an AWS KMS key in AWS KMS. In this section, you will use this key to encrypt some data.

You should run the commands in this section using an AWS principal (IAM User or Role) with kms:Encrypt permission on that AWS KMS key.

Overview

The Anjuna Nitro Runtime tools provide the ability to automatically configure enclaves with secrets encrypted by a KMS key.

When building the Enclave Image File (EIF), the Anjuna Nitro Runtime can optionally take a configuration file that contains various information on how to set up the enclave when it starts. One of the configuration items in that file is the location of the encrypted data in an S3 bucket. When the enclave starts, the Anjuna Nitro Runtime performs the following steps:

  • Read the location of the encrypted file from the configuration file

  • Download the encrypted file from AWS S3

  • Prepare an attestation document

  • Submit a request to KMS to decrypt the file (which includes submitting the attestation document)

  • Store the secrets in environment variables or files which are accessible to applications running in the enclave

In this example, you will learn how to expose secrets to the application running in the AWS Nitro Enclave through environment variables.

The first step is to encrypt some data using a KMS key, and store the encrypted file in an S3 bucket. On the next page, you will build an enclave that can automatically download this file, and decrypt it using AWS KMS.

Prerequisites

This section assumes that a few AWS objects have been created, and permissions to access them are granted to the current user:

  • an AWS KMS key

  • an AWS Nitro-based EC2 instance associated with an IAM role that has access to KMS

  • an S3 bucket for storing KMS-encrypted files

The following AWS objects must reside in the same AWS region:

  • The AWS Nitro-based EC2 instance

  • The AWS KMS key

  • The AWS S3 bucket that will contain the KMS-encrypted data

If the requirements above are met, declare the following environment variables (the example commands shown on this page will use those environment variables):

$ export AWS_DEFAULT_REGION=<your-region>
$ export KMS_KEY_ID=<your-kms-key>
$ export AWS_S3_BUCKET=<your-s3-bucket>

For example:

$ export AWS_DEFAULT_REGION=us-east-2
$ export KMS_KEY_ID=arn:aws:kms:us-east-2:111122223333:alias/nitro-kms-key
$ export AWS_S3_BUCKET=anjuna-encrypted-data

The KMS_KEY_ID can be specified in multiple forms:

For example:

  • Key ID: 1234abcd-12ab-34cd-56ef-1234567890ab

  • Key ARN: arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab

  • Alias name: alias/nitro-kms-key

  • Alias ARN: arn:aws:kms:us-east-2:111122223333:alias/nitro-kms-key

Verification

Before using the AWS objects mentioned above, check that they are valid by running the commands listed below. The commands will produce output to help verify the state of your AWS resources.

This assumes that you have the AWS CLI tools installed in this host and that you have defined the environment variables AWS_DEFAULT_REGION, KMS_KEY_ID, and AWS_S3_BUCKET.

Validate the IAM Role

Run the following command (replacing the string <iam-role-for-instance> with the IAM role you want to use for your AWS Nitro-based EC2 instance):

$ aws iam get-role --role-name <iam-role-for-instance> | jq .

The command should produce output similar to this:

{
  "Role": {
    "AssumeRolePolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Action": "sts:AssumeRole",
          "Principal": {
            "Service": "ec2.amazonaws.com"
          },
          "Effect": "Allow",
          "Sid": ""
        }
      ]
    },
    "MaxSessionDuration": 3600,
    "RoleId": "AROAYDZEBCMBGI7I65HWP",
    "CreateDate": "2021-04-20T06:33:56Z",
    "RoleName": "<iam-role-for-instance>",
    "Path": "/",
    "RoleLastUsed": {
      "Region": "<your-region>",
      "LastUsedDate": "2021-04-21T08:03:51Z"
    },
    "Arn": "arn:aws:iam::account:role/<iam-role-for-instance>"
  }
}

This output shows that the IAM role exists. If you receive an error like "An error occurred (NoSuchEntity) when calling the GetRole operation", create a new IAM role using the AWS console or CLI.

Validate the AWS KMS Key

Enter the following command to get some information on the AWS KMS key:

$ aws kms describe-key --key-id ${KMS_KEY_ID} | jq .

If the command succeeds, ensure that the region for this key (in the Arn field) matches the value you set for the variable AWS_DEFAULT_REGION.

Once you have verified that the key is valid, inspect the policy associated with the key to make sure that the permissions are set up correctly.

Run the following command to inspect the policy associated with the KMS key:

$ KMS_KEY_ARN=$(aws kms describe-key --key-id ${KMS_KEY_ID} | jq -r .KeyMetadata.Arn)
$ aws kms get-key-policy --key-id ${KMS_KEY_ARN} --policy-name default | jq -r .Policy | jq .

Inspect the output of the command above, and make sure the policies are correct.

  • As the owner of the key, you should have permission to manage the KMS key (change the policies associated with this KMS key) and encrypt data using the KMS key. One of the entries in the JSON policy attached to the key should look like this:

{
  "Sid": "Allow admin and encryption for the user that created the key, but not decryption",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::<account>:user/<your-username>"
  },
  "Action": [
    "kms:CancelKeyDeletion",
    "kms:Create*",
    "kms:Delete*",
    "kms:Describe*",
    "kms:DescribeKey",
    "kms:Disable*",
    "kms:Enable*",
    "kms:Encrypt",
    "kms:GenerateDataKey*",
    "kms:Get*",
    "kms:List*",
    "kms:Put*",
    "kms:ReEncrypt*",
    "kms:Revoke*",
    "kms:ScheduleKeyDeletion",
    "kms:TagResource",
    "kms:UntagResource",
    "kms:Update*"
  ],
  "Resource": "*"
}
  • The IAM role attached to the AWS Nitro-based EC2 instance can be used to authenticate to KMS and decrypt data when an AWS Nitro Enclave presents a valid attestation document. One of the entries in the JSON policy attached to the key should look like this (in this example, the policy uses the PCR0 value, but any combination of the enclave measurements can be used):

{
  "Sid": "Enable decrypt for Nitro Enclaves running on EC2 instance associated with the proper role",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::<account>:<iam-role-for-instance>/<>"
  },
  "Action": "kms:Decrypt",
  "Resource": "*",
  "Condition": {
    "StringEqualsIgnoreCase": {
      "kms:RecipientAttestation:PCR0": ""
    }
  }
}
Since you have not built the AWS Nitro Enclave (EIF), the enclave measurements are not yet known, and the value for PCR0 can be left empty. You will use the actual values of the enclave measurements on the next page.

Validate the AWS S3 bucket

Run this command to ensure that the AWS S3 bucket you specified is valid:

$ aws s3 ls --summarize ${AWS_S3_BUCKET}

If the command succeeds, verify that the policy attached to the bucket has GetObject (i.e. READ) permission granted to the IAM role attached to the AWS Nitro-based EC2 instance.

$ aws s3api get-bucket-policy --bucket ${AWS_S3_BUCKET} | jq -r .Policy | jq .

The output of the command should look like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Allow nitro instance role the ability to retrieve the bucket objects",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<account>:role/<iam-role-for-instance>"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::<your-s3-bucket>/*"
    }
  ]
}

Create a Secret File

Create a file named secret-data.yaml with list of environment variables and files that should be made available to the application running in the AWS Nitro Enclave.

The following example provides two environment variables (INITDB_ROOT_USERNAME and INITDB_ROOT_PASSWORD) and a configuration file:

version: 1.7

environment:
  - INITDB_ROOT_USERNAME=anjuna
  - INITDB_ROOT_PASSWORD=0mMZMU01NChE095yZ7

files:
  - path:  "/etc/my-application-startup.conf"
    mode:  0644
    content: |
      # This is an example of configuration file.
      # It could be anything, including TLS certificates
      Hello world!
You can specify as many environment variables as you want in this file.

The Anjuna Nitro Runtime will not create the directories for the file entries. Make sure the directory exists as part of the Docker container specification.

In the example above, the enclave startup will fail if you specify a path like /etc/my-application/startup.conf if the directory /etc/my-application does not exist.

Encrypt the data

Make sure you have the following information available before running the command:

  • the ARN of your AWS KMS key

  • the AWS region of the AWS KMS key

  • the S3 bucket for encrypted secrets

Run the following command (assuming you have defined the variables with the values for AWS_DEFAULT_REGION, KMS_KEY_ID and AWS_S3_BUCKET):

$ anjuna-nitro-encrypt                  \
    --cmk $KMS_KEY_ID                   \
    --bucket-name $AWS_S3_BUCKET        \
    --bucket-key kms-encrypted-data.bin \
    --config secret-data.yaml

If the command succeeds, a new file will be created in the specified S3 bucket, encrypted by the specified AWS KMS key. Only enclaves that match the condition specified in the AWS KMS key policy will be allowed to decrypt this file.

The next step is to build an enclave, measure it, and update the policy attached to your AWS KMS key to authorize this enclave to decrypt the data.

Most of the steps listed in this section verified that the AWS S3 objects were created and set up properly. The actual encryption of the secrets is achieved by calling the anjuna-nitro-encrypt command-line tool.