Airflow 조그맣게 시작하기 - Connections 메뉴 오류 해결

햄도·2021년 9월 14일
1

Airflow slowstart

목록 보기
6/6

airflow를 잘 쓰고 있었는데 갑자기 UI에서 Connections 메뉴에 들어가면 아래와 같은 오류가 발생하기 시작했다.

Can't decrypt encrypted password for login=ADMIN, FERNET_KEY configuration is missing

처음에는 config에 FERNET_KEY 가 없어서 생긴 문제인 줄 알고 AIRFLOW__CORE__FERNET_KEY를 환경변수로 추가했는데도 비슷한 오류가 발생했다.

cryptography.fernet.InvalidToken

이리저리 검색했는데 해결 방법은 airflow db reset뿐이었고, QA 환경에서는 AIRFLOW__CORE__FERNET_KEY를 추가하고 db reset을 해 해결했지만,
운영 환경에서는 db reset을 하면 모든 수행기록이 날아가 곤란한 상황이었다.
그래서 도대체 문제가 뭔지 코드를 열어봤다.

오류가 발생하는 곳은 이 곳이었다.

# airflow/models/connection.py
...

    def get_password(self) -> Optional[str]:
        """Return encrypted password."""
        if self._password and self.is_encrypted:
            fernet = get_fernet()
            if not fernet.is_encrypted:
                raise AirflowException(
                    "Can't decrypt encrypted password for login={}, \
                    FERNET_KEY configuration is missing".format(
                        self.login
                    )
                )
            return fernet.decrypt(bytes(self._password, 'utf-8')).decode()
        else:
            return self._password

self._passwordself.is_encrypted가 True이면 fernet을 가져오는데, 이 때 fernet.is_encrypted가 False이면 raise한다.
그럼 어떤 상황에서 fernet.is_encrypted가 False인지 확인해보자.

get_fernet()여기 있다.

# airflow/models/crypto.py
...

def get_fernet():
    """
    Deferred load of Fernet key.
    This function could fail either because Cryptography is not installed
    or because the Fernet key is invalid.
    :return: Fernet object
    :raises: airflow.exceptions.AirflowException if there's a problem trying to load Fernet
    """
    global _fernet  # pylint: disable=global-statement

    if _fernet:
        return _fernet

    try:
        fernet_key = conf.get('core', 'FERNET_KEY')
        if not fernet_key:
            log.warning("empty cryptography key - values will not be stored encrypted.")
            _fernet = NullFernet()
        else:
            _fernet = MultiFernet(
                [Fernet(fernet_part.encode('utf-8')) for fernet_part in fernet_key.split(',')]
            )
            _fernet.is_encrypted = True
    except (ValueError, TypeError) as value_error:
        raise AirflowException(f"Could not create Fernet object: {value_error}")

    return _fernet

config로 따로 설정한 FERNET_KEY가 없는 경우 fernet.is_encrypted가 False가 되는 것 같다. (FERNET_KEY가 있는 경우 명시적으로 True가 세팅되므로)

하지만 우리는 FERNET_KEY를 설정하지 않고도 그동안 잘 사용하고 있었다. 그럼 self._passwordself.is_encrypted가 True가 되는 부분이 문제 아닐까?

이 생각이 들고 나서야 get_password 메소드가 있는 Connection 클래스가 뭔지 자세히 봤다.

# airflow/models/connection.py
...

class Connection(Base, LoggingMixin):  # pylint: disable=too-many-instance-attributes
    """
    Placeholder to store information about different database instances
    connection information. The idea here is that scripts use references to
    database instances (conn_id) instead of hard coding hostname, logins and
    passwords when using operators or hooks.
...

Connection은 metadata db의 connection 테이블에서 커넥션 정보를 들고오는 클래스이며, self._passwordself.is_encrypted에는 테이블의 각 row에 들어있는 정보가 그대로 담긴다.

그리고 운영에서 사용중인 metadata db의 connection 테이블에는 airflow가 예시로 넣어놓은 (쓸모없는) 커넥션 중 password도 존재하면서 is_encrypted가 True인 커넥션이 몇 개 있었다.

이것들을 모두 is_encrypted=False로 바꿔주니 connections 메뉴를 정상적으로 조회할 수 있었다.

기타 생각

  • 해결은 했는데.. Blame을 봐도 관련 로직은 다 3년 전에 수정된거라서 왜 갑자기 이런 문제가 발생했는지 모르겠다.
  • 그래도 생각보다 빨리 해결되어 다행이다.
  • 내부망에서만 airflow가 동작하기 때문에 fernet_key에 대해서는 생각도 해보지 않았는데, 보안상 사용해야 하는건지도 확인해봐야겠다. 클라우드 등 외부 서비스를 이용해야 한다면 필요할 것 같다.

참고

profile
developer hamdoe

3개의 댓글

comment-user-thumbnail
2021년 11월 28일

와...멋지네요!!

답글 달기
comment-user-thumbnail
2022년 9월 7일

안되서 당황했는데, 감사합니다 :)

답글 달기
comment-user-thumbnail
2023년 5월 11일

제 생명을 살리셨습니다. 감사합니다 :)

답글 달기