diff --git a/airflow/cli/commands/variable_command.py b/airflow/cli/commands/variable_command.py index 088dfec89819b0..1e2be6c853abc6 100644 --- a/airflow/cli/commands/variable_command.py +++ b/airflow/cli/commands/variable_command.py @@ -36,10 +36,11 @@ def variables_get(args): """Displays variable by a given name""" try: if args.default is None: - Variable.get( + var = Variable.get( args.key, deserialize_json=args.json ) + print(var) else: var = Variable.get( args.key, diff --git a/airflow/providers/hashicorp/secrets/vault.py b/airflow/providers/hashicorp/secrets/vault.py index fc1bbf06ebcee5..27058e9c46ac68 100644 --- a/airflow/providers/hashicorp/secrets/vault.py +++ b/airflow/providers/hashicorp/secrets/vault.py @@ -24,11 +24,12 @@ from cached_property import cached_property from hvac.exceptions import InvalidPath, VaultError -from airflow import AirflowException +from airflow.exceptions import AirflowException from airflow.secrets import BaseSecretsBackend from airflow.utils.log.logging_mixin import LoggingMixin +# pylint: disable=too-many-instance-attributes class VaultBackend(BaseSecretsBackend, LoggingMixin): """ Retrieves Connections and Variables from Hashicorp Vault @@ -58,7 +59,7 @@ class VaultBackend(BaseSecretsBackend, LoggingMixin): :param url: Base URL for the Vault instance being addressed. :type url: str :param auth_type: Authentication Type for Vault (one of 'token', 'ldap', 'userpass', 'approle', - 'github', 'gcp). Default is ``token``. + 'github', 'gcp', 'kubernetes'). Default is ``token``. :type auth_type: str :param mount_point: The "path" the secret engine was mounted on. (Default: ``secret``) :type mount_point: str @@ -73,6 +74,11 @@ class VaultBackend(BaseSecretsBackend, LoggingMixin): :type password: str :param role_id: Role ID for Authentication (for ``approle`` auth_type) :type role_id: str + :param kubernetes_role: Role for Authentication (for ``kubernetes`` auth_type) + :type kubernetes_role: str + :param kubernetes_jwt_path: Path for kubernetes jwt token (for ``kubernetes`` auth_type, deafult: + ``/var/run/secrets/kubernetes.io/serviceaccount/token``) + :type kubernetes_jwt_path: str :param secret_id: Secret ID for Authentication (for ``approle`` auth_type) :type secret_id: str :param gcp_key_path: Path to GCP Credential JSON file (for ``gcp`` auth_type) @@ -92,6 +98,8 @@ def __init__( # pylint: disable=too-many-arguments username: Optional[str] = None, password: Optional[str] = None, role_id: Optional[str] = None, + kubernetes_role: Optional[str] = None, + kubernetes_jwt_path: str = '/var/run/secrets/kubernetes.io/serviceaccount/token', secret_id: Optional[str] = None, gcp_key_path: Optional[str] = None, gcp_scopes: Optional[str] = None, @@ -107,6 +115,8 @@ def __init__( # pylint: disable=too-many-arguments self.username = username self.password = password self.role_id = role_id + self.kubernetes_role = kubernetes_role + self.kubernetes_jwt_path = kubernetes_jwt_path self.secret_id = secret_id self.mount_point = mount_point self.kv_engine_version = kv_engine_version @@ -131,6 +141,12 @@ def client(self) -> hvac.Client: _client.auth_userpass(username=self.username, password=self.password) elif self.auth_type == "approle": _client.auth_approle(role_id=self.role_id, secret_id=self.secret_id) + elif self.auth_type == "kubernetes": + if not self.kubernetes_role: + raise VaultError("kubernetes_role cannot be None for auth_type='kubernetes'") + with open(self.kubernetes_jwt_path) as f: + jwt = f.read() + _client.auth_kubernetes(role=self.kubernetes_role, jwt=jwt) elif self.auth_type == "github": _client.auth.github.login(token=self.token) elif self.auth_type == "gcp": diff --git a/tests/providers/hashicorp/secrets/test_vault.py b/tests/providers/hashicorp/secrets/test_vault.py index d3da69d90442b1..eb576a68cfab0b 100644 --- a/tests/providers/hashicorp/secrets/test_vault.py +++ b/tests/providers/hashicorp/secrets/test_vault.py @@ -232,3 +232,24 @@ def test_empty_token_raises_error(self, mock_hvac): with self.assertRaisesRegex(VaultError, "token cannot be None for auth_type='token'"): VaultBackend(**kwargs).get_connections(conn_id='test') + + def test_auth_type_kubernetes_without_role_raises_error(self): + kwargs = { + "auth_type": "kubernetes", + "url": "http://127.0.0.1:8200", + } + + with self.assertRaisesRegex(VaultError, "kubernetes_role cannot be None for auth_type='kubernetes'"): + VaultBackend(**kwargs).get_connections(conn_id='test') + + def test_auth_type_kubernetes_with_unreadable_jwt_raises_error(self): + path = "/var/tmp/this_does_not_exist/334e918ef11987d3ef2f9553458ea09f" + kwargs = { + "auth_type": "kubernetes", + "kubernetes_role": "default", + "kubernetes_jwt_path": path, + "url": "http://127.0.0.1:8200", + } + + with self.assertRaisesRegex(FileNotFoundError, path): + VaultBackend(**kwargs).get_connections(conn_id='test')