auth: neutral example org identifiers

This commit is contained in:
zeekay
2026-06-10 19:27:35 -07:00
parent add99ec004
commit 9f3174372a
4 changed files with 32 additions and 32 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ One BIP39 mnemonic is shared across all Lux-derived L1s (Lux, Hanzo,
Zoo, Pars). Each chain reads it from `providers/<org>/deploy-mnemonic`
under its own org-scoped JWT — same bytes, different KMS paths, N
independent auth boundaries. Each tenant is jurisdictionally separate
and holds its own mnemonic in `providers/liquidity/*`. See
and holds its own mnemonic in `providers/<org>/*`. See
`~/work/lux/CLAUDE.md` §"Mnemonic + Key Derivation" for the
canonical reference (paths, derivation formula, IAM apps required).
+3 -3
View File
@@ -35,12 +35,12 @@ import (
// against. The org binding follows two flavors of IAM JWT:
//
// 1. User tokens (Google OAuth, password) — `owner` IS the org slug.
// Example: a user signs in to organization=liquidity, the JWT
// carries owner="liquidity", name="<username>".
// Example: a user signs in to organization=org1, the JWT
// carries owner="org1", name="<username>".
// 2. Application tokens (client_credentials) — `owner`="admin"
// (the parent record), and the application's `name` carries the
// org as a prefix per the documented `<org>-<service>` naming
// convention. Example: kms-app for liquidity is name="kms",
// convention. Example: kms-app for org1 is name="org1-kms",
// owner="admin", type="application".
//
// `roles` carries the IAM role list (kms-admin override).
+26 -26
View File
@@ -42,7 +42,7 @@ func TestRequireOrgJWT_missingBearer(t *testing.T) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
@@ -57,7 +57,7 @@ func TestRequireOrgJWT_invalidBearerShape(t *testing.T) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
req.Header.Set("Authorization", "Token abc")
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
@@ -79,7 +79,7 @@ func TestRequireOrgJWT_validTokenForCorrectOrg(t *testing.T) {
Subject: "user-z",
Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)),
},
Owner: "liquidity", // user-token case: owner IS the org
Owner: "org1", // user-token case: owner IS the org
})
called := false
@@ -89,7 +89,7 @@ func TestRequireOrgJWT_validTokenForCorrectOrg(t *testing.T) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
req.Header.Set("Authorization", "Bearer "+tok)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
@@ -108,14 +108,14 @@ func TestRequireOrgJWT_crossOrgRejected(t *testing.T) {
auth := newOrgJWTAuth(iam.URL, "")
// Token for org=mlc, request for org=liquidity → 403.
// Token for org=org2, request for org=org1 → 403.
tok := signOrgClaims(t, signer, orgClaims{
Claims: jwt.Claims{
Issuer: iam.URL,
Subject: "mlc-app",
Subject: "org2-app",
Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)),
},
Owner: "mlc",
Owner: "org2",
})
mux := http.NewServeMux()
@@ -123,7 +123,7 @@ func TestRequireOrgJWT_crossOrgRejected(t *testing.T) {
t.Fatal("inner handler must NOT be called for cross-org token")
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
req.Header.Set("Authorization", "Bearer "+tok)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
@@ -155,7 +155,7 @@ func TestRequireOrgJWT_kmsAdminCrossesOrgs(t *testing.T) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
req.Header.Set("Authorization", "Bearer "+tok)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
@@ -177,7 +177,7 @@ func TestRequireOrgJWT_expiredRejected(t *testing.T) {
Subject: "old",
Expiry: jwt.NewNumericDate(time.Now().Add(-time.Hour)),
},
Owner: "liquidity",
Owner: "org1",
})
mux := http.NewServeMux()
@@ -185,7 +185,7 @@ func TestRequireOrgJWT_expiredRejected(t *testing.T) {
t.Fatal("must reject expired token")
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
req.Header.Set("Authorization", "Bearer "+tok)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
@@ -208,7 +208,7 @@ func TestRequireOrgJWT_wrongIssuerRejected(t *testing.T) {
Subject: "u",
Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)),
},
Owner: "liquidity",
Owner: "org1",
})
mux := http.NewServeMux()
@@ -216,7 +216,7 @@ func TestRequireOrgJWT_wrongIssuerRejected(t *testing.T) {
t.Fatal("must reject wrong issuer")
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
req.Header.Set("Authorization", "Bearer "+tok)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
@@ -238,11 +238,11 @@ func TestRequireOrgJWT_applicationToken_orgFromName(t *testing.T) {
tok := signOrgClaims(t, signer, orgClaims{
Claims: jwt.Claims{
Issuer: iam.URL,
Subject: "admin/kms",
Subject: "admin/org1-kms",
Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)),
},
Owner: "admin",
Name: "kms",
Name: "org1-kms",
Type: "application",
})
@@ -251,7 +251,7 @@ func TestRequireOrgJWT_applicationToken_orgFromName(t *testing.T) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
req.Header.Set("Authorization", "Bearer "+tok)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
@@ -260,7 +260,7 @@ func TestRequireOrgJWT_applicationToken_orgFromName(t *testing.T) {
}
}
// An application named "kms" must NOT be able to read mlc's
// An application named "org1-kms" must NOT be able to read org2's
// secrets — the name prefix is the only org binding, no fall-through
// to other orgs.
func TestRequireOrgJWT_applicationToken_crossOrgRejected(t *testing.T) {
@@ -273,11 +273,11 @@ func TestRequireOrgJWT_applicationToken_crossOrgRejected(t *testing.T) {
tok := signOrgClaims(t, signer, orgClaims{
Claims: jwt.Claims{
Issuer: iam.URL,
Subject: "admin/kms",
Subject: "admin/org1-kms",
Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)),
},
Owner: "admin",
Name: "kms",
Name: "org1-kms",
Type: "application",
})
@@ -286,7 +286,7 @@ func TestRequireOrgJWT_applicationToken_crossOrgRejected(t *testing.T) {
t.Fatal("must reject app token reaching across orgs")
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/mlc/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org2/secrets/iam/X", nil)
req.Header.Set("Authorization", "Bearer "+tok)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
@@ -313,7 +313,7 @@ func TestRequireOrgJWT_tagPicksOrg(t *testing.T) {
Owner: "admin",
Name: "some-app",
Type: "application",
Tag: "liquidity",
Tag: "org1",
})
mux := http.NewServeMux()
@@ -321,7 +321,7 @@ func TestRequireOrgJWT_tagPicksOrg(t *testing.T) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
req.Header.Set("Authorization", "Bearer "+tok)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
@@ -352,7 +352,7 @@ func TestRequireOrgJWT_missingOwnerClaimRejected(t *testing.T) {
t.Fatal("must reject token with no resolvable org")
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
req.Header.Set("Authorization", "Bearer "+tok)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
@@ -376,11 +376,11 @@ func TestRequireOrgJWT_splitJwksAndIssuer(t *testing.T) {
tok := signOrgClaims(t, signer, orgClaims{
Claims: jwt.Claims{
Issuer: publicIssuer, // minted with public host
Subject: "admin/kms",
Subject: "admin/org1-kms",
Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)),
},
Owner: "admin",
Name: "kms",
Name: "org1-kms",
Type: "application",
})
@@ -389,7 +389,7 @@ func TestRequireOrgJWT_splitJwksAndIssuer(t *testing.T) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/liquidity/secrets/iam/X", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/kms/orgs/org1/secrets/iam/X", nil)
req.Header.Set("Authorization", "Bearer "+tok)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
+2 -2
View File
@@ -1,11 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="lqdtyGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<linearGradient id="brandGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#3B82F6"/>
<stop offset="100%" style="stop-color:#1D4ED8"/>
</linearGradient>
</defs>
<rect width="1024" height="1024" rx="200" fill="url(#lqdtyGrad)"/>
<rect width="1024" height="1024" rx="200" fill="url(#brandGrad)"/>
<path d="M 220 220 L 220 720 L 680 720 L 680 620 L 330 620 L 330 220 Z" fill="white"/>
<path d="M 400 500 Q 500 420 600 500 Q 700 580 800 500" stroke="rgba(255,255,255,0.4)" stroke-width="12" fill="none" stroke-linecap="round"/>
<path d="M 400 560 Q 500 480 600 560 Q 700 640 800 560" stroke="rgba(255,255,255,0.25)" stroke-width="8" fill="none" stroke-linecap="round"/>

Before

Width:  |  Height:  |  Size: 765 B

After

Width:  |  Height:  |  Size: 765 B