← 학습 카테고리

Learn

Airflow

151개 모듈 · 현재 113번째

Airflow 모듈 113/151 airflow-learn-113

Fernet 암호화와 민감정보 마스킹

Apache Airflow Official Documentation (in-repo snapshot) — Apache Software Foundation security/secrets/fernet.rst (전체), security/secrets/mask-sensitive-values.rst (전체), security/secrets/index.rst (개요)

이 모듈을 다 읽으면

  • Fernet 키가 무엇을 암호화하며, 안전하게 로테이션하는 절차를 순서대로 설명할 수 있다
  • Airflow의 민감정보 마스킹이 '값' 자체를 찾아 치환하는 방식이라는 점에서 오는 부작용을 설명할 수 있다
  • 환경 변수로 전달된 시크릿이 마스킹되지 않는 이유와 그 대안을 설명할 수 있다

Airflow는 Connection 비밀번호와 Variable 값을 저장 시 Fernet으로 암호화하고, Task 로그·Variables UI·Rendered fields에서 민감한 값을 자동으로 마스킹한다. 두 메커니즘 모두 값 자체(또는 키/필드 이름의 알려진 패턴)에 의존하기 때문에 각각 고유한 함정을 갖는다.

Fernet을 이용한 저장 시 암호화

Airflow는 Connection 설정과 Variable 설정에 저장되는 비밀번호를 암호화하기 위해 Fernet을 사용한다. Fernet은 대칭키(비밀 키) 인증 암호화 방식이며, Fernet으로 암호화된 비밀번호는 키 없이는 조작되거나 읽힐 수 없음을 보장한다.

Airflow가 처음 시작될 때 ``airflow.cfg`` 파일이 기본 설정과 함께 고유한 Fernet 키로 생성되며, 이 키는 ``[core]`` 섹션의 ``fernet_key`` 옵션에 저장된다. 환경 변수 ``AIRFLOW__CORE__FERNET_KEY``로도 Fernet 키를 설정할 수 있으며, 이는 ``airflow.cfg``의 값을 덮어쓴다. 새 Fernet 키가 필요하면 ``cryptography.fernet.Fernet.generate_key()``로 생성할 수 있다.

핵심 포인트

  • Fernet은 Connection 비밀번호와 Variable 값을 저장 시 암호화하는 대칭키 인증 암호화 방식이다
  • 최초 실행 시 airflow.cfg에 자동 생성된 fernet_key가 저장되며 AIRFLOW__CORE__FERNET_KEY 환경 변수로 덮어쓸 수 있다
  • Fernet.generate_key()로 새 키를 생성할 수 있다

Fernet 키 로테이션 절차

일단 Connection 자격증명과 Variable이 특정 Fernet 키로 암호화되고 나면, 키를 바꾸는 순간 기존에 암호화된 값의 복호화가 실패한다. 기존 암호화된 값을 무효화하지 않고 Fernet 키를 로테이션하려면 다음 순서를 따라야 한다: 먼저 ``fernet_key`` 설정을 ``새_키,기존_키`` 형태로 새 키를 앞에 붙여 설정하고, ``airflow rotate-fernet-key`` 명령을 실행해 기존 자격증명을 새 키로 재암호화한 다음, 마지막으로 ``fernet_key``에서 원래 키를 제거하고 새 키만 남긴다.

핵심 포인트

  • 키 교체 순서를 지키지 않으면 기존 암호화된 Connection/Variable의 복호화가 실패한다
  • 로테이션 절차: fernet_key를 '새키,기존키'로 설정 -> airflow rotate-fernet-key 실행 -> fernet_key를 새키만 남기고 기존키 제거

민감정보 자동 마스킹의 동작 방식

Airflow는 기본적으로 Connection 비밀번호, 민감한 Variable, 그리고 이름에 민감 키워드가 포함된 Connection extra(JSON) 필드의 키를 Task 로그, Variables UI, Rendered fields 화면에서 마스킹한다. 민감 키워드를 포함하지 않는 extra JSON의 키는 자동으로는 redact되지 않는다.

마스킹은 출력 어디에서든 그 특정 '값'이 나타나는지를 찾아 치환하는 방식으로 동작한다. 즉 비밀번호 값이 ``a`` 같은 짧은 문자열이라면, 로그에 등장하는 모든 ``a`` 문자가 ``***``로 치환되어버리는 부작용이 생길 수 있다. 마스킹은 ``core__hide_sensitive_var_conn_fields`` 설정을 false로 두면 비활성화할 수 있다.

자동 마스킹은 Connection 또는 Variable '접근'에 의해 트리거된다. 즉, 민감한 값을 XCom이나 다른 사이드채널을 통해 전달했다면, 그 값이 다운스트림 태스크에서 출력될 때는 마스킹되지 않는다.

마스킹이 활성화되어 있으면, Airflow는 태스크가 접근하는 모든 Connection의 password 필드를 항상 마스킹한다. 또한 Variable 이름이나 Connection extra JSON 필드 이름이 알려진 민감 키워드를 포함하면 그 Variable 값, 렌더링된 템플릿 딕셔너리, XCom 딕셔너리, extra 필드 값도 마스킹한다. 기본 민감 키워드는 ``access_token``, ``api_key``, ``apikey``, ``authorization``, ``passphrase``, ``passwd``, ``password``, ``private_key``, ``secret``, ``token``, ``keyfile_dict``, ``service_account``이며, 환경 변수 ``AIRFLOW__CORE__SENSITIVE_VAR_CONN_NAMES``로 이 목록을 확장할 수 있다.

핵심 포인트

  • 마스킹은 민감 키워드를 포함한 키/이름의 필드 값과, 모든 Connection의 password 필드에 적용된다
  • 마스킹은 '값'이 출력 어디에 등장하든 찾아 치환하는 방식이라, 값이 짧으면(예: 'a') 무관한 문자까지 마스킹되는 부작용이 있다
  • 마스킹은 Connection/Variable '접근'으로 트리거되므로, XCom 등 사이드채널로 전달된 값은 마스킹되지 않는다
  • 기본 민감 키워드 목록은 AIRFLOW__CORE__SENSITIVE_VAR_CONN_NAMES 환경 변수로 확장할 수 있다

커스텀 마스킹과 환경 변수의 함정

기본 메커니즘으로 마스킹되지 않는 추가 시크릿을 마스킹하고 싶다면, Dag 파일이나 Operator의 ``execute`` 함수 안에서 ``airflow.sdk.log``의 ``mask_secret`` 함수를 사용할 수 있다. 이 마스크는 어떤 로그/출력이 발생하기 전에 설정되어야만 효과가 있다.

:class:`~airflow.providers.cncf.kubernetes.operators.pod.KubernetesPodOperator` 같은 Operator를 쓸 때, 시크릿을 환경 변수로 전달하고 싶은 유혹이 있을 수 있다. 하지만 이는 매우 나쁜 관행이다 - 환경 변수는 프로세스 환경을 볼 수 있는 누구에게나 노출되며, 이런 식으로 전달된 시크릿은 Airflow에 의해 마스킹되지 않는다. KubernetesPodOperator에 시크릿을 전달해야 한다면, 네이티브 Kubernetes Secret을 사용하거나 Airflow Connection/Variable을 통해 동적으로 시크릿을 가져오는 방식을 사용해야 한다.

핵심 포인트

  • mask_secret()으로 커스텀 값을 마스킹할 수 있으며, 로그 출력 전에 호출해야 효과가 있다
  • KubernetesPodOperator 등에서 환경 변수로 전달한 시크릿은 Airflow의 마스킹 대상이 아니다
  • 환경 변수 대신 네이티브 Kubernetes Secret이나 Airflow Connection/Variable을 통한 동적 조회를 사용해야 한다