Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support k8s auth method in vault secrets provider #8640

Merged
merged 6 commits into from
May 3, 2020
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
3 changes: 2 additions & 1 deletion airflow/cli/commands/variable_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
20 changes: 18 additions & 2 deletions airflow/providers/hashicorp/secrets/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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":
Expand Down
21 changes: 21 additions & 0 deletions tests/providers/hashicorp/secrets/test_vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')