]
F -->|no| H[Reject: explicit obj_id required]
C --> I[Check Keto relation]
E --> I
G --> I
I --> J{allowed?}
J -->|yes| K[Inject trusted Baron headers]
J -->|no| L[Reject request]
K --> M[Write audit with obj_id, relation, client_id, X-Request-Id]`
const rpExternalKeyMermaid = `flowchart TD
A[User authenticates through Baron SSO] --> B[Baron resolves internal identity]
B --> C[Baron derives or loads Baron-issued alias]
C --> D[Baron injects X-Baron-External-Key]
D --> E[Baron injects X-Baron-Subject]
E --> I[RP receives trusted headers from Baron gateway]
I --> F[RP upserts local user with provider + X-Baron-External-Key]
F --> G[RP stores the full external key as opaque value]
G --> H[RP never parses or stores raw kratos_identity_id]`
func NewRPManifestHandler() *RPManifestHandler {
return &RPManifestHandler{}
}
func (h *RPManifestHandler) GetJSON(c *fiber.Ctx) error {
c.Set(fiber.HeaderCacheControl, "public, max-age=300")
return c.JSON(buildRPManifest(c))
}
func (h *RPManifestHandler) GetSchema(c *fiber.Ctx) error {
c.Set(fiber.HeaderCacheControl, "public, max-age=300")
return c.JSON(rpManifestSchema())
}
func (h *RPManifestHandler) GetHTML(c *fiber.Ctx) error {
manifest := buildRPManifest(c)
issuer, _ := manifest["issuer"].(string)
c.Set(fiber.HeaderCacheControl, "public, max-age=300")
c.Type("html", "utf-8")
return c.SendString(`
Baron RP IAM Manifest
Baron RP IAM Manifest
외부 RP가 Baron SSO/Ory Stack/Keto 기반 공용 IAM을 연동하기 위한 공개 규격입니다.
Issuer
` + html.EscapeString(issuer) + `
Identity Contract
| 용도 | Header | 정책 |
| Keto subject | X-Baron-Subject | User:<baron_identity_id> 전체 문자열을 opaque subject로 취급합니다. |
| RP upsert key | X-Baron-External-Key | Baron-issued alias입니다. RP가 만들거나 제출하지 않고, Baron이 주입한 전체 문자열을 local user external key로 저장합니다. |
| RP client | X-Baron-Client-ID | 현재 접근 중인 RP client id입니다. |
External Key Flow
X-Baron-External-Key는 RP 입력값이 아니라 Baron이 인증된 subject에서 발급/조회해 주입하는 opaque alias입니다. RP upserts local user from the Baron-issued alias.
` + "```mermaid\n" + html.EscapeString(rpExternalKeyMermaid) + "\n```" + `
Object Lookup
check(User:abc, viewers, RelyingParty:<client_id>)
check(User:abc, members, Tenant:<tenant_id>)
check(User:abc, viewers, Resource:<resource_type>:<resource_id>)
audit_contract
권한과 설정을 변경하는 command는 sync audit write에 실패하면 요청도 실패해야 합니다. Read audit은 allowlist된 조회에 한해 best effort로 취급합니다.
{
"mutating_command_mode": "fail_closed_sync",
"missing_audit_sink_behavior": "reject_mutation",
"correlation_header": "X-Request-Id"
}
Object Lookup Flow
` + "```mermaid\n" + html.EscapeString(rpObjectLookupMermaid) + "\n```" + `
`)
}
func buildRPManifest(c *fiber.Ctx) map[string]any {
issuer := resolvePublicRequestBaseURL(c, os.Getenv("BACKEND_PUBLIC_URL"))
if issuer == "" {
issuer = strings.TrimRight(os.Getenv("USERFRONT_URL"), "/")
}
if issuer == "" {
issuer = "https://sso.hmac.kr"
}
issuer = strings.TrimRight(issuer, "/")
return map[string]any{
"version": "2026-05-11",
"issuer": issuer,
"oidc": map[string]any{
"discovery_url": issuer + "/.well-known/openid-configuration",
"jwks_url": issuer + "/.well-known/jwks.json",
"supported_flows": []string{"authorization_code_pkce"},
"required_scopes": []string{"openid", "profile", "email"},
},
"iam": map[string]any{
"authorization_engine": "ory-keto",
"subject_format": "User:",
"target_object_patterns": []string{
"RelyingParty:",
"Tenant:",
"Resource::",
},
"supported_relations": []string{
"admins",
"users",
"viewers",
"operators",
"members",
"owners",
"editors",
},
},
"identity_contract": map[string]any{
"subject_header": "X-Baron-Subject",
"external_key_header": "X-Baron-External-Key",
"external_key_is_opaque": true,
"external_key_issuer": "baron",
"external_key_delivery": "baron_injected_header",
"external_key_lifecycle": "issued_or_loaded_after_successful_authentication_before_rp_request",
"rp_supplied_external_key_allowed": false,
"rp_user_upsert_source": "rp_must_upsert_from_header_value",
"raw_kratos_identity_id_exposed": false,
"rp_user_upsert_key": "provider + external_key",
"email_is_stable_primary_key": false,
"initial_external_key_expression": "X-Baron-External-Key",
"fallback_to_subject_allowed": false,
},
"trusted_headers": map[string]any{
"subject": "X-Baron-Subject",
"external_key": "X-Baron-External-Key",
"email": "X-Baron-Email",
"tenant": "X-Baron-Tenant",
"relations": "X-Baron-Relations",
"client_id": "X-Baron-Client-ID",
},
"object_lookup": map[string]any{
"rp_level": map[string]any{
"object": "RelyingParty:",
"relations": []string{"viewers", "users", "operators", "admins"},
"example": "check(User:abc, viewers, RelyingParty:mh-dashboard)",
},
"tenant_level": map[string]any{
"object": "Tenant:",
"relations": []string{"members", "admins", "owners"},
"example": "check(User:abc, members, Tenant:9caf62e1-297d-4e8f-870b-61780998bbe)",
},
"resource_level": map[string]any{
"object": "Resource::",
"relations": []string{"viewers", "editors", "owners"},
"example": "check(User:abc, viewers, Resource:dashboard:mh-monthly-2026-05)",
},
"recommended_order": []string{
"authenticated",
"rp_level",
"tenant_or_resource_level",
"trusted_header_injection",
},
},
"object_lookup_flow": map[string]any{
"format": "mermaid",
"mermaid": rpObjectLookupMermaid,
},
"external_key_flow": map[string]any{
"format": "mermaid",
"mermaid": rpExternalKeyMermaid,
},
"audit_contract": map[string]any{
"mutating_command_mode": "fail_closed_sync",
"missing_audit_sink_behavior": "reject_mutation",
"read_audit_mode": "best_effort_allowlisted",
"correlation_header": "X-Request-Id",
"rp_business_audit_required": true,
"baron_gateway_audit_required": true,
"required_detail_fields": []string{
"obj_id",
"relation",
"client_id",
"subject",
"decision",
},
"guarantee_scope": "Baron-mediated IAM mutations fail closed on audit write failure; RP-owned business events must be emitted by the RP with the same correlation header.",
},
"security_requirements": map[string]any{
"strip_external_identity_headers": true,
"backend_direct_exposure_allowed": false,
"static_snapshot_requires_auth": true,
"email_as_primary_key_allowed": false,
},
}
}
func rpManifestSchema() map[string]any {
return map[string]any{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Baron RP IAM Manifest",
"type": "object",
"required": []string{
"version",
"issuer",
"oidc",
"iam",
"trusted_headers",
"identity_contract",
"object_lookup",
"object_lookup_flow",
"external_key_flow",
"audit_contract",
"security_requirements",
},
"properties": map[string]any{
"version": map[string]any{"type": "string"},
"issuer": map[string]any{"type": "string", "format": "uri"},
"oidc": map[string]any{"type": "object"},
"iam": map[string]any{"type": "object"},
"trusted_headers": map[string]any{"type": "object"},
"identity_contract": map[string]any{"type": "object"},
"object_lookup": map[string]any{"type": "object"},
"object_lookup_flow": map[string]any{"type": "object"},
"external_key_flow": map[string]any{"type": "object"},
"audit_contract": map[string]any{"type": "object"},
"security_requirements": map[string]any{"type": "object"},
},
}
}