Skip to main content
Glama

kubernetes-mcp-server

authorization_test.go10.8 kB
package http import ( "strings" "testing" "github.com/go-jose/go-jose/v4/jwt" ) const ( // https://jwt.io/#token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJtY3Atc2VydmVyIl0sImV4cCI6MjUzNDAyMjk3MTk5LCJpYXQiOjAsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiOTkyMjJkNTYtMzQwZS00ZWI2LTg1ODgtMjYxNDExZjM1ZDI2Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiJlYWNiNmFkMi04MGI3LTQxNzktODQzZC05MmViMWU2YmJiYTYifX0sIm5iZiI6MCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCJ9.ld9aJaQX5k44KOV1bv8MCY2RceAZ9jAjN2vKswKmINNiOpRMl0f8Y0trrq7gdRlKwGLsCUjz8hbHsGcM43QtNrcwfvH5imRnlAKANPUgswwEadCTjASihlo6ADsn9fjAWB4viplFwq8VdzcwpcyActYJi2TBFoRq204STZJIcAW_B40HOuCB2XxQ81V4_XWLzL03Bt-YmYUhliiiE5YSKS1WEEWIbdel--b7Gvp-VS1I2eeiOqV3SelMBHbF9EwKGAkyObg0JhGqr5XHLd6WOmhvLus4eCkyakQMgr2tZIdvbt2yEUDiId6r27tlgAPLmqlyYMEhyiM212_Sth3T3Q // notsecret tokenBasicNotExpired = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJtY3Atc2VydmVyIl0sImV4cCI6MjUzNDAyMjk3MTk5LCJpYXQiOjAsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiOTkyMjJkNTYtMzQwZS00ZWI2LTg1ODgtMjYxNDExZjM1ZDI2Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiJlYWNiNmFkMi04MGI3LTQxNzktODQzZC05MmViMWU2YmJiYTYifX0sIm5iZiI6MCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCJ9.ld9aJaQX5k44KOV1bv8MCY2RceAZ9jAjN2vKswKmINNiOpRMl0f8Y0trrq7gdRlKwGLsCUjz8hbHsGcM43QtNrcwfvH5imRnlAKANPUgswwEadCTjASihlo6ADsn9fjAWB4viplFwq8VdzcwpcyActYJi2TBFoRq204STZJIcAW_B40HOuCB2XxQ81V4_XWLzL03Bt-YmYUhliiiE5YSKS1WEEWIbdel--b7Gvp-VS1I2eeiOqV3SelMBHbF9EwKGAkyObg0JhGqr5XHLd6WOmhvLus4eCkyakQMgr2tZIdvbt2yEUDiId6r27tlgAPLmqlyYMEhyiM212_Sth3T3Q" // notsecret // https://jwt.io/#token=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk4ZDU3YmUwNWI3ZjUzNWIwMzYyYjg2MDJhNTJlNGYxIn0.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJtY3Atc2VydmVyIl0sImV4cCI6MSwiaWF0IjowLCJpc3MiOiJodHRwczovL2t1YmVybmV0ZXMuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbCIsImp0aSI6Ijk5MjIyZDU2LTM0MGUtNGViNi04NTg4LTI2MTQxMWYzNWQyNiIsImt1YmVybmV0ZXMuaW8iOnsibmFtZXNwYWNlIjoiZGVmYXVsdCIsInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJkZWZhdWx0IiwidWlkIjoiZWFjYjZhZDItODBiNy00MTc5LTg0M2QtOTJlYjFlNmJiYmE2In19LCJuYmYiOjAsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.iVrxt6glbY3Qe_mEtK-lYpx4Z3VC1a7zgGRSmfu29pMmnKhlTk56y0Wx45DQ4PSYCTwC6CJnGGZNbJyr4JS8PQ // notsecret tokenBasicExpired = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk4ZDU3YmUwNWI3ZjUzNWIwMzYyYjg2MDJhNTJlNGYxIn0.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJtY3Atc2VydmVyIl0sImV4cCI6MSwiaWF0IjowLCJpc3MiOiJodHRwczovL2t1YmVybmV0ZXMuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbCIsImp0aSI6Ijk5MjIyZDU2LTM0MGUtNGViNi04NTg4LTI2MTQxMWYzNWQyNiIsImt1YmVybmV0ZXMuaW8iOnsibmFtZXNwYWNlIjoiZGVmYXVsdCIsInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJkZWZhdWx0IiwidWlkIjoiZWFjYjZhZDItODBiNy00MTc5LTg0M2QtOTJlYjFlNmJiYmE2In19LCJuYmYiOjAsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.iVrxt6glbY3Qe_mEtK-lYpx4Z3VC1a7zgGRSmfu29pMmnKhlTk56y0Wx45DQ4PSYCTwC6CJnGGZNbJyr4JS8PQ" // notsecret // https://jwt.io/#token=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk4ZDU3YmUwNWI3ZjUzNWIwMzYyYjg2MDJhNTJlNGYxIn0.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJtY3Atc2VydmVyIl0sImV4cCI6MjUzNDAyMjk3MTk5LCJpYXQiOjAsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiOTkyMjJkNTYtMzQwZS00ZWI2LTg1ODgtMjYxNDExZjM1ZDI2Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiJlYWNiNmFkMi04MGI3LTQxNzktODQzZC05MmViMWU2YmJiYTYifX0sIm5iZiI6MCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCIsInNjb3BlIjoicmVhZCB3cml0ZSJ9.m5mFXp0TDSvgLevQ76nX65N14w1RxTClMaannLLOuBIUEsmXhMYZjGtf5mWMcxVOkSh65rLFiKugaMXgv877Mg // notsecret tokenMultipleAudienceNotExpired = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk4ZDU3YmUwNWI3ZjUzNWIwMzYyYjg2MDJhNTJlNGYxIn0.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJtY3Atc2VydmVyIl0sImV4cCI6MjUzNDAyMjk3MTk5LCJpYXQiOjAsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiOTkyMjJkNTYtMzQwZS00ZWI2LTg1ODgtMjYxNDExZjM1ZDI2Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiJlYWNiNmFkMi04MGI3LTQxNzktODQzZC05MmViMWU2YmJiYTYifX0sIm5iZiI6MCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCIsInNjb3BlIjoicmVhZCB3cml0ZSJ9.m5mFXp0TDSvgLevQ76nX65N14w1RxTClMaannLLOuBIUEsmXhMYZjGtf5mWMcxVOkSh65rLFiKugaMXgv877Mg" // notsecret ) func TestParseJWTClaimsPayloadValid(t *testing.T) { basicClaims, err := ParseJWTClaims(tokenBasicNotExpired) t.Run("Is parseable", func(t *testing.T) { if err != nil { t.Fatalf("expected no error, got %v", err) } if basicClaims == nil { t.Fatal("expected claims, got nil") } }) t.Run("Parses issuer", func(t *testing.T) { if basicClaims.Issuer != "https://kubernetes.default.svc.cluster.local" { t.Errorf("expected issuer 'https://kubernetes.default.svc.cluster.local', got %s", basicClaims.Issuer) } }) t.Run("Parses audience", func(t *testing.T) { expectedAudiences := []string{"https://kubernetes.default.svc.cluster.local", "mcp-server"} for _, expected := range expectedAudiences { if !basicClaims.Audience.Contains(expected) { t.Errorf("expected audience to contain %s", expected) } } }) t.Run("Parses expiration", func(t *testing.T) { if *basicClaims.Expiry != jwt.NumericDate(253402297199) { t.Errorf("expected expiration 253402297199, got %d", basicClaims.Expiry) } }) t.Run("Parses scope", func(t *testing.T) { scopeClaims, err := ParseJWTClaims(tokenMultipleAudienceNotExpired) if err != nil { t.Fatalf("expected no error, got %v", err) } if scopeClaims == nil { t.Fatal("expected claims, got nil") } scopes := scopeClaims.GetScopes() expectedScopes := []string{"read", "write"} if len(scopes) != len(expectedScopes) { t.Errorf("expected %d scopes, got %d", len(expectedScopes), len(scopes)) } for i, expectedScope := range expectedScopes { if scopes[i] != expectedScope { t.Errorf("expected scope[%d] to be '%s', got '%s'", i, expectedScope, scopes[i]) } } }) t.Run("Parses expired token", func(t *testing.T) { expiredClaims, err := ParseJWTClaims(tokenBasicExpired) if err != nil { t.Fatalf("expected no error, got %v", err) } if *expiredClaims.Expiry != jwt.NumericDate(1) { t.Errorf("expected expiration 1, got %d", basicClaims.Expiry) } }) } func TestParseJWTClaimsPayloadInvalid(t *testing.T) { t.Run("invalid token segments", func(t *testing.T) { invalidToken := "header.payload.signature.extra" _, err := ParseJWTClaims(invalidToken) if err == nil { t.Fatal("expected error for invalid token segments, got nil") } if !strings.Contains(err.Error(), "compact JWS format must have three parts") { t.Errorf("expected invalid token segments error message, got %v", err) } }) t.Run("invalid base64 payload", func(t *testing.T) { invalidPayload := strings.ReplaceAll(tokenBasicNotExpired, ".", ".invalid") _, err := ParseJWTClaims(invalidPayload) if err == nil { t.Fatal("expected error for invalid base64, got nil") } if !strings.Contains(err.Error(), "illegal base64 data") { t.Errorf("expected decode error message, got %v", err) } }) } func TestJWTTokenValidateOffline(t *testing.T) { t.Run("expired token returns error", func(t *testing.T) { claims, err := ParseJWTClaims(tokenBasicExpired) if err != nil { t.Fatalf("expected no error for expired token parsing, got %v", err) } err = claims.ValidateOffline("mcp-server") if err == nil { t.Fatalf("expected error for expired token, got nil") } if !strings.Contains(err.Error(), "token is expired (exp)") { t.Errorf("expected expiration error message, got %v", err) } }) t.Run("multiple audiences with correct one", func(t *testing.T) { claims, err := ParseJWTClaims(tokenMultipleAudienceNotExpired) if err != nil { t.Fatalf("expected no error for multiple audience token parsing, got %v", err) } if claims == nil { t.Fatalf("expected claims to be returned, got nil") } err = claims.ValidateOffline("mcp-server") if err != nil { t.Fatalf("expected no error for valid audience, got %v", err) } }) t.Run("multiple audiences with mismatch returns error", func(t *testing.T) { claims, err := ParseJWTClaims(tokenMultipleAudienceNotExpired) if err != nil { t.Fatalf("expected no error for multiple audience token parsing, got %v", err) } if claims == nil { t.Fatalf("expected claims to be returned, got nil") } err = claims.ValidateOffline("missing-audience") if err == nil { t.Fatalf("expected error for token with wrong audience, got nil") } if !strings.Contains(err.Error(), "invalid audience claim (aud)") { t.Errorf("expected audience mismatch error, got %v", err) } }) } func TestJWTClaimsGetScopes(t *testing.T) { t.Run("no scopes", func(t *testing.T) { claims, err := ParseJWTClaims(tokenBasicExpired) if err != nil { t.Fatalf("expected no error for parsing token, got %v", err) } if scopes := claims.GetScopes(); len(scopes) != 0 { t.Errorf("expected no scopes, got %d", len(scopes)) } }) t.Run("single scope", func(t *testing.T) { claims := &JWTClaims{ Scope: "read", } scopes := claims.GetScopes() expected := []string{"read"} if len(scopes) != 1 { t.Errorf("expected 1 scope, got %d", len(scopes)) } if scopes[0] != expected[0] { t.Errorf("expected scope 'read', got '%s'", scopes[0]) } }) t.Run("multiple scopes", func(t *testing.T) { claims := &JWTClaims{ Scope: "read write admin", } scopes := claims.GetScopes() expected := []string{"read", "write", "admin"} if len(scopes) != 3 { t.Errorf("expected 3 scopes, got %d", len(scopes)) } for i, expectedScope := range expected { if i >= len(scopes) || scopes[i] != expectedScope { t.Errorf("expected scope[%d] to be '%s', got '%s'", i, expectedScope, scopes[i]) } } }) t.Run("scopes with extra whitespace", func(t *testing.T) { claims := &JWTClaims{ Scope: " read write admin ", } scopes := claims.GetScopes() expected := []string{"read", "write", "admin"} if len(scopes) != 3 { t.Errorf("expected 3 scopes, got %d", len(scopes)) } for i, expectedScope := range expected { if i >= len(scopes) || scopes[i] != expectedScope { t.Errorf("expected scope[%d] to be '%s', got '%s'", i, expectedScope, scopes[i]) } } }) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/containers/kubernetes-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server