forked from baron/baron-sso
3단계 권한 모델 확장, keto 권한 정책
This commit is contained in:
42
backend/cmd/keto_test/main.go
Normal file
42
backend/cmd/keto_test/main.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/service"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// KetoService 초기화
|
||||
// KETO_READ_URL과 KETO_WRITE_URL은 컨테이너 외부 포트 또는 내부 주소에 맞게 설정 필요
|
||||
os.Setenv("KETO_READ_URL", "http://keto:4466")
|
||||
os.Setenv("KETO_WRITE_URL", "http://keto:4467")
|
||||
|
||||
keto := service.NewKetoService()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := "test-user-id"
|
||||
tenantID := "test-tenant-id"
|
||||
|
||||
fmt.Println("--- Keto ReBAC Test Start ---")
|
||||
|
||||
// 1. 초기 권한 체크 (당연히 거부되어야 함)
|
||||
allowed, _ := keto.CheckPermission(ctx, userID, "Tenant", tenantID, "view")
|
||||
fmt.Printf("1. Initial Check (view): %v (Expected: false)\n", allowed)
|
||||
|
||||
// 2. 관계(Relation) 추가
|
||||
fmt.Println("2. Adding relation: User is member of Tenant...")
|
||||
err := keto.CreateRelation(ctx, "Tenant", tenantID, "members", userID)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create relation: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 다시 권한 체크 (허용되어야 함)
|
||||
// OPL 정의에 의해 members는 view 권한을 포함함
|
||||
allowed, _ = keto.CheckPermission(ctx, userID, "Tenant", tenantID, "view")
|
||||
fmt.Printf("3. Final Check (view): %v (Expected: true)\n", allowed)
|
||||
|
||||
fmt.Println("--- Test Completed ---")
|
||||
}
|
||||
80
backend/cmd/keygen/main.go
Normal file
80
backend/cmd/keygen/main.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ApiKey struct {
|
||||
ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
|
||||
Name string
|
||||
ClientID string `gorm:"uniqueIndex"`
|
||||
ClientSecretHash string
|
||||
Scopes string
|
||||
Status string `gorm:"default:'active'"`
|
||||
}
|
||||
|
||||
func generateToken(n int) string {
|
||||
b := make([]byte, n)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
func main() {
|
||||
godotenv.Load(".env")
|
||||
godotenv.Load("backend/.env")
|
||||
|
||||
pgHost := os.Getenv("DB_HOST")
|
||||
if pgHost == "" { pgHost = "localhost" }
|
||||
pgPort := os.Getenv("DB_PORT")
|
||||
if pgPort == "" { pgPort = "5432" }
|
||||
pgUser := os.Getenv("DB_USER")
|
||||
if pgUser == "" { pgUser = "baron" }
|
||||
pgPass := os.Getenv("DB_PASSWORD")
|
||||
if pgPass == "" { pgPass = "password" }
|
||||
pgName := os.Getenv("DB_NAME")
|
||||
if pgName == "" { pgName = "baron_sso" }
|
||||
|
||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
|
||||
pgHost, pgUser, pgPass, pgName, pgPort)
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to DB: %v", err)
|
||||
}
|
||||
|
||||
clientID := generateToken(8)
|
||||
plainSecret := generateToken(16)
|
||||
hashedSecret, _ := bcrypt.GenerateFromPassword([]byte(plainSecret), bcrypt.DefaultCost)
|
||||
|
||||
key := ApiKey{
|
||||
Name: "Test Admin Key",
|
||||
ClientID: clientID,
|
||||
ClientSecretHash: string(hashedSecret),
|
||||
Scopes: "tenant:read tenant:write user:read user:write audit:read audit:write",
|
||||
Status: "active",
|
||||
}
|
||||
|
||||
if err := db.Table("api_keys").Create(&key).Error; err != nil {
|
||||
log.Fatalf("Failed to create API key: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("====================================================")
|
||||
fmt.Println("✅ API Key Generated Successfully!")
|
||||
fmt.Printf("Client ID: %s\n", clientID)
|
||||
fmt.Printf("Client Secret: %s\n", plainSecret)
|
||||
fmt.Println("====================================================")
|
||||
fmt.Println("Usage Example:")
|
||||
fmt.Printf("curl -H \"X-Baron-Key-ID: %s\" -H \"X-Baron-Key-Secret: %s\" http://localhost:3000/api/v1/admin/tenants\n", clientID, plainSecret)
|
||||
fmt.Println("====================================================")
|
||||
}
|
||||
@@ -200,6 +200,8 @@ func main() {
|
||||
slog.Warn("Failed to connect to Redis. Auth features may fail.", "error", err)
|
||||
}
|
||||
|
||||
ketoService := service.NewKetoService()
|
||||
|
||||
// Oathkeeper 상태를 주기적으로 확인해 다운을 감지합니다.
|
||||
var oathkeeperProbe *HTTPProbe
|
||||
if strings.ToLower(getEnv("OATHKEEPER_HEALTH_ENABLED", "true")) != "false" {
|
||||
@@ -225,16 +227,17 @@ func main() {
|
||||
// 2. Initialize Handlers
|
||||
tenantRepo := repository.NewTenantRepository(db)
|
||||
tenantService := service.NewTenantService(tenantRepo)
|
||||
tenantService.SetKetoService(ketoService) // Keto 주입
|
||||
userRepo := repository.NewUserRepository(db)
|
||||
|
||||
auditHandler := handler.NewAuditHandler(auditRepo)
|
||||
authHandler := handler.NewAuthHandler(redisService, idpProvider, auditRepo, tenantService, userRepo)
|
||||
authHandler := handler.NewAuthHandler(redisService, idpProvider, auditRepo, tenantService, ketoService, userRepo)
|
||||
adminHandler := handler.NewAdminHandler()
|
||||
devHandler := handler.NewDevHandler(redisService)
|
||||
tenantHandler := handler.NewTenantHandler(db, tenantService)
|
||||
kratosAdminService := service.NewKratosAdminService()
|
||||
oryAdminProvider := service.NewOryProvider()
|
||||
userHandler := handler.NewUserHandler(kratosAdminService, oryAdminProvider, tenantService, userRepo)
|
||||
userHandler := handler.NewUserHandler(kratosAdminService, oryAdminProvider, tenantService, ketoService, userRepo)
|
||||
apiKeyHandler := handler.NewApiKeyHandler(db)
|
||||
|
||||
// 3. Initialize Fiber
|
||||
@@ -452,6 +455,9 @@ func main() {
|
||||
api.Get("/audit", auditHandler.ListLogs)
|
||||
api.Get("/audit/auth/timeline", authHandler.GetAuthTimeline)
|
||||
|
||||
// Public Tenant Registration
|
||||
api.Post("/tenants/registration", tenantHandler.RegisterTenantPublic)
|
||||
|
||||
// Auth Proxy Routes
|
||||
auth := api.Group("/auth")
|
||||
auth.Post("/enchanted-link/init", authHandler.InitEnchantedLink)
|
||||
@@ -463,6 +469,15 @@ func main() {
|
||||
auth.Get("/consent", authHandler.GetConsentRequest)
|
||||
auth.Post("/consent/accept", authHandler.AcceptConsentRequest)
|
||||
|
||||
auth.Post("/enchanted-link/init", authHandler.InitEnchantedLink)
|
||||
auth.Post("/enchanted-link/poll", authHandler.PollEnchantedLink)
|
||||
auth.Post("/magic-link/verify", authHandler.VerifyMagicLink)
|
||||
auth.Post("/login/code/verify", authHandler.VerifyLoginCode)
|
||||
auth.Post("/login/code/verify-short", authHandler.VerifyLoginShortCode)
|
||||
auth.Post("/password/login", authHandler.PasswordLogin)
|
||||
auth.Get("/consent", authHandler.GetConsentRequest)
|
||||
auth.Post("/consent/accept", authHandler.AcceptConsentRequest)
|
||||
|
||||
auth.Post("/password/reset/initiate", authHandler.InitiatePasswordReset)
|
||||
// [Changed] Use Interstitial Page for GET to prevent Scanner consumption
|
||||
auth.Get("/password/reset/verify", authHandler.VerifyPasswordResetPage)
|
||||
@@ -496,25 +511,41 @@ func main() {
|
||||
// Admin Routes
|
||||
admin := api.Group("/admin")
|
||||
admin.Use(middleware.ApiKeyAuth(middleware.ApiKeyAuthConfig{DB: db})) // API Key 인증 추가
|
||||
admin.Get("/check", adminHandler.CheckAuth)
|
||||
admin.Get("/stats", adminHandler.GetSystemStats)
|
||||
admin.Get("/tenants", tenantHandler.ListTenants)
|
||||
admin.Post("/tenants", tenantHandler.CreateTenant)
|
||||
admin.Get("/tenants/:id", tenantHandler.GetTenant)
|
||||
admin.Put("/tenants/:id", tenantHandler.UpdateTenant)
|
||||
admin.Delete("/tenants/:id", tenantHandler.DeleteTenant)
|
||||
|
||||
// RBAC Middleware Instances
|
||||
requireSuperAdmin := middleware.RequireRole(middleware.RBACConfig{
|
||||
AllowedRoles: []string{domain.RoleSuperAdmin},
|
||||
AuthHandler: authHandler,
|
||||
KetoService: ketoService,
|
||||
})
|
||||
requireAdmin := middleware.RequireRole(middleware.RBACConfig{
|
||||
AllowedRoles: []string{domain.RoleSuperAdmin, domain.RoleTenantAdmin},
|
||||
AuthHandler: authHandler,
|
||||
KetoService: ketoService,
|
||||
})
|
||||
|
||||
admin.Get("/check", adminHandler.CheckAuth) // 기본 Admin 체크는 requireAdmin 없이 ApiKeyAuth로만 보호될 수 있음 (또는 추가 가능)
|
||||
admin.Get("/stats", requireSuperAdmin, adminHandler.GetSystemStats)
|
||||
|
||||
// Tenant Management (Super Admin Only)
|
||||
admin.Get("/tenants", requireSuperAdmin, tenantHandler.ListTenants)
|
||||
admin.Post("/tenants", requireSuperAdmin, tenantHandler.CreateTenant)
|
||||
admin.Post("/tenants/:id/approve", requireSuperAdmin, tenantHandler.ApproveTenant)
|
||||
admin.Get("/tenants/:id", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "view"), tenantHandler.GetTenant)
|
||||
admin.Put("/tenants/:id", requireSuperAdmin, tenantHandler.UpdateTenant)
|
||||
admin.Delete("/tenants/:id", requireSuperAdmin, tenantHandler.DeleteTenant)
|
||||
|
||||
// Admin User Management
|
||||
admin.Get("/users", userHandler.ListUsers)
|
||||
admin.Post("/users", userHandler.CreateUser)
|
||||
admin.Get("/users/:id", userHandler.GetUser)
|
||||
admin.Put("/users/:id", userHandler.UpdateUser)
|
||||
admin.Delete("/users/:id", userHandler.DeleteUser)
|
||||
admin.Get("/users", requireAdmin, userHandler.ListUsers) // TODO: TenantAdmin인 경우 해당 테넌트 사용자만 보이도록 Handler 수정 필요
|
||||
admin.Post("/users", requireAdmin, userHandler.CreateUser)
|
||||
admin.Get("/users/:id", requireAdmin, userHandler.GetUser)
|
||||
admin.Put("/users/:id", requireAdmin, userHandler.UpdateUser)
|
||||
admin.Delete("/users/:id", requireAdmin, userHandler.DeleteUser)
|
||||
|
||||
// API Key Management (M2M)
|
||||
admin.Get("/api-keys", apiKeyHandler.ListApiKeys)
|
||||
admin.Post("/api-keys", apiKeyHandler.CreateApiKey)
|
||||
admin.Delete("/api-keys/:id", apiKeyHandler.DeleteApiKey)
|
||||
// API Key Management (M2M) - Super Admin Only
|
||||
admin.Get("/api-keys", requireSuperAdmin, apiKeyHandler.ListApiKeys)
|
||||
admin.Post("/api-keys", requireSuperAdmin, apiKeyHandler.CreateApiKey)
|
||||
admin.Delete("/api-keys/:id", requireSuperAdmin, apiKeyHandler.DeleteApiKey)
|
||||
|
||||
// 개발자 포털 라우트 (RP/Consent 관리 및 IdP 설정)
|
||||
dev := api.Group("/dev")
|
||||
|
||||
Reference in New Issue
Block a user