1
0
forked from baron/baron-sso

3단계 권한 모델 확장, keto 권한 정책

This commit is contained in:
2026-02-03 14:21:37 +09:00
parent 6dbdd5d483
commit d09abab5a2
24 changed files with 1071 additions and 141 deletions

View 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 ---")
}

View 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("====================================================")
}

View File

@@ -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")