Browse Source
feat: Optional path-prefix and method scoping for Filer HTTP JWT (#8014)
feat: Optional path-prefix and method scoping for Filer HTTP JWT (#8014)
* Implement optional path-prefix and method scoping for Filer HTTP JWT * Fix security vulnerability and improve test error handling * Address PR feedback: replace debug logging and improve tests * Use URL.Path in logs to avoid leaking query paramsmaster
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 182 additions and 5 deletions
@ -0,0 +1,143 @@ |
|||
package weed_server |
|||
|
|||
import ( |
|||
"net/http/httptest" |
|||
"testing" |
|||
"time" |
|||
|
|||
"github.com/golang-jwt/jwt/v5" |
|||
"github.com/seaweedfs/seaweedfs/weed/security" |
|||
) |
|||
|
|||
func TestFilerServer_maybeCheckJwtAuthorization_Scoped(t *testing.T) { |
|||
signingKey := "secret" |
|||
filerGuard := security.NewGuard(nil, signingKey, 0, signingKey, 0) |
|||
fs := &FilerServer{ |
|||
filerGuard: filerGuard, |
|||
} |
|||
|
|||
// Helper to generate token
|
|||
genToken := func(allowedPrefixes []string, allowedMethods []string) string { |
|||
claims := security.SeaweedFilerClaims{ |
|||
AllowedPrefixes: allowedPrefixes, |
|||
AllowedMethods: allowedMethods, |
|||
RegisteredClaims: jwt.RegisteredClaims{ |
|||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)), |
|||
}, |
|||
} |
|||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
|||
str, err := token.SignedString([]byte(signingKey)) |
|||
if err != nil { |
|||
t.Fatalf("failed to sign token: %v", err) |
|||
} |
|||
return str |
|||
} |
|||
|
|||
tests := []struct { |
|||
name string |
|||
token string |
|||
method string |
|||
path string |
|||
isWrite bool |
|||
expectAuthorized bool |
|||
}{ |
|||
{ |
|||
name: "no restrictions", |
|||
token: genToken(nil, nil), |
|||
method: "GET", |
|||
path: "/data/test", |
|||
isWrite: false, |
|||
expectAuthorized: true, |
|||
}, |
|||
{ |
|||
name: "allowed prefix match", |
|||
token: genToken([]string{"/data"}, nil), |
|||
method: "GET", |
|||
path: "/data/test", |
|||
isWrite: false, |
|||
expectAuthorized: true, |
|||
}, |
|||
{ |
|||
name: "allowed prefix mismatch", |
|||
token: genToken([]string{"/private"}, nil), |
|||
method: "GET", |
|||
path: "/data/test", |
|||
isWrite: false, |
|||
expectAuthorized: false, |
|||
}, |
|||
{ |
|||
name: "allowed method match", |
|||
token: genToken(nil, []string{"GET"}), |
|||
method: "GET", |
|||
path: "/data/test", |
|||
isWrite: false, |
|||
expectAuthorized: true, |
|||
}, |
|||
{ |
|||
name: "allowed method mismatch", |
|||
token: genToken(nil, []string{"POST"}), |
|||
method: "GET", |
|||
path: "/data/test", |
|||
isWrite: false, |
|||
expectAuthorized: false, |
|||
}, |
|||
{ |
|||
name: "both match", |
|||
token: genToken([]string{"/data"}, []string{"GET"}), |
|||
method: "GET", |
|||
path: "/data/test", |
|||
isWrite: false, |
|||
expectAuthorized: true, |
|||
}, |
|||
{ |
|||
name: "prefix match, method mismatch", |
|||
token: genToken([]string{"/data"}, []string{"POST"}), |
|||
method: "GET", |
|||
path: "/data/test", |
|||
isWrite: false, |
|||
expectAuthorized: false, |
|||
}, |
|||
{ |
|||
name: "multiple prefixes match", |
|||
token: genToken([]string{"/other", "/data"}, nil), |
|||
method: "GET", |
|||
path: "/data/test", |
|||
isWrite: false, |
|||
expectAuthorized: true, |
|||
}, |
|||
{ |
|||
name: "write operation with method restriction", |
|||
token: genToken(nil, []string{"POST", "PUT"}), |
|||
method: "POST", |
|||
path: "/data/upload", |
|||
isWrite: true, |
|||
expectAuthorized: true, |
|||
}, |
|||
{ |
|||
name: "root path with prefix restriction", |
|||
token: genToken([]string{"/data"}, nil), |
|||
method: "GET", |
|||
path: "/", |
|||
isWrite: false, |
|||
expectAuthorized: false, |
|||
}, |
|||
{ |
|||
name: "exact prefix match", |
|||
token: genToken([]string{"/data"}, nil), |
|||
method: "GET", |
|||
path: "/data", |
|||
isWrite: false, |
|||
expectAuthorized: true, |
|||
}, |
|||
} |
|||
|
|||
for _, tt := range tests { |
|||
t.Run(tt.name, func(t *testing.T) { |
|||
req := httptest.NewRequest(tt.method, tt.path, nil) |
|||
req.Header.Set("Authorization", "Bearer "+tt.token) |
|||
if authorized := fs.maybeCheckJwtAuthorization(req, tt.isWrite); authorized != tt.expectAuthorized { |
|||
t.Errorf("expected authorized=%v, got %v", tt.expectAuthorized, authorized) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue