Roles

A role is an assumable identity — a principal you can switch into by calling Authz.AssumeRole, the way AWS IAM roles work. Roles let one principal act on behalf of another with a time-bounded session, without sharing long-lived credentials. They're the right primitive for cross-workspace access, scoped delegation, and tightly-audited "I need to do one risky thing" workflows. The Go SDK exposes the role methods on client.IAM. For the HTTP surface see API → Roles; for the assume-role flow see Concepts → Authorization.

Field on the Client

client.IAM — the role methods sit alongside the user methods. To assume a role at runtime see client.Authz.AssumeRole(...) on the Authz page.

Methods

ListRoles

Signature. func (r *IamResource) ListRoles(ctx context.Context, auth RequestAuth) ([]IamRole, error)

Lists every role in the workspace. Returns the trust policy inline — handy for an admin UI that shows "who can assume this".

roles, _ := client.IAM.ListRoles(ctx, huudis.RequestAuth{})
for _, r := range roles {
    fmt.Println(r.ID, r.Name, r.Description)
}

GetRole

Signature. func (r *IamResource) GetRole(ctx context.Context, id string, auth RequestAuth) (*IamRole, error)

Fetches one role.

role, _ := client.IAM.GetRole(ctx, "role_01HX...", huudis.RequestAuth{})
b, _ := json.MarshalIndent(role.TrustPolicy, "", "  ")
fmt.Println(string(b))

CreateRole

Signature. func (r *IamResource) CreateRole(ctx context.Context, in CreateRoleInput, auth RequestAuth) (*IamRole, error)

Creates a role. TrustPolicy is required — it spells out which principals can assume this role. The document follows the same JSON shape as access policies with one rule:

  • Effect: Allow
  • Principal: the ARN(s) allowed to assume
  • Action: iam:AssumeRole
role, err := client.IAM.CreateRole(ctx, huudis.CreateRoleInput{
    Name:        "BillingReader",
    Description: "Cross-team read access to billing data",
    TrustPolicy: map[string]any{
        "Version": "2026-01-01",
        "Statement": []map[string]any{{
            "Effect":    "Allow",
            "Principal": map[string]any{"AWS": fmt.Sprintf("arn:huudis:iam::%s:group/Finance", accountID)},
            "Action":    "iam:AssumeRole",
        }},
    },
}, huudis.RequestAuth{})
if err != nil {
    return err
}
log.Println(role.ID)

After creation, attach an access policy to the role (client.IAM.AttachPolicy(...)) — the trust policy says who can be the role; the access policy says what the role can do.

DeleteRole

Signature. func (r *IamResource) DeleteRole(ctx context.Context, id string, auth RequestAuth) error

Removes the role. Active assumed-role sessions issued from it stay alive until they expire (max 12 hours; usually much less); refresh is blocked.

_ = client.IAM.DeleteRole(ctx, "role_01HX...", huudis.RequestAuth{})

Types

type IamRole struct {
    ID          string         `json:"id"`           // "role_..."
    AccountID   string         `json:"accountId"`
    Name        string         `json:"name"`
    Description string         `json:"description,omitempty"`
    TrustPolicy map[string]any `json:"trustPolicy,omitempty"`
    CreatedAt   string         `json:"createdAt"`    // ISO-8601
}

The trust policy document follows the Huudis policy language — same grammar as access policies, restricted action set. See API → Roles for the full spec.

Common patterns

Cross-workspace delegation

Workspace A grants workspace B's support team read access to a slice of data. A creates a role whose trust policy names B's support group; B's users AssumeRole into A.

// In workspace A:
role, _ := client.IAM.CreateRole(ctx, huudis.CreateRoleInput{
    Name: "SupportReader",
    TrustPolicy: map[string]any{
        "Version": "2026-01-01",
        "Statement": []map[string]any{{
            "Effect":    "Allow",
            "Principal": map[string]any{"AWS": "arn:huudis:iam::acc_B_id:group/Support"},
            "Action":    "iam:AssumeRole",
            "Condition": map[string]any{
                "StringEquals": map[string]any{"aws:MultiFactorAuthPresent": "true"},
            },
        }},
    },
}, huudis.RequestAuth{})
_, _ = client.IAM.AttachPolicy(ctx, huudis.AttachPolicyInput{
    PolicyID:     readonlyPolicyID,
    PrincipalArn: fmt.Sprintf("arn:huudis:iam::%s:role/%s", accountIDA, role.Name),
}, huudis.RequestAuth{})

// In workspace B (later):
session, _ := client.Authz.AssumeRole(ctx, huudis.AssumeRoleInput{
    RoleArn:         "arn:huudis:iam::acc_A_id:role/SupportReader",
    SessionName:     "ticket-" + ticketID,
    DurationSeconds: 3600,
}, huudis.RequestAuth{})
_ = session

Time-bounded escalation

A user is normally a viewer but needs admin briefly. Build a role with broader access and a trust policy gated by MFA + a self-approval workflow:

_, _ = client.IAM.CreateRole(ctx, huudis.CreateRoleInput{
    Name: "BreakglassAdmin",
    TrustPolicy: map[string]any{
        "Version": "2026-01-01",
        "Statement": []map[string]any{{
            "Effect":    "Allow",
            "Principal": map[string]any{"AWS": fmt.Sprintf("arn:huudis:iam::%s:group/On-call", accountID)},
            "Action":    "iam:AssumeRole",
            "Condition": map[string]any{
                "Bool":           map[string]any{"aws:MultiFactorAuthPresent": "true"},
                "DateLessThan":   map[string]any{"aws:CurrentTime": futureIso},
            },
        }},
    },
}, huudis.RequestAuth{})

Pair with an audit watch on huudis.iam.role_assumed.v1 events so the security team sees every escalation.

Audit unused roles

A role you can't recall the purpose of is one you can probably delete. Cross-reference with the audit log:

func unusedRoles(ctx context.Context, c *huudis.Client, maxDays int) ([]huudis.IamRole, error) {
    roles, err := c.IAM.ListRoles(ctx, huudis.RequestAuth{})
    if err != nil {
        return nil, err
    }
    since := time.Now().Add(-time.Duration(maxDays) * 24 * time.Hour).Format(time.RFC3339)
    audit, err := c.Account.Audit(ctx, huudis.AuditParams{
        EventType: "huudis.iam.role_assumed.v1",
        Since:     since,
        Limit:     1000,
    }, huudis.RequestAuth{})
    if err != nil {
        return nil, err
    }
    used := map[string]bool{}
    for _, e := range audit {
        used[e.Resource] = true
    }
    var stale []huudis.IamRole
    for _, r := range roles {
        arn := fmt.Sprintf("arn:huudis:iam::%s:role/%s", r.AccountID, r.Name)
        if !used[arn] {
            stale = append(stale, r)
        }
    }
    return stale, nil
}

Errors

Code Status Cause
validation_error 400 Bad trust policy JSON, missing Action: "iam:AssumeRole", name too long.
not_found 404 Role ID doesn't exist or lives in another workspace.
conflict 409 Name already used in this workspace.
forbidden 403 Caller isn't an admin/owner.

Next

  • Policies — the access policies you attach to roles.
  • Authz — the AssumeRole + Check runtime surface.
  • API → Roles — HTTP-level reference.