AWS Security Blog

How to Eliminate the Need for Hardcoded AWS Credentials in Devices by Using the AWS IoT Credentials Provider

January 12, 2023: This post was updated to remove some instructions that are no longer needed on recent versions of Mac OSX and when run would result in an error.

August 31, 2021: AWS IoT Core Credential Provider enables customers to request temporary, limited-privilege security tokens that are valid up to 12 hours and use the tokens to sign and authenticate any AWS request. Until now, the AWS IoT Core Credential Provider issued security tokens that were valid up to 1 hour only. Now with tokens valid up to 12 hours, customers have the ability to optimize the number of calls made to the Credential Provider by caching the credentials for a longer duration per their business needs. We have updated this post to indicate that security tokens are now valid up to 12 hours.


The Internet of Things (IoT) has precipitated to an influx of connected devices and data that can be mined to gain useful business insights. If you own an IoT device, you might want the data to be uploaded seamlessly from your connected devices to the cloud so that you can make use of cloud storage and the processing power to perform sophisticated analysis of data. To upload the data to the AWS Cloud, devices must pass authentication and authorization checks performed by the respective AWS services. The standard way of authenticating AWS requests is the Signature Version 4 algorithm that requires the caller to have an access key ID and secret access key. Consequently, you need to hardcode the access key ID and the secret access key on your devices. Alternatively, you can use the built-in X.509 certificate as the unique device identity to authenticate AWS requests.

AWS IoT has introduced the credentials provider feature that allows a caller to authenticate AWS requests by having an X.509 certificate. The credentials provider authenticates a caller using an X.509 certificate, and vends a temporary, limited-privilege security token. The token can be used to sign and authenticate any AWS request. Thus, the credentials provider relieves you from having to manage and periodically refresh the access key ID and secret access key remotely on your devices.

In the process of retrieving a security token, you use AWS IoT to create a thing (a representation of a specific device or logical entity), register a certificate, and create AWS IoT policies. You also configure an AWS Identity and Access Management (IAM) role and attach appropriate IAM policies to the role so that the credentials provider can assume the role on your behalf. You also make an HTTP-over-Transport Layer Security (TLS) mutual authentication request to the credentials provider that uses your preconfigured thing, certificate, policies, and IAM role to authenticate and authorize the request, and obtain a security token on your behalf. You can then use the token to sign any AWS request using Signature Version 4.

In this blog post, I explain the AWS IoT credentials provider design and then demonstrate the end-to-end process of retrieving a security token from AWS IoT and using the token to write a temperature and humidity record to a specific Amazon DynamoDB table.

Note: This post assumes you are familiar with AWS IoT and IAM to perform steps using the AWS CLI and OpenSSL. Make sure you are running the latest version of the AWS CLI.

Overview of the credentials provider workflow

The following numbered diagram illustrates the credentials provider workflow. The diagram is followed by explanations of the steps.

Diagram of AWS IoT authentication and authorization module

To explain the steps of the workflow as illustrated in the preceding diagram:

  1. The AWS IoT device uses the AWS SDK or custom client to make an HTTPS request to the credentials provider for a security token. The request includes the device X.509 certificate for authentication.
  2. The credentials provider forwards the request to the AWS IoT authentication and authorization module to verify the certificate and the permission to request the security token.
  3. If the certificate is valid and has permission to request a security token, the AWS IoT authentication and authorization module returns success. Otherwise, it returns failure, which goes back to the device with the appropriate exception.
  4. On successful validation, the credentials provider invokes the AWS Security Token Service (AWS STS) to assume the preconfigured IAM role.
  5. If assuming the role succeeds, AWS STS returns a temporary, limited-privilege security token to the credentials provider.
  6. The credentials provider returns the security token to the device.
  7. The AWS SDK on the device uses the security token to sign an AWS request with AWS Signature Version 4.
  8. The requested service invokes IAM to validate the signature and authorize the request against access policies attached to the preconfigured IAM role.
  9. If IAM validates the signature successfully and authorizes the request, the request goes through.

In another solution, you could configure an AWS Lambda rule that ingests your device data and sends it to another AWS service. However, in applications that require the uploading of large files such as videos or aggregated telemetry to the AWS Cloud, you may want your devices to be able to authenticate and send data directly to the AWS service of your choice. The credentials provider enables you to do that.

Outline of the steps to retrieve and use security token

Perform the following steps as part of this solution:

  1. Create an AWS IoT thing: Start by creating a thing that corresponds to your home thermostat in the AWS IoT thing registry database. This allows you to authenticate the request as a thing and use thing attributes as policy variables in AWS IoT and IAM policies.
  2. Register a certificate: Create and register a certificate with AWS IoT, and attach it to the thing for successful device authentication.
  3. Create and configure an IAM role: Create an IAM role to be assumed by the service on behalf of your device. I illustrate how to configure a trust policy and an access policy so that AWS IoT has permission to assume the role, and the token has necessary permission to make requests to DynamoDB.
  4. Create a role alias: Create a role alias in AWS IoT. A role alias is an alternate data model pointing to an IAM role. The credentials provider request must include a role alias name to indicate which IAM role to assume for obtaining a security token from AWS STS. You may update the role alias on the server to point to a different IAM role and thus make your device obtain a security token with different permissions.
  5. Attach a policy: Create an authorization policy with AWS IoT and attach it to the certificate to control which device can assume which role aliases.
  6. Request a security token: Make an HTTPS request to the credentials provider and retrieve a security token and use it to sign a DynamoDB request with Signature Version 4.
  7. Use the security token to sign a request: Use the retrieved token to sign a request to DynamoDB and successfully write a temperature and humidity record from your home thermostat in a specific table. Thus, starting with an X.509 certificate on your home thermostat, you can successfully upload your thermostat record to DynamoDB and use it for further analysis. Before the availability of the credentials provider, you could not do this.

Deploy the solution

1.  Create an AWS IoT thing

Register your home thermostat in the AWS IoT thing registry database by creating a thing type and a thing. You can use the AWS CLI with the following command to create a thing type. The thing type allows you to store description and configuration information that is common to a set of things.

aws iot create-thing-type --thing-type-name Thermostat

The following is sample output of the create-thing-type command. It contains the thingTypeName, thingTypeId, and thingTypeArn.

{
    "thingTypeName": "Thermostat", 
    "thingTypeId": "11f6d708-919e-479d-8a37-790ce48204d8",
    "thingTypeArn": "arn:aws:iot:us-east-1:<your_aws_account_id>:thingtype/Thermostat"
}

Run the following command in the AWS CLI to create a thing.

aws iot create-thing --thing-name MyHomeThermostat --thing-type-name Thermostat --attribute-payload "{\"attributes\": {\"Owner\":\"Alice\"}}"

The following is sample output of the create-thing command. It contains the thingArn, thingName, and thingId.

{
    "thingArn": "arn:aws:iot:us-east-1:<your_aws_account_id>:thing/MyHomeThermostat", 
    "thingName": "MyHomeThermostat",
    "thingId": "a9b098ac-2ee9-4ba6-aa40-163113eb18d0"
}

2.  Register a certificate

Now, you need to have a Certificate Authority (CA) certificate, sign a device certificate using the CA certificate, and register both certificates with AWS IoT before your device can authenticate to AWS IoT. If you do not already have a CA certificate, you can use OpenSSL to create a CA certificate, as described in Use Your Own Certificate. To register your CA certificate with AWS IoT, follow the steps on Registering Your CA Certificate.

You then have to create a device certificate signed by the CA certificate and register it with AWS IoT, which you can do by following the steps on Creating a Device Certificate Using Your CA Certificate. Save the certificate and the corresponding key pair; you will use them when you request a security token later. Also, remember the password you provide when you create the certificate.

Run the following command in the AWS CLI to attach the device certificate to your thing so that you can use thing attributes in policy variables.

aws iot attach-thing-principal --thing-name MyHomeThermostat --principal <certificate-arn>

If the attach-thing-principal command succeeds, the output is empty.

3.  Configure an IAM role

Next, configure an IAM role in your AWS account that will be assumed by the credentials provider on behalf of your device. You are required to associate two policies with the role: a trust policy that controls who can assume the role, and an access policy that controls which actions can be performed on which resources by assuming the role.

The following trust policy grants the credentials provider permission to assume the role. Put it in a text document and save the document with the name, trustpolicyforiot.json.

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Principal": {"Service": "credentials.iot.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }
}

Run the following command in the AWS CLI to create an IAM role with the preceding trust policy.

aws iam create-role --role-name dynamodb-access-role --assume-role-policy-document file://trustpolicyforiot.json

The following is sample output of the create-role command.

{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17", 
            "Statement": {
                "Action": "sts:AssumeRole", 
                "Effect": "Allow", 
                "Principal": {
                    "Service": "credentials.iot.amazonaws.com"
                }
            }
        }, 
        "RoleId": "AROAJWE6XX2DBF3PMINT6", 
        "CreateDate": "2018-01-18T08:40:03.788Z", 
        "RoleName": "dynamodb-access-role", 
        "Path": "/", 
        "Arn": "arn:aws:iam::<your_aws_account_id>:role/dynamodb-access-role"
    }
}

The following access policy allows DynamoDB operations on the table that has the same name as the thing name that you created in Step 1, MyHomeThermostat, by using credentials-iot:ThingName as a policy variable. I explain after Step 5 about using thing attributes as policy variables. Put the following policy in a text document and save the document with the name, accesspolicyfordynamodb.json.

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": [
      "dynamodb:GetItem",
      "dynamodb:BatchGetItem",
      "dynamodb:PutItem",
      "dynamodb:UpdateItem",
      "dynamodb:DeleteItem"
    ],
    "Resource": "arn:aws:dynamodb:us-east-1:<your_aws_account_id>:table/${credentials-iot:ThingName}"
  }
}

Run the following command in the AWS CLI to create the access policy.

aws iam create-policy --policy-name accesspolicyfordynamodb --policy-document file://accesspolicyfordynamodb.json

The following is sample output of the create-policy command.

{
    "Policy": {
        "PolicyName": "accesspolicyfordynamodb", 
        "CreateDate": "2018-01-18T08:47:38.368Z", 
        "AttachmentCount": 0, 
        "IsAttachable": true, 
        "PolicyId": "ANPAJMRTTZH25SEHZOBYG", 
        "DefaultVersionId": "v1", 
        "Path": "/", 
        "Arn": "arn:aws:iam::<your_aws_account_id>:policy/accesspolicyfordynamodb", 
        "UpdateDate": "2018-01-18T08:47:38.368Z"
    }
}

Finally, run the following command in the AWS CLI to attach the access policy to your role.

aws iam attach-role-policy --role-name dynamodb-access-role --policy-arn arn:aws:iam::<your_aws_account_id>:policy/accesspolicyfordynamodb

If the attach-role-policy command succeeds, the output is empty.

Configure the PassRole permissions

The IAM role that you have created must be passed to AWS IoT to create a role alias, as described in Step 4. The user who performs the operation requires iam:PassRole permission to authorize this action. You also should add permission for the iam:GetRole action to allow the user to retrieve information about the specified role. Create the following policy to grant iam:PassRole and iam:GetRole permissions. Name this policy, passrolepermission.json.

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": [
        "iam:GetRole",
        "iam:PassRole"
    ],
    "Resource": "arn:aws:iam::<your_aws_account_id>:role/dynamodb-access-role"
  }
}

Run the following command in the AWS CLI to create the policy in your AWS account.

aws iam create-policy --policy-name passrolepermission --policy-document file://passrolepermission.json

The following is sample output of the create-policy command.

{
    "Policy": {
        "PolicyName": "passrolepermission", 
        "CreateDate": "2018-01-18T08:53:35.016Z", 
        "AttachmentCount": 0, 
        "IsAttachable": true, 
        "PolicyId": "ANPAJ6HSQYSBLAUCR5XRC", 
        "DefaultVersionId": "v1", 
        "Path": "/", 
        "Arn": "arn:aws:iam::<your_aws_account_id>:policy/passrolepermission", 
        "UpdateDate": "2018-01-18T08:53:35.016Z"
    }
}

Now, run the following command to attach the policy to the user.

aws iam attach-user-policy --policy-arn arn:aws:iam::<your_aws_account_id>:policy/passrolepermission --user-name <user_name>

If the attach-user-policy command succeeds, the output is empty.

4.  Create a role alias

Now that you have configured the IAM role, you will create a role alias with AWS IoT. You must provide the following pieces of information when creating a role alias:

  1. RoleAlias: This is the primary key of the role alias data model and hence a mandatory attribute. It is a string; the minimum length is 1 character, and the maximum length is 128 characters.
  2. RoleArn: This is the Amazon Resource Name (ARN) of the IAM role you have created. This is also a mandatory attribute.
  3. CredentialDurationSeconds: This is an optional attribute specifying the validity (in seconds) of the security token. The minimum value is 900 seconds (15 minutes), and the maximum value is 43,200 seconds (12 hours); the default value is 3,600 seconds, if not specified.

Run the following command in the AWS CLI to create a role alias. Use the credentials of the user to whom you have given the iam:PassRole permission.

aws iot create-role-alias --role-alias Thermostat-dynamodb-access-role-alias --role-arn arn:aws:iam::<your_aws_account_id>:role/dynamodb-access-role --credential-duration-seconds 43200

The following is sample output of the create-role-alias command. It contains the roleAlias as specified in the request and the roleArn.

{
    "roleAlias": "Thermostat-dynamodb-access-role-alias",
    "roleAliasArn": "arn:aws:iot:us-east-1:<your_aws_account_id>:rolealias/Thermostat-dynamodb-access-role-alias"
}

5.  Attach a policy

You created and registered a certificate with AWS IoT earlier for successful authentication of your device. Now, you need to create and attach a policy to the certificate to authorize the request for the security token.

Let’s say you want to allow a thing to get credentials for the role alias, Thermostat-dynamodb-access-role-alias, with thing owner Alice, thing type thermostat, and the thing attached to a principal. The following policy, with thing attributes as policy variables, achieves these requirements. After this step, I explain more about using thing attributes as policy variables. Put the policy in a text document, and save it with the name, alicethermostatpolicy.json.

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "iot:AssumeRoleWithCertificate",
    "Resource": "arn:aws:iot:us-east-1:<your_aws_account_id>:rolealias/${iot:Connection.Thing.ThingTypeName}-dynamodb-access-role-alias",
    "Condition": {
      "StringEquals": {
        "iot:Connection.Thing.Attributes[Owner]": "Alice",
        "iot:Connection.Thing.ThingTypeName": "Thermostat"
      },
      "Bool": {
        "iot:Connection.Thing.IsAttached": "true"
      }
    }
  }
}

Run the following command in the AWS CLI to create the policy in your AWS IoT database.

aws iot create-policy --policy-name AliceThermostatPolicy --policy-document file://alicethermostatpolicy.json

The following is sample output of the create-policy command. It contains the policyName, policyArn, policyDocument, and policyVersionId.

{
    "policyName": "AliceThermostatPolicy", 
    "policyArn": "arn:aws:iot:us-east-1:<your_aws_account_id>:policy/AliceThermostatPolicy", 
    "policyDocument": "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": {\n    \"Effect\": \"Allow\",\n    \"Action\": \"iot:AssumeRoleWithCertificate\",\n    \"Resource\": \"arn:aws:iot:us-east-1:<your_aws_account_id>:rolealias/${iot:Connection.Thing.ThingTypeName}-dynamodb-access-role-alias\",\n    \"Condition\": {\n      \"StringEquals\": {\n        \"iot:Connection.Thing.Attributes[Owner]\": \"Alice\",\n        \"iot:Connection.Thing.ThingTypeName\": \"Thermostat\"\n      },\n      \"Bool\": {\n        \"iot:Connection.Thing.IsAttached\": \"true\"\n      }\n    }\n  }\n}\n", 
    "policyVersionId": "1"
}

Use the following command to attach the policy with the certificate you registered earlier.

aws iot attach-policy --policy-name AliceThermostatPolicy --target <certificate-arn>

If the attach-policy command succeeds, the output is empty.

You have completed all the necessary steps to request an AWS security token from the credentials provider!

Using thing attributes as policy variables

Before I show how to request a security token, I want to explain more about how to use thing attributes as policy variables and the advantage of using them. As a prerequisite, a device must provide a thing name in the credentials provider request.

Thing substitution variables in AWS IoT policies

AWS IoT Simplified Permission Management allows you to associate a connection with a specific thing, and allow the thing name, thing type, and other thing attributes to be available as substitution variables in AWS IoT policies. You can write a generic AWS IoT policy as in alicethermostatpolicy.json in Step 5, attach it to multiple certificates, and authorize the connection as a thing. For example, you could attach alicethermostatpolicy.json to certificates corresponding to each of the thermostats you have that you want to assume the role alias, Thermostat-dynamodb-access-role-alias, and allow operations only on the table with the name that matches the thing name. For more information, see the full list of thing policy variables.

Thing substitution variables in IAM policies

You also can use the following three substitution variables in the IAM role’s access policy (I used credentials-iot:ThingName in accesspolicyfordynamodb.json in Step 3):

  • credentials-iot:ThingName
  • credentials-iot:ThingTypeName
  • credentials-iot:AwsCertificateId

When the device provides the thing name in the request, the credentials provider fetches these three variables from the database and adds them as context variables to the security token. When the device uses the token to access DynamoDB, the variables in the role’s access policy are replaced with the corresponding values in the security token. Note that you also can use credentials-iot:AwsCertificateId as a policy variable; AWS IoT  returns certificateId during registration.

6. Request a security token

Make an HTTPS request to the credentials provider to fetch a security token. You have to supply the following information:

  • Certificate and key pair: Because this is an HTTP request over TLS mutual authentication, you have to provide the certificate and the corresponding key pair to your client while making the request. Use the same certificate and key pair that you used during certificate registration with AWS IoT.
  • RoleAlias: Provide the role alias (in this example, Thermostat-dynamodb-access-role-alias) to be assumed in the request.
  • ThingName: Provide the thing name that you created earlier in the AWS IoT thing registry database. This is passed as a header with the name, x-amzn-iot-thingname. Note that the thing name is mandatory only if you have thing attributes as policy variables in AWS IoT or IAM policies.

Run the following command in the AWS CLI to obtain your AWS account-specific endpoint for the credentials provider. See the DescribeEndpoint API documentation for further details.

aws iot describe-endpoint --endpoint-type iot:CredentialProvider

The following is sample output of the describe-endpoint command. It contains the endpointAddress.

{
    "endpointAddress": "<your_aws_account_specific_prefix>.credentials.iot.us-east-1.amazonaws.com"
}

Now, make an HTTPS request to the credentials provider to fetch a security token. You may use your preferred HTTP client for the request. I use curl in the following examples. Replace <device-certificate-pem> with the path and name of your certificate file, and <device-certificate-key-pair> with the path and name of the that contains both your public and private key.

curl --cert <device-certificate-pem> --key <device-certificate-key-pair> -H "x-amzn-iot-thingname: MyHomeThermostat" https://<your_credentials_provider_endpoint>/role-aliases/Thermostat-dynamodb-access-role-alias/credentials

This command returns a security token object that has an accessKeyId, a secretAccessKey, a sessionToken, and an expiration. The following is sample output of the curl command.

{"credentials":{"accessKeyId":"ASIAJAKDHYFVFZCETC4A","secretAccessKey":"6RcojWDX0uWj3hRekTu/kOhXyyfqMU+T5U81LZzf","sessionToken":"AgoGb3JpZ2luEHIaCXVzLWVhc3QtMSKAAlEYX58Gy0MT+PLwB3SJohrUPVQP0Ai65XgwWy+k7j2knjGgvTc5XL/s5AEZsCHtCGdhXZrjk3SKnE2WG5+HQUz+H8qems7yuphjDsbRSVdBRLRDwu5FSlks2fMzSrjY8yrjjkmjixzClcWEE680+0vu5/Zmfb81uGarXvWQnibl+HNMPMePblcat/rgKvxwQujI5oQX+BEyKPmcKPi0u5WlWlOCEe2KsHA//Em6nAYIZjbLK9dWQ5MZrEgJ8wibbMsKfGu6gfqm1Qp/cqD1KFLHUNNlKHSQGD7GDsExV268FVltX+6mskAwRYS4wQFhIDPGglPc4+KbDFwUp2meJAwqiwMI+P//////////ARABGgw4ODA2ODQ1MzQxNDEiDIIRUr2U84+UW3syVyrfAult54skNMSlm2cEhNZnU6FtdFbcNvxP602OfnzF2ovtTanipMeP9fhO5Mcbnb/fuAK2HZ4W3MLVY+nzWGloDWrcrQ0tH1sxdeDp1fPJlwOg/vUIPZcLICGubsMxbPCLz5g6oX4C2fgJcxDiV3pHfCQR2wpvdfltct0eJVT+b9VFWtESsC+DCmoZGOf1QygT16UjLEDQiMwoH0a2KwItl338VbjJdTNTqx11Rzmm6PIHGu5J5efCmaN+ZUoDwbedTCdT1BBZpOihaEqi2tcsUx9BGx1jjkfyfjBO9XhJXgEpYHv7yBSJRkPJOXbcBlWcy02Pp8rQvdSu8c5nQvt+2pzZC8xAB5oFqb6OVJa1wa8/u4k82/jjjrxeka5kSkZQ751eykeWn/ZPo092E07ZDSCxqF1fEGBZ+ieTIfH3MqRcvhTnZADfdRj4v2unVPTXp7lH5cMQuSgqKC0hyR3pNDC6yKXSBQ==","expiration":"2018-01-18T09:18:06Z"}}

7.    Use the security token to sign a request

Create a DynamoDB table called MyHomeThermostat in your AWS account. You will have to choose the hash (partition key) and the range (sort key) while creating the table to uniquely identify a record. Make the hash the serial_number of the thermostat and the range the timestamp of the record. Create a text file with the following JSON to put a temperature and humidity record in the table. Name the file, item.json.

{
  "serial_number": {"S": "123456789"},
  "timestamp": {"S": "2017-11-20T06:00:00.000Z"},
  "current_temp": {"N": "65"},
  "target_temp": {"N": "70"},
  "humidity": {"N": "45"}
}

You can use the accessKeyId, secretAccessKey, and sessionToken retrieved from the output of the curl command to sign a request that writes the temperature and humidity record to the DynamoDB table. Use the following commands to accomplish this.

export AWS_ACCESS_KEY_ID=<access-key-id>
export AWS_SECRET_ACCESS_KEY=<secret-access-key>
export AWS_SESSION_TOKEN=<session-token>
aws dynamodb put-item --table-name MyHomeThermostat --item file://item.json --return-consumed-capacity TOTAL

The following is sample output of the put-item command.

{
    "ConsumedCapacity": {
        "CapacityUnits": 1.0, 
        "TableName": "MyHomeThermostat"
    }
}

Conclusion

In this blog post, I demonstrated how to retrieve a security token by using an X.509 certificate and then writing an item to a DynamoDB table by using the security token. Similarly, you could run applications on surveillance cameras or sensor devices that exchange the X.509 certificate for an AWS security token and use the token to upload video streams to Amazon Kinesis or telemetry data to Amazon CloudWatch.

If you have comments about this blog post, submit them in the “Comments” section below. If you have questions about or issues implementing this solution, start a new thread on the AWS IoT forum.

– Ramkishore