InfraGuard

역할 · 팀 · 담당자

역할과 팀은 정보성 라벨이며 접근은 오직 등급이 결정합니다 — 사용자·팀 통합 담당자(Owner) 선택기까지 설명합니다.

개요 — 무엇을 하나

역할(Role) 과 팀(Team) 은 사람을 분류하는 정보성 라벨입니다. 화면·기능에 대한 접근을 여는 것은 오직 등급(user_level) 입니다. 즉 어떤 사용자가 무엇을 볼 수 있고 무엇을 저장·삭제할 수 있는지는 100% 등급으로 결정되고, 역할("DBA"·"DevOps"·"보안담당자")과 팀("인프라팀"·"보안팀")은 표시·조직 관리용 꼬리표일 뿐 어떤 권한도 부여하지 않습니다.

이 규칙 덕분에 권한 모델이 단순합니다. 새 사람에게 관리 권한을 주려면 역할을 바꾸는 것이 아니라 등급을 올립니다. "보안담당자" 라벨을 붙였다고 보안 화면이 열리는 것이 아니라, 등급이 스탭(5) 이상이어야 열립니다.

이 문서는 세 가지를 다룹니다.

  1. 역할(infr_role) — 직무 라벨 6종(DBA·DevOps·보안담당자·관리자·일반직원·개발자).
  2. 팀(infr_team) — 조직 단위. 사용자 소속 + 자산 담당자 지정에 사용.
  3. 담당자(Owner) 선택기 — 사용자와 팀을 하나의 드롭다운에서 고르는 통합 SOT(App\Support\Owners). Instances·Hardware·Storage 등 자산 화면의 "담당자" 필드가 이걸 씁니다.

접근 통제 = 오직 등급(user_level). 역할·팀은 프로그램적으로 어떤 권한도 열지 않습니다. 등급 체계는 등급(RBAC) 이해를 참고하세요.

언제 쓰나

화면 구성

/admin/users(Administration ▸ Users & Roles)는 두 개의 탭으로 구성됩니다.

탭 내용
사용자 사용자 목록(로그인ID·이름·이메일·역할·팀·등급·상태·마지막 접속) + 모달 CRUD
팀 팀 목록(이름·색상·리더·설명·인원수) + 모달 CRUD

사용자 편집 모달에는 등급·역할·소속 팀 선택기가 있고, 아래에 다음 안내가 붙습니다.

등급이 접근을 결정합니다 — 데모(읽기)·스탭(운영)·관리자(전체). 역할은 표시용입니다.

사용법

역할(Role) — 직무 라벨

역할 마스터는 시더(InfrRoleSeeder)가 key 기준으로 멱등 upsert 하는 6종입니다.

key 표시명 색 토큰 seq
dba DBA info 10
devops DevOps accent 20
security 보안담당자 crit 30
admin 관리자 warn 40
staff 일반직원 muted 50
developer 개발자 good 60

역할 admin(라벨 "관리자")은 등급 관리자(10)와 무관합니다. 역할은 라벨일 뿐이므로, 역할이 "관리자"여도 등급이 데모(1)면 아무것도 저장할 수 없습니다. 반대로 등급이 관리자(10)면 역할이 비어 있어도 전체 관리가 됩니다.

팀(Team) — 조직 단위

팀은 [팀] 탭에서 자유롭게 CRUD 합니다. 시드 예시 4팀:

이름 색 설명
인프라팀 accent 서버·네트워크·스토리지 운영
보안팀 crit 탐지·대응·차단 관제
개발팀 good 서비스 개발·배포
DBA팀 info 데이터베이스 운영·튜닝

팀 편집 필드:

필드 설명
이름(name) 필수. 팀 이름
색상(color) 배지 톤(accent/crit/good/info/muted …)
팀 리더(lead_user_id) 사용자 1명(선택)
설명(description) 자유 텍스트
활성(is_active) 비활성 팀은 담당자 선택기에서 숨겨짐

담당자(Owner) 선택기 — 사용자 + 팀 통합

자산(서버 인스턴스·하드웨어·스토리지)에는 "담당자" 필드가 있고, 개인 또는 팀 어느 쪽이든 지정할 수 있습니다. 이 통합 선택기의 규약은 App\Support\Owners 가 SOT 입니다.

저장 형태 = 두 컬럼

자산 테이블은 담당자를 두 컬럼으로 저장합니다.

컬럼 의미
owner_id 사용자 또는 팀의 정수 PK
owner_type 'user' 또는 'team'

프런트 교환 형태 = 단일 ref 문자열

브라우저(폼·필터)는 owner_type 과 owner_id 를 한 문자열로 주고받습니다.

user:5     → 사용자 id 5
team:3     → 팀 id 3

부트스트랩 옵션(Owners::options())은 optgroup 렌더용으로 사용자·팀 두 그룹을 내려줍니다(20초 캐시). 닫힌 사용자(is_close=1)와 비활성 팀(is_active=0)은 제외됩니다.

{
  "users": [ { "ref": "user:5", "id": 5, "name": "홍길동" } ],
  "teams": [ { "ref": "team:3", "id": 3, "name": "인프라팀" } ]
}

서버측 분해·복원

헬퍼 동작
Owners::split($ref) "user:5" → ['user', 5]. 형식이 잘못되면 ['user', 0](= 미지정)
Owners::ref($ownerId, $ownerType) (5, 'user') → "user:5". ownerId 가 비면 null
Owners::resolveName($ownerId, $ownerType) 카드·목록에 표시할 이름(없으면 null, 20초 캐시)
Owners::flush() 사용자/팀 변경 후 캐시 무효화

예를 들어 인스턴스 저장 컨트롤러는 다음과 같이 처리합니다.

// 담당자 ref("user:5"/"team:3") → owner_id + owner_type
[$ownerType, $ownerId] = Owners::split($request->input('owner_ref'));
$payload['owner_id']   = $ownerId ?: null;
$payload['owner_type'] = $ownerId ? $ownerType : 'user';   // 미지정이면 기본 'user'

담당자로 필터할 때도 같은 ref 를 씁니다 — 목록 쿼리는 owner 파라미터(user:5/team:3)를 split() 으로 분해해 WHERE owner_id = ? AND owner_type = ? 로 좁힙니다.

담당자 선택기는 Instances · Hardware · Storage & Files 자산 화면에서 동일하게 재사용됩니다. 팀 하나를 담당자로 지정하면 그 팀원 전체가 책임 라인에 들어옵니다(개인 이탈에 강함).

아바타 · 이니셜 · 색

라벨과 함께 표시되는 아바타는 RolePolicy 가 이름에서 자동 생성합니다.

데이터 소스

테이블 용도
infr_role 역할 마스터(6종, 정보성 라벨) — key·name·color·seq·is_active
infr_team 팀 마스터 — name·color·lead_user_id·description·is_active·seq
users 사용자 — user_level(접근 결정), role_id(nullable), team_id(nullable)
자산 테이블 owner_id + owner_type(user/team) 2컬럼으로 담당자 저장(Instances/Hardware/Storage 등)

SOT 헬퍼:

권한

안전장치(마지막 관리자 보호)

상황 결과
마지막 활성 관리자를 강등·비활성·삭제 거부 — "마지막 활성 관리자는 강등·비활성·삭제할 수 없습니다."
자기 자신의 관리자 등급을 낮추거나 비활성 거부 — "자기 자신의 관리자 권한을 낮추거나 비활성화할 수 없습니다."
자기 자신 삭제 거부 — "자기 자신은 삭제할 수 없습니다."
이미 쓰는 로그인 ID 거부 — "이미 사용 중인 로그인 ID 입니다."

문제해결 · FAQ

Q. "보안담당자" 역할을 줬는데 보안 화면이 안 열립니다. 역할은 라벨이라 권한을 열지 않습니다. 보안 화면(대부분 읽기)은 등급이 필요합니다 — 스탭(5) 이상으로 올려주세요. 등급(RBAC) 참고.

Q. 담당자 드롭다운에 어떤 사용자/팀이 안 보입니다. 닫힌 사용자(is_close=1)와 비활성 팀(is_active=0)은 선택기에서 제외됩니다. 사용자/팀을 활성으로 돌리거나, 목록이 오래됐다면 20초 캐시가 만료될 때까지 잠시 기다리세요(Owners::flush() 로 즉시 무효화됨).

Q. 팀을 삭제하면 그 팀 사람들이 사라지나요? 아니요. 사용자는 남고 team_id 만 null(미지정)이 됩니다. 자산의 owner_type='team' 참조는 별도라 자산도 삭제되지 않습니다(담당자 표시만 비게 됨).

Q. 담당자를 저장했는데 이름이 안 뜹니다. resolveName() 은 20초 캐시입니다. 방금 이름을 바꿨다면 잠시 후 갱신됩니다. ref 형식이 user:5/team:3 인지도 확인하세요 — 형식이 깨지면 split() 이 미지정(user:0)으로 취급합니다.

Q. 사용자에게 역할을 두 개 줄 수 있나요? 아니요. role_id 는 단일(nullable)입니다. 조직 단위 표현이 필요하면 팀을 함께 쓰세요(팀도 하나).

관련 항목

사용자 · 팀 관리
사용자 · 팀 관리