【エラー解消】 aws-mwaa-local-runner で cryptography.fernet.InvalidToken と言われたら

2022.09.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

aws-mwaa-local-runner は Amazon Managed Workflows for Apache Airflow (MWAA) の環境をローカルで模倣するツールです。

中身はDockerイメージのビルド、コンテナの実行を行うシェルスクリプトになっています。

このツールを使用していたとき、 cryptography.fernet.InvalidToken というエラーに遭遇したので、解消方法を紹介します。

環境

Docker version 20.10.17, build 100c701
Docker Desktop for Mac v4.12.0
aws-mwaa-local-runner v2.2.2

【問題】コネクション一覧を表示しようとすると cryptography.fernet.InvalidToken と言われる

READMEの通りに ./mwaa-local-env build-image でDockerイメージをビルドし、./mwaa-local-env start でコンテナを立ち上げて開発をしていました。

ウェブUIの [Admin] > [Connections] からコネクション一覧を表示しようとすると、以下のようにエラーが表示されました。

Something bad has happened.

Airflow is used by many users, and it is very likely that others had similar problems and you can easily find
a solution to your problem.

Consider following these steps:

  * gather the relevant information (detailed logs with errors, reproduction steps, details of your deployment)

  * find similar issues using:
     * GitHub Discussions
     * GitHub Issues
     * Stack Overflow
     * the usual search engine you use on a daily basis

  * if you run Airflow on a Managed Service, consider opening an issue using the service support channels

  * if you tried and have difficulty with diagnosing and fixing the problem yourself, consider creating a bug report.
    Make sure however, to include all relevant details and results of your investigation so far.

Python version: 3.7.10
Airflow version: 2.2.2
Node: redact
-------------------------------------------------------------------------------
Error! Please contact server admin.

コンテナのログを見てみると、 cryptography.fernet.InvalidToken と言われています。

aws-mwaa-local-runner-2_2-local-runner-1  | [2022-09-29 02:44:58,098] {{app.py:1892}} ERROR - Exception on /connection/list/ [GET]
aws-mwaa-local-runner-2_2-local-runner-1  | Traceback (most recent call last):
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2447, in wsgi_app
aws-mwaa-local-runner-2_2-local-runner-1  |     response = self.full_dispatch_request()
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
aws-mwaa-local-runner-2_2-local-runner-1  |     rv = self.handle_user_exception(e)
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1821, in handle_user_exception
aws-mwaa-local-runner-2_2-local-runner-1  |     reraise(exc_type, exc_value, tb)
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
aws-mwaa-local-runner-2_2-local-runner-1  |     raise value
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1950, in full_dispatch_request
aws-mwaa-local-runner-2_2-local-runner-1  |     rv = self.dispatch_request()
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1936, in dispatch_request
aws-mwaa-local-runner-2_2-local-runner-1  |     return self.view_functions[rule.endpoint](**req.view_args)
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask_appbuilder/security/decorators.py", line 109, in wraps
aws-mwaa-local-runner-2_2-local-runner-1  |     return f(self, *args, **kwargs)
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask_appbuilder/views.py", line 554, in list
aws-mwaa-local-runner-2_2-local-runner-1  |     widgets = self._list()
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask_appbuilder/baseviews.py", line 1134, in _list
aws-mwaa-local-runner-2_2-local-runner-1  |     page_size=page_size,
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask_appbuilder/baseviews.py", line 1033, in _get_list_widget
aws-mwaa-local-runner-2_2-local-runner-1  |     page_size=page_size,
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/flask_appbuilder/models/sqla/interface.py", line 435, in query
aws-mwaa-local-runner-2_2-local-runner-1  |     query_results = query.all()
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/sqlalchemy/orm/query.py", line 3373, in all
aws-mwaa-local-runner-2_2-local-runner-1  |     return list(self)
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/sqlalchemy/orm/loading.py", line 100, in instances
aws-mwaa-local-runner-2_2-local-runner-1  |     cursor.close()
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/sqlalchemy/util/langhelpers.py", line 70, in __exit__
aws-mwaa-local-runner-2_2-local-runner-1  |     with_traceback=exc_tb,
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/sqlalchemy/util/compat.py", line 182, in raise_
aws-mwaa-local-runner-2_2-local-runner-1  |     raise exception
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/sqlalchemy/orm/loading.py", line 80, in instances
aws-mwaa-local-runner-2_2-local-runner-1  |     rows = [proc(row) for row in fetch]
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/sqlalchemy/orm/loading.py", line 80, in <listcomp>
aws-mwaa-local-runner-2_2-local-runner-1  |     rows = [proc(row) for row in fetch]
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/sqlalchemy/orm/loading.py", line 601, in _instance
aws-mwaa-local-runner-2_2-local-runner-1  |     state.manager.dispatch.load(state, context)
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/sqlalchemy/event/attr.py", line 322, in __call__
aws-mwaa-local-runner-2_2-local-runner-1  |     fn(*args, **kw)
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/sqlalchemy/orm/mapper.py", line 3397, in _event_on_load
aws-mwaa-local-runner-2_2-local-runner-1  |     instrumenting_mapper._reconstructor(state.obj())
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/airflow/models/connection.py", line 153, in on_db_load
aws-mwaa-local-runner-2_2-local-runner-1  |     if self.password:
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/sqlalchemy/orm/attributes.py", line 365, in __get__
aws-mwaa-local-runner-2_2-local-runner-1  |     retval = self.descriptor.__get__(instance, owner)
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib/python3.7/site-packages/airflow/models/connection.py", line 238, in get_password
aws-mwaa-local-runner-2_2-local-runner-1  |     return fernet.decrypt(bytes(self._password, 'utf-8')).decode()
aws-mwaa-local-runner-2_2-local-runner-1  |   File "/usr/local/lib64/python3.7/site-packages/cryptography/fernet.py", line 194, in decrypt
aws-mwaa-local-runner-2_2-local-runner-1  |     raise InvalidToken
aws-mwaa-local-runner-2_2-local-runner-1  | cryptography.fernet.InvalidToken
aws-mwaa-local-runner-2_2-local-runner-1  | 172.20.0.1 - - [29/Sep/2022:02:44:58 +0000] "GET /connection/list/ HTTP/1.1" 500 1538 "http://localhost:8080/configuration" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"

コンテナ内で airflow connections list コマンドを実行しても、エラーが返ってきます。

$ docker exec aws-mwaa-local-runner-local-runner-1 airflow connections list
Traceback (most recent call last):
  File "/usr/local/bin/airflow", line 5, in <module>
    from airflow.__main__ import main
  File "/usr/local/lib/python3.7/site-packages/airflow/__init__.py", line 34, in <module>
    from airflow import settings
  File "/usr/local/lib/python3.7/site-packages/airflow/settings.py", line 37, in <module>
    from airflow.configuration import AIRFLOW_HOME, WEBSERVER_CONFIG, conf  # NOQA F401
  File "/usr/local/lib/python3.7/site-packages/airflow/configuration.py", line 1642, in <module>
    conf.validate()
  File "/usr/local/lib/python3.7/site-packages/airflow/configuration.py", line 321, in validate
    self._validate_config_dependencies()
  File "/usr/local/lib/python3.7/site-packages/airflow/configuration.py", line 429, in _validate_config_dependencies
    f"error: sqlite C library version too old (< {min_sqlite_version_str}). "
airflow.exceptions.AirflowConfigException: error: sqlite C library version too old (< 3.15.0). See https://airflow.apache.org/docs/apache-airflow/2.4.0/howto/set-up-database.html#setting-up-a-sqlite-database

このエラー文ではSQLiteのバージョンが古いと言っています。
しかしaws-mwaa-local-runner はデフォルトではSQLiteを使わず、PostgreSQLを利用しています。
DBの設定は変更していなかったため、他に問題箇所がありそうでした。

エラー原因

実はこのエラーが発生する前後で./mwaa-local-env build-image を実行して、AirflowのDockerイメージをビルドし直していました。これが今回のエラーの原因でした。

Airflowは接続情報や変数情報を暗号化してDBに保存します。

aws-mwaa-local-runner は、デフォルトではイメージのビルド時に暗号化キーを生成し、アプリケーションで使用します。このため、イメージをビルドし直すと暗号化キーが変わってしまい、DBに保存された値を正常に復号できずにエラーが発生しているようでした。

docker-compose-local.yml ファイルを見るとわかりますが、DBのデータは ./db-data ディレクトリにマウントされているため、DBコンテナを再作成しても保存されていた暗号化データはそのままです。

解消方法

データベースを初期化し、前の暗号化キーで作成された情報を削除すれば解消できます。

データベースの初期化方法は2通りです。

  • ./mwaa-local-env reset-db を実行する
    ./mwaa-local-env start で起動したコンテナを一度停止し、./mwaa-local-env reset-db でDBを初期化します。
    その後停止したコンテナを再開させます。

  • DBコンテナのマウントディレクトリを削除する
    コンテナを削除し、db-dataディレクトリを削除します。

    $ docker rm aws-mwaa-local-runner-local-runner-1 aws-mwaa-local-runner-postgres-1
    $ rm -rf db-data

    その後再度 ./mwaa-local-env start でコンテナを作成します。

なお、DBを初期化したくない場合は、コンテナ起動時に元の暗号化キーを指定するといった手法も存在します。

Airflowコンテナの起動時、環境変数 FERNET_KEY に暗号化キーを指定すれば、そのキーを使用してくれます。./mwaa-local-env start コマンドは docker-compose-local.yml ファイルからコンテナを起動しているので、docker-compose-local.ymllocal-runner の定義で環境変数にFERNET_KEY を追加します。

environment:
      - LOAD_EX=n
      - EXECUTOR=Local
      - FERNET_KEY=xxxxxxxxxxxxxxxxxxxxx

元の暗号化キーがわかる場合はこちらを利用しても良いでしょう。

余談

Airflow CLIを実行した際の sqlite C library version too old というエラーは別の問題でした。

AirflowのDBへの接続情報は airflow.cfg ファイル、もしくは環境変数 AIRFLOW__CORE__SQL_ALCHEMY_CONN で指定できます。1 何も指定しない場合はSQLiteを使用するようになっているため、Airflow CLIを実行した際はSQLiteに接続を行い、エラーが発生していました。なお、Airflowアプリケーションを立ち上げた際は [entrypoint.sh](http://entrypoint.sh) ファイルで環境変数 AIRFLOW__CORE__SQL_ALCHEMY_CONN を指定してアプリケーションを起動しているため、SQLiteではなくPostgreSQLを使うようになっています。

こちらのエラーの対処法としては、Airflow CLIの実行時もしくはコンテナ自体に環境変数 AIRFLOW__CORE__SQL_ALCHEMY_CONN を指定し、PostgreSQLに接続するようにします。方法は以下の3通りです。

  • コンテナの立ち上げ時に環境変数を指定する
    docker/docker-compose-local.ymllocal-runner コンテナの環境変数を指定します。

    local-runner:
            image: amazon/mwaa-local:2.0
            restart: always
            depends_on:
                - postgres
            environment:
                - LOAD_EX=n
                - EXECUTOR=Local
                - AIRFLOW__CORE__SQL_ALCHEMY_CONN=postgresql+psycopg2://airflow:airflow@postgres:5432/airflow

  • Airflow CLI実行時に環境変数を指定する

    $ docker exec aws-mwaa-local-runner-2_2-local-runner-1 /bin/bash -c "AIRFLOW__CORE__SQL_ALCHEMY_CONN=postgresql+psycopg2://airflow:airflow@postgres:5432/airflow airflow version"

  • entrypoint.sh からコマンドを実行する
    Airflowアプリケーションの立ち上げと同様、 entrypoint.sh を経由してコマンドを実行します。
    entrypoint.sh では環境変数 AIRFLOW__CORE__SQL_ALCHEMY_CONN が空の場合、PostgreSQLへの接続情報を AIRFLOW__CORE__SQL_ALCHEMY_CONN に代入するようになっています。

    $ docker exec aws-mwaa-local-runner-2_2-local-runner-1 /bin/bash -c "/entrypoint.sh airflow version"

参考リンク

Apache Airflow Fernet
https://github.com/aws/aws-mwaa-local-runner/issues/19