Policies

A policy is a JSON document that grants or denies permissions on Huudis resources. Attach a policy to a principal (user, group, role, service account) and that principal gains the policy's permissions. Huudis policies follow an AWS-style grammar — Effect, Action, Resource, Condition — with Huudis-specific action namespaces (huudis:iam:CreateUser, huudis:oidc:RotateSecret, …). The Go SDK exposes the policy + attachment methods on client.IAM. For the HTTP surface see API → Policies; for the grammar see API → Policies → Policy document.

Field on the Client

client.IAM — the policy + attachment methods sit alongside the user methods.

Methods

ListPolicies

Signature. func (r *IamResource) ListPolicies(ctx context.Context, p ListPoliciesParams, auth RequestAuth) ([]IamPolicy, error)

Lists every policy visible to the workspace. Kind filters: "custom" returns policies your workspace authored; "canned" returns the Huudis-built baselines (ReadOnly, BillingReader, OpsSupport, …) that ship with every account.

custom, err := client.IAM.ListPolicies(ctx, huudis.ListPoliciesParams{Kind: "custom"}, huudis.RequestAuth{})
if err != nil {
    return err
}
for _, p := range custom {
    label := "(custom)"
    if p.BuiltIn {
        label = "(canned)"
    }
    fmt.Println(p.ID, p.Name, label)
}

GetPolicy

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

Fetches one policy, including its Document JSON.

p, _ := client.IAM.GetPolicy(ctx, "plc_01HX...", huudis.RequestAuth{})
b, _ := json.MarshalIndent(p.Document, "", "  ")
fmt.Println(string(b))

CreatePolicy

Signature. func (r *IamResource) CreatePolicy(ctx context.Context, in CreatePolicyInput, auth RequestAuth) (*IamPolicy, error)

Creates a custom policy. Document is parsed and validated server-side; an invalid shape returns validation_error with a path pointer.

policy, err := client.IAM.CreatePolicy(ctx, huudis.CreatePolicyInput{
    Name:        "OIDCClientsReadOnly",
    Description: "List + read OIDC clients, no mutations",
    Document: map[string]any{
        "Version": "2026-01-01",
        "Statement": []map[string]any{{
            "Sid":      "AllowListAndRead",
            "Effect":   "Allow",
            "Action":   []string{"huudis:oidc:ListClients", "huudis:oidc:GetClient"},
            "Resource": "*",
        }},
    },
}, huudis.RequestAuth{})
if err != nil {
    return err
}
log.Println(policy.ID)

UpdatePolicy

Signature. func (r *IamResource) UpdatePolicy(ctx context.Context, id string, patch map[string]any, auth RequestAuth) (*IamPolicy, error)

PATCH — only keys you pass change. Passing a new document replaces it entirely; there's no statement-level merge. Built-in policies are immutable; updating one returns state_conflict.

_, err := client.IAM.UpdatePolicy(ctx, "plc_01HX...", map[string]any{
    "description": "List + read OIDC clients in production only",
    "document": map[string]any{
        "Version": "2026-01-01",
        "Statement": []map[string]any{{
            "Effect":   "Allow",
            "Action":   []string{"huudis:oidc:ListClients", "huudis:oidc:GetClient"},
            "Resource": "*",
            "Condition": map[string]any{
                "StringEquals": map[string]any{"huudis:Environment": "production"},
            },
        }},
    },
}, huudis.RequestAuth{})

DeletePolicy

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

Deletes a custom policy. All attachments referencing it are detached (cascade). Built-in policies cannot be deleted.

_ = client.IAM.DeletePolicy(ctx, "plc_01HX...", huudis.RequestAuth{})

ListPolicyAttachments

Signature. func (r *IamResource) ListPolicyAttachments(ctx context.Context, p ListPolicyAttachmentsParams, auth RequestAuth) ([]IamPolicyAttachment, error)

Lists attachments. Filter by PolicyID (what this policy is attached to) or PrincipalArn (what this principal has attached). Both filters can be combined.

onPrincipal, _ := client.IAM.ListPolicyAttachments(ctx, huudis.ListPolicyAttachmentsParams{
    PrincipalArn: fmt.Sprintf("arn:huudis:iam::%s:group/Engineering", accountID),
}, huudis.RequestAuth{})

AttachPolicy

Signature. func (r *IamResource) AttachPolicy(ctx context.Context, in AttachPolicyInput, auth RequestAuth) (*IamPolicyAttachment, error)

Attaches a policy to a principal. Idempotent at the (policyId, principalArn) level — re-attaching returns the existing row.

_, _ = client.IAM.AttachPolicy(ctx, huudis.AttachPolicyInput{
    PolicyID:     "plc_01HX...",
    PrincipalArn: fmt.Sprintf("arn:huudis:iam::%s:group/Engineering", accountID),
}, huudis.RequestAuth{})

DetachPolicy

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

Removes one attachment. Takes the attachment row ID, not the policy ID — find it first via ListPolicyAttachments.

_ = client.IAM.DetachPolicy(ctx, "att_01HX...", huudis.RequestAuth{})

Types

type IamPolicy struct {
    ID          string         `json:"id"`           // "plc_..."
    AccountID   string         `json:"accountId"`
    Name        string         `json:"name"`
    Description string         `json:"description,omitempty"`
    Document    map[string]any `json:"document"`     // JSON policy document
    BuiltIn     bool           `json:"builtIn,omitempty"`
    CreatedAt   string         `json:"createdAt"`    // ISO-8601
    UpdatedAt   string         `json:"updatedAt"`
}

type IamPolicyAttachment struct {
    ID           string `json:"id"`            // "att_..."
    AccountID    string `json:"accountId"`
    PolicyID     string `json:"policyId"`
    PrincipalArn string `json:"principalArn"`
    CreatedAt    string `json:"createdAt"`
}

Full grammar reference at API → Policies → Policy document.

Common patterns

Attach to a group instead of every user

Almost always the right shape — write the policy once, attach to a group, manage membership of the group:

eng, _ := client.IAM.GetGroup(ctx, engGroupID, huudis.RequestAuth{})
arn := fmt.Sprintf("arn:huudis:iam::%s:group/%s", eng.AccountID, eng.Name)
_, _ = client.IAM.AttachPolicy(ctx, huudis.AttachPolicyInput{
    PolicyID:     policyID,
    PrincipalArn: arn,
}, huudis.RequestAuth{})

Audit "who can do X?"

Find which principals have this policy attached:

attachments, _ := client.IAM.ListPolicyAttachments(ctx, huudis.ListPolicyAttachmentsParams{
    PolicyID: policyID,
}, huudis.RequestAuth{})
for _, a := range attachments {
    fmt.Println(a.PrincipalArn, "attached", a.CreatedAt)
}

For the inverse ("what can this principal do?"), use client.Authz.Whoami(...) plus the Scopes list — or call client.Authz.Check(...) for the specific action you care about.

Replace a policy without orphaning attachments

UpdatePolicy mutates in place — attachments keep working. The replacement document takes effect immediately for new checks, but cached authz decisions in downstream services (TTL ≤ 60s) may take up to a minute to flip.

_, _ = client.IAM.UpdatePolicy(ctx, policyID, map[string]any{"document": newDocument}, huudis.RequestAuth{})

Promote a custom policy to "everyone gets it"

A common shape: a default policy attached to a Members group every user joins on creation.

policy, _ := client.IAM.CreatePolicy(ctx, huudis.CreatePolicyInput{
    Name: "BaselineReadOnly",
    Document: map[string]any{
        "Version": "2026-01-01",
        "Statement": []map[string]any{{
            "Effect":   "Allow",
            "Action":   "huudis:*:List*",
            "Resource": "*",
        }},
    },
}, huudis.RequestAuth{})
_, _ = client.IAM.AttachPolicy(ctx, huudis.AttachPolicyInput{
    PolicyID:     policy.ID,
    PrincipalArn: fmt.Sprintf("arn:huudis:iam::%s:group/Members", accountID),
}, huudis.RequestAuth{})

// New user onboarding then just adds them to "Members".

Errors

Code Status Cause
validation_error 400 Policy document fails grammar check (path-pointer in the error message); unknown Action; bad Resource ARN.
not_found 404 Policy ID doesn't exist; principal ARN doesn't resolve (on AttachPolicy).
conflict 409 Policy name already used in this workspace.
state_conflict 409 Tried to update or delete a built-in policy.
forbidden 403 Caller isn't an admin/owner.

Next

  • Groups — the principals you usually attach to.
  • Roles — assumable principals that also take policies.
  • Authz — the runtime Check surface every policy is evaluated against.
  • API → Policies — HTTP-level reference.