콘텐츠로 이동

엔터프라이즈 관리 설정과 게이팅 계약

Backend.AI GO는 엔터프라이즈 정책으로 중앙에서 관리할 수 있다. 정책을 쓰면 관리자가 설정을 덮어쓰고, 사용자가 바꾸지 못하도록 잠그고, (2단계부터) 페이지와 설정 탭을 숨길 수 있다. 이 문서는 정책이 무엇을 대상으로 삼을 수 있는지를 정의하는 안정된 버전 관리 레퍼런스, 곧 관리 설정 게이팅 계약이다.

이 문서는 공개 관리자 API로 다룬다. 여기서 정의한 정책 제어 가능 설정 경로와 유효한 UI 게이팅 id 집합은 정책을 불러올 때 검증되고 앱 안 게이팅이 그대로 소비한다. UI를 다시 구성하면 이 계약을 맞춰 고치게 되며, 배포된 정책이 조용히 깨지지 않는다.

정책이 설정을 지정하는 방법

정책은 관리 구성을 끌고 가는 두 필드를 담는다.

  • settingsOverrides: 사용자 설정 위에 적용하는 RFC 7386 JSON 머지 패치. 객체는 재귀로 병합하고, 스칼라와 배열은 통째로 교체하며, 명시적 null은 키를 삭제한다.
  • lockedPaths: 사용자가 바꿀 수 없는 점 표기 설정 경로의 집합. 잠긴 경로는 settingsOverrides에 값을 함께 담을 수도 있고(정책이 값을 정하고 고정), 단독으로 둘 수도 있다(정책이 현재 유효 값에서 경로를 고정).

두 필드 모두 설정을 점 표기 camelCase 경로로 가리킨다. 이는 앱 설정 JSON에 나타나는 경로와 같다. 예를 들면 tools.denylist, endpoints.modelHub.baseUrl, advanced.enableRemoteAccess이다.

이 영역에는 별도 정책 스키마가 없다. 관리 도메인 구성은 일반 설정 필드로 존재하므로, 정책은 모든 도메인(endpoints, tools, features 등)에서 동일한 머지 패치와 잠금 장치로 설정을 지정한다. 그래서 관리 구성 계약이 하나로 통일된다.

큰 소리로 실패하는 검증

정책이 유효 설정에 반영되기 전에, 정책이 지정한 모든 경로를 검증한다. 이 빌드가 처리할 수 없는 경로를 지정한 정책은 불러올 때 실행 가능한 오류와 함께 거부한다. 불러온 뒤 조용히 무시하지 않는다. 이 점은 보안 통제에서 특히 중요하다. 경로를 잘못 적은 deny run_command 잠금은, 관리자가 차단했다고 믿는 동안 도구를 켜진 채로 남겨서는 안 된다.

검증은 실제 설정 스키마를 아는 클라이언트에서, 정책 문서를 파싱하고 스키마 버전을 확인한 뒤에 실행한다. 머신 정책과 검증된 중앙 정책 모두에 적용한다.

상위 호환 규칙

더 새 빌드가 작성한 정책은 이 빌드가 아직 모르는 설정 도메인을 정당하게 지정할 수 있다. 반면 이 빌드가 아는 도메인 안의 오타는 작성 실수다. 둘을 가르는 규칙은 다음과 같다.

  • 경로의 최상위 구간이 이 빌드가 아는 설정 도메인이면(예: tools, endpoints, features), 경로의 모든 구간이 실제 필드로 풀려야 한다. 그렇지 않으면 정책을 거부한다. endpoints.modelHub.baseUrI(대문자 I)나 tools.denylst 같은 오타는 큰 소리로 실패한다.
  • 경로의 최상위 구간이 이 빌드가 전혀 모르는 도메인이면(예: 미래의 quotas 영역), 그 경로는 받아들이고 건너뛴다. 관리 상태에는 계속 보이며, 그 도메인을 이해하는 빌드를 설치하면 적용되기 시작한다.

UI 게이팅 id는 상위 호환 구간이 없는 닫힌 집합이다. 알 수 없는 페이지 id나 설정 탭 id는 항상 거부한다. 프런트엔드가 페이지나 탭을 그리거나 그리지 않거나 둘 중 하나이기 때문이다.

정책 전용, 사용자에게 읽기 전용인 설정

일부 설정은 관리자만 정해야 하는 거버넌스 개념이며 개별 사용자가 정해서는 안 된다. 이런 설정은 정책 전용이다. 정책은 값을 채울 수 있지만, 이를 대상으로 한 사용자 설정 업데이트는 조용히 버리지 않고 거부한다. 관리되지 않는 기본값은 비활성이라, 정책이 없는 설치는 이전과 똑같이 동작한다.

정책 전용 영역은 features(페이지와 설정 탭 숨김)와 deployment(배포 프로필과 기능 게이팅)이다. features 필드는 아래 "도구 거버넌스와 기능 게이팅"에서, deployment 필드는 아래 "배포 프로필"에서 설명한다.

계약 버전

게이팅 계약은 버전을 매긴다. 현재 버전은 1이다. 버전은 계약의 형태가 바뀔 때만(게이팅 id 종류가 새로 생기거나 검증 규칙이 바뀔 때) 올라가며, 기존 형태 안에서 설정 필드나 페이지가 추가될 때는 올라가지 않는다.

정책 제어 가능 설정 경로

앱 설정 스키마의 모든 리프나 영역은, 위 상위 호환 규칙을 따르는 한 정책으로 지정할 수 있다. 빌드가 이해하는 설정 도메인은 그 빌드 설정 스키마의 최상위 영역과 정확히 같다. 검증기는 실행 중인 빌드의 스키마에서 알려진 경로 집합을 끌어내므로 코드와 절대 어긋나지 않는다.

대표 도메인은 다음과 같다.

  • general: 언어, 테마, 모델 디렉터리 등 앱 수준 설정.
  • inference: 서빙 포트, 기본 컨텍스트 길이, 추론 기본값.
  • advanced: 로깅 레벨, 원격 접근, 코드 실행 샌드박스 등 고급 토글.
  • tools: 도구 호출 구성과 도구 허용/거부 거버넌스(아래 참고).
  • features: 정책 전용 페이지·설정 탭 숨김(아래 참고).
  • deployment: 정책 전용 배포 프로필과 기능 게이팅(아래 참고).
  • endpoints: 관리 서비스 엔드포인트(업데이트, 모델 허브, 레지스트리, 고정 추론 엔드포인트, 웹 검색, 채널).
  • memory, conversation, notifications, window, favorites, managementApi, defaultModels, acp, containerEngine, mlxEngine, voiceInput.

영역 전체를 잠그려면 영역 경로를 지정한다(예: tools). 단일 필드를 잠그려면 리프 경로를 지정한다(예: tools.maxToolLoops).

도구 거버넌스와 기능 게이팅

이 두 영역은 2단계 거버넌스 표면이다.

tools 영역은 사용자가 설정할 수 있고 정책으로 잠글 수 있다. 사용자는 자기 자신을 위해 설정할 수 있고, 정책은 lockedPaths로 잠근다. 거버넌스 필드는 다음과 같다.

  • tools.allowlist: 허용할 도구 이름. 비어 있으면 모든 도구를 허용한다.
  • tools.denylist: 거부할 도구 이름. 거부가 허용을 이기므로 두 목록에 모두 있는 도구는 거부한다.
  • tools.mcpAllowlist: 허용할 MCP 서버 이름. 비어 있으면 등록된 모든 MCP 서버를 허용한다.
  • tools.pythonSandboxEnabled: Python 샌드박스 도구를 쓸 수 있는지 여부. 기본값은 true이다.
  • tools.runCommandEnabled: run_command 도구를 쓸 수 있는지 여부. 기본값은 true이다.

도구 허용/거부 목록이 적용되는 방식

전역 tools 허용/거부 목록은 UI 표시에 그치지 않고 모든 도구 실행 경로에서 강제된다. 이 게이트는 에이전트별 프로필의 도구 설정에 더해지며 그보다 우선한다. 전역 거부는 해당 도구를 명시적으로 켠 프로필보다 우선한다. 강제는 백엔드의 모든 실행 표면에서 일어난다.

  • 에이전트: 에이전트 루프(도구가 내장 레지스트리나 MCP 서버에 도달하기 전)와 직접 에이전트 도구 실행 명령·엔드포인트 모두.
  • 데스크톱 도구 실행 명령.
  • 헤드리스 REST 도구 실행 엔드포인트(POST /api/v1/tools/execute).
  • MCP 계층: 비어 있지 않은 tools.mcpAllowlist 밖의 서버는 등록될 수 없고, 거부된 MCP 도구는 노출 카탈로그에서 걸러지며 실행 시에도 거부된다.

해석 규칙: tools.denylist에 있는 도구는 거부된다. pythonSandboxEnabled / runCommandEnabled 토글이 false이면 거부 집합에 합쳐진다. tools.allowlist가 비어 있지 않으면 거기에 없는 도구는 모두 거부된다. 도구 이름의 별칭(예: run_shellrun_command)은 같은 도구로 취급되므로 어느 표기로 적은 목록 항목이든 일치한다. MCP 도구는 server:tool 이름으로 참조되므로 tools.denylistmy-server:delete_file 항목은 그 MCP 도구 하나를 거부한다.

게이트는 닫힘 우선(fail closed)으로 동작한다. 실행 시점에 유효 정책을 확인할 수 없으면 허용이 아니라 거부한다. 관리되지 않는 기본값(allowlistdenylist가 비어 있고 두 토글이 모두 true)은 아무것도 거부하지 않으므로, 관리되지 않는 설치는 이전과 똑같이 동작한다. 설정 화면에서 정책으로 거부된 도구는 "조직에서 관리함" 배지(정책이 조직명을 지정하면 "{organization}에서 관리함")와 함께 잠금 상태로 표시되며, 사용자가 직접 끈 도구와 구분된다. 관리되지 않는 설치에서는 배지가 전혀 표시되지 않는다.

features 영역은 정책 전용이며 사용자에게 읽기 전용이다(features.*에 대한 사용자 쓰기는 거부된다). 정책만 값을 채우며, 관리되지 않는 기본값은 아무것도 숨기지 않는다. 필드는 다음과 같다.

  • features.hiddenPages: 내비게이션에서 숨길 페이지 id 목록. 각 id는 유효한 페이지 id여야 한다(아래 참고).
  • features.hiddenSettingsTabs: 숨길 설정 탭 id 목록. 각 id는 유효한 설정 탭 id여야 한다(아래 참고).

적용 방식

숨김은 UI에만 적용되는 것이 아니라 백엔드에서 강제된다. 관리 정책이 어떤 페이지를 숨기면, 앱은 그 페이지의 API 요청과 데스크톱 IPC 명령을 거부하므로, 프런트엔드를 수정하거나 API를 직접 호출하는 사용자도 그 페이지에 닿을 수 없다. 숨겨진 페이지의 REST 경로는 HTTP 403을 반환하고, 그 페이지를 떠받치는 Tauri 명령은 IPC에서 거부된다. 이는 권위 있는 제어다. 내비게이션 항목을 없애는 것은 편의이고, 실제 통제는 백엔드가 한다. 관리되지 않는 설치는 아무것도 숨기지 않고 아무것도 거부하지 않으므로 동작이 그대로다.

두 전송 계층은 페이지에 서로 다른 방식으로 닿는다. REST 차단은 접두사 기반이라, 숨겨진 페이지의 경로 하위 트리 전체가 라우트 스코프 미들웨어에서 한꺼번에 거부된다. IPC 차단은 명령 단위다. 각 페이지는 그 페이지를 떠받치는 사용자 대면 및 보안 민감 Tauri 명령을 선언하고, 숨겨진 페이지는 그 명령을 모두 거부한다. 명령 단위 모델이 허용하는 한 REST 접두사 범위에 최대한 가깝게 목록을 유지하며, 페이지를 떠받치는 명령이 차단도 의도적 예외도 되지 않은 채 남으면 빌드 단계 커버리지 테스트가 실패한다. 그래서 새 명령이 추가되어도 IPC 표면이 조용히 열리지 않는다.

여러 페이지를 떠받치는 명령은 공유 명령 규칙을 따른다. 그 명령을 노출하는 모든 페이지가 숨겨졌을 때만 거부되고, 그중 하나라도 보이면 거부되지 않는다. 로드된 모델 목록 표면이 대표 사례다. 로드된 모델을 나열하는 명령은 모델 페이지와 세션 페이지 양쪽을 떠받치므로, 세션만 숨기면 여전히 보이는 모델 페이지에서 그 명령이 호출 가능하고, 두 페이지가 모두 숨겨졌을 때 비로소 거부된다. 이로써 페이지 숨김이, 명령을 공유하는 다른 보이는 페이지를 망가뜨리지 않는다.

페이지를 떠받치더라도 페이지가 숨겨졌을 때 의도적으로 호출 가능한 상태로 두는 명령이 소수 있다. 모델을 실행하는 모든 페이지가 의존하는 공유 추론·풀 인프라, 여전히 보이는 채팅·플러그인 페이지와 공유하는 런타임 메모리 주입 및 채팅 메모리 파이프라인, 디렉터리·유지보수 도우미, 플러그인 런타임 저장소, 그리고 해당 페이지가 존재하기 전 시작 시점에 실행되는 액세스 키 부트스트랩 명령이 그렇다. 이들을 차단하면 여전히 보이는 페이지나 앱 자체의 시작이 망가지므로, 누락이 아니라 설계상 예외이며, 커버리지 테스트가 그 예외 목록이 정확하게 유지되는지 확인한다.

설정 탭의 경우, 탭이 소유한 전용 경로(예: connectors, managed-endpoints, supervisor, acp, agent-registry, nodes 탭)에도 같은 원리가 적용된다. 공유 설정 엔드포인트로만 읽고 쓰는 탭은 UI에서 사라지지만 그 하나의 엔드포인트를 공유한다. 그런 탭을 숨기는 대신 읽기 전용으로 만들려면, 아래 설명대로 그 하위 설정 경로를 잠근다.

관리 표시와 감사

거버넌스 표면 전체에서, 관리되는 설치는 일관된 읽기 전용 표시를 보여주고 차단된 동작을 모두 기록한다.

UI에서는 관리자가 통제하는 설정이 비활성화되며 "조직에서 관리함" 배지(정책이 조직명을 지정하면 "{organization}에서 관리함")가 붙는다. 정책으로 거부된 도구는 같은 배지와 함께 설정에서 잠금 상태로 표시된다. 숨겨진 페이지와 탭은 내비게이션에서 그냥 사라지며 끊어진 링크를 남기지 않는다. 관리되지 않는 설치에서는 이러한 표시가 전혀 나타나지 않으며 렌더링 부담도 추가되지 않는다.

백엔드에서는 게이트되거나 차단된 모든 동작이 기존 감사 채널(tracingtarget = "audit")에 정확히 하나의 구조화된 감사 레코드를 남긴다. 이는 reason = "policy_gated"를 가진 authz_denial 이벤트다. 레코드는 동작(도구, 명령, 경로), 거부한 경로(agent, tauri, rest, mcp), 그리고 결정한 정책(조직명이 지정된 경우)을 기록한다. 이는 스코프 거부가 이미 사용하는 채널을 공유하므로 하나의 필터로 둘 다 잡을 수 있다. 각 전송 경로의 가장 바깥쪽 강제 지점에서 기록하므로, 차단된 동작 하나는 계층마다가 아니라 한 건의 레코드가 된다.

배포 프로필

deployment 영역은 정책 전용이며 사용자에게 읽기 전용이다(deployment.*에 대한 사용자 쓰기는 조용히 버리지 않고 거부된다). 정책만 값을 채운다. 관리되지 않는 기본값은 standard 프로필에 모든 기능이 허용되고 모델 제한이 없는 상태라, 정책이 없는 설치는 이전과 똑같이 동작한다.

이 영역은 설치의 배포 형태와 사용자가 할 수 있는 일을 정한다.

  • deployment.profile: standard(기본값, 관리되지 않는 동작) 또는 fixed_endpoint(설치가 단일 관리 추론 엔드포인트하고만 통신).
  • deployment.allowedModelIds: 설치가 사용할 수 있도록 제한되는 모델 id 목록. 비어 있으면(기본값) 제한이 없다. 프로필과 무관한 소프트 제한이다.
  • deployment.allowLocalServing: 사용자가 로컬 추론 서버를 시작할 수 있는지 여부. 기본값은 true이다.
  • deployment.allowProviderConfig: 사용자가 업스트림 프로바이더를 추가·편집할 수 있는지 여부. 기본값은 true이다.
  • deployment.allowModelDownload: 사용자가 모델을 내려받을 수 있는지 여부. 기본값은 true이다.

fixed_endpoint 배포는 endpoints.inferenceApi 서비스 엔드포인트, 곧 OpenAI 호환 서빙 URL을 가리킨다. 그 URL과 자격 증명은 별도의 배포 설정이 아니라 일반 관리 서비스 엔드포인트다. URL은 endpoints.inferenceApi.baseUrl에 들어가고(또는 mode = disabled), 자격 증명은 OS 키체인에 쓰기 전용으로 저장되며 설정 파일에 남거나 다시 출력되지 않는다. deployment 영역은 엔드포인트 종류를 참조할 뿐 URL이나 비밀을 직접 담지 않는다. 고정 엔드포인트 배포를 운영하는 관리자는 보통 profile = fixed_endpointallowLocalServing, allowProviderConfig, allowModelDownload를 모두 false로, 그리고 endpoints.inferenceApicustom 모드로 함께 둬서 설치가 관리 엔드포인트에만 닿도록 한다.

고정 엔드포인트 강제

deployment.profile = fixed_endpoint이면 설치는 로컬 라우터를 우회하지 않고 추론을 관리 엔드포인트로 고정한다. 채팅 경로는 그대로다. 모든 모델 호출은 여전히 로컬 continuum-router를 거치므로 모델 별칭 재작성, 웹 검색 주입, 헬스 체크, 재시도가 보존된다. 시작 시 앱은 확인된 endpoints.inferenceApi URL을 가리키는 OpenAI 호환 라우터 백엔드를 정확히 하나 미리 등록한다. API 키는 키체인에서 읽어 환경 변수 참조로 라우터에 전달하므로 평문 키는 디스크의 라우터 설정에 남지 않는다. 프로필을 다시 standard로 바꾸면 그 백엔드는 제거된다.

기능 플래그가 무엇을 숨기고 무엇을 거부할지 정한다. 프런트엔드 숨김은 편의일 뿐이고, 백엔드는 두 전송 경로(Tauri 명령 가드와 REST 경로 게이트) 모두에서 같은 결정을 항상 강제하며, 관리되는 설치는 실패 시 닫힌다(fail closed).

  • allowLocalServing = false: 온디맨드 모델 로딩이 두 전송 경로에서 거부된다. 라우터와 고정 백엔드는 계속 떠 있고 로컬 서빙만 멈춘다. profile = fixed_endpoint과 함께 두면 Models·Engines 페이지도 숨겨진다(정책 작성자가 나열하지 않아도 프로필에서 파생되어 실효 features.hiddenPages에 합쳐진다).
  • allowModelDownload = false: 모델 다운로드 표면이 거부된다. /hf REST 하위 트리와 다운로드 개시 명령(download_model, batch_download_model, download_hf_repo, retry_download)이 차단되고, 이미 설치된 카탈로그 탐색과 HuggingFace 토큰 관리는 계속 동작한다.
  • allowProviderConfig = false: 업스트림 프로바이더 구성 표면이 거부된다. /providers REST 하위 트리와 프로바이더 관리 명령이 차단되고 API 페이지에서 Providers 탭이 숨겨진다. API 페이지의 나머지(라우팅, 헬스, 보안, 키)는 영향을 받지 않는다.

차단된 동작마다 동작 이름, 전송 경로, 결정 정책을 담은 policy_gated 감사 기록이 정확히 하나 남는다. 플래그는 프로필과 무관하다. standard 설치도 소프트 제한으로 어느 플래그든 설정할 수 있고, fixed_endpoint 설치도 어느 플래그든 true로 둘 수 있다.

에어갭 모드

egress.airGapped는 단일 페일클로즈드 마스터 스위치다. 적용된 정책에서 이 값을 true로 두면, 한 번의 전환으로 정책이 허용한 내부 호스트를 제외한 모든 외부 네트워크 경로가 비활성화된다. 이 스위치는 엔드포인트별 제어(이그레스 방화벽과 엔드포인트별 disabled 모드) 위에 위치한다. 방화벽을 강제로 켜고, 그 제어들이 닿지 못하는 경로까지 차단한다.

에어갭 상태에서는 다음과 같이 동작한다.

  • 이그레스 방화벽이 강제로 적용된다(egress.enforce는 기록된 값과 무관하게 true로 취급된다). 비어 있거나 내부 전용인 egress.allowedHosts는 그대로 존중되므로, 설치본은 관리자가 명시한 호스트에만 도달한다.
  • 모든 서비스 엔드포인트가 disabled로 해석된다. 따라서 default 모드 엔드포인트가 공개 기본 URL로 폴백하는 일이 없다(모델 허브, 엔진 및 런타임 레지스트리, 웹 검색 제공자, 채널 커넥터, 업데이트 피드, 추론 API).
  • 엔드포인트 리졸버 밖에서 동작하는 베어 클라이언트 네트워크 경로는 소켓을 열기 전에 거부되며, 각각 명확한 "에어갭 모드에서 비활성화됨" 오류를 반환한다. OAuth 디바이스 플로우(OpenAI Codex, Google Calendar), continuum-router 버전 프로브, 에이전트 URL 가져오기 도구, http_request 에이전트 도구, 에이전트 및 스쿼드 템플릿 카탈로그 레지스트리가 여기에 해당한다.
  • 클라우드 제공자 OAuth 디바이스 플로우 로그인이 차단되고 제공자는 API 키 인증으로 폴백한다. OAuth 디바이스 플로우 진입점은 UI에서 숨겨진다.
  • 익명 텔레메트리는 강제로 꺼지고 잠긴다. 토글은 "조직에서 관리함" 배지와 함께 비활성화되며, 활성화를 시도하는 쓰기는 거부된다.
  • 인앱 업데이터와 "업데이트 확인" 기능은 UI에서 숨겨진다.

상태 표면에도 반영된다. get_managed_status(Tauri)와 GET /api/v1/enterprise/status(REST) 모두 egress.airGapped를 함께 전달한다. 이 플래그는 사용자에게 읽기 전용이며, 정책만 설정할 수 있다. 방화벽이 시작 시 가장 먼저 설치되어 외부 이그레스 차단의 마스터 보증 역할을 하므로 egress에 두기를 권장한다. 이 필드를 생략한 기존 정책은 에어갭이 아닌 상태로 역직렬화되므로, 정책이 없는 설치본은 이전과 동일하게 동작한다. 운영자용 전체 문서는 이후 개정에서 제공한다.

유효한 UI 게이팅 id

이 id들은 정책이 UI를 숨길 때 쓸 수 있는 닫힌 집합이다. 애플리케이션의 내비게이션과 설정 정의를 그대로 반영하며, 드리프트 테스트가 이 목록을 코드와 발맞춰 유지한다.

페이지 id

정책은 페이지의 라우트 id를 적어 그 페이지를 숨긴다. 유효한 페이지 id는 다음과 같다.

  • /home
  • /dashboard
  • /models
  • /sessions
  • /chat
  • /draw
  • /translation
  • /agent-extension
  • /cowork
  • /schedules
  • /squad
  • /autonomous-agents
  • /creations
  • /statistics
  • /benchmark
  • /logs
  • /connector-audit
  • /engines
  • /api
  • /plugins
  • /data
  • /settings

설정 탭 id

정책은 탭 id를 적어 그 설정 탭을 숨긴다. 유효한 설정 탭 id는 다음과 같다.

  • general
  • ui
  • models
  • generation
  • tools
  • voice
  • memory
  • demo
  • supervisor
  • policies
  • acp
  • agent-registry
  • connectors
  • managed-endpoints
  • organization
  • advanced
  • nodes
  • maintenance

설정 탭을 숨기면 UI에서 사라지고, 그 탭이 소유한 전용 경로도 거부된다(위 "적용 방식" 참고). 탭의 설정을 숨기는 대신 읽기 전용으로 만들려면 그 하위 설정 경로를 잠근다. 그러면 입력이 비활성화되고 관리 배지가 표시된다.

정책 예시

원격 접근을 강제로 끄고 잠근 뒤 모델 허브 엔드포인트를 비활성화하는 정책:

{
  "schemaVersion": 1,
  "policyId": "example-corp-baseline",
  "issuedAt": "2026-06-24T00:00:00Z",
  "egress": { "enforce": false, "audit": false },
  "settingsOverrides": {
    "advanced": { "enableRemoteAccess": false },
    "endpoints": { "modelHub": { "mode": "disabled" } }
  },
  "lockedPaths": ["advanced.enableRemoteAccess", "endpoints.modelHub.mode"]
}

설치를 내부 추론 서버에 고정하고 로컬 서빙·프로바이더 설정·모델 다운로드를 끄는 고정 엔드포인트 배포:

{
  "schemaVersion": 1,
  "policyId": "example-corp-fixed-endpoint",
  "issuedAt": "2026-06-24T00:00:00Z",
  "egress": { "enforce": false, "audit": false },
  "settingsOverrides": {
    "deployment": {
      "profile": "fixed_endpoint",
      "allowedModelIds": ["internal/llama-3"],
      "allowLocalServing": false,
      "allowProviderConfig": false,
      "allowModelDownload": false
    },
    "endpoints": {
      "inferenceApi": { "mode": "custom", "baseUrl": "https://models.internal/v1" }
    }
  },
  "lockedPaths": [
    "deployment.profile",
    "deployment.allowLocalServing",
    "deployment.allowProviderConfig",
    "deployment.allowModelDownload",
    "endpoints.inferenceApi.mode",
    "endpoints.inferenceApi.baseUrl"
  ]
}

고정 엔드포인트의 API 키는 서버가 요구할 경우 정책 파일이 아니라 관리 엔드포인트 자격 증명 명령으로 쓰기 전용으로 설정한다.

머신 정책은 관리자만 쓸 수 있는 경로(예: 리눅스의 /etc/aigo/policy.json)에 두며 그 경로로 신뢰한다. 중앙 정책은 네트워크로 받아 오며 Ed25519 서명으로 신뢰한다.

오프라인 라이선스 게이트

관리형 설치는 네트워크 없이 파일만으로 엔터프라이즈 사용 권한을 증명하는 서명된 오프라인 라이선스를 추가로 요구할 수 있다. 라이선스는 <app_data>/enterprise/license.json에 있는 JSON 문서로, License(스키마 버전, 라이선스 id, 조직, 발급일, 선택적 만료일, 선택적 좌석·장치 한도, 선택적 엔타이틀먼트)와 분리된 Ed25519 서명을 담는다. 프로비저닝 프로파일로 배포되는 동일한 신뢰 앵커로 서명하며, 시작 시점과 상태를 읽을 때마다 완전히 오프라인으로 검증하므로 만료가 재시작 없이 즉시 반영된다.

라이선스 게이트는 설계상 정책 시행과 독립적이다. 없음·만료·무효·한도 초과 라이선스는 정책 잠금이나 이그레스 방화벽을 절대 해제하지 않는다. 해제하면 다운그레이드 공격(라이선스 파일을 지우면 제한이 사라짐)이 되기 때문이다. 대신 정책 잠금과 이그레스 허용 목록은 그대로 완전히 시행되고, 유효하지 않은 라이선스는 엔터프라이즈 작동을 막는 눈에 띄는 현지화된 "라이선스 만료/없음, 관리자에게 문의" 배너로 표시된다.

라이선스 상태는 엔터프라이즈 상태의 license 필드로 보고된다(Tauri get_managed_statusGET /api/v1/enterprise/status, 하나의 공유 서비스에서). 비관리형 설치에서는 이 필드가 생략된다. 관리형 설치에서는 다음 중 하나를 담는다.

  • valid: 검증됨, 만료 안 됨, 이 설치를 포함함. 엔터프라이즈 작동이 활성화된다.
  • expired: 서명은 검증됐지만 라이선스의 expiresAt가 과거다.
  • over_limit: 검증됐고 만료 안 됐지만 좌석 또는 장치 한도가 이 설치를 포함할 수 없다. 조직 전체 좌석·장치 수는 관리 서버가 집계하며, 오프라인 게이트는 라이선스가 최소한 이 단일 설치는 포함해야 한다는 하한만 시행한다(한도가 1 미만이거나, 장치 한도가 있는 라이선스인데 장치 ID가 없는 설치).
  • invalid: 라이선스 파일은 있으나 검증에 실패함(잘못되거나 위조된 서명, 범위를 벗어난 스키마, 알 수 없거나 만료된 신뢰 앵커, 또는 읽을 수 없는 파일).
  • absent: 라이선스 파일이 없음.

valid만 엔터프라이즈 작동을 허용하며, 그 외 상태는 게이트 배너를 표시한다. 라이선스 서명과 키 자료는 상태에 절대 반환되지 않는다(구조적으로 비밀이 없음).

서명된 라이선스 파일 예:

{
  "license": {
    "schemaVersion": 1,
    "licenseId": "acme-2026",
    "organization": "Acme Corp",
    "issuedAt": "2026-01-01T00:00:00Z",
    "expiresAt": "2027-01-01T00:00:00Z",
    "seatLimit": 250,
    "deviceLimit": 250,
    "entitlements": ["premium_models"]
  },
  "signature": "<정규 라이선스 바이트에 대한 base64 Ed25519 서명>",
  "anchorId": "acme-policy-anchor"
}

프로비저닝과 등록

관리되는 설치는 프로비저닝 프로필을 통해 조직에 연결된다. Backend.AI GO는 실행 시 프로필을 탐색하고, 프로필이 있으면 관리 모드로 진입한다. 이그레스 방화벽, 잠긴 설정, 라이선스 게이트, 중앙 정책 폴링 루프가 모두 활성화된다. 기기를 프로비저닝하는 방법은 세 가지이며, 모두 동일한 백엔드(기기 ID + 일회용 등록 핸드셰이크 + 프로필 탐색)로 수렴한다.

1. 딥링크(aigo://enroll)

aigo://enroll?token=<일회용-토큰>&url=<관리-서버-URL> 링크를 열면 사용자가 조직 설정 탭으로 이동하며 등록 대화상자가 미리 채워진다. 토큰은 일회용 베어러로, 절대 저장되지 않으며 등록 핸드셰이크에서 한 번만 소비된다. 두 필드 중 하나는 생략할 수 있다(URL은 배포된 프로비저닝 파일에서 올 수 있고, 토큰은 직접 입력할 수 있다).

2. 수동 등록(조직 설정 탭)

설정 -> 조직에서 일회용 토큰과 관리 서버 URL을 붙여넣을 수 있다. 핸드셰이크가 실행되기 전에, 대화상자는 서버 URL과 알려진 트러스트 앵커 지문을 보여주고 "이 서버를 신뢰함" 확인을 명시적으로 요구한다. 등록 후에는 기기 ID(기기 ID + 공개 키 지문), 조직/등록 상태(조직, 라이선스 상태, 잠긴 설정 개수, 이그레스 상태, 에어갭 표시), 수동 "정책 새로 고침" 동작이 탭에 표시된다.

3. 설치 관리자 / MDM provision.json 배포

무인 배포를 위해 OS 관리 경로에 provision.json을 배포하면 Backend.AI GO가 다음 실행 시 탐색한다.

  • Linux: /etc/aigo/provision.json
  • Windows: %ProgramData%\aigo\provision.json
  • macOS: /Library/Application Support/ai.backend.go/provision.json

설치 관리자 후크(.deb postinst, NSIS 설치 후크, macOS .pkg postinstall)는 관리 디렉터리를 준비하고, 패키지에 포함된 provision.json이 있으면 설치한다. 표준 설치를 자동으로 관리 모드로 전환하지는 않는다. 템플릿은 packaging/provisioning/provision.example.json이다. 프로필은 중앙 정책과 동일한 camelCase 스키마(schemaVersion, mode, organization, managementServerUrl, trustAnchors, pollIntervalSecs, 선택적 enrollment.token)를 사용한다. 파일에 포함된 일회용 토큰은 첫 실행 시 소비되어 디스크 캐시에서 제거되므로 영구적으로 남지 않는다.

{
  "schemaVersion": 1,
  "mode": "managed",
  "organization": "Acme Corp",
  "managementServerUrl": "https://policy.acme.example",
  "trustAnchors": [{ "id": "acme-policy-anchor", "publicKey": "<base64 Ed25519 공개 키>" }],
  "pollIntervalSecs": 900,
  "enrollment": { "token": "<일회용 등록 토큰>" }
}

자세한 MDM 배포 절차(Intune, Jamf, GPO), 설치 관리자 산출물, 재등록, 토큰 교체는 기기 등록과 신뢰 앵커에서 다룬다. 전체 운영 흐름은 관리자 가이드와 나머지 엔터프라이즈 배포 문서를 참고한다. 정책 서버 배포와 운영, 배포 모델, 에어갭 배포, 오프라인 라이선스, 고정 엔드포인트 배포, 감사와 컴플라이언스.