안녕하세요, 저는 3년째 AI API 게이트웨이 인프라를 구축하고 운영하는 백엔드 엔지니어입니다. 이번 글에서는 HolySheep AI의 RBAC + ABAC 하이브리드 권한 모델을 활용한 실전 접근 제어 구현 방법을 다룹니다. HolySheep AI의 RESTful API를 기반으로 한 역할 기반 + 속성 기반 접근 제어의 설계와 구현을 단계별로 설명드리겠습니다.

왜 RBAC + ABAC 하이브리드 모델인가?

단순한 RBAC만으로는 세밀한 권한 관리가 어렵습니다. 예를 들어, "개발자" 역할이더라도 특정 모델(GPT-4.1)에만 접근 가능하게 하거나, 부서별 사용량 한도를 다르게 설정해야 하는 경우가 있습니다. ABAC를 결합하면 속성(사용자 특성, 리소스 메타데이터, 환경 컨텍스트)을 기반으로 동적으로 권한을 판단할 수 있습니다.

핵심 개념 정리

HolySheep AI 권한 모델 아키텍처

HolySheep AI는 API 키 단위의 권한 관리를 지원하며, 각 API 키에 대한 RBAC 역할 할당과 ABAC 속성 필터를 적용할 수 있습니다. 이를 통해 기업 환경에서 요구하는 세분화된 접근 제어를 구현할 수 있습니다.

프로젝트 설정 및 환경 구성

# 프로젝트 디렉토리 생성 및 초기화
mkdir ai-access-control && cd ai-access-control
npm init -y

필수 의존성 설치

npm install express helmet cors express-rate-limit npm install jsonwebtoken bcryptjs uuid npm install dotenv

HolySheep AI SDK 설치 (선택사항)

npm install @holysheep/ai-sdk

프로젝트 구조 생성

mkdir -p src/{middleware,routes,services,models,config} touch src/index.js

HolySheep AI API 키 생성 및 권한 설정

먼저 HolySheep AI 콘솔에서 API 키를 생성하고 역할별 권한을 설정해야 합니다. HolySheep AI는 로컬 결제(해외 신용카드 불필요)을 지원하므로 빠르게 시작할 수 있습니다.

# .env 파일 설정

HolySheep AI API 설정

HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1 HOLYSHEEP_ORG_ID=your_org_id

JWT 시크릿 키

JWT_SECRET=your_super_secret_jwt_key_change_in_production

서버 설정

PORT=3000 NODE_ENV=development

RBAC + ABAC 권한 시스템 구현

1. 역할 정의 및 권한 매핑

// src/config/roles.js
// HolySheep AI의 RBAC 역할 정의

const ROLES = {
  ADMIN: 'admin',
  DEVELOPER: 'developer',
  ANALYST: 'analyst',
  READONLY: 'readonly'
};

const PERMISSIONS = {
  // 모델 접근 권한
  MODEL_ACCESS: {
    GPT4: 'gpt4',
    CLAUDE: 'claude',
    GEMINI: 'gemini',
    DEEPSEEK: 'deepseek'
  },
  // 작업 유형 권한
  TASK_TYPES: {
    CHAT: 'chat',
    COMPLETION: 'completion',
    EMBEDDING: 'embedding',
    IMAGE: 'image'
  },
  // 관리 권한
  ADMIN_ACTIONS: {
    CREATE_KEY: 'create_api_key',
    REVOKE_KEY: 'revoke_api_key',
    VIEW_USAGE: 'view_usage',
    MANAGE_ROLES: 'manage_roles'
  }
};

// 역할별 권한 매핑 (RBAC)
const ROLE_PERMISSIONS = {
  [ROLES.ADMIN]: {
    models: ['gpt4', 'claude', 'gemini', 'deepseek'],
    tasks: ['chat', 'completion', 'embedding', 'image'],
    admin: ['create_api_key', 'revoke_api_key', 'view_usage', 'manage_roles'],
    budgetLimit: null, // 무제한
    rateLimit: 10000
  },
  [ROLES.DEVELOPER]: {
    models: ['gpt4', 'claude', 'gemini', 'deepseek'],
    tasks: ['chat', 'completion', 'embedding'],
    admin: [],
    budgetLimit: 100000, // $100 USD
    rateLimit: 1000
  },
  [ROLES.ANALYST]: {
    models: ['gpt4', 'gemini'],
    tasks: ['chat', 'embedding'],
    admin: [],
    budgetLimit: 50000, // $50 USD
    rateLimit: 500
  },
  [ROLES.READONLY]: {
    models: ['gpt4'],
    tasks: ['chat'],
    admin: [],
    budgetLimit: 10000, // $10 USD
    rateLimit: 100
  }
};

module.exports = { ROLES, PERMISSIONS, ROLE_PERMISSIONS };

2. ABAC 속성 시스템 구현

// src/services/abacService.js
// ABAC (Attribute-Based Access Control) 속성 평가 엔진

const ENVIRONMENT_ATTRIBUTES = {
  // 시간 기반 속성
  getTimeWindow: () => {
    const hour = new Date().getHours();
    if (hour >= 9 && hour < 18) return 'business_hours';
    if (hour >= 18 && hour < 22) return 'evening';
    return 'night';
  },
  
  // IP 기반 속성 (실제 구현 시 IP 라이브러리 사용)
  getIpCategory: (ip) => {
    const internalRanges = ['192.168.', '10.', '172.16.', '172.31.'];
    const isInternal = internalRanges.some(range => ip.startsWith(range));
    return isInternal ? 'internal' : 'external';
  }
};

class ABACEngine {
  constructor() {
    this.policies = [];
  }

  // ABAC 정책 등록
  registerPolicy(policy) {
    this.policies.push({
      id: policy.id,
      effect: policy.effect, // 'permit' or 'deny'
      target: policy.target,
      conditions: policy.conditions || []
    });
  }

  // 속성 추출
  extractAttributes(user, resource, environment) {
    return {
      // 사용자 속성 (Subject Attributes)
      subject: {
        userId: user.id,
        role: user.role,
        department: user.department,
        clearanceLevel: user.clearanceLevel || 1,
        apiKeyId: user.activeApiKeyId,
        createdAt: user.createdAt
      },
      // 리소스 속성 (Resource Attributes)
      resource: {
        model: resource.model,
        taskType: resource.taskType,
        costPerToken: resource.costPerToken,
        dataClassification: resource.dataClassification || 'public'
      },
      // 환경 속성 (Environment Attributes)
      environment: {
        timeWindow: ENVIRONMENT_ATTRIBUTES.getTimeWindow(),
        ipCategory: ENVIRONMENT_ATTRIBUTES.getIpCategory(environment.ip),
        requestCount: environment.requestCount || 1,
        totalSpent: environment.totalSpent || 0
      }
    };
  }

  // ABAC 정책 평가
  evaluatePolicies(attributes) {
    let decision = 'permit'; // 기본값: 허용
    
    for (const policy of this.policies) {
      if (this.matchesTarget(policy.target, attributes)) {
        if (this.evaluateConditions(policy.conditions, attributes)) {
          if (policy.effect === 'deny') {
            return { decision: 'deny', policyId: policy.id };
          }
          decision = 'permit';
        }
      }
    }
    
    return { decision };
  }

  // 타겟 매칭
  matchesTarget(target, attributes) {
    if (!target) return true;
    
    if (target.roles && !target.roles.includes(attributes.subject.role)) {
      return false;
    }
    
    if (target.models && !target.models.includes(attributes.resource.model)) {
      return false;
    }
    
    if (target.maxCost && attributes.resource.costPerToken > target.maxCost) {
      return false;
    }
    
    return true;
  }

  // 조건 평가
  evaluateConditions(conditions, attributes) {
    if (!conditions || conditions.length === 0) return true;
    
    return conditions.every(condition => {
      const attrValue = this.getAttributeValue(condition.attribute, attributes);
      
      switch (condition.operator) {
        case 'eq':
          return attrValue === condition.value;
        case 'neq':
          return attrValue !== condition.value;
        case 'gt':
          return attrValue > condition.value;
        case 'lt':
          return attrValue < condition.value;
        case 'in':
          return condition.value.includes(attrValue);
        case 'notIn':
          return !condition.value.includes(attrValue);
        case 'contains':
          return String(attrValue).includes(condition.value);
        default:
          return false;
      }
    });
  }

  // 중첩 속성 값 조회
  getAttributeValue(path, attributes) {
    const parts = path.split('.');
    let current = attributes;
    
    for (const part of parts) {
      if (current && typeof current === 'object' && part in current) {
        current = current[part];
      } else {
        return undefined;
      }
    }
    
    return current;
  }
}

module.exports = { ABACEngine, ENVIRONMENT_ATTRIBUTES };

3. 하이브리드 권한 미들웨어

// src/middleware/hybridAuth.js
// RBAC + ABAC 하이브리드 권한 검증 미들웨어

const { ROLE_PERMISSIONS, ROLES } = require('../config/roles');
const { ABACEngine } = require('../services/abacService');

class HybridAccessControl {
  constructor() {
    this.abacEngine = new ABACEngine();
    this.initializeDefaultPolicies();
  }

  // 기본 ABAC 정책 설정
  initializeDefaultPolicies() {
    // 높은 비용 모델은 높은 클리어런스 레벨 필요
    this.abacEngine.registerPolicy({
      id: 'high-cost-model-restriction',
      effect: 'deny',
      target: {
        models: ['gpt4', 'claude']
      },
      conditions: [
        { attribute: 'subject.clearanceLevel', operator: 'lt', value: 3 }
      ]
    });

    // 야간 시간에는 읽기 전용 작업만 허용
    this.abacEngine.registerPolicy({
      id: 'night-time-restriction',
      effect: 'deny',
      target: {},
      conditions: [
        { attribute: 'environment.timeWindow', operator: 'eq', value: 'night' },
        { attribute: 'resource.taskType', operator: 'in', value: ['chat', 'completion'] }
      ]
    });

    // 외부 IP에서 민감 데이터 분류 리소스 접근 차단
    this.abacEngine.registerPolicy({
      id: 'external-sensitive-data',
      effect: 'deny',
      target: {},
      conditions: [
        { attribute: 'environment.ipCategory', operator: 'eq', value: 'external' },
        { attribute: 'resource.dataClassification', operator: 'eq', value: 'confidential' }
      ]
    });

    // 예산 초과 시 모든 요청 차단
    this.abacEngine.registerPolicy({
      id: 'budget-exceeded',
      effect: 'deny',
      target: {},
      conditions: [
        { attribute: 'environment.totalSpent', operator: 'gte', value: 100000 }
      ]
    });
  }

  // RBAC 권한 확인
  checkRBAC(userRole, requiredPermission) {
    const roleConfig = ROLE_PERMISSIONS[userRole];
    
    if (!roleConfig) {
      return { allowed: false, reason: 'Unknown role' };
    }

    // admin 역할은 모든 권한 보유
    if (userRole === ROLES.ADMIN) {
      return { allowed: true };
    }

    // 모델 접근 권한 확인
    if (requiredPermission.model && !roleConfig.models.includes(requiredPermission.model)) {
      return { allowed: false, reason: Model ${requiredPermission.model} not permitted for role ${userRole} };
    }

    // 작업 유형 권한 확인
    if (requiredPermission.task && !roleConfig.tasks.includes(requiredPermission.task)) {
      return { allowed: false, reason: Task ${requiredPermission.task} not permitted for role ${userRole} };
    }

    // 관리 권한 확인
    if (requiredPermission.admin && !roleConfig.admin.includes(requiredPermission.admin)) {
      return { allowed: false, reason: Admin action ${requiredPermission.admin} not permitted };
    }

    return { allowed: true };
  }

  // 하이브리드 접근 제어 미들웨어 생성
  createMiddleware(options = {}) {
    return async (req, res, next) => {
      try {
        // 1. 요청에서 사용자 및 리소스 정보 추출
        const user = req.user; // JWT에서 디코딩된 사용자 정보
        const resource = {
          model: req.body.model || req.query.model,
          taskType: this.inferTaskType(req),
          costPerToken: this.getModelCost(req.body.model),
          dataClassification: req.headers['x-data-classification'] || 'public'
        };

        const environment = {
          ip: req.ip || req.connection.remoteAddress,
          requestCount: parseInt(req.headers['x-request-count'] || '1'),
          totalSpent: parseInt(req.headers['x-total-spent'] || '0')
        };

        // 2. RBAC 체크 (첫 번째 필터)
        const requiredPermission = {
          model: resource.model,
          task: resource.taskType,
          admin: req.headers['x-admin-action']
        };

        const rbacResult = this.checkRBAC(user.role, requiredPermission);
        
        if (!rbacResult.allowed) {
          return res.status(403).json({
            error: 'Access Denied',
            reason: rbacResult.reason,
            layer: 'RBAC'
          });
        }

        // 3. ABAC 체크 (두 번째 필터)
        const attributes = this.abacEngine.extractAttributes(user, resource, environment);
        const abacResult = this.abacEngine.evaluatePolicies(attributes);

        if (abacResult.decision === 'deny') {
          return res.status(403).json({
            error: 'Access Denied',
            reason: 'ABAC policy violation',
            policyId: abacResult.policyId,
            layer: 'ABAC'
          });
        }

        // 4. Rate Limit 체크
        const roleConfig = ROLE_PERMISSIONS[user.role];
        if (roleConfig.rateLimit) {
          const currentRate = await this.getCurrentRateLimit(user.id);
          if (currentRate >= roleConfig.rateLimit) {
            return res.status(429).json({
              error: 'Rate limit exceeded',
              limit: roleConfig.rateLimit,
              current: currentRate
            });
          }
        }

        // 5. Budget 체크
        if (roleConfig.budgetLimit && environment.totalSpent >= roleConfig.budgetLimit) {
          return res.status(402).json({
            error: 'Budget exceeded',
            limit: roleConfig.budgetLimit,
            spent: environment.totalSpent
          });
        }

        // 6. 모든 검증을 통과한 경우 요청 속성 주입
        req.accessContext = {
          userRole: user.role,
          permissions: roleConfig,
          attributes
        };

        next();
      } catch (error) {
        console.error('Hybrid Access Control Error:', error);
        return res.status(500).json({ error: 'Access control evaluation failed' });
      }
    };
  }

  // 작업 유형 추론
  inferTaskType(req) {
    if (req.body.messages) return 'chat';
    if (req.body.prompt) return 'completion';
    if (req.body.input) return 'embedding';
    if (req.body.image) return 'image';
    return 'unknown';
  }

  // 모델 비용 조회 (HolySheep AI 가격 기준)
  getModelCost(model) {
    const costs = {
      'gpt-4': 30,      // $30/MTok
      'gpt-4-turbo': 15, // $15/MTok
      'gpt-4.1': 8,     // $8/MTok
      'claude-3-opus': 75,
      'claude-3-sonnet': 15, // $15/MTok
      'claude-3.5-sonnet': 15,
      'gemini-pro': 5,
      'gemini-2.5-flash': 2.5, // $2.50/MTok
      'deepseek-chat': 0.42   // $0.42/MTok
    };
    return costs[model] || 10;
  }

  // 현재 rate limit 조회 (실제 구현 시 Redis 등 사용)
  async getCurrentRateLimit(userId) {
    // 임시 구현
    return 0;
  }
}

module.exports = new HybridAccessControl();

4. HolySheep AI API 호출 통합

// src/services/aiGatewayService.js
// HolySheep AI API 통합 서비스

const axios = require('axios');

class AIGatewayService {
  constructor() {
    this.baseURL = process.env.HOLYSHEEP_BASE_URL || 'https://api.holysheep.ai/v1';
  }

  // HolySheep AI API 클라이언트 생성
  createClient(apiKey) {
    return axios.create({
      baseURL: this.baseURL,
      headers: {
        'Authorization': Bearer ${apiKey},
        'Content-Type': 'application/json'
      },
      timeout: 30000
    });
  }

  // 채팅 완료 요청 (HolySheep AI 경유)
  async chatCompletion(apiKey, params, accessContext) {
    const client = this.createClient(apiKey);
    
    try {
      const startTime = Date.now();
      
      const response = await client.post('/chat/completions', {
        model: params.model,
        messages: params.messages,
        temperature: params.temperature || 0.7,
        max_tokens: params.max_tokens || 2048,
        stream: params.stream || false
      });

      const latency = Date.now() - startTime;

      return {
        success: true,
        data: response.data,
        metadata: {
          latency,
          model: params.model,
          role: accessContext.userRole,
          costEstimate: this.estimateCost(params.model, response.data.usage)
        }
      };
    } catch (error) {
      throw this.handleError(error);
    }
  }

  // 임베딩 생성
  async createEmbedding(apiKey, params) {
    const client = this.createClient(apiKey);
    
    try {
      const response = await client.post('/embeddings', {
        model: params.model || 'text-embedding-3-small',
        input: params.input
      });

      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      throw this.handleError(error);
    }
  }

  // 비용 추정
  estimateCost(model, usage) {
    const inputCost = (usage.prompt_tokens / 1000000) * this.getModelCost(model);
    const outputCost = (usage.completion_tokens / 1000000) * this.getModelCost(model);
    return inputCost + outputCost;
  }

  // 모델 비용 조회
  getModelCost(model) {
    const costs = {
      'gpt-4': 30,
      'gpt-4-turbo': 15,
      'gpt-4.1': 8,
      'claude-3-opus': 75,
      'claude-3-sonnet': 15,
      'claude-3.5-sonnet': 15,
      'gemini-pro': 5,
      'gemini-2.5-flash': 2.5,
      'deepseek-chat': 0.42,
      'deepseek-v3': 0.42
    };
    return costs[model] || 10;
  }

  // 에러 처리
  handleError(error) {
    if (error.response) {
      const { status, data } = error.response;
      
      switch (status) {
        case 401:
          return new Error('Invalid API key or unauthorized');
        case 403:
          return new Error('Access forbidden: ' + (data.message || 'Permission denied'));
        case 429:
          return new Error('Rate limit exceeded');
        case 500:
          return new Error('HolySheep AI service error');
        default:
          return new Error(API Error: ${status} - ${JSON.stringify(data)});
      }
    }
    
    return new Error('Network error: ' + error.message);
  }
}

module.exports = new AIGatewayService();

5. API 라우트 구현

// src/routes/aiRoutes.js
// AI API 라우트

const express = require('express');
const router = express.Router();
const aiGateway = require('../services/aiGatewayService');
const hybridAuth = require('../middleware/hybridAuth');

// HolySheep AI 채팅 완료 엔드포인트
router.post('/chat', 
  hybridAuth.createMiddleware(), 
  async (req, res) => {
    try {
      const { model, messages, temperature, max_tokens, stream } = req.body;
      
      if (!model || !messages) {
        return res.status(400).json({ 
          error: 'Missing required fields: