Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ assists users migrating to a new version.

## Airflow Master

### Fernet is enabled by default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make it more descriptive, please?

  • Previous Behaviour
  • Changed Behavior
  • Reason of the change

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.


The fernet mechanism is enabled by default to increase the security of the default installation. In order to
restore the previous behavior, the user must consciously set an empty key in the ``fernet_key`` option of
section ``[core]`` in the ``airflow.cfg`` file.

At the same time, this means that the `apache-airflow[crypto]` extra-packages are always installed.
However, this requires that your operating system has ``libffi-dev`` installed.

### Changes to Google PubSub Operators, Hook and Sensor
In the `PubSubPublishOperator` and `PubSubHook.publsh` method the data field in a message should be bytestring (utf-8 encoded) rather than base64 encoded string.

Expand Down
12 changes: 2 additions & 10 deletions airflow/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
# Ignored Mypy on configparser because it thinks the configparser module has no _UNSET attribute
from configparser import _UNSET, ConfigParser, NoOptionError, NoSectionError # type: ignore

from cryptography.fernet import Fernet
from zope.deprecation import deprecated

from airflow.exceptions import AirflowConfigException
Expand All @@ -43,15 +44,6 @@
action='default', category=PendingDeprecationWarning, module='airflow')


def generate_fernet_key():
try:
from cryptography.fernet import Fernet
except ImportError:
return ''
else:
return Fernet.generate_key().decode()


def expand_env_var(env_var):
"""
Expands (potentially nested) env vars by repeatedly applying
Expand Down Expand Up @@ -517,7 +509,7 @@ def get_airflow_test_config(airflow_home):

# only generate a Fernet key if we need to create a new config file
if not os.path.isfile(TEST_CONFIG_FILE) or not os.path.isfile(AIRFLOW_CONFIG):
FERNET_KEY = generate_fernet_key()
FERNET_KEY = Fernet.generate_key().decode()
else:
FERNET_KEY = ''

Expand Down
19 changes: 2 additions & 17 deletions airflow/models/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

from typing import Optional

from cryptography.fernet import Fernet, MultiFernet

from airflow.configuration import conf
from airflow.exceptions import AirflowException
from airflow.typing import Protocol
Expand All @@ -33,12 +35,6 @@ def encrypt(self, b):
...


class InvalidFernetToken(Exception):
# If Fernet isn't loaded we need a valid exception class to catch. If it is
# loaded this will get reset to the actual class once get_fernet() is called
pass


class NullFernet:
"""
A "Null" encryptor class that doesn't encrypt or decrypt but that presents
Expand Down Expand Up @@ -75,17 +71,6 @@ def get_fernet():

if _fernet:
return _fernet
try:
from cryptography.fernet import Fernet, MultiFernet, InvalidToken
global InvalidFernetToken
InvalidFernetToken = InvalidToken

except ImportError:
log.warning(
"cryptography not found - values will not be stored encrypted."
)
_fernet = NullFernet()
return _fernet

try:
fernet_key = conf.get('core', 'FERNET_KEY')
Expand Down
9 changes: 4 additions & 5 deletions airflow/models/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@
import json
from typing import Any

from cryptography.fernet import InvalidToken as InvalidFernetToken
from sqlalchemy import Boolean, Column, Integer, String, Text
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import synonym

from airflow.models.base import ID_LEN, Base
from airflow.models.crypto import InvalidFernetToken, get_fernet
from airflow.models.crypto import get_fernet
from airflow.utils.db import provide_session
from airflow.utils.log.logging_mixin import LoggingMixin

Expand All @@ -50,12 +51,10 @@ def get_val(self):
fernet = get_fernet()
return fernet.decrypt(bytes(self._val, 'utf-8')).decode()
except InvalidFernetToken:
log.error("Can't decrypt _val for key={}, invalid token "
"or value".format(self.key))
log.error("Can't decrypt _val for key=%s, invalid token or value", self.key)
return None
except Exception:
log.error("Can't decrypt _val for key={}, FERNET_KEY "
"configuration missing".format(self.key))
log.error("Can't decrypt _val for key=%s, FERNET_KEY configuration missing", self.key)
return None
else:
return self._val
Expand Down
5 changes: 0 additions & 5 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,6 @@ How do I trigger tasks based on another task's failure?

Check out the :ref:`concepts/trigger_rule`.

Why are connection passwords still not encrypted in the metadata db after I installed airflow[crypto]?
------------------------------------------------------------------------------------------------------

Check out the :doc:`howto/secure-connections`.

What's the deal with ``start_date``?
------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/howto/connection/gcp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Keyfile Path
Keyfile JSON
Contents of a `service account
<https://cloud.google.com/docs/authentication/#service_accounts>`_ key
file (JSON format) on disk. It is recommended to :doc:`Secure your connections <../secure-connections>` if using this method to authenticate.
file (JSON format) on disk.

Not required if using application default credentials.

Expand Down
1 change: 0 additions & 1 deletion docs/howto/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ configuring an Airflow environment.
initialize-database
operator/index
connection/index
secure-connections
write-logs
run-behind-proxy
run-with-systemd
Expand Down
63 changes: 0 additions & 63 deletions docs/howto/secure-connections.rst

This file was deleted.

2 changes: 2 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ You can also install Airflow with support for extra features like ``gcp`` or ``p

pip install 'apache-airflow[postgres,gcp]'

Airflow require that your operating system has ``libffi-dev`` installed.

Extra Packages
''''''''''''''

Expand Down
47 changes: 47 additions & 0 deletions docs/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,50 @@ DAG Level Role
``Admin`` can create a set of roles which are only allowed to view a certain set of dags. This is called DAG level access. Each dag defined in the dag model table
is treated as a ``View`` which has two permissions associated with it (``can_dag_read`` and ``can_dag_edit``). There is a special view called ``all_dags`` which
allows the role to access all the dags. The default ``Admin``, ``Viewer``, ``User``, ``Op`` roles can all access ``all_dags`` view.


.. _security/fernet:

Securing Connections
--------------------

Airflow uses `Fernet <https://github.com/fernet/spec/>`__ to encrypt passwords in the connection
configuration. It guarantees that a password encrypted using it cannot be manipulated or read without the key.
Fernet is an implementation of symmetric (also known as “secret key”) authenticated cryptography.

The first time Airflow is started, the ``airflow.cfg`` file is generated with the default configuration and the unique Fernet
key. The key is saved to option ``fernet_key`` of section ``[core]``.

You can also configure a fernet key using environment variables. This will overwrite the value from the
`airflow.cfg` file

.. code-block:: bash

# Note the double underscores
export AIRFLOW__CORE__FERNET_KEY=your_fernet_key

Generating fernet key
'''''''''''''''''''''

If you need to generate a new fernet key you can use the following code snippet.

.. code-block:: python

from cryptography.fernet import Fernet
fernet_key= Fernet.generate_key()
print(fernet_key.decode()) # your fernet_key, keep it in secured place!


Rotating encryption keys
''''''''''''''''''''''''

Once connection credentials and variables have been encrypted using a fernet
key, changing the key will cause decryption of existing credentials to fail. To
rotate the fernet key without invalidating existing encrypted values, prepend
the new key to the ``fernet_key`` setting, run
``airflow rotate_fernet_key``, and then drop the original key from
``fernet_keys``:

#. Set ``fernet_key`` to ``new_fernet_key,old_fernet_key``
#. Run ``airflow rotate_fernet_key`` to re-encrypt existing credentials with the new fernet key
#. Set ``fernet_key`` to ``new_fernet_key``
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ def write_version(filename: str = os.path.join(*["airflow", "git_version"])):
'cgroupspy>=0.1.4',
]
cloudant = ['cloudant>=2.0']
crypto = ['cryptography>=0.9.3']
dask = [
'distributed>=1.17.1, <2'
]
Expand Down Expand Up @@ -316,7 +315,7 @@ def write_version(filename: str = os.path.join(*["airflow", "git_version"])):

devel_minreq = devel + kubernetes + mysql + doc + password + cgroups
devel_hadoop = devel_minreq + hive + hdfs + webhdfs + kerberos
devel_all = (sendgrid + devel + all_dbs + doc + samba + slack + crypto + oracle +
devel_all = (sendgrid + devel + all_dbs + doc + samba + slack + oracle +
docker + ssh + kubernetes + celery + redis + gcp + grpc +
datadog + zendesk + jdbc + ldap + kerberos + password + webhdfs + jenkins +
druid + pinot + segment + snowflake + elasticsearch + sentry +
Expand Down Expand Up @@ -356,6 +355,7 @@ def do_setup():
'cached_property~=1.5',
'colorlog==4.0.2',
'croniter>=0.3.17, <0.4',
'cryptography>=0.9.3',
'dill>=0.2.2, <0.3',
'flask>=1.1.0, <2.0',
'flask-appbuilder>=1.12.5, <2.0.0',
Expand Down Expand Up @@ -413,7 +413,6 @@ def do_setup():
'celery': celery,
'cgroups': cgroups,
'cloudant': cloudant,
'crypto': crypto,
'dask': dask,
'databricks': databricks,
'datadog': datadog,
Expand Down