DuckDB 보안
DuckDB 보안 (Securing DuckDB)
DuckDB는 강력한 분석 데이터베이스 엔진이에요. 파일을 읽고 쓰고, 네트워크에 접근하고, 확장을 로드하고, 시스템 리소스를 사용할 수 있답니다. 민감한 데이터를 다루거나 공유 환경에서 작업할 때는 이 능력들이 적절한 구성이 필요해요. 함께 살펴볼까요?
출처: 문서
본문
DuckDB는 강력한 분석 데이터베이스 엔진이에요. 파일을 읽고 쓰고, 네트워크에 접근하고, 확장을 로드하고, 시스템 리소스를 사용할 수 있어요. 강력한 도구답게, 민감한 데이터로 작업하거나 공유 환경에서 작업할 때 이 능력들은 적절한 구성이 필요해요.
이 페이지는 DuckDB의 보안 모델과 보안 관련 설정을 문서화해요. 올바른 구성은 사용 사례, 환경, 위협 모델에 따라 달라져요. 애플리케이션에 DuckDB를 임베드할 계획이라면 ["Embedding DuckDB"]({% link docs/current/operations_manual/securing_duckdb/embedding_duckdb.md %}) 페이지도 참고하세요.
신뢰할 수 없는 입력 (Untrusted Input)
신뢰할 수 없는 SQL 입력 (Untrusted SQL Input)
경고 DuckDB의 SQL을 Bash나 Python의 코드처럼 취급하세요. 적절한 샌드박싱 없이 신뢰할 수 없는 소스의 SQL을 실행하지 마세요.
DuckDB는 셸이나 스크립팅 인터프리터(bash나 Python 같은)처럼, 실행하는 사용자의 전체 권한으로 SQL을 실행해요. 샌드박싱 없이 신뢰할 수 없는 셸 스크립트나 Python 프로그램을 실행하지 않듯이, DuckDB의 SQL에도 같은 주의를 적용하세요.
애플리케이션이 신뢰할 수 없는 소스의 SQL을 실행해야 한다면, 신뢰할 수 없는 코드를 실행할 때 다음과 같은 추가 안전장치를 사용하세요:
- 샌드박싱을 위해 duckdb-wasm 사용
- DuckDB를 격리된 컨테이너에서 실행 (예: 제한된 기능의 Docker)
- 최소 권한을 가진 가상 머신이나 별도 프로세스 사용
- 운영 체제 수준 샌드박싱 적용
- 데이터 유출을 막기 위한 네트워크 격리 사용
- 애플리케이션 수준에서 엄격한 쿼리 타임아웃 구현
이 페이지에 설명된 설정은 심층 방어(defense-in-depth) 를 제공하고 일부 기능을 제한할 수 있지만, 적절한 샌드박싱을 대체하지는 않아요. 또한 샌드박싱은 보안 목적뿐 아니라 서비스 거부(DoS) 공격 방지에도 고려해야 한다는 점을 명심하세요: 악의적인 입력은 쉬게 DuckDB가 메모리, 디스크, CPU, 네트워크 같은 과도한 리소스를 소비하게 만들 수 있어요.
신뢰할 수 없는 비-SQL 입력 (Untrusted Non-SQL Input)
경고 DuckDB에 들어가는 비-SQL 입력도 쉽게 의도하지 않은 결과를 낳을 수 있어요. DuckDB로 보안에 민감한 애플리케이션을 구축할 때는 항상 신뢰할 수 없는 입력을 DuckDB에 넣는 영향이 무엇인지 제대로 이해하고 있는지 확인하세요.
SQL 외에도 DuckDB에는 데이터베이스와 상호작용하는 데 사용할 수 있는 여러 비-SQL API가 있어요. 예를 들어 Python에는 프로그래밍 방식으로 쿼리를 만들 수 있는 [relational API]({% link docs/current/clients/python/relational_api.md %})가 있어요.
이 API들은 파일 경로, 테이블 이름, 컬럼 이름, 필터 표현식 같은 사용자 입력을 받아요. 원시 SQL 문자열을 실행하지는 않지만, 여전히 파일을 읽고, 네트워크에 접근하고, 시스템 리소스를 사용할 수 있는 DuckDB 연산을 트리거해요.
비-SQL API에 대한 고려 사항 예시:
- 파일 경로:
duckdb.read_csv(path)나duckdb.read_parquet(path)같은 함수는 파일 경로를 받아요. 공격자가 제어하는 경로는 민감한 파일(예:/etc/passwd)을 읽거나 원격 URL에 접근할 수 있어요. - 테이블과 컬럼 이름: 이들은 보통 실행 가능한 코드라기보다는 식별자이지만, 소독되지 않은 입력은 예상치 못한 동작이나 정보 유출로 이어질 수 있어요.
- 필터 표현식: 일부 API는 DuckDB 표현식으로 컴파일되는 필터 표현식을 받는데, 이것은 종종 임의 SQL을 포함하는 서브쿼리를 지원해요. 이것들을 SQL과 같은 주의로 취급하세요.
권장사항:
- DuckDB API에 전달하기 전에 모든 사용자 제공 입력을 검증하고 소독하세요.
- 신뢰할 수 없는 소스에서 입력을 받을 때는 신뢰할 수 없는 SQL과 같은 샌드박싱 원칙을 적용하세요.
- 특정 사용 사례에서 해당 함수가 신뢰할 수 없는 입력과 함께 사용하기에 안전한지 이해했는지 확인하기 위해 모든 사용 함수의 문서를 제대로 읽으세요.
확장 (Extensions)
DuckDB는 새 파일 포맷, 함수, 원격 파일시스템 접근 같은 기능을 추가하는 유연한 [확장 메커니즘]({% link docs/current/extensions/overview.md %})을 갖고 있어요. 확장은 DuckDB 프로세스 자체와 같은 권한으로 실행되므로, 보안에 민감한 환경에서 신중한 고려가 필요해요.
자동 로드 (Autoloading)
DuckDB는 특정 SQL 문장이 필요로 할 때 [핵심 확장]({% link docs/current/core_extensions/overview.md %})을 자동으로 로드할 수 있어요. 어떤 확장이 로드되는지 완전히 통제하려면 자동 로드를 비활성화할 수 있어요:
SET autoload_known_extensions = false;
SET autoinstall_known_extensions = false;
Core vs. Community 확장 (Core vs. Community Extensions)
DuckDB 확장은 두 가지 범주로 나뉘어요:
- Core 확장: DuckDB 팀이 완전한 지원으로 유지보수해요.
parquet,json,httpfs같은 확장이 포함돼요. - [Community 확장]({% link community_extensions/index.md %}): 서드파티가 기여하고
INSTALL extension_name FROM community로 설치돼요. DuckDB 팀이 유지보수하지 않으므로 신뢰하는 소스에서만 community 확장을 설치하세요.
Community 확장을 완전히 비활성화하려면:
SET allow_community_extensions = false;
취약점 보고 (Reporting Vulnerabilities)
잠재적 취약점을 발견하면 GitHub를 통해 기밀로 보고해 주세요.
DuckDB의 기능을 제한하는 설정 (Settings to Limit DuckDB's Capabilities)
이 섹션에서 문서화된 설정은 DuckDB 배포에 추가 하드닝을 제공해요. 그러나 모든 구성에서 포괄적인 보안 메커니즘으로 의존해서는 안 돼요. 이 설정들은 잠재적 보안 문제의 영향을 제한하기 위한 심층 방어 조치로 설계되었지만, 특히 신뢰할 수 없는 SQL을 실행할 때 모든 공격 벡터에 대한 완전한 보호를 제공할 수는 없어요. 신뢰할 수 없는 입력을 다룰 때 강력한 보안을 위해서는 이 설정들을 "Untrusted SQL Input" 섹션에 설명된 대로 운영 체제나 컨테이너 수준의 적절한 샌드박싱과 결합하세요.
안전 모드 (Safe Mode, CLI)
DuckDB의 CLI 클라이언트는 DuckDB가 데이터베이스 파일 외의 외부 파일에 접근하는 것을 막는 ["safe mode"]({% link docs/current/clients/cli/safe_mode.md %})를 지원해요. 이것은 명령줄 인자나 [dot 명령]({% link docs/current/clients/cli/dot_commands.md %})으로 활성화할 수 있어요:
duckdb -safe ...
.safe_mode
파일 접근 제한 (Restricting File Access)
DuckDB는 CSV 파서의 [read_csv 함수]({% link docs/current/data/csv/overview.md %})로 디렉토리를 나열하고 임의의 파일을 읽거나, [read_text 함수]({% link docs/current/sql/functions/text.md %}#read_textsource)로 텍스트를 읽을 수 있어요.
이것은 로컬 파일시스템에서 읽는 것을 가능하게 해요, 예를 들어:
SELECT *
FROM read_csv('/etc/passwd', sep = ':');
파일 접근 비활성화 (Disabling File Access)
파일 접근은 두 가지 방법으로 비활성화할 수 있어요. 첫째, 개별 파일시스템을 비활성화할 수 있어요. 예를 들어:
SET disabled_filesystems = 'LocalFileSystem';
둘째, [enable_external_access 옵션]({% link docs/current/configuration/overview.md %}#configuration-reference)을 false로 설정해서 외부 접근을 완전히 비활성화할 수도 있어요.
SET enable_external_access = false;
이 설정은 다음을 의미해요:
ATTACH는 파일에 있는 데이터베이스에 연결할 수 없어요.COPY는 파일에서 읽거나 파일에 쓸 수 없어요.read_csv,read_parquet,read_json같은 함수는 외부 소스에서 읽을 수 없어요.
allowed_directories와 allowed_paths 옵션
allowed_directories와 allowed_paths 옵션을 각각 사용해 DuckDB의 특정 디렉토리나 파일 접근을 제한할 수 있어요.
이 옵션들은 파일시스템에 대한 세분화된 접근 제어를 허용해요.
예를 들어 DuckDB가 /tmp 디렉토리만 사용하도록 설정할 수 있어요.
SET allowed_directories = ['/tmp'];
SET enable_external_access = false;
FROM read_csv('test.csv');
설정이 적용되면 DuckDB는 현재 작업 디렉토리의 파일 읽기를 거부해요:
Permission Error:
Cannot access file "test.csv" - file system operations are disabled by configuration
구성 잠금 (Locking Configurations)
보안 관련 구성 설정은 일반적으로 안전을 위해 스스로를 잠가요. 예를 들어 [allow_community_extensions = false]({% link community_extensions/index.md %})로 community 확장을 비활성화할 수 있지만, 데이터베이스를 재시작하지 않고는 다시 활성화할 수 없어요. 시도하면 오류가 발생해요:
Invalid Input Error:
Cannot upgrade allow_community_extensions setting while database is running
이것은 명시적으로 비활성화된 설정을 다시 활성화하는 것을 막아요.
그럼에도 많은 구성 설정은 리소스 제약처럼 스스로를 비활성화하지 않아요. 사용자가 여러분의 하드웨어에서 제한 없이 SQL 문장을 실행하도록 허용한다면, 자신의 구성이 끝난 후 다음 명령으로 구성을 잠그는 것을 고려해 보세요:
SET lock_configuration = true;
이것은 그 시점부터 어떤 구성 설정도 수정되는 것을 막아요.
lock_configuration이 활성화돼도 특정 설정이 구성 가능한 상태로 유지되도록 하려면 allowed_configs 옵션을 사용하세요:
SET allowed_configs = ['memory_limit', 'threads'];
SET lock_configuration = true;
이 구성에서는 memory_limit과 threads를 여전히 바꿀 수 있지만, 다른 모든 설정은 잠겨요.
Secrets
[Secrets]({% link docs/current/configuration/secrets_manager.md %})는 AWS나 Azure 같은 서드파티 서비스에 로그인하기 위한 자격 증명을 관리하는 데 사용돼요. DuckDB는 duckdb_secrets() 테이블 함수로 secrets 목록을 보여줄 수 있어요. 이것은 기본적으로 보안 키 같은 민감한 정보를 수정(redact)해요. allow_unredacted_secrets 옵션을 설정하면 보안 키에 포함된 모든 정보를 보여줄 수 있어요. 신뢰할 수 없는 SQL 입력을 실행 중이라면 이 옵션을 켜지 않는 것이 권장돼요.
쿼리는 Secrets Manager에 정의된 secrets에 접근할 수 있어요. 예를 들어 주어진 AWS S3 버킷에 쓰기 권한이 있는 사용자로 인증하도록 정의된 secret이 있다면, 쿼리가 그 버킷에 쓸 수 있어요. 이것은 영속 및 임시 secrets 모두에 적용돼요.
[영속 secrets]({% link docs/current/configuration/secrets_manager.md %}#persistent-secrets)는 디스크에 암호화되지 않은 이진 형식으로 저장돼요. 이들은 SSH 키와 같은 권한인 600을 가지며, 즉 DuckDB(부모) 프로세스를 실행하는 사용자만 읽고 쓸 수 있어요.
SQL 인젝션 방지를 위한 준비된 문장 (Prepared Statements to Prevent SQL Injection)
다른 SQL 데이터베이스와 유사하게, SQL 인젝션을 막기 위해 DuckDB에서 [준비된 문장]({% link docs/current/sql/query_syntax/prepared_statements.md %})을 사용하는 것이 권장돼요.
중요 준비된 문장은 쿼리 구조를 여러분이 제어하면서 신뢰할 수 없는 데이터 값(예: 사용자가 제공한 검색어나 ID)을 받아들일 때 SQL 인젝션으로부터 보호해요. 사용자가 SQL 쿼리 자체를 공급할 수 있다면, 이것은 임의 코드를 실행하도록 허용하는 것과 같아요 – "Untrusted SQL Input"을 참고하세요.
따라서 쿼리를 위한 문자열 연결을 피하세요:
import duckdb
duckdb.execute("SELECT * FROM (VALUES (32, 'a'), (42, 'b')) t(x) WHERE x = " + str(42)).fetchall()
대신 준비된 문장을 사용하세요:
import duckdb
duckdb.execute("SELECT * FROM (VALUES (32, 'a'), (42, 'b')) t(x) WHERE x = ?", [42]).fetchall()
리소스 사용 제약 (Constrain Resource Usage)
DuckDB는 상당한 양의 CPU, RAM, 디스크 공간을 사용할 수 있어요. 이 리소스들은 DuckDB 인스턴스의 사용을 제어하도록 제한될 수 있어요.
DuckDB가 사용할 수 있는 CPU 스레드 수는 예를 들어 다음과 같이 설정할 수 있어요:
SET threads = 4;
여기서 4는 허용된 스레드 수예요.
최대 메모리(RAM) 양도 제한할 수 있어요, 예를 들어:
SET memory_limit = '4GB';
임시 파일 디렉토리의 크기는 다음과 같이 제한할 수 있어요:
SET max_temp_directory_size = '4GB';
권한 (Privileges)
DuckDB를 루트 사용자(예: sudo 사용)로 실행하는 것을 피하세요.
DuckDB를 루트로 실행할 좋은 이유가 없어요.