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
Checksurface every policy is evaluated against. - API → Policies — HTTP-level reference.