보안
보안
이 페이지는 Quack의 보안 태세(posture)를 처음부터 끝까지 다뤄요. 서버가 무엇을 노출하는지, 무엇이 로컬에 남는지, TLS를 종료하는 리버스 프록시의 역할, 그리고 서버가 모든 연결과 모든 쿼리마다 실행하는 인증/권한 부여 콜백까지 살펴볼게요.
출처: 문서
본문
노출 모델
Quack 서버는 밑바탕 DuckDB 인스턴스의 전체 SQL 표면을 노출하며, 서버 세션에서 볼 수 있는 모든 테이블에 대한 읽기·쓰기 접근을 포함해요. 그래서 확장은 우발적 노출을 막는 보수적인 기본값과 함께 제공됩니다.
- 서버는 시작 시 임의 인증 토큰을 생성하며, 클라이언트는 모든 연결에서 이 토큰을 제공해야 해요.
- 서버는
localhost에만 바인딩하고, 로컬이 아닌 호스트 이름은 명시적인allow_other_hostname => true가 필요해요. - 서버는 자체적으로 TLS를 사용하지 않아요. localhost 통신에 TLS를 끌어들이는 것은 실질적인 이점 없이 의존성만 추가할 뿐이에요.
모범 사례 — 로컬 전용을 넘어서는 어떤 배포든 Quack을 인터넷에 직접 노출하지 마세요. 검증된 HTTP 리버스 프록시를 앞에 두고 프록시가 TLS를 종료하도록 권장해요.
Quack 클라이언트는 이러한 가정을 염두에 두고 제공돼요. 로컬이 아닌 URI에 대해서는 기본적으로 HTTPS를 가정하므로, 제대로 프록시를 둔 서버는 클라이언트 쪽에서도 "그냥 동작"해요. nginx와 Caddy 레시피(프로덕션·로컬 테스트)는 Securing Quack with a Reverse Proxy를 참고해 주세요.
인증과 권한 부여
모든 데이터베이스 호출에는 두 가지 별개의 결정이 있어요.
- 인증(Authentication): 호출자가 누구인가? 보통 호출자가 자격 증명(토큰, 비밀번호, 클라이언트 인증서)을 제공해 아이덴티티를 확립해요.
- 권한 부여(Authorization): 그래도 이것을 해도 되는가? 이미 인증된 호출자가 특정 객체 집합에 대해 특정 쿼리를 실행할 수 있는지 확립해요.
Quack은 이것을 두 개의 별개 훅으로 실행해요. 클라이언트가 처음 연결할 때 인증을, 클라이언트가 발행하려는 각 쿼리 전에 권한 부여를 실행하죠.
기본 구성
두 훅 모두 로컬 개발과 단일 사용자 배포에 적합한 내장 기본값을 갖고 있으며, 각각 더 엄격한 요구사항의 배포를 위해 덮어쓸 수 있는 콜백으로 노출됩니다.
기본적으로 다음과 같은 구성으로 제공돼요.
- 인증은 토큰 기반이에요.
quack_serve를 호출하면 서버가 임의 토큰을 생성해auth_token컬럼으로 반환해요 (또는quack_serve(uri, token := '...')로 명시적으로 제공할 수도 있어요). 클라이언트는 모든 연결에서 서버 URI에 범위가 지정된quack시크릿을 통하거나,ATTACH문/quack_query함수 호출의 명시적TOKEN옵션을 통해 이 토큰을 제시해야 해요. 기본 인증 콜백은 클라이언트가 제공한 토큰을 서버의 저장된 토큰과 비교해요. - 권한 부여는 관대해요. 기본 권한 부여 콜백은 모든 쿼리에 대해
true를 반환해요. 추가 필터링은 없습니다.
두 콜백 모두 사용자 제공 코드, 일반 SQL 매크로를 포함해 교체할 수 있어요. 아래 예시를 참고해 주세요.
콜백 계약
두 설정이 인증/권한 부여용 훅으로 호출할 함수의 이름을 담고 있어요.
| 설정 | 기본값 | 호출 시점 |
|---|---|---|
quack_authentication_function |
quack_check_token |
새 클라이언트가 연결할 때 (CONNECTION_REQUEST). |
quack_authorization_function |
quack_nop_authorization |
클라이언트가 쿼리를 발행할 때 (PREPARE_REQUEST). |
두 호출 모두 BOOLEAN 반환을 제공해야 해요. true는 요청을 허용하고, 그 외의 것(쿼리 오류 포함)은 Authentication failed / Authorization failed로 거부합니다. 일치하는 인자 개수와 BOOLEAN 반환 타입을 가진 함수로 해석될 수 있는 무엇이든 동작해요: 내장 스칼라 함수, 다른 확장이 등록한 스칼라 UDF, SQL 매크로. 인증은 (VARCHAR, VARCHAR, VARCHAR)를, 권한 부여는 (VARCHAR, VARCHAR)를 받아요.
콜백은 새로운 일시적인 서버 측 연결에서 실행돼요. 즉 테이블을 읽고, 다른 UDF를 호출하고, 확장을 참조할 수 있지만, 각 호출은 새 세션을 시작하므로 세션 로컬 상태에 의존할 수 없어요.
인증 훅
서버는 매 CONNECTION_REQUEST 호출에서 다음 SQL 문을 발행해 인증 함수를 호출해요.
SELECT ⟨quack_authentication_function⟩(⟨session_id⟩, ⟨client_token⟩, ⟨server_token⟩);
인자는 다음과 같이 정의됩니다.
session_id: 서버 생성 세션 id (임의 32자 문자열). 그 클라이언트의quack_connection_id가 돼요.client_token: 클라이언트가 보낸 토큰.server_token: 서버에 구성된 토큰 (quack_serve(token := ...)또는 자동 생성).
인증 덮어쓰기
사용자 지정 인증을 연결하는 가장 깔끔한 방법은 MACRO를 사용하는 거예요.
예시: 다중 토큰 테이블
허용된 토큰의 작은 테이블(예: 사용자당 하나)에 대해 인증하기:
CREATE TABLE quack_tokens (auth_token VARCHAR, user_name VARCHAR);
INSERT INTO quack_tokens VALUES
('alice-key-123', 'alice'),
('bob-key-456', 'bob');
CREATE MACRO check_token(sid, client_token, server_token) AS (
EXISTS (SELECT 1 FROM quack_tokens WHERE auth_token = client_token)
);
SET GLOBAL quack_authentication_function = 'check_token';
이제 토큰이 quack_tokens에 있는 클라이언트는 모두 허용되고 나머지는 거부돼요. 사용자 추가/제거는 일반적인 INSERT/DELETE 연산이에요.
예시: 개발자 모드 (항상 허용)
샌드박스 환경에서 로컬로 개발할 때는 들어오는 모든 연결을 허용하는 "개발자 모드" 인증을 고려할 수 있어요.
CREATE MACRO developer_mode_auth(sid, client_token, server_token) AS true;
SET GLOBAL quack_authentication_function = 'developer_mode_auth';
권한 부여 훅
서버는 매 PREPARE_RESPONSE 호출에서 다음 SQL 문을 발행해 권한 부여 함수를 호출해요.
SELECT ⟨quack_authorization_function⟩(⟨connection_id⟩, ⟨query⟩);
인자는 다음과 같이 정의됩니다.
connection_id: 호출 클라이언트의quack_connection_id(즉, 인증 훅이session_id인자로 봤던 것과 같은 id).query: 클라이언트가 실행하려는 전체 SQL 텍스트.
권한 부여 덮어쓰기
권한 부여는 PREPARE_REQUEST마다 한 번, 연결 id와 전체 SQL 텍스트와 함께 실행돼요. 일반적인 형태는 다음과 같아요.
예시: 읽기 전용
CREATE MACRO read_only(sid, query) AS
regexp_matches(upper(trim(query)), '^(SELECT|FROM|WITH|EXPLAIN|DESCRIBE|SHOW)\\b');
SET GLOBAL quack_authorization_function = 'read_only';
더 복잡한 권한 부여 함수는 Beyond SQL Macros 섹션을 참고해 주세요.
SQL 매크로 너머
SQL 매크로는 대부분의 인증·권한 부여 사례를 다루지만, 매크로 본문은 단일 표현식으로 제한되며 DML을 직접 실행할 수 없어요. 매크로 안에는 INSERT, UPDATE, DELETE가 없습니다. 모든 호출을 테이블에 기록하고, 호출 간 프로세스 내 상태를 유지하거나, 달리 명령형 로직을 구동해야 하는 정책이라면, 대신 DuckDB 확장으로 스칼라 함수를 등록하세요.
DuckDB 확장은 C++(주 언어) 또는 Rust, C, Go를 포함해 DuckDB의 C 확장 API에 바인딩을 가진 어떤 언어로도 작성할 수 있어요. 등록된 인증/권한 부여 함수는 위 SQL 매크로와 같은 (VARCHAR, ...) → BOOLEAN 시그니처를 노출해야 해요. 확장을 로드한 뒤 quack_authentication_function 또는 quack_authorization_function을 그 함수 이름으로 지정하세요.
Python UDF는
con.create_function을 통해 등록하면 만든 그 연결에 범위가 지정돼요. Quack은 각 콜백을 새 서버 측 연결에서 호출하므로, Python UDF는 디스패치 시점에 보이지 않아 인증·권한 부여 콜백으로 쓸 수 없어요. 전역으로 보이도록 하려면 DuckDB 확장으로 함수를 등록하세요.
예시: 읽기 전용 쿼리어
자체 완결 예시입니다. 사용자별 토큰을 요구하고 각 사용자를 읽기 전용 쿼리로 제한하는 서버를 만들어 볼게요.
CREATE TABLE quack_tokens (auth_token VARCHAR, user_name VARCHAR);
INSERT INTO quack_tokens VALUES ('analytics-team-token', 'analytics');
CREATE MACRO check_token(sid, client_token, server_token) AS (
EXISTS (SELECT 1 FROM quack_tokens WHERE auth_token = client_token)
);
CREATE MACRO read_only(sid, query) AS (
regexp_matches(upper(trim(query)), '^(SELECT|FROM|WITH|EXPLAIN)\\b')
);
CALL quack_serve('quack:localhost', token => 'analytics-team-token');
SET GLOBAL quack_authentication_function = 'check_token';
SET GLOBAL quack_authorization_function = 'read_only';
올바른 토큰을 가진 클라이언트는 이제 연결해 SELECT를 실행할 수 있지만, 표준 SQL 경로로 발행된 INSERT INTO quack.t ...는 권한 부여 시점에 실패해요.
경고 — 위의 프리픽스 정규식은 예시일 뿐 견고한 읽기 전용 필터가 아니에요.
WITH x AS (SELECT 1) INSERT INTO t SELECT * FROM x같은 쿼리는WITH로 시작하면서도 여전히 데이터를 변경해요. 프로덕션 읽기 전용 강제에는 데이터베이스를 읽기 전용 모드로 attached하거나, 원시 쿼리 텍스트가 아닌 파싱된 문장 타입을 검사하는 권한 부여 함수를 사용하세요.
예시: 사용자별 접근 제어 목록
사용자별 접근 제어 목록(ACL)을 구현하려면 sid → user 쌍을 기록해 권한 부여가 누가 물어보는지 조회할 수 있게 하는 사용자 지정 인증 훅을 만들어요. 매크로는 쓸 수 없으므로, 기록하는 쪽은 예를 들어 사용자 지정 DuckDB 확장으로 정의된 스칼라 UDF여야 해요. 권한 부여 쪽은 매크로일 수 있어요.
-- (클라이언트가 연결할 때 auth UDF가 채움)
CREATE TABLE quack_sessions (sid VARCHAR PRIMARY KEY, user_name VARCHAR);
-- 사용자별 쿼리 허용 목록 (여러분의 데이터 모델)
CREATE TABLE quack_user_acls (user_name VARCHAR, query_kind VARCHAR);
CREATE MACRO acl_check(sid, query) AS (
EXISTS (
SELECT 1
FROM quack_sessions s
JOIN quack_user_acls a ON a.user_name = s.user_name
WHERE s.sid = sid
AND regexp_matches(upper(trim(query)), '^' || a.query_kind || '\\b')
)
);
SET GLOBAL quack_authorization_function = 'acl_check';
더 알아보기 (Learn more)
- 리버스 프록시로 Quack을 보호하는 방법은
quack/setup/reverse_proxy문서를 참고해 주세요.