접근 인가 플러그인
접근 인가 플러그인 (Access authorization plugin)
이 문서는 Docker Engine에서 사용 가능한 Docker Engine 플러그인을 설명해요. Docker Engine이 관리하는 플러그인에 대한 정보는 Docker Engine 플러그인 시스템을 참고하세요.
Docker의 기본 제공 인가 모델은 전부 아니면 전무(all or nothing)예요. Docker 데몬에 접근 권한이 있는 모든 사용자는 어떤 Docker 클라이언트 명령이든 실행할 수 있어요. Docker의 Engine API를 사용해 데몬에 접촉하는 호출자에게도 마찬가지예요. 더 큰 접근 제어가 필요하다면 인가 플러그인을 만들고 Docker 데몬 구성에 추가할 수 있어요. 인가 플러그인을 사용하면 Docker 관리자가 Docker 데몬 접근을 관리하기 위한 세분화된 접근 정책을 구성할 수 있어요.
적절한 기술을 가진 사람은 누구나 인가 플러그인을 개발할 수 있어요. 이 기술의 가장 기본은 Docker에 대한 지식, REST에 대한 이해, 그리고 건전한 프로그래밍 지식이에요. 이 문서는 인가 플러그인 개발자에게 제공되는 아키텍처, 상태, 메서드 정보를 설명해요.
출처: 문서
본문
기본 원칙 (Basic principles)
Docker의 플러그인 인프라는 일반 API를 사용해 타사 구성 요소를 로드, 제거, 통신함으로써 Docker를 확장할 수 있게 해줘요. 접근 인가 하위 시스템은 이 메커니즘으로 구축됐어요.
이 하위 시스템을 사용하면 인가 플러그인을 추가하기 위해 Docker 데몬을 다시 빌드할 필요가 없어요. 설치된 Docker 데몬에 플러그인을 추가할 수 있어요. 새 플러그인을 추가하려면 Docker 데몬을 재시작해야 해요.
인가 플러그인은 현재 인증 컨텍스트와 명령 컨텍스트 모두를 바탕으로 Docker 데몬에 대한 요청을 승인하거나 거부해요. 인증 컨텍스트는 모든 사용자 세부 정보와 인증 방법을 담고 있어요. 명령 컨텍스트는 모든 관련 요청 데이터를 담고 있어요.
인가 플러그인은 Docker 플러그인 API에 설명된 규칙을 따라야 해요. 각 플러그인은 Plugin discovery 섹션에 설명된 디렉토리 안에 있어야 해요.
Note 약어
AuthZ와AuthN은 각각 authorization(인가)과 authentication(인증)을 뜻해요.
기본 사용자 인가 메커니즘 (Default user authorization mechanism)
Docker 데몬에서 TLS가 활성화되어 있으면 기본 사용자 인가 흐름은 인증서 주체 이름에서 사용자 세부 정보를 추출해요. 즉, User 필드는 클라이언트 인증서 주체 일반 이름으로 설정되고, AuthenticationMethod 필드는 TLS로 설정돼요.
기본 아키텍처 (Basic architecture)
플러그인을 Docker 데몬 시작의 일부로 등록하는 것은 개발자의 책임이에요. 여러 플러그인을 설치하고 함께 체인으로 연결할 수 있어요. 이 체인은 순서가 있을 수 있어요. 데몬에 대한 각 요청은 체인을 순서대로 통과해요. 모든 플러그인이 리소스 접근을 허용할 때만 접근이 허용돼요.
CLI를 통해 또는 Engine API를 통해 Docker 데몬에 HTTP 요청이 이루어지면 인증 하위 시스템은 그 요청을 설치된 인증 플러그인에 전달해요. 요청은 사용자(호출자)와 명령 컨텍스트를 담고 있어요. 플러그인은 요청을 허용할지 거부할지 결정할 책임이 있어요.
아래 시퀀스 다이어그램은 허용(allow)과 거부(deny) 인가 흐름을 묘사해요:
플러그인에 전송되는 각 요청은 인증된 사용자, HTTP 헤더, 요청/응답 본문을 포함해요. 플러그인에는 사용자 이름과 사용된 인증 방법만 전달돼요. 가장 중요한 것은 어떤 사용자 자격 증명이나 토큰도 전달되지 않는다는 것이에요.
Note 인가 플러그인은 Docker 데몬의 HTTP API에 대한 요청만 강제해요. gRPC 메서드 호출은 네이티브로 전달되든
POST /grpc를 통해 업그레이드되든 인가의 대상이 아니에요. 또한Content-Type이application/json인 HTTP 요청/응답 본문만 전달돼요. 다른 유형의 본문은 플러그인에 보이지 않으며, 데몬이 그 데이터에 대해 동작하더라도 인가 강제에 사용될 수 없어요.
exec 같이 HTTP 연결을 잠재적으로 하이재킹할 수 있는 명령(HTTP Upgrade)의 경우 인가 플러그인은 초기 HTTP 요청에 대해서만 호출돼요. 플러그인이 명령을 승인한 후에는 나머지 흐름에 인가가 적용되지 않아요. 구체적으로 스트리밍 데이터는 인가 플러그인에 전달되지 않아요. logs와 events 같이 청크(chunked) HTTP 응답을 반환하는 명령의 경우 HTTP 요청만 인가 플러그인에 전송돼요.
Engine의 인가 미들웨어는 fail-closed(실패 시 닫힘) 방식으로 동작해요: 플러그인이 오류를 반환하거나 Allow: false를 반환하면 요청이 거부되고 오류가 클라이언트에 표시돼요. 플러그인도 fail-closed로 동작해야 해요: 플러그인이 요청을 자신 있게 평가할 수 없다면 오류 또는 Allow: false를 반환해야 해요.
Warning 플러그인은 데몬에서 원시(raw) 요청 본문을 받으므로, 데몬이 동작할 요청을 평가한다는 것을 보장하려면 데몬과 같은 디코딩 의미를 적용해야 해요. 데몬은 Go의
encoding/json.Unmarshal로 JSON을 디코딩해요. 응답 본문에도 같은 요구 사항이 적용돼요.ResponseBody검사에 의존해 수정(redaction)이나 콘텐츠 필터링을 하는 플러그인은 응답이 단일 쓰기로 생성되는(REST 스타일 API 응답의 전형) 엔드포인트로 정책을 제한해야 해요. 응답이 스트리밍되거나 여러 쓰기를 통해 버퍼를 초과할 가능성이 있는 명령의 경우 보안 관련 결정에ResponseBody에 의존하지 말고 데몬 앞의 별도 레이어에서 필터링을 수행하세요.
응답 본문 크기와 부분 버퍼링 (Response body size and partial buffering)
데몬의 HTTP 핸들러와 플러그인의 응답 인가 콜백(pkg/authorization/response.go에 정의된 responseModifier) 사이에서 응답 본문을 보유하는 내부 버퍼는 maxBufferSize의 고정 용량 64 KiB를 가져요.
대부분의 비스트리밍 엔드포인트에서 Go의 encoding/json 인코더가 전체 페이로드를 단일 기본 쓰기로 직렬화하므로, 전체 크기와 무관하게 전체 응답이 플러그인 검사를 위해 버퍼링돼요. 위에서 언급한 스트리밍 응답 제외(logs, events 등)는 이 64 KiB 임계값과 스트리밍 핸들러가 사용하는 io.WriteFlusher 쓰기 패턴을 결합한 실질적 효과예요. 각 쓰기는 클라이언트로 즉시 배출되어 핸들러가 반환될 때쯤에는 더 이상 플러그인 검사에 사용할 수 없어요.
요청/응답 처리 중에 일부 인가 흐름은 Docker 데다운에 추가 쿼리를 해야 할 수도 있어요. 그러한 흐름을 완료하려면 플러그인이 일반 사용자와 유사하게 데몬 API를 호출할 수 있어요. 이러한 추가 쿼리를 활성화하려면 플러그인이 관리자가 적절한 인증과 보안 정책을 구성할 수단을 제공해야 해요.
Docker 클라이언트 흐름 (Docker client flows)
인가 플러그인을 활성화하고 구성하려면 플러그인 개발자가 이 섹션에 자세히 설명된 Docker 클라이언트 상호작용을 지원해야 해요.
Docker 데몬 설정 (Setting up Docker daemon)
--authorization-plugin=PLUGIN_ID 형식의 전용 커맨드 라인 플래그로 인가 플러그인을 활성화해요. 이 플래그는 PLUGIN_ID 값을 제공해요. 이 값은 플러그인의 소켓 또는 스펙 파일의 경로일 수 있어요. 인가 플러그인은 데몬을 재시작하지 않고도 로드할 수 있어요. 자세한 내용은 dockerd 문서를 참고하세요.
$ dockerd --authorization-plugin=plugin1 --authorization-plugin=plugin2,...
Docker의 인가 하위 시스템은 여러 --authorization-plugin 매개변수를 지원해요.
인가된 명령 호출 (허용)
$ docker pull centos
<...>
f1b10cd84249: Pull complete
<...>
인가되지 않은 명령 호출 (거부)
$ docker pull centos
<...>
docker: Error response from daemon: authorization denied by plugin PLUGIN_NAME: volumes are not allowed.
플러그인의 오류
$ docker pull centos
<...>
docker: Error response from daemon: plugin PLUGIN_NAME failed with error: AuthZPlugin.AuthZReq: Cannot connect to the Docker daemon. Is the docker daemon running on this host?.
API 스키마와 구현 (API schema and implementation)
Docker의 표준 플러그인 등록 방법 외에 각 플러그인은 다음 두 가지 메서드를 구현해야 해요:
/AuthZPlugin.AuthZReq이 인가 요청 메서드는 Docker 데몬이 클라이언트 요청을 처리하기 전에 호출돼요./AuthZPlugin.AuthZRes이 인가 응답 메서드는 응답이 Docker 데몬에서 클라이언트로 반환되기 전에 호출돼요.
/AuthZPlugin.AuthZReq
Request
{
"User": "The user identification",
"UserAuthNMethod": "The authentication method used",
"RequestMethod": "The HTTP method",
"RequestURI": "The HTTP request URI",
"RequestBody": "Byte array containing the raw HTTP request body",
"RequestHeader": "Byte array containing the raw HTTP request header as a map[string][]string "
}
Response
{
"Allow": "Determined whether the user is allowed or not",
"Msg": "The authorization message",
"Err": "The error message if things go wrong"
}
/AuthZPlugin.AuthZRes
Request:
{
"User": "The user identification",
"UserAuthNMethod": "The authentication method used",
"RequestMethod": "The HTTP method",
"RequestURI": "The HTTP request URI",
"RequestBody": "Byte array containing the raw HTTP request body",
"RequestHeader": "Byte array containing the raw HTTP request header as a map[string][]string",
"ResponseBody": "Byte array containing the raw HTTP response body",
"ResponseHeader": "Byte array containing the raw HTTP response header as a map[string][]string",
"ResponseStatusCode":"Response status code"
}
Response:
{
"Allow": "Determined whether the user is allowed or not",
"Msg": "The authorization message",
"Err": "The error message if things go wrong"
}
요청 인가 (Request authorization)
각 플러그인은 두 가지 요청 인가 메시지 형식을 지원해야 해요. 하나는 데몬에서 플러그인으로, 그 다음은 플러그인에서 데몬으로 가는 것이에요. 아래 표는 각 메시지에 기대되는 내용을 상세히 설명해요.
데몬 -> 플러그인
| Name | Type | Description |
|---|---|---|
| User | string | 사용자 식별 |
| Authentication method | string | 사용된 인증 방법 |
| Request method | enum | HTTP 메서드 (GET/DELETE/POST) |
| Request URI | string | API 버전을 포함한 HTTP 요청 URI, 클라이언트가 보낸 대로(예: v.1.17/containers/json) |
| Request headers | map[string]string | 키-값 쌍으로 된 요청 헤더(인가 헤더 제외) |
| Request body | []byte | 원시 요청 본문 |
플러그인 -> 데몬
| Name | Type | Description |
|---|---|---|
| Allow | bool | 요청이 허용되는지 거부되는지를 나타내는 부울 값 |
| Msg | string | 인가 메시지(접근이 거부되면 클라이언트에 반환됨) |
| Err | string | 오류 메시지(플러그인이 오류를 만나면 클라이언트에 반환됨. 제공된 문자열 값은 로그에 나타날 수 있으므로 기밀 정보를 포함하지 않아야 함) |
응답 인가 (Response authorization)
플러그인은 두 가지 인가 메시지 형식을 지원해야 해요. 하나는 데몬에서 플러그인으로, 그 다음은 플러그인에서 데몬으로 가는 것이에요. 아래 표는 각 메시지에 기대되는 내용을 상세히 설명해요.
데몬 -> 플러그인
| Name | Type | Description |
|---|---|---|
| User | string | 사용자 식별 |
| Authentication method | string | 사용된 인증 방법 |
| Request method | string | HTTP 메서드 (GET/DELETE/POST) |
| Request URI | string | API 버전을 포함한 HTTP 요청 URI, 클라이언트가 보낸 대로(예: v.1.17/containers/json) |
| Request headers | map[string]string | 키-값 쌍으로 된 요청 헤더(인가 헤더 제외) |
| Request body | []byte | 원시 요청 본문 |
| Response status code | int | Docker 데몬의 상태 코드 |
| Response headers | map[string]string | 키-값 쌍으로 된 응답 헤더 |
| Response body | []byte | 원시 Docker 데몬 응답 본문 |
플러그인 -> 데몬
| Name | Type | Description |
|---|---|---|
| Allow | bool | 응답이 허용되는지 거부되는지를 나타내는 부울 값 |
| Msg | string | 인가 메시지(접근이 거부되면 클라이언트에 반환됨) |
| Err | string | 오류 메시지(플러그인이 오류를 만나면 클라이언트에 반환됨. 제공된 문자열 값은 로그에 나타날 수 있으므로 기밀 정보를 포함하지 않아야 함) |