Qdrant 보안과 접근 제어

Qdrant 보안과 접근 제어 (security)

Qdrant 배포를 안전하게 유지하려면 누가 데이터에 접근할 수 있는지 제어하고, 트래픽을 암호화하고, 규정 준수를 위해 감사 추적(audit trail)을 남겨야 해요. 이 문서에서 Qdrant가 제공하는 보안 기능을 하나씩 설명해 드릴게요. 특히 셀프호스팅 배포에서 어떤 설정을 해야 안전한지가 핵심이에요.

출처: Qdrant 공식문서

Qdrant 배포를 보호한다는 것은 누가 데이터에 접근할 수 있는지 제어하고, 트래픽을 암호화하고, 규정 준수를 위해 감사 추적을 유지하는 것을 뜻해요. Qdrant는 배포를 보호하기 위해 API 키 인증(조회 전용 소비자를 위한 읽기 전용 API 키와 컬렉션별 읽기/쓰기 스코프를 가진 세분화된 접근 API 키 포함), 네트워크 바인딩, 암호화 연결을 위한 TLS, 그리고 규정 준수를 위한 감사 로깅을 지원해요. Qdrant Cloud에서는 이런 기능이 기본적으로 활성화돼 있어요. 셀프호스팅 오픈소스 배포에서는 프로덕션에 들어가기 전에 명시적으로 구성해야 해요.

셀프호스팅 Qdrant 인스턴스를 보호하는 실습 튜토리얼은 Secure a Self-Hosted Qdrant Instance를 참조하세요.

인스턴스 보안 설정하기 (Secure Your Instance)

기본적으로 모든 자체 배포 Qdrant 인스턴스는 안전하지 않아요. 모든 네트워크 인터페이스에 열려 있고 어떤 인증도 구성돼 있지 않아요. 인터넷의 누구에게나 제한 없이 열려 있을 수 있죠. 따라서 인스턴스를 프로덕션 준비 상태로 만들려면 반드시 보안 조치를 취해야 해요. 인스턴스를 보호하는 방법에 대한 지침을 이 섹션에서 잘 읽어보시길 바랍니다.

Qdrant Cloud 배포는 항상 기본적으로 안전해요. AuthenticationClient IP Restrictions을 참조하세요.

자체 인스턴스를 제대로 보호하려면 다음 단계를 적극 권장해요.

  1. 인증: 무단 접근을 막기 위해 API 키를 설정해요.
  2. 감사 로깅: 규정 준수와 포렌식을 위해 모든 API 연산을 로그 파일에 기록해요.
  3. 네트워크 바인딩: 특정 네트워크 인터페이스나 IP 주소에 바인딩해요.
    로컬에서 개발할 때는 모든 외부 접근을 막기 위해 127.0.0.1에 바인딩해요. 프로덕션에 배포할 때는 사설 네트워크 인터페이스나 IP에 바인딩해요.
  4. TLS: 모든 곳에서 TLS로 트래픽을 암호화해요.

인증 (Authentication)

기본적으로 오픈소스 Qdrant 배포는 연결할 수 있는 모든 사람의 요청을 받아들여요. 인스턴스를 보호하려면 API 키 인증을 활성화해요. Qdrant Cloud에서는 API 키 인증이 기본적으로 활성화돼 있어요.

Qdrant는 세 가지 유형의 API 키를 지원해요.

  • 관리자 API 키: 모든 연산과 컬렉션에 대한 전체 접근 권한을 부여해요.
  • 읽기 전용 API 키: 모든 연산과 컬렉션에 대한 읽기 전용 접근 권한을 부여해요. 데이터를 조회만 하면 되는 서비스나 사용자에게 쓸 수 있어요.
  • 세분화된 접근 API 키: 더 세분화된 접근 제어를 위해, 개별 컬렉션에 대한 읽기 또는 쓰기 권한을 지정하는 API 키를 사용할 수 있어요.

API 키로 인증하기 (Authenticate with an API Key)

API 키로 인증하려면, 관리자 키든 읽기 전용 키든 세분화된 접근 토큰이든 관계없이 api-key 요청 헤더에 제공하면 돼요.

curl -X GET https://xyz-example.eu-central.aws.cloud.qdrant.io:6333 \
  --header 'api-key: your_api_key_here'
from qdrant_client import QdrantClient

client = QdrantClient(
    url="https://xyz-example.eu-central.aws.cloud.qdrant.io:6333",
    api_key="your_api_key_here",
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({
  url: "https://xyz-example.eu-central.aws.cloud.qdrant.io",
  port: 6333,
  apiKey: "your_api_key_here",
});
use qdrant_client::Qdrant;

let client = Qdrant::from_url("https://xyz-example.eu-central.aws.cloud.qdrant.io:6334")
    .api_key("your_api_key_here")
    .build()?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;

QdrantClient client = new QdrantClient(
    QdrantGrpcClient.newBuilder("xyz-example.eu-central.aws.cloud.qdrant.io", 6334, true)
        .withApiKey("your_api_key_here")
        .build());
using Qdrant.Client;

var client = new QdrantClient(
    host: "xyz-example.eu-central.aws.cloud.qdrant.io",
    port: 6334,
    https: true,
    apiKey: "your_api_key_here");
import (
	"github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
	Host:   "xyz-example.eu-central.aws.cloud.qdrant.io",
	Port:   6334,
	APIKey: "your_api_key_here",
	UseTLS: true,
})

대안으로 Authorization: Bearer 헤더를 사용할 수도 있어요.

curl -X GET https://xyz-example.eu-central.aws.cloud.qdrant.io:6333 \
  --header 'Authorization: Bearer your_token_here'
from qdrant_client import QdrantClient

client = QdrantClient(
    url="https://xyz-example.eu-central.aws.cloud.qdrant.io:6333",
    auth_token_provider=lambda: "your_token_here",
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({
  url: "https://xyz-example.eu-central.aws.cloud.qdrant.io",
  port: 6333,
  headers: {
    authorization: "Bearer your_token_here",
  },
});
use qdrant_client::Qdrant;

let client = Qdrant::from_url("https://xyz-example.eu-central.aws.cloud.qdrant.io:6334")
    .header("authorization", "Bearer your_token_here")
    .build()?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import java.util.Map;

QdrantClient client = new QdrantClient(
    QdrantGrpcClient.newBuilder("xyz-example.eu-central.aws.cloud.qdrant.io", 6334, true)
        .withHeaders(Map.of("authorization", "Bearer your_token_here"))
        .build());
using Qdrant.Client;

var client = new QdrantClient(
    host: "xyz-example.eu-central.aws.cloud.qdrant.io",
    port: 6334,
    https: true,
    apiKey: "your_token_here",
    grpcTimeout: default,
    loggerFactory: null,
    headers: new Dictionary<string, string>
    {
        { "authorization", "Bearer your_token_here" }
    });
import (
	"github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
	Host:   "xyz-example.eu-central.aws.cloud.qdrant.io",
	Port:   6334,
	UseTLS: true,
	Headers: map[string]string{
		"authorization": "Bearer your_token_here",
	},
})

관리자 API 키 (Admin API Key)

v1.2.0부터 사용 가능해요.

관리자 API 키는 모든 연산과 컬렉션에 대한 전체 접근 권한을 부여하는 기본 키예요. 설정 파일의 api_key 설정으로 구성해요.

service:
  # Set an api-key.
  # If set, all requests must include a header with the api-key.
  # example header: `api-key: your_api_key_here`
  #
  # If you enable this you should also enable TLS.
  # (Either above or via an external service like nginx.)
  # Sending an api-key over an unencrypted channel is insecure.
  api_key: your_secret_api_key_here

또는 QDRANT__SERVICE__API_KEY 환경 변수를 사용할 수 있어요.

docker run -p 6333:6333 \
    -e QDRANT__SERVICE__API_KEY=your_secret_api_key_here \
    qdrant/qdrant

Qdrant Cloud에서 API 키 기반 인증을 사용하는 방법은 Cloud Authentication 섹션을 참조하세요.

관리자 API 키 교체하기 (Rotate an Admin API Key)

v1.17.0부터 사용 가능해요.

분산 배포에서는 다운타임 없이 관리자 API 키를 교체할 수 있어요. alt_api_key 설정을 사용해 기본 api_key와 동일하게 동작하는 두 번째 API 키를 임시로 구성하면, 기존 키와 새 키가 동시에 활성화될 수 있어요.

service:
  api_key: your_current_api_key_here
  alt_api_key: your_new_api_key_here

다운타임 없이 API 키를 교체하려면:

  1. 각 피어(peer)에 새 키를 alt_api_key로 설정해요. 다운타임을 피하기 위해 피어를 한 번에 하나씩만 재시작해요(롤링 재시작). 교체 기간 동안에는 두 키 중 하나로 인증된 요청이 모두 허용돼요.
  2. 클라이언트를 새 키로 전환해요.
  3. 피어를 다시 롤링 재시작하면서 새 키를 api_key로 승격시키고 alt_api_key를 제거해요.

읽기 전용 API 키 (Read-Only API Key)

v1.7.0부터 사용 가능해요.

Qdrant는 읽기 전용 API 키도 지원해요. 이 키는 인스턴스에서 읽기 전용 연산에 접근하는 데 사용할 수 있어요.

service:
  read_only_api_key: your_secret_read_only_api_key_here

환경 변수로도 설정할 수 있어요.

export QDRANT__SERVICE__READ_ONLY_API_KEY=your_secret_read_only_api_key_here

관리자 API 키와 읽기 전용 API 키는 동시에 사용할 수 있어요.

세분화된 접근 API 키 (Granular Access API Keys)

v1.9.0부터 사용 가능해요.

세분화된 접근 API 키를 사용하면 개별 컬렉션에 읽기 또는 쓰기 권한을 지정할 수 있어 역할 기반 접근 제어(RBAC)를 구현할 수 있어요. 이 키는 JSON Web Tokens(JWT) 표준을 기반으로 해요.

Qdrant Cloud에서는 세분화된 접근 API 키 인증이 기본적으로 활성화돼 있어요. 오픈소스 Qdrant 인스턴스에서 세분화된 접근 API 키 인증을 활성화하려면 api-key를 지정하고 설정에서 jwt_rbac 기능을 켜면 돼요.

service:
  api_key: you_secret_api_key_here
  jwt_rbac: true

환경 변수로는 이렇게 해요.

export QDRANT__SERVICE__API_KEY=your_secret_api_key_here
export QDRANT__SERVICE__JWT_RBAC=true

설정에서 지정한 api_key는 JWT를 인코딩하고 디코딩하는 데 사용돼요. 그러니 말할 필요도 없이 안전하게 보관해야 해요. api_key가 바뀌면 기존의 모든 토큰이 무효화돼요.

JSON Web Tokens 생성하기 (Generating JSON Web Tokens)

JWT는 관리자 API 키로 생성할 수 있어요. 토큰을 생성할 때는 기존 라이브러리와 도구 중 아무거나 사용할 수 있어요. Qdrant Web UI에서 Access Tokens를 선택해 JWT를 생성할 수도 있어요.

  • JWT Header - Qdrant는 토큰을 디코딩할 때 HS256 알고리즘을 사용해요.

    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
  • JWT Payload - 페이로드에 사용 가능한 파라미터의 어떤 조합이든 포함할 수 있어요.

    {
      "exp": 1640995200, // Expiration time
      "value_exists": ..., // Validate this token by looking for a point with a payload value
      "access": "r", // Define the access level.
    }
    

토큰 서명하기 - 생성된 토큰이 유효한지 확인하려면 설정에서 지정한 api_key로 서명해야 해요. 즉, 관리자 API 키를 아는 사람이라면 누구나 새 토큰을 Qdrant 인스턴스와 함께 사용하도록 승인할 수 있어요. Qdrant는 관리자 API 키를 알고 있기 때문에 서명을 검증하고 토큰을 디코딩할 수 있어요.

토큰 생성 과정은 클라이언트 쪽에서 오프라인으로 수행할 수 있고 Qdrant 인스턴스와의 어떤 통신도 필요하지 않아요.

JWT 토큰을 생성하는 데 사용할 수 있는 라이브러리 예시는 다음과 같아요.

jwt-cli를 사용한 예시는 다음과 같아요.

jwt encode --payload '{
  "access": "r",
  "exp": 1766055305
}' --secret 'your-api-key'

JWT 구성 (JWT Configuration)

다음은 사용 가능한 옵션, 즉 JWT 용어로 claims에 해당하는 것들이에요. JWT 페이로드에서 이들을 사용해 토큰의 기능을 정의할 수 있어요.

  • exp - 토큰의 만료 시간이에요. 초 단위의 Unix 타임스탬프예요. 이 시간 이후에는 토큰이 무효화돼요. 이 claim에 대한 검사에는 시계 오차(clock skew)를 감안한 30초 여유(leeway)가 포함돼요.

    {
      "exp": 1640995200, // Expiration time
    }
    
  • value_exists - 컬렉션에 저장된 데이터에 대해 토큰을 검증하는 데 사용할 수 있는 claim이에요. 이 claim의 구조는 다음과 같아요.

    {
      "value_exists": {
        "collection": "my_validation_collection",
        "matches": [
          { "key": "my_key", "value": "value_that_must_exist" }
        ],
      },
    }
    

    이 claim이 있으면 Qdrant는 컬렉션에 지정된 키-값을 가진 포인트가 있는지 확인해요. 그런 포인트가 존재하면 토큰은 유효해요.

    이 claim은 api_key를 바꾸지 않고 토큰을 취소할 수 있는 기능을 원할 때 특히 유용해요. 사용자 컬렉션이 있고 특정 사용자의 접근을 취소하고 싶은 경우를 생각해 보죠.

    {
      "value_exists": {
        "collection": "users",
        "matches": [
          { "key": "user_id", "value": "andrey" },
          { "key": "role", "value": "manager" }
        ],
      },
    }
    

    이 claim으로 토큰을 만들고, 접근을 취소하고 싶을 때 사용자의 role을 다른 값으로 바꾸면 토큰이 무효가 돼요.

  • access - 토큰의 접근 레벨을 정의하는 claim이에요. 이 claim이 있으면 Qdrant는 토큰이 연산을 수행하는 데 필요한 접근 레벨을 가지고 있는지 확인해요. 이 claim이 없으면 manage 접근이 가정돼요.

    전역 접근(global access)을 제공할 때는 r(읽기 전용) 또는 m(manage)을 쓸 수 있어요. 예를 들어:

    {
      "access": "r"
    }
    

    하나 이상의 컬렉션에 특화할 수도 있어요. 각 컬렉션의 access 레벨은 r(읽기 전용) 또는 rw(읽기-쓰기)예요. 이렇게요.

    {
      "access": [
        {
          "collection": "my_collection",
          "access": "rw"
        }
      ]
    }
    

접근 테이블 (Table of Access)

접근 레벨에 따라 어떤 동작이 허용되거나 거부되는지 이 표에서 확인해 보세요.

이 표는 API 키를 사용하는 경우에도 동일하게 적용돼요. 그 경우 api_keymanage에, read_only_api_keyread-only에 매핑돼요.

Symbols: ✅ Allowed | ❌ Denied | 🟡 Allowed, but filtered
Action manage read-only collection read-write collection read-only
list collections 🟡 🟡
get collection info
create collection
delete collection
update collection params
get collection cluster info
collection exists
update collection cluster setup
update aliases
list collection aliases 🟡 🟡
list aliases 🟡 🟡
create shard key
delete shard key
create payload index
delete payload index
list collection snapshots
create collection snapshot
delete collection snapshot
download collection snapshot
upload collection snapshot
recover collection snapshot
list shard snapshots
create shard snapshot
delete shard snapshot
download shard snapshot
upload shard snapshot
recover shard snapshot
list full snapshots
create full snapshot
delete full snapshot
download full snapshot
get cluster info
recover raft state
delete peer
get quotas
set quotas
get point
get points
upsert points
update points batch
delete points
update vectors
delete vectors
set payload
overwrite payload
delete payload
clear payload
scroll points
query points
search points
search groups
recommend points
recommend groups
discover points
count points
version
readyz, healthz, livez
telemetry
metrics

감사 로깅 (Audit Logging)

v1.17.0부터 사용 가능해요.

감사 로깅은 인증이나 권한 부여가 필요한 모든 API 연산을 기록하고, JSON 형식의 로그 파일에 써요.

감사 로깅은 기본적으로 활성화되어 있지 않아요. 활성화하려면 다음 구성 옵션을 사용해요.

audit:
  enabled: false
  dir: ./storage/audit
  rotation: daily
  max_log_files: 7
  # Only enable when Qdrant is behind a trusted reverse proxy or load balancer.
  # When true, the client IP is taken from the X-Forwarded-For header instead of
  # the TCP connection. Enabling this on a publicly reachable instance allows
  # clients to spoof their IP address in audit logs.
  trust_forwarded_headers: false

기본적으로 감사 로그는 매일 순환(rotation)되고 가장 최근 7개의 로그 파일이 유지돼요. 시간 단위 회전을 구성하려면 rotationhourly로 설정해요. 로그 파일 수가 max_log_files를 초과하면 가장 오래된 로그 파일이 삭제돼요.

추적 ID (Tracing IDs)

v1.18.0부터 사용 가능해요.

개별 요청에 추적 ID(tracing ID)를 붙일 수 있어요. 감사 로깅이 활성화되면 Qdrant는 추적 ID를 감사 로그 항목에 포함시켜, 클라이언트 쪽 연산을 대응하는 로그 항목과 연관 지을 수 있게 해줘요.

Qdrant는 x-request-id, x-tracing-id, traceparent 순서로 첫 번째로 일치하는 헤더에서 추적 ID를 읽어요. 256자를 초과하는 추적 ID는 잘려요.

curl -X GET http://localhost:6333/collections \
    --header 'api-key: your_api_key_here' \
    --header 'x-request-id: my-trace-id'
from qdrant_client import QdrantClient
from qdrant_client.context_headers import headers

with headers({"x-request-id": "my-trace-id"}):
    client.get_collections()
import { QdrantClient, withHeaders } from "@qdrant/js-client-rest";

const result = await withHeaders({ "x-request-id": "my-trace-id" }, () =>
    client.getCollections()
);
use qdrant_client::Qdrant;

client
    .with_header("x-request-id", "my-trace-id")
    .list_collections()
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.RequestHeaders;
import io.grpc.Context;

Context ctx = RequestHeaders.withHeader(Context.current(), "x-request-id", "my-trace-id");
ctx.run(() -> client.listCollectionsAsync());
using Qdrant.Client;

using (RequestHeaders.Use("x-request-id", "my-trace-id"))
    await client.ListCollectionsAsync();
import (
	"context"

	"github.com/qdrant/go-client/qdrant"
)

ctx := qdrant.WithHeader(context.Background(), "x-request-id", "my-trace-id")
client.ListCollections(ctx)

감사 로그 조회하기 (Query Audit Logs)

v1.18.0부터 사용 가능해요.

감사 로그는 /audit/logs API로 조회할 수 있어요(manage 레벨 접근 필요). 예를 들어:

curl -X POST 'https://YOUR-CLUSTER-URL:6333/audit/logs' \
  -H 'api-key: your_api_key_here' \
  -H 'Content-Type: application/json' \
  -d '{}'

기본적으로 이 API는 가장 최근 항목 100개를 반환하지만, limit 파라미터로 이 숫자를 바꿀 수 있어요(최대 10,000). 분산 클러스터에서는 이 API가 반환 전에 모든 노드의 결과를 집계해요. 선택적인 timeout(초) 쿼리 파라미터는 클러스터의 원격 피어를 얼마나 기다릴지 제어해요.

항목은 역시간순(가장 최근이 먼저)으로 반환돼요. 각 항목에는 다음 필드가 있어요.

Field Type Description
timestamp ISO-8601 접근 검사가 발생한 시각이에요.
method string API 메서드 이름이에요. 예: upsert_points, search_points.
auth_type "Jwt" | "ApiKey" | "None" 요청이 어떻게 인증됐는지를 나타내요.
result "ok" | "denied" 접근이 허용됐는지 여부예요.
subject string JWT sub claim이에요. JWT로 인증된 요청에서만 존재해요.
remote string 클라이언트 IP 주소예요(가능한 경우).
collection string 컬렉션 스코프 연산을 위한 컬렉션 이름이에요.
tracing_id string x-request-id, x-tracing-id, traceparent 요청 헤더의 값이에요.
error string 접근이 거부된 이유예요. result"denied"일 때만 존재해요.

시간 범위와 필터로 결과 좁히기 (Narrowing Results with Time Ranges and Filters)

결과를 특정 시간 범위로 좁히려면 time_from(포함)과 time_to(제외) 파라미터를 사용해요.

filters 파라미터는 특정 필드 값을 기준으로 항목을 정확히 일치(exact-match) 필터링할 수 있게 해줘요. 이 파라미터는 필드-값 쌍의 딕셔너리를 받아요. 둘 이상의 쌍을 지정하면 지정된 모든 기준에 일치하는 항목만 반환돼요(논리 AND).

알 수 없는 필터 필드는 조용히 일치 결과가 없게 반환돼요. 필터 필드 이름은 대소문자를 구분해요. 잘못된 대소문자로 필드 이름을 필터링하면 조용히 일치 결과가 없게 되죠.

항목 필드 표의 timestamp를 제외한 어떤 필드에서든 필터링할 수 있어요. 시간 범위 필터링에는 time_fromtime_to를 대신 사용해요.

예를 들어 2026년 3월 26일 my_collection 컬렉션에 대한 가장 최근의 거부(denied) 요청 50개를 검색하려면:

curl -X POST 'https://YOUR-CLUSTER-URL:6333/audit/logs' \
  -H 'api-key: your_api_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "limit": 50,
    "time_from": "2026-03-26T00:00:00Z",
    "time_to": "2026-03-27T00:00:00Z",
    "filters": {
      "result": "denied",
      "collection": "my_collection"
    }
  }'

네트워크 바인딩 (Network Bind)

기본적으로 사용자 정의 Qdrant 배포는 모든 네트워크 인터페이스에 바인딩돼요. 인스턴스가 인터넷의 모든 사람에게 열려 있을 수 있어요. 로컬 개발 머신에는 공개 접근을 막는 방화벽이 있을 가능성이 높지만, 공개 VPS나 전용 서버에서는 그렇지 않을 수 있어요.

원치 않는 접근을 막기 위해 특정 인터페이스나 IP 주소에 바인딩하는 것을 강력히 권장해요.

  • 로컬에서 개발할 때는 외부 접근이 불가능하도록 127.0.0.1에 바인딩해요.
  • 또는 프로덕션에 배포할 때는 사설 네트워크 인터페이스나 IP에 바인딩해요.

Docker를 사용할 때는 publish 플래그로 특정 인터페이스에 바인딩할 수 있어요. 예를 들어:

docker run -p 127.0.0.1:6333:6333 qdrant/qdrant

다른 유형의 배포를 사용한다면 Qdrant 자체에서 바인드 주소를 구성할 수 있어요. 설정에서 service.host: 127.0.0.1을 지정하거나, 이렇게 환경 변수를 사용해요.

QDRANT__SERVICE__HOST=127.0.0.1 ./qdrant

관리형 Qdrant Cloud 배포는 항상 기본적으로 안전해요. 이들은 공개적으로 접근 가능하고 클러스터에 할당된 엔드포인트에 바인딩돼 있어요. API 키로 인증을 구성하고, Client IP Restrictions를 통해 특정 IP 주소로의 접근을 제한할 수 있어요. Hybrid CloudPrivate Cloud 배포는 각각 자체적인 구성 방식을 가지고 있어요.

TLS

v1.2.0부터 사용 가능해요.

암호화 연결을 위해 Qdrant 인스턴스에서 TLS를 활성화해 연결을 보호할 수 있어요.

먼저 TLS용 인증서와 개인 키가 있어야 해요. 보통 .pem 형식이에요. 로컬 머신에서는 mkcert를 사용해 자체 서명 인증서를 생성할 수 있어요.

TLS를 활성화하려면 올바른 경로로 Qdrant 설정에 다음 속성을 설정하고 재시작해요.

service:
  # Enable HTTPS for the REST and gRPC API
  enable_tls: true

# TLS configuration.
# Required if either service.enable_tls or cluster.p2p.enable_tls is true.
tls:
  # Server certificate chain file
  cert: ./tls/cert.pem

  # Server private key file
  key: ./tls/key.pem

클러스터 모드로 실행할 때 내부 통신용 TLS는 이렇게 활성화할 수 있어요.

cluster:
  # Configuration of the inter-cluster communication
  p2p:
    # Use TLS for communication between peers
    enable_tls: true

TLS가 활성화되면 HTTPS 연결을 사용하기 시작해야 해요. 예를 들어:

curl -X GET https://localhost:6333
from qdrant_client import QdrantClient

client = QdrantClient(
    url="https://localhost:6333",
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ url: "https://localhost", port: 6333 });
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

인증서 교체(rotation)는 기본 새로고침 시간이 1시간으로 활성화돼 있어요. Qdrant가 실행되는 동안 매 시간 인증서 파일을 다시 로드하죠. 이렇게 하면 인증서가 외부에서 갱신될 때 변경 사항이 반영돼요. 새로고침 시간은 tls.cert_ttl 설정을 변경해 조정할 수 있어요. 인증서를 갱신할 계획이 없어도 이 기능은 켜둬도 돼요. 현재 이 기능은 REST API에서만 지원돼요.

선택적으로 로컬 인증 기관(certificate authority)에 대해 서버에서 클라이언트 인증서 검증을 활성화할 수 있어요. 다음 속성을 설정하고 재시작해요.

service:
  # Check user HTTPS client certificate against CA file specified in tls config
  verify_https_client_certificate: false

# TLS configuration.
# Required if either service.enable_tls or cluster.p2p.enable_tls is true.
tls:
  # Certificate authority certificate file.
  # This certificate will be used to validate the certificates
  # presented by other nodes during inter-cluster communication.
  #
  # If verify_https_client_certificate is true, it will verify
  # HTTPS client certificate
  #
  # Required if cluster.p2p.enable_tls is true.
  ca_cert: ./tls/cacert.pem

보안 강화 (Hardening)

Qdrant 컨테이너에 부여되는 권한을 줄여서 악용 위험을 낮추는 것을 권장해요. Qdrant 컨테이너의 권한을 줄이는 몇 가지 방법은 다음과 같아요.

  • Qdrant를 루트가 아닌 사용자로 실행해요. 이는 향후 컨테이너 탈출(container breakout) 취약점의 위험을 완화하는 데 도움이 돼요. Qdrant는 어떤 목적으로도 루트 사용자의 권한이 필요하지 않아요.

    • 기본 Qdrant 이미지 대신 qdrant/qdrant:<version>-unprivileged 이미지를 사용할 수 있어요.
    • docker run 실행 시 --user=1000:2000 플래그를 사용할 수 있어요.
    • Docker Compose를 사용할 때 user: 1000을 설정할 수 있어요.
    • Kubernetes에서 실행할 때 runAsUser: 1000을 설정할 수 있어요(우리 Helm 차트는 기본적으로 이렇게 해요).
  • Qdrant를 읽기 전용 루트 파일시스템으로 실행해요. 시스템 파일을 수정해야 하는 취약점을 완화하는 데 도움이 돼요. 이는 Qdrant에 필요하지 않은 권한이죠. 컨테이너가 스토리지용 마운트 볼륨(/qdrant/storage/qdrant/snapshots가 기본값)을 사용하는 한, Qdrant는 해당 볼륨 밖에 데이터를 쓰지 못하게 하면서도 계속 동작할 수 있어요.

  • Qdrant의 외부 네트워크 접근을 차단해요. 서버 측 요청 위조(SSRF) 공격을 완화하는 데 도움이 돼요. 예를 들면 스냅샷 복구 API를 통한 공격이 그렇죠. 단일 노드 Qdrant 클러스터는 외부 네트워크 접근이 필요 없어요. 다중 노드 Qdrant 클러스터는 TCP 포트 6333, 6334, 6335를 통해 다른 Qdrant 노드에 연결할 수 있는 기능만 필요해요.

배포 방식에 따라 Linux capabilities을 제거하는 등 권한을 줄이는 다른 기법도 있지만, 위에서 언급한 방법들이 가장 중요해요.

더 알아보기 (Learn more)