Apache Airflow 3.x에서 Snowflake를 연결하는 방법을 정리해보려고 합니다.
단순히 Airflow Connection에 username과 password를 넣는 방법만 설명하는 것이 아니라, 2026년 현재 Snowflake에서 권장하는 서비스 계정과 인증 방식을 기준으로 Airflow를 연결하는 방법을 다뤄보겠습니다.
일단 먼저,
비밀번호 +
ACCOUNTADMIN로 붙지 마세요. Snowflake는 단일 요소 비밀번호 로그인을 폐지하는 중이고,ACCOUNTADMIN는 커넥션 용이 아닙니다. 서비스 계정은 전용 최소 권한 역할 +TYPE = SERVICE유저 + 키페어 인증을 권장하고 있습니다.
현재 환경
| 항목 | 버전 |
|---|---|
| Airflow Version | Airflow 3.3.0 |
| Python Version | Python 3.14 |
| Snowflake provider | apache-airflow-providers-snowflake==6.11.0 |
예제는 Astro CLI를 사용한 환경입니다. 컨테이너 경로가 /usr/local/airflow/...이고 로컬 시크릿은 .env와 include/keys/에 있습니다. 다른 배포판이면 경로만 바꾸면 됩니다.
Snowflake에 서비스 계정 만들기
Airflow가 쓸 웨어하우스, 데이터베이스, 역할, 서비스 계정을 만듭니다. ACCOUNTADMIN은 이 프로비저닝에만 씁니다. SQL은 Snowsight의 Worksheets에서 실행합니다.
만들 것은 넷입니다.
- 웨어하우스
AIRFLOW_WH→ 쿼리를 돌리는 컴퓨팅 자원 - 데이터베이스
AIRFLOW_AI_DEMO_DB→ 데이터를 담을 곳 - 역할
AIRFLOW_ROLE→ 이 사용자가 할 수 있는 일 - 서비스 계정
AIRFLOW_SVC→ Airflow가 로그인하는 사용자
웨어하우스와 데이터베이스
USE ROLE ACCOUNTADMIN;
CREATE WAREHOUSE IF NOT EXISTS AIRFLOW_WH
WAREHOUSE_SIZE = 'XSMALL'
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE;
CREATE DATABASE IF NOT EXISTS AIRFLOW_AI_DEMO_DB;
전용 역할 부여
CREATE ROLE IF NOT EXISTS AIRFLOW_ROLE;
GRANT USAGE ON WAREHOUSE AIRFLOW_WH TO ROLE AIRFLOW_ROLE;
GRANT USAGE ON DATABASE AIRFLOW_AI_DEMO_DB TO ROLE AIRFLOW_ROLE;
GRANT CREATE SCHEMA ON DATABASE AIRFLOW_AI_DEMO_DB TO ROLE AIRFLOW_ROLE;
CREATE SCHEMA는 추후에 Airflow가 스키마를 만들기 때문에 넣었습니다. 스키마를 관리자가 미리 만들어 둔다면 이 권한은 빼고 그 스키마 권한만 주면 됩니다.
Cortex를 쓸 때만 역할에 하나 더 붙입니다.
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE AIRFLOW_ROLE;
Cortex 호출에는 이 DB 롤과 함께 계정의 USE AI FUNCTIONS 권한도 필요합니다. 기본값이 PUBLIC이라 보통 이미 있고, PUBLIC에서 회수한 계정이라면 GRANT USE AI FUNCTIONS ON ACCOUNT TO ROLE AIRFLOW_ROLE도 줍니다.
서비스 계정
CREATE USER IF NOT EXISTS AIRFLOW_SVC
TYPE = SERVICE
DEFAULT_ROLE = AIRFLOW_ROLE
DEFAULT_WAREHOUSE = AIRFLOW_WH
COMMENT = 'Airflow service account';
GRANT ROLE AIRFLOW_ROLE TO USER AIRFLOW_SVC;
TYPE = SERVICE 사용자는 비밀번호로 로그인이 안 되고 MFA 대상도 아닙니다. 사람이 안 붙는 통합에 맞는 유형입니다(Snowflake Admin User Management). 비밀번호를 허용하던 LEGACY_SERVICE는 폐지 예정이라, 새로 만들 땐 SERVICE를 씁니다.
ACCOUNTADMIN이 필요한 건 여기까지입니다. 실행하면 사용자·역할이 서고, 역할에 붙은 권한은 딱 필요한 것만 남습니다.
Key Pair 만들고 등록하기
비밀번호 대신 key-pair를 쓰는 이유는 분명하게 있습니다. Snowflake가 단일 비밀번호 로그인을 폐지하는 중이고, 롤아웃이 끝나면 비밀번호 사용자는 MFA를 강제 받습니다.
자동화된 서비스에는 애초에 맞지 않기도 합니다. 서비스 유저는 key-pair(또는 PAT, WIF)로만 붙습니다. 키 생성, 등록, 회전은 Snowflake 의 키페어 가이드를 그대로 따르는게 좋습니다.
# 암호화된 개인키(passphrase로 암호화) + 공개키
openssl genrsa 2048 | openssl pkcs8 -topk8 -v2 des3 -inform PEM -out airflow_rsa_key.p8
openssl rsa -in airflow_rsa_key.p8 -pubout -out airflow_rsa_key.pub
처음 실행하면 passphrase를 물어봅니다. 입력값은 화면에 표시되지 않습니다.
여기에 입력한 값이 Connection의 password로 사용됩니다.
Airflow Connection의
password는 Snowflake User의 로그인 비밀번호가 아닙니다.
Private Key를 복호화하기 위한 passphrase입니다.
생성되는 파일은 다음과 같습니다.
airflow_rsa_key.p8 # 개인키 --> Airflow가 인증에 사용. 안전하게 보관.
airflow_rsa_key.pub # 공개키 --> Snowflake에 등록.
개인키를 프로젝트의 include/keys/로 옮깁니다. 추후 Airflow Connection의 private_key_file이 가리키는 그 파일입니다.
mkdir -p include/keys
mv airflow_rsa_key.p8 include/keys/
개인키(.p8)는 Git이나 Docker 이미지에 넣지 않습니다. .gitignore로 빼고 볼륨이나 시크릿으로만 넘깁니다.
Snowflake에 Public key 등록하기
공개키를 AIRFLOW_SVC에 등록합니다. 이 때도 Snowsight Worksheets를 사용합니다.
ALTER USER AIRFLOW_SVC ADD KEY PAIR airflow_key PUBLIC_KEY = 'MIIBIj...';
PUBLIC_KEY에는 airflow_rsa_key.pub 내용을 넣습니다. 공식 문서는 -----BEGIN/END----- 구분자를 빼고 본문(MIIBIj...)만 넣으라고 하지만, .pub 파일을 통째로 붙여도 등록은 됩니다.
등록됐는지는 SHOW USER KEY PAIRS로 확인합니다.
SHOW USER KEY PAIRS FOR USER AIRFLOW_SVC;
fingerprint(SHA256:…)가 로컬 키의 지문과 같아야 합니다.
ADD KEY PAIR로 넣은 이름 키페어는 DESC USER의 RSA_PUBLIC_KEY_FP가 아니라 여기에 나옵니다.
로컬 키 지문은 이렇게 뽑습니다.
openssl rsa -pubin -in airflow_rsa_key.pub -outform DER 2>/dev/null \
| openssl dgst -sha256 -binary | openssl enc -base64
출력된 base64 값이 위 SHOW USER KEY PAIRS의 SHA256: 뒤 지문과 같으면 제대로 등록된 것입니다.
참고
워커가 AWS, Azure, GCP같은 곳에서 돌고 있다면, WIF(Workload Identity, Federation)로 장기 비밀번호 없이 클라우드 신원으로 붙는 방법을 사용할 수 있습니다. 이 방법을 사용하면 비밀번호, key-pair, token 같은 장기 비밀번호를 아예 없이 사용할 수 있습니다. 환경이 맞다면 key-pair대신 이것을 권장한다고 합니다.
Airflow에서 Snowflake Connection 설정하기
Connection ID는 Snowflake provider 기본값인 snowflake_default를 썼습니다. 넣을 값은 이렇습니다.
| 필드 | 위치 | 값 |
|---|---|---|
| Connection Id | 기본 필드 | snowflake_default |
| Connection Type | 기본 필드 | Snowflake |
| Login | 기본 필드 | AIRFLOW_SVC |
| Password | 기본 필드 | 개인키 passphrase |
| Schema | 기본 필드 | PUBLIC |
account |
Extra | 계정 식별자 |
warehouse |
Extra | AIRFLOW_WH |
database |
Extra | AIRFLOW_AI_DEMO_DB |
role |
Extra | AIRFLOW_ROLE |
private_key_file |
Extra | include/keys/airflow_rsa_key.p8 경로 |
여기서 실수를 자주 합니다. password는 Snowflake 비밀번호가 아니라 개인키 passphrase입니다. 인증은 개인키가 하고, passphrase는 로컬에서 키를 풀 뿐 Snowflake로 가지 않습니다.
account는 Snowsight 계정 URL에서 얻는 계정 식별자입니다 (ORGNAME-ACCOUNT 형식).
Snowflake 웹 UI에서 연결과 관련된 정보들을 쉽게 확인할 수 있습니다.
커넥션을 환경변수 하나로 정의할 수 있습니다. 이름은 AIRFLOW_CONN_<CONN_ID를 대문자로> 규칙이라 AIRFLOW_CONN_SNOWFLAKE_DEFAULT가 됩니다.
{
"conn_type": "snowflake",
"login": "AIRFLOW_SVC",
"password": "<개인키 passphrase>",
"schema": "PUBLIC",
"extra": {
"account": "ORGNAME-ACCOUNT",
"warehouse": "AIRFLOW_WH",
"database": "AIRFLOW_AI_DEMO_DB",
"role": "AIRFLOW_ROLE",
"private_key_file": "/usr/local/airflow/include/keys/airflow_rsa_key.p8"
}
}
private_key_file은 태스크가 도는 워커 컨테이너 안의 경로입니다. provider가 그 파일을 직접 열어 읽으니, 호스트 경로가 아니라 워커 안 경로여야 합니다. Astro는 프로젝트를 /usr/local/airflow에 마운트하므로 여기서는 include/keys/...가 그 경로입니다.
UI로 만들 땐 Login, Password, Schema와 Extra(account, warehouse, database, role, private_key_file)에 나눠 넣습니다. 키를 파일 대신 값으로 넣으려면 private_key_content(UI는 base64 권장)를 쓰고, private_key_file과는 둘 중 하나만 씁니다.
연결 확인
전부 돌리기 전에 커넥션이 살아 있고 우리가 설정한 역할로 붙는지 아래의 Dag를 활용해 확인합니다.
# dags/connection_check.py
import pendulum
from airflow.sdk import dag, task
@dag(schedule=None, start_date=pendulum.datetime(2026, 1, 1, tz="UTC"), catchup=False)
def connection_check():
@task
def whoami():
from airflow.providers.snowflake.hooks.snowflake import SnowflakeHook
row = SnowflakeHook(snowflake_conn_id="snowflake_default").get_first(
"SELECT CURRENT_ROLE(), CURRENT_USER(), CURRENT_WAREHOUSE(), CURRENT_VERSION()"
)
print(f"CONNECTED AS: role={row[0]} user={row[1]} warehouse={row[2]}")
whoami()
connection_check()
whoami 태스크 로그에 접속 역할이 찍힙니다.
CURRENT_ROLE()이 ACCOUNTADMIN으로 나오면 커넥션 설정이 잘못된 것입니다. 최소 권한 역할로 붙어야 합니다.
첫 SQL 실행하기
DDL은 파이썬으로 문자열을 조립하지 않고 .sql 파일을 SQLExecuteQueryOperator에 넘깁니다. Connection id만 넣어주면 됩니다.
# dags/first_sql.py
from pathlib import Path
import pendulum
from airflow.sdk import dag
from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator
@dag(
schedule=None,
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
catchup=False,
template_searchpath=[str(Path(__file__).parent / "sql")],
)
def first_sql():
SQLExecuteQueryOperator(
task_id="create_objects",
conn_id="snowflake_default",
sql="create_objects.sql", # sql/ 폴더 기준
split_statements=True,
return_last=False,
)
first_sql()
Operator는 인증 정보를 직접 들고 있지 않습니다. conn_id만 물리면 그 커넥션으로 AIRFLOW_SVC로 로그인해 AIRFLOW_ROLE로 붙습니다. 이 태스크가 AIRFLOW_ROLE로 스키마를 만들려면 전용 역할 부여에서 진행했던 GRANT CREATE SCHEMA가 필요합니다. 스키마를 관리자가 미리 만들어 두고 그 위 권한만 역할에 준다면, 권한을 더 좁힐 수 있습니다.
create_objects.sql은 이런 DDL입니다. 전체는 스키마 하나에 테이블과 뷰가 여럿이라 길어서 일부만 첨부합니다.
-- include/sql/create_objects.sql
CREATE SCHEMA IF NOT EXISTS FIN_PIT_DEMO;
CREATE TABLE IF NOT EXISTS FIN_PIT_DEMO.RAW_PRICES (
TICKER STRING NOT NULL,
TRADE_DATE DATE NOT NULL,
CLOSE_PRICE NUMBER(18, 2),
VOLUME NUMBER(20, 0),
KNOWABLE_DATE DATE NOT NULL,
INGESTED_AT TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);
-- RAW_FILINGS·PIT_*·FACT_*와 뷰가 이어집니다.
CREATE ... IF NOT EXISTS라 다시 돌려도 기존 테이블을 건드리지 않습니다. 이런 문장을 한 파일에 여러 개 두고 split_statements=True로 한 번에 실행합니다.
Dag를 돌린 뒤 Snowflake에서 스키마를 조회하면 만들어진 객체가 보입니다.
SELECT TABLE_NAME, TABLE_TYPE, ROW_COUNT
FROM AIRFLOW_AI_DEMO_DB.INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'FIN_PIT_DEMO'
ORDER BY TABLE_TYPE, TABLE_NAME;
여기까지 Airflow가 AIRFLOW_SVC로 로그인해 AIRFLOW_ROLE을 달고 Snowflake에 붙었다는 것을 확인할 수 있었습니다. 이제 테이블 생성이든 적재든 변환이든, 그다음 작업은 이 커넥션 위에 올리면 됩니다.
추가로
시크릿을 어디에 두는가?
로컬 개발: json 형식들을 모두 .env에 넣습니다. Astronomer도 로컬 개발용 환경 변수는 .env에 두고 .gitignore에 넣기를 권합니다. 개인키는include/keys/(.gitignore)에 두고 마운트합니다.
운영: 시크릿을 .env에 두지 않습니다. Airflow는 커넥션을 시크릿 백엔드에서 먼저 조회합니다. (백엔드 → 환경변수 → 메타DB) . 커넥션 JSON과 개인키를 AWS Secrets Manager, GCP Secret Manager, Vault 등에 두고, 워커엔 Read 권한만 줍니다.
Reference
- Snowflake: Key Pair Authentication
- Snowflake: User Management
- Snowflake: Authentication Policies
- Snowflake: MFA Rollout
- Snowflake: Account Identifiers
- Snowflake: Workload Identity Federation
- Snowflake: Cortex 권한과 모델 접근
- Apache Airflow: Snowflake Connection (provider)
- Apache Airflow: Managing Connections
- Apache Airflow: Secrets Backend
- Apache Airflow: SQL Operators
- Astronomer: Develop your project (local .env)










