Authentication and Permissions
How an admin session is established, and what the IAM role behind it can do. user_auth.md is the authority for the identity model as a whole; this page covers the admin half of it.
Identity providers
ESP RainMaker Neo uses two separate Cognito User Pools:
Population |
Provider |
Federated as |
Self-signup |
|---|---|---|---|
Admins |
Cognito user pool |
a Cognito provider on the pool’s |
Disabled — admins are provisioned |
End users |
the ESP User OIDC issuer |
an IAM OIDC identity provider, keyed on the issuer URL |
Enabled |
The ESP-End-Users Cognito pool exists, but end users do not present its tokens
to ESP RainMaker Neo: it sits behind the ESP User issuer, which is what the Identity Pool
trusts. An admin token and an end-user tofperefore differ in issuer, which is
how POST /v1/user/credentials tells them apart.
The admin pool declares two custom attributes: custom:super_admin (max 4
characters, so the value is the string "true") and custom:user_id.
Admin sign-in factors
Admins seeded by the registration custom resource are created with no password:
AdminCreateUser omits TemporaryPassword, which is legal because the pool
advertises a passwordless factor, and which is why such an account never enters
FORCE_CHANGE_PASSWORD. Their only first factor is the emailed one-time code.
Admins seeded before that change keep the password they were given and gain
EMAIL_OTP alongside it — Cognito offers no way to remove a password once set, so
they hold both factors permanently.
A passwordless admin can adopt a password from Account settings → Password.
ChangePassword accepts no PreviousPassword when the user has none, and the
dashboard picks the form from GetUserAuthFactors. Afterwards they hold both
factors like any pre-existing admin.
One-time password factors are incompatible with required MFA, and under the pool’s optional MFA an admin who enrols MFA stops being eligible for OTP sign-in. Nothing in the dashboard enrols MFA today.
Email-quota lockout. The pool uses Cognito’s default email sender, capped
around 50 emails/day per pool. selectOtp (the SELECT_CHALLENGE step) is
unauthenticated and sends one email per call, so anyone who knows one admin
address can loop USER_AUTH → SELECT_CHALLENGE and exhaust that quota.
Before this feature, exhausting it blocked only /forgot-password; now it also
blocks sign-in outright for every admin whose sole factor is EMAIL_OTP — the
AdminTempPassword break-glass output no longer exists to fall back on.
Mitigations, either of which closes the vector: configure an Amazon SES sending
account for the pool (EmailSendingAccount: DEVELOPER, its own daily limits
under SES rather than Cognito’s default sender), or ensure at least one admin
per pool keeps a password, which sign-in via USER_PASSWORD_AUTH does not
consume any email quota to use.
From admin login to AWS credentials
The admin signs in against
ESP-Admin-Usersand receives Cognito tokens. The pool allows two first factors,PASSWORDandEMAIL_OTP, and the dashboard uses the choice-basedUSER_AUTHflow: it submits the address alone, Cognito answers with the factors that admin actually has, and only those are offered. A one-time code is answered withRespondToAuthChallenge, first selectingEMAIL_OTPand then supplyingEMAIL_OTP_CODE.POST /v1/user/credentialsexchanges them for Identity-Pool credentials. The route carries no gateway authorizer; the handler verifies the access token (header) andid_token(body) against the admin pool’s JWKS as a pair. See user_auth.md §3.1.Every other admin endpoint is
AWS_IAM, so the admin SigV4-signs it with those credentials. No bearer token is presented to the admin APIs.
Role selection
The Identity Pool has exactly one role mapping, on the admin pool’s client:
A token from the admin pool carrying
custom:super_admin == "true"maps toAdminDeviceUsersRole.Everything else — including every federated end user — falls through to the pool’s default
authenticatedrole,DeviceUsersRole. There is no role mapping on the OIDC provider and no per-audbranching.
So admin privilege is an infrastructure fact: which role the identity pool will hand you, decided before any ESP RainMaker Neo Lambda runs.
Super-admin resolution in the handlers
Being on AdminDeviceUsersRole gets a caller to the endpoint; the handler still
re-checks super-admin status, and where it reads that from depends on the
path:
SigV4 admin endpoints (the normal case). There is no token in the request, only the API Gateway identity. The handler resolves the caller and calls Cognito
AdminGetUser, reading thecustom:super_adminuser attribute. Its value is authoritative.Token-verifying endpoints (
/v1/user/credentialsand the ESP-User surface). Thecustom:super_adminclaim is read from the verified Cognito token. Cognito emits custom attributes in the ID token, not the access token.
Either way the handler answers 403 when the caller is not a super admin.
The user_details table declares an is_super_admin attribute, but no ESP RainMaker Neo code
path reads or writes it — only the test harness sets it. Do not treat it as a
source of truth.
AdminDeviceUsersRole
Trust: sts:AssumeRoleWithWebIdentity by cognito-identity.amazonaws.com,
conditioned on this identity pool’s id and an amr of authenticated. The role
trusts the pool, not the login provider.
AWS IoT Core (all on *)
Action |
What it enables |
|---|---|
|
Enumerate devices; per-device registry detail |
|
Query the fleet index (registry + |
|
Read group hierarchy and membership |
|
Manage groups, including editing a description |
|
Manage dynamic (query-backed) groups |
|
Assign / unassign a device |
|
OTA job lifecycle |
|
Per-job and per-device execution status |
|
IoT streams for OTA firmware delivery |
S3
Action |
Resource |
|---|---|
|
|
|
|
The bucket name is resolved per deployment and region, so it is not a fixed string. OTA firmware and the node-registration certificate CSVs are the two prefixes granted.
Other
Effect |
Action |
Resource |
Purpose |
|---|---|---|---|
Allow |
|
|
Refresh credentials |
Allow |
|
|
Call any ESP RainMaker Neo API endpoint, and nothing outside it |
Allow |
|
IoT user role |
Tag assumed sessions |
Allow |
|
OTA service role |
Required to create an OTA job |
Allow |
|
|
Detect which optional module stacks are deployed, so the dashboard surfaces a feature only when its stack exists. |
Deny |
|
|
Blocks credential escalation into any other role |
The sts:AssumeRole deny is explicit, so it wins over any allow — an admin
cannot widen its own scope by assuming a broader role.
Not granted
Missing action |
Consequence |
|---|---|
|
Shadows are not readable through the REST API; the dashboard sees only what the fleet index projects |
|
Shadows are read-only from the dashboard. IoT Data Plane also requires an IoT policy attached to the Cognito identity, so adding the IAM action alone would not be enough |
|
Shadows cannot be deleted |
|
Shadow names cannot be enumerated |
|
The fleet-indexing configuration is not inspectable |
Any |
No direct table access — every read and write goes through a Lambda API, which is where RBAC is enforced |
OTA service role
A separate role, assumed by iot.amazonaws.com rather than by the admin.
AdminDeviceUsersRole holds iam:PassRole on it, which is what lets an admin
create an OTA job that AWS IoT then executes under this role:
s3:GetObject,s3:GetObjectVersionon<files-bucket>/ota/*iot:CreateJob,iot:DescribeJob,iot:DeleteJob,iot:GetOTAUpdateiot:CreateStream,iot:DescribeStream,iot:DeleteStreamiam:PassRoleon itself