Skip to content

Identities

Load naming and paths from config/walkthrough.env (Prerequisites). All commands assume the repo root as the current directory. This page is plain AWS CLI
AWS Command Line Interface v2 — every cloud and deployment step in this walkthrough is a visible aws command, not a wrapper script.
— no wrappers.

Terminal window
set -a && source config/walkthrough.env && set +a
(no output)

What this page builds:

  • Core cert ≠ client cert.
  • Token exchange is core-only: cert → role alias → IAM role → temp creds.
  • Service role is account-level, not on either Thing.

Build order (plain CLI):

  1. 1IoT data endpointdescribe-endpoint
  2. 2Core Thing + certcreate-thing · create-keys
  3. 3Thing groupcreate-thing-group
  4. 4Token exchange rolecreate-role · create-role-alias
  5. 5Client Thing + certcreate-thing · create-keys
  6. 6Greengrass service roleassociate-service-role-to-account
Plain aws iot / aws iam / aws greengrassv2calls, in order. Each resource is created before the installer needs it.

MQTT data endpoint (ATS) for firmware and Nucleus config:

Terminal window
aws iot describe-endpoint --endpoint-type iot:Data-ATS \
--query endpointAddress --output text
example123abc-ats.iot.ap-southeast-2.amazonaws.com

Save it as AWS_IOT_ENDPOINT in walkthrough.env for the ESP32-S3
Espressif ESP32-S3 — Wi‑Fi microcontroller used here as the Greengrass client device (not a second core).
firmware.

Local folders for core and client PEMs:

Terminal window
mkdir -p "$CORE_CERTS_DIR" "$CLIENT_CERTS_DIR"
(no output)

Register the core
Greengrass core device — the Linux host running Nucleus (this lab: Intel NUC6CAY). Client devices discover and connect to it.
as an IoT Thing:

Terminal window
aws iot create-thing --thing-name "$CORE_THING_NAME"
{
"thingName": "gg-edge-wt-dev-core",
"thingArn": "arn:aws:iot:ap-southeast-2:123456789012:thing/gg-edge-wt-dev-core",
"thingId": "01234567-89ab-cdef-0123-456789abcdef"
}

Create and activate the core device certificate; keep the ARN for attaches:

Terminal window
aws iot create-keys-and-certificate --set-as-active \
--certificate-pem-outfile "$CORE_CERTS_DIR/device.pem.crt" \
--public-key-outfile "$CORE_CERTS_DIR/public.pem.key" \
--private-key-outfile "$CORE_CERTS_DIR/private.pem.key" \
> "$CORE_CERTS_DIR/create-cert.json"
CORE_CERT_ARN=$(jq -r .certificateArn "$CORE_CERTS_DIR/create-cert.json")
echo "$CORE_CERT_ARN" | tee "$CORE_CERTS_DIR/certificateArn.txt"
arn:aws:iot:ap-southeast-2:123456789012:cert/abcdef0123456789…

So the core can trust IoT Core:

Terminal window
curl -sS https://www.amazontrust.com/repository/AmazonRootCA1.pem \
-o "$CORE_CERTS_DIR/AmazonRootCA1.pem"
(no output)

From this repo’s policy JSON:

Terminal window
aws iot create-policy \
--policy-name "${PROJECT_NAME}-${ENVIRONMENT}-core-policy" \
--policy-document "file://artifacts/policies/core-device-policy.json"
{
"policyName": "gg-edge-wt-dev-core-policy",
"policyArn": "arn:aws:iot:ap-southeast-2:123456789012:policy/gg-edge-wt-dev-core-policy",
"policyVersionId": "1"
}

Attach the IoT policy
AWS IoT policy — JSON permissions attached to a certificate (core connect/publish; client greengrass:Discover and related actions).
to the cert, then bind the cert to the core Thing:

Terminal window
aws iot attach-policy \
--policy-name "${PROJECT_NAME}-${ENVIRONMENT}-core-policy" \
--target "$CORE_CERT_ARN"
aws iot attach-thing-principal \
--thing-name "$CORE_THING_NAME" \
--principal "$CORE_CERT_ARN"
(no output)

So cloud deployments can target the core
Greengrass core device — the Linux host running Nucleus (this lab: Intel NUC6CAY). Client devices discover and connect to it.
.

Terminal window
aws iot create-thing-group --thing-group-name "$CORE_THING_GROUP"
{
"thingGroupName": "gg-edge-wt-dev-cores",
"thingGroupArn": "arn:aws:iot:ap-southeast-2:123456789012:thinggroup/gg-edge-wt-dev-cores",
"thingGroupId": "01234567-89ab-cdef-0123-456789abcdef"
}

Put the core in the Thing group
AWS IoT Thing group — deployment target for Greengrass cloud deployments (this lab: the core's group).
and keep the ARN for later deployments:

Terminal window
aws iot add-thing-to-thing-group \
--thing-group-name "$CORE_THING_GROUP" \
--thing-name "$CORE_THING_NAME"
CORE_THING_GROUP_ARN=$(aws iot describe-thing-group \
--thing-group-name "$CORE_THING_GROUP" \
--query thingGroupArn --output text)
echo "$CORE_THING_GROUP_ARN"
arn:aws:iot:ap-southeast-2:123456789012:thinggroup/gg-edge-wt-dev-cores

4. Token exchange role (Nucleus credentials)

Section titled “4. Token exchange role (Nucleus credentials)”

Nucleus
Greengrass Nucleus — the edge runtime on the core device. Installs components, talks to AWS, and hosts the local client-device stack.
needs temporary AWS credentials via a token exchange
IoT role alias + IAM role — lets Nucleus exchange its device certificate for temporary AWS credentials on the core.
role alias ( IAM
AWS Identity and Access Management — roles for Nucleus token exchange and the Greengrass service role used with client devices.
+ IoT).

Terminal window
TOKEN_ROLE_NAME="${PROJECT_NAME}-${ENVIRONMENT}-token-exchange"
ROLE_ALIAS_NAME="${PROJECT_NAME}-${ENVIRONMENT}-token-alias"
echo "$TOKEN_ROLE_NAME"
echo "$ROLE_ALIAS_NAME"
gg-edge-wt-dev-token-exchange
gg-edge-wt-dev-token-alias

Role that IoT Credentials Provider can assume:

Terminal window
aws iam create-role \
--role-name "$TOKEN_ROLE_NAME" \
--assume-role-policy-document "file://artifacts/policies/token-exchange-trust.json"
{
"Role": {
"Path": "/",
"RoleName": "gg-edge-wt-dev-token-exchange",
"RoleId": "AROAEXAMPLETOKENEXCH",
"Arn": "arn:aws:iam::123456789012:role/gg-edge-wt-dev-token-exchange",
"CreateDate": "2026-10-04T10:34:39+00:00",
…
}
}

Grant logs / S3 artifact access Nucleus needs; print the role ARN:

Terminal window
aws iam put-role-policy \
--role-name "$TOKEN_ROLE_NAME" \
--policy-name "${TOKEN_ROLE_NAME}-access" \
--policy-document "file://artifacts/policies/token-exchange-access.json"
TOKEN_ROLE_ARN=$(aws iam get-role --role-name "$TOKEN_ROLE_NAME" --query Role.Arn --output text)
echo "$TOKEN_ROLE_ARN"
arn:aws:iam::123456789012:role/gg-edge-wt-dev-token-exchange

Points at the IAM role above:

Terminal window
aws iot create-role-alias \
--role-alias "$ROLE_ALIAS_NAME" \
--role-arn "$TOKEN_ROLE_ARN"
{
"roleAlias": "gg-edge-wt-dev-token-alias",
"roleAliasArn": "arn:aws:iot:ap-southeast-2:123456789012:rolealias/gg-edge-wt-dev-token-alias"
}

Needed for the IoT policy document:

Terminal window
ROLE_ALIAS_ARN=$(aws iot describe-role-alias \
--role-alias "$ROLE_ALIAS_NAME" \
--query roleAliasDescription.roleAliasArn --output text)
echo "$ROLE_ALIAS_ARN"
arn:aws:iot:ap-southeast-2:123456789012:rolealias/gg-edge-wt-dev-token-alias

Allows the core certificate to assume the alias (iot:AssumeRoleWithCertificate):

Terminal window
cat > /tmp/token-exchange-iot-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iot:AssumeRoleWithCertificate",
"Resource": "${ROLE_ALIAS_ARN}"
}
]
}
EOF
aws iot create-policy \
--policy-name "${PROJECT_NAME}-${ENVIRONMENT}-token-exchange-policy" \
--policy-document file:///tmp/token-exchange-iot-policy.json
{
"policyName": "gg-edge-wt-dev-token-exchange-policy",
"policyArn": "arn:aws:iot:ap-southeast-2:123456789012:policy/gg-edge-wt-dev-token-exchange-policy",
"policyVersionId": "1"
}
Terminal window
aws iot attach-policy \
--policy-name "${PROJECT_NAME}-${ENVIRONMENT}-token-exchange-policy" \
--target "$CORE_CERT_ARN"
(no output)

The client device
Greengrass client device — an IoT Thing (here the ESP32-S3) that discovers a core and uses local MQTT. It does not run Nucleus.
gets its own Thing
AWS IoT Thing — logical device identity bound to a certificate (core Thing on the NUC; client Thing on the ESP32-S3).
and certificate (separate from the core).

Terminal window
aws iot create-thing --thing-name "$CLIENT_THING_NAME"
{
"thingName": "gg-edge-wt-dev-esp32-1",
"thingArn": "arn:aws:iot:ap-southeast-2:123456789012:thing/gg-edge-wt-dev-esp32-1",
"thingId": "fedcba98-7654-3210-fedc-ba9876543210"
}

Create and activate; keep the ARN:

Terminal window
aws iot create-keys-and-certificate --set-as-active \
--certificate-pem-outfile "$CLIENT_CERTS_DIR/device.pem.crt" \
--public-key-outfile "$CLIENT_CERTS_DIR/public.pem.key" \
--private-key-outfile "$CLIENT_CERTS_DIR/private.pem.key" \
> "$CLIENT_CERTS_DIR/create-cert.json"
CLIENT_CERT_ARN=$(jq -r .certificateArn "$CLIENT_CERTS_DIR/create-cert.json")
echo "$CLIENT_CERT_ARN"
arn:aws:iot:ap-southeast-2:123456789012:cert/fedcba9876543210…
Terminal window
curl -sS https://www.amazontrust.com/repository/AmazonRootCA1.pem \
-o "$CLIENT_CERTS_DIR/AmazonRootCA1.pem"
aws iot create-policy \
--policy-name "${PROJECT_NAME}-${ENVIRONMENT}-client-policy" \
--policy-document "file://artifacts/policies/client-device-policy.json"
{
"policyName": "gg-edge-wt-dev-client-policy",
"policyArn": "arn:aws:iot:ap-southeast-2:123456789012:policy/gg-edge-wt-dev-client-policy",
"policyVersionId": "1"
}
Terminal window
aws iot attach-policy \
--policy-name "${PROJECT_NAME}-${ENVIRONMENT}-client-policy" \
--target "$CLIENT_CERT_ARN"
aws iot attach-thing-principal \
--thing-name "$CLIENT_THING_NAME" \
--principal "$CLIENT_CERT_ARN"
(no output)

Skip if you only have one board. One client is enough for the closed loop.

Uncomment in walkthrough.env (names must stay under CLIENT_THING_PREFIX so Auth’s thingName: …-esp32* rule matches all):

Terminal window
CLIENT_THING_NAME_2="${PROJECT_NAME}-${ENVIRONMENT}-esp32-2"
CLIENT_CERTS_DIR_2=certs/client-2
# Optional third zone:
# CLIENT_THING_NAME_3="${PROJECT_NAME}-${ENVIRONMENT}-esp32-3"
# CLIENT_CERTS_DIR_3=certs/client-3
(no output)

Then create each extra Thing + cert (reuse the same client IoT policy). Example for zone 2:

Terminal window
mkdir -p "$CLIENT_CERTS_DIR_2"
aws iot create-thing --thing-name "$CLIENT_THING_NAME_2"
aws iot create-keys-and-certificate --set-as-active \
--certificate-pem-outfile "$CLIENT_CERTS_DIR_2/device.pem.crt" \
--public-key-outfile "$CLIENT_CERTS_DIR_2/public.pem.key" \
--private-key-outfile "$CLIENT_CERTS_DIR_2/private.pem.key" \
> "$CLIENT_CERTS_DIR_2/create-cert.json"
CLIENT_CERT_ARN_2=$(jq -r .certificateArn "$CLIENT_CERTS_DIR_2/create-cert.json")
curl -sS https://www.amazontrust.com/repository/AmazonRootCA1.pem \
-o "$CLIENT_CERTS_DIR_2/AmazonRootCA1.pem"
aws iot attach-policy \
--policy-name "${PROJECT_NAME}-${ENVIRONMENT}-client-policy" \
--target "$CLIENT_CERT_ARN_2"
aws iot attach-thing-principal \
--thing-name "$CLIENT_THING_NAME_2" \
--principal "$CLIENT_CERT_ARN_2"
echo "$CLIENT_CERT_ARN_2"
arn:aws:iot:ap-southeast-2:123456789012:cert/…

Repeat with $CLIENT_THING_NAME_3 / $CLIENT_CERTS_DIR_3 for a third zone. Associate and flash each board later with its own certs and Thing name.

Required for client-device identity checks and connectivity info under Greengrass V2
AWS IoT Greengrass V2 — edge runtime (Nucleus) plus cloud control plane for deployments, client devices, and local MQTT.
.

Substitute account/region into the trust document:

Terminal window
sed -e "s/\${AWS_ACCOUNT_ID}/${AWS_ACCOUNT_ID}/g" \
-e "s/\${AWS_REGION}/${AWS_REGION}/g" \
artifacts/policies/greengrass-service-role-trust.json \
> /tmp/gg-service-role-trust.json
GG_SERVICE_ROLE="${PROJECT_NAME}-${ENVIRONMENT}-gg-service"
echo "$GG_SERVICE_ROLE"
gg-edge-wt-dev-gg-service

Account-level role for cloud-side Greengrass ops:

Terminal window
aws iam create-role \
--role-name "$GG_SERVICE_ROLE" \
--assume-role-policy-document file:///tmp/gg-service-role-trust.json
{
"Role": {
"Path": "/",
"RoleName": "gg-edge-wt-dev-gg-service",
"RoleId": "AROAEXAMPLEGGSERVICE",
"Arn": "arn:aws:iam::123456789012:role/gg-edge-wt-dev-gg-service",
"CreateDate": "2026-10-04T10:34:50+00:00",
…
}
}

AWS’s AWSGreengrassResourceAccessRolePolicy; print the role ARN:

Terminal window
aws iam attach-role-policy \
--role-name "$GG_SERVICE_ROLE" \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSGreengrassResourceAccessRolePolicy
GG_SERVICE_ROLE_ARN=$(aws iam get-role --role-name "$GG_SERVICE_ROLE" --query Role.Arn --output text)
echo "$GG_SERVICE_ROLE_ARN"
arn:aws:iam::123456789012:role/gg-edge-wt-dev-gg-service
Terminal window
aws greengrassv2 associate-service-role-to-account --role-arn "$GG_SERVICE_ROLE_ARN"
{
"associatedAt": "2026-10-04T10:35:08Z"
}
Terminal window
aws greengrassv2 get-service-role-for-account
{
"associatedAt": "2026-10-04T10:35:08Z",
"roleArn": "arn:aws:iam::123456789012:role/gg-edge-wt-dev-gg-service"
}
Terminal window
aws iot describe-thing --thing-name "$CORE_THING_NAME"
{
"defaultClientId": "gg-edge-wt-dev-core",
"thingName": "gg-edge-wt-dev-core",
"thingId": "01234567-89ab-cdef-0123-456789abcdef",
"thingArn": "arn:aws:iot:ap-southeast-2:123456789012:thing/gg-edge-wt-dev-core",
"attributes": {},
"version": 1
}
Terminal window
aws iot describe-thing --thing-name "$CLIENT_THING_NAME"
{
"defaultClientId": "gg-edge-wt-dev-esp32-1",
"thingName": "gg-edge-wt-dev-esp32-1",
"thingId": "fedcba98-7654-3210-fedc-ba9876543210",
"thingArn": "arn:aws:iot:ap-southeast-2:123456789012:thing/gg-edge-wt-dev-esp32-1",
"attributes": {},
"version": 1
}
Terminal window
aws greengrassv2 get-service-role-for-account
{
"associatedAt": "2026-10-04T10:35:08Z",
"roleArn": "arn:aws:iam::123456789012:role/gg-edge-wt-dev-gg-service"
}

Next: Prepare the core.