ZITADEL does not put roles into a machine-user access token on this instance. The grant exists, the scope asks for urn:zitadel:iam:org:project:roles, and the claim still never arrives; a colon-free role key makes no difference either. A service account therefore cannot satisfy requiredCapability by any configuration. auth.allowListedMachineIdentities admits such a caller when its client_id appears in zitadelClientIds. The caller is still authenticated: it signed an assertion with a private key whose public half ZITADEL holds. The allow-list entry is a reviewed line of configuration, not a wildcard. The new path is off by default, and a token that does state its roles is judged by the role check as before, so the weaker rule never applies twice.
66 lines
2.2 KiB
Go
66 lines
2.2 KiB
Go
package proxy
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
|
|
"forgejo.riotpiao.com/rock/homelab-frontend/internal/config"
|
|
)
|
|
|
|
func handlerWith(allow bool, ids ...string) *Handler {
|
|
return &Handler{config: &config.Config{Auth: config.AuthConfig{
|
|
AllowListedMachineIdentities: allow,
|
|
ZitadelClientIDs: ids,
|
|
}}}
|
|
}
|
|
|
|
func TestAllowListedMachineIdentityAdmitsANamedClientWithoutRoles(t *testing.T) {
|
|
h := handlerWith(true, "poimen-llm")
|
|
claims := jwt.MapClaims{"client_id": "poimen-llm", "sub": "397071051344839054"}
|
|
if !h.allowListedMachineIdentity(claims) {
|
|
t.Fatalf("an allow-listed machine client must be admitted when the token has no roles")
|
|
}
|
|
}
|
|
|
|
func TestAllowListedMachineIdentityRefusesAnUnknownClient(t *testing.T) {
|
|
h := handlerWith(true, "poimen-llm")
|
|
claims := jwt.MapClaims{"client_id": "someone-else"}
|
|
if h.allowListedMachineIdentity(claims) {
|
|
t.Fatalf("a client that is not on the list must not be admitted")
|
|
}
|
|
}
|
|
|
|
func TestAllowListedMachineIdentityRefusesWhenTheTokenStatesRoles(t *testing.T) {
|
|
h := handlerWith(true, "poimen-llm")
|
|
claims := jwt.MapClaims{
|
|
"client_id": "poimen-llm",
|
|
"urn:zitadel:iam:org:project:roles": map[string]interface{}{"memory:read": nil},
|
|
}
|
|
if h.allowListedMachineIdentity(claims) {
|
|
t.Fatalf("a token with roles must be judged by the role check, not by the allow-list")
|
|
}
|
|
}
|
|
|
|
func TestAllowListedMachineIdentityIsOffByDefault(t *testing.T) {
|
|
h := handlerWith(false, "poimen-llm")
|
|
claims := jwt.MapClaims{"client_id": "poimen-llm"}
|
|
if h.allowListedMachineIdentity(claims) {
|
|
t.Fatalf("without the setting the gateway must fall back to the role check only")
|
|
}
|
|
}
|
|
|
|
func TestRolesClaimReadsBothShapes(t *testing.T) {
|
|
asMap := jwt.MapClaims{"urn:zitadel:iam:org:project:roles": map[string]interface{}{"llm:inference": nil}}
|
|
if got := rolesClaim(asMap); len(got) != 1 || got[0] != "llm:inference" {
|
|
t.Fatalf("map shape must yield the role names, got %v", got)
|
|
}
|
|
asList := jwt.MapClaims{"roles": []interface{}{"llm:inference", "memory:read"}}
|
|
if got := rolesClaim(asList); len(got) != 2 {
|
|
t.Fatalf("list shape must yield every role, got %v", got)
|
|
}
|
|
if got := rolesClaim(jwt.MapClaims{}); len(got) != 0 {
|
|
t.Fatalf("a token with no roles must yield nothing, got %v", got)
|
|
}
|
|
}
|