Skip to content

Commit

Permalink
[Scheduler] Store creating user creds and use on reload schedules (#1172
Browse files Browse the repository at this point in the history
)
  • Loading branch information
Hedingber committed Aug 5, 2021
1 parent bd6ab8e commit 3e46b22
Show file tree
Hide file tree
Showing 15 changed files with 646 additions and 175 deletions.
92 changes: 8 additions & 84 deletions mlrun/api/api/endpoints/secrets.py
Expand Up @@ -4,22 +4,17 @@
import fastapi

import mlrun.api.api.deps
import mlrun.api.crud
import mlrun.api.utils.clients.opa
import mlrun.errors
from mlrun.api import schemas
from mlrun.api.utils.singletons.k8s import get_k8s
from mlrun.utils.vault import (
VaultStore,
add_vault_project_secrets,
add_vault_user_secrets,
init_project_vault_configuration,
)
from mlrun.utils.vault import add_vault_user_secrets

router = fastapi.APIRouter()


@router.post("/projects/{project}/secrets", status_code=HTTPStatus.CREATED.value)
def initialize_project_secrets(
def store_project_secrets(
project: str,
secrets: schemas.SecretsData,
auth_verifier: mlrun.api.api.deps.AuthVerifier = fastapi.Depends(
Expand All @@ -33,33 +28,15 @@ def initialize_project_secrets(
mlrun.api.schemas.AuthorizationAction.create,
auth_verifier.auth_info,
)
if secrets.provider == schemas.SecretProviderName.vault:
# Init is idempotent and will do nothing if infra is already in place
init_project_vault_configuration(project)

# If no secrets were passed, no need to touch the actual secrets.
if secrets.secrets:
add_vault_project_secrets(project, secrets.secrets)
elif secrets.provider == schemas.SecretProviderName.kubernetes:
# K8s secrets is the only other option right now
if get_k8s():
get_k8s().store_project_secrets(project, secrets.secrets)
else:
raise mlrun.errors.MLRunInternalServerError(
"K8s provider cannot be initialized"
)
else:
raise mlrun.errors.MLRunInvalidArgumentError(
f"Provider requested is not supported. provider = {secrets.provider}"
)
mlrun.api.crud.Secrets().store_secrets(project, secrets)

return fastapi.Response(status_code=HTTPStatus.CREATED.value)


@router.delete("/projects/{project}/secrets", status_code=HTTPStatus.NO_CONTENT.value)
def delete_project_secrets(
project: str,
provider: str,
provider: schemas.SecretProviderName,
secrets: List[str] = fastapi.Query(None, alias="secret"),
auth_verifier: mlrun.api.api.deps.AuthVerifier = fastapi.Depends(
mlrun.api.api.deps.AuthVerifier
Expand All @@ -72,21 +49,7 @@ def delete_project_secrets(
mlrun.api.schemas.AuthorizationAction.delete,
auth_verifier.auth_info,
)
if provider == schemas.SecretProviderName.vault:
raise mlrun.errors.MLRunInvalidArgumentError(
f"Delete secret is not implemented for provider {provider}"
)
elif provider == schemas.SecretProviderName.kubernetes:
if get_k8s():
get_k8s().delete_project_secrets(project, secrets)
else:
raise mlrun.errors.MLRunInternalServerError(
"K8s provider cannot be initialized"
)
else:
raise mlrun.errors.MLRunInvalidArgumentError(
f"Provider requested is not supported. provider = {provider}"
)
mlrun.api.crud.Secrets().delete_secrets(project, provider, secrets)

return fastapi.Response(status_code=HTTPStatus.NO_CONTENT.value)

Expand All @@ -107,34 +70,7 @@ def list_secret_keys(
mlrun.api.schemas.AuthorizationAction.read,
auth_verifier.auth_info,
)
if provider == schemas.SecretProviderName.vault:
if not token:
raise mlrun.errors.MLRunInvalidArgumentError(
"Vault list project secret keys request without providing token"
)

vault = VaultStore(token)
secret_values = vault.get_secrets(None, project=project)
return schemas.SecretKeysData(
provider=provider, secret_keys=list(secret_values.keys())
)
elif provider == schemas.SecretProviderName.kubernetes:
if token:
raise mlrun.errors.MLRunInvalidArgumentError(
"Cannot specify token when requesting k8s secret keys"
)

if get_k8s():
secret_keys = get_k8s().get_project_secret_keys(project) or []
return schemas.SecretKeysData(provider=provider, secret_keys=secret_keys)
else:
raise mlrun.errors.MLRunInternalServerError(
"K8s provider cannot be initialized"
)
else:
raise mlrun.errors.MLRunInvalidArgumentError(
f"Provider requested is not supported. provider = {provider}"
)
return mlrun.api.crud.Secrets().list_secret_keys(project, provider, token)


@router.get("/projects/{project}/secrets", response_model=schemas.SecretsData)
Expand All @@ -154,19 +90,7 @@ def list_secrets(
mlrun.api.schemas.AuthorizationAction.read,
auth_verifier.auth_info,
)
if provider == schemas.SecretProviderName.vault:
if not token:
raise mlrun.errors.MLRunInvalidArgumentError(
"Vault list project secrets request without providing token"
)

vault = VaultStore(token)
secret_values = vault.get_secrets(secrets, project=project)
return schemas.SecretsData(provider=provider, secrets=secret_values)
else:
raise mlrun.errors.MLRunInvalidArgumentError(
f"Provider requested is not supported. provider = {provider}"
)
return mlrun.api.crud.Secrets().list_secrets(project, provider, secrets, token)


@router.post("/user-secrets", status_code=HTTPStatus.CREATED.value)
Expand Down
1 change: 1 addition & 0 deletions mlrun/api/crud/__init__.py
Expand Up @@ -6,3 +6,4 @@
from .projects import Projects # noqa: F401
from .runs import Runs # noqa: F401
from .runtimes import Runtimes # noqa: F401
from .secrets import Secrets # noqa: F401
2 changes: 1 addition & 1 deletion mlrun/api/crud/marketplace.py
Expand Up @@ -75,7 +75,7 @@ def _get_source_credentials(self, source_name):
return {}

secret_prefix = self._generate_credentials_secret_key(source_name)
secrets = self._get_k8s().get_project_secret_values(self._internal_project_name)
secrets = self._get_k8s().get_project_secret_data(self._internal_project_name)
source_secrets = {}
for key, value in secrets.items():
if key.startswith(secret_prefix):
Expand Down
186 changes: 186 additions & 0 deletions mlrun/api/crud/secrets.py
@@ -0,0 +1,186 @@
import typing

import mlrun.api.schemas
import mlrun.api.utils.singletons.k8s
import mlrun.errors
import mlrun.utils.helpers
import mlrun.utils.regex
import mlrun.utils.singleton
import mlrun.utils.vault


class Secrets(metaclass=mlrun.utils.singleton.Singleton,):
internal_secrets_key_prefix = "mlrun."

def generate_schedule_secret_key(self, schedule_name: str):
return f"{self.internal_secrets_key_prefix}schedules.{schedule_name}"

def store_secrets(
self,
project: str,
secrets: mlrun.api.schemas.SecretsData,
allow_internal_secrets: bool = False,
):
if secrets.secrets:
for secret_key in secrets.secrets.keys():
mlrun.utils.helpers.verify_field_regex(
"secret.key", secret_key, mlrun.utils.regex.secret_key
)
if (
self._is_internal_secret_key(secret_key)
and not allow_internal_secrets
):
raise mlrun.errors.MLRunAccessDeniedError(
f"Not allowed to create/update internal secrets (key starts with "
f"{self.internal_secrets_key_prefix})"
)
if secrets.provider == mlrun.api.schemas.SecretProviderName.vault:
# Init is idempotent and will do nothing if infra is already in place
mlrun.utils.vault.init_project_vault_configuration(project)

# If no secrets were passed, no need to touch the actual secrets.
if secrets.secrets:
mlrun.utils.vault.store_vault_project_secrets(project, secrets.secrets)
elif secrets.provider == mlrun.api.schemas.SecretProviderName.kubernetes:
if mlrun.api.utils.singletons.k8s.get_k8s():
mlrun.api.utils.singletons.k8s.get_k8s().store_project_secrets(
project, secrets.secrets
)
else:
raise mlrun.errors.MLRunInternalServerError(
"K8s provider cannot be initialized"
)
else:
raise mlrun.errors.MLRunInvalidArgumentError(
f"Provider requested is not supported. provider = {secrets.provider}"
)

def delete_secrets(
self,
project: str,
provider: mlrun.api.schemas.SecretProviderName,
secrets: typing.Optional[typing.List[str]] = None,
allow_internal_secrets: bool = False,
):
if not allow_internal_secrets:
if secrets:
for secret_key in secrets:
if self._is_internal_secret_key(secret_key):
raise mlrun.errors.MLRunAccessDeniedError(
f"Not allowed to delete internal secrets (key starts with "
f"{self.internal_secrets_key_prefix})"
)
else:
# When secrets are not provided the default behavior will be to delete them all, but if internal secrets
# are not allowed, we don't want to delete them, so we list the non internal keys
secrets = self.list_secret_keys(
project, provider, allow_internal_secrets=False
).secret_keys
if not secrets:
# nothing to remove - return
return

if provider == mlrun.api.schemas.SecretProviderName.vault:
raise mlrun.errors.MLRunInvalidArgumentError(
f"Delete secret is not implemented for provider {provider}"
)
elif provider == mlrun.api.schemas.SecretProviderName.kubernetes:
if mlrun.api.utils.singletons.k8s.get_k8s():
mlrun.api.utils.singletons.k8s.get_k8s().delete_project_secrets(
project, secrets
)
else:
raise mlrun.errors.MLRunInternalServerError(
"K8s provider cannot be initialized"
)
else:
raise mlrun.errors.MLRunInvalidArgumentError(
f"Provider requested is not supported. provider = {provider}"
)

def list_secret_keys(
self,
project: str,
provider: mlrun.api.schemas.SecretProviderName,
token: typing.Optional[str] = None,
allow_internal_secrets: bool = False,
) -> mlrun.api.schemas.SecretKeysData:
if provider == mlrun.api.schemas.SecretProviderName.vault:
if not token:
raise mlrun.errors.MLRunInvalidArgumentError(
"Vault list project secret keys request without providing token"
)

vault = mlrun.utils.vault.VaultStore(token)
secret_values = vault.get_secrets(None, project=project)
secret_keys = list(secret_values.keys())
elif provider == mlrun.api.schemas.SecretProviderName.kubernetes:
if token:
raise mlrun.errors.MLRunInvalidArgumentError(
"Cannot specify token when requesting k8s secret keys"
)

if mlrun.api.utils.singletons.k8s.get_k8s():
secret_keys = (
mlrun.api.utils.singletons.k8s.get_k8s().get_project_secret_keys(
project
)
or []
)
else:
raise mlrun.errors.MLRunInternalServerError(
"K8s provider cannot be initialized"
)
else:
raise mlrun.errors.MLRunInvalidArgumentError(
f"Provider requested is not supported. provider = {provider}"
)
if not allow_internal_secrets:
secret_keys = list(
filter(lambda key: not self._is_internal_secret_key(key), secret_keys)
)

return mlrun.api.schemas.SecretKeysData(
provider=provider, secret_keys=secret_keys
)

def list_secrets(
self,
project: str,
provider: mlrun.api.schemas.SecretProviderName,
secrets: typing.Optional[typing.List[str]] = None,
token: typing.Optional[str] = None,
allow_secrets_from_k8s: bool = False,
allow_internal_secrets: bool = False,
) -> mlrun.api.schemas.SecretsData:
if provider == mlrun.api.schemas.SecretProviderName.vault:
if not token:
raise mlrun.errors.MLRunInvalidArgumentError(
"Vault list project secrets request without providing token"
)

vault = mlrun.utils.vault.VaultStore(token)
secrets_data = vault.get_secrets(secrets, project=project)
elif provider == mlrun.api.schemas.SecretProviderName.kubernetes:
if not allow_secrets_from_k8s:
raise mlrun.errors.MLRunAccessDeniedError(
"Not allowed to list secrets data from kubernetes provider"
)
secrets_data = mlrun.api.utils.singletons.k8s.get_k8s().get_project_secret_data(
project, secrets
)

else:
raise mlrun.errors.MLRunInvalidArgumentError(
f"Provider requested is not supported. provider = {provider}"
)
if not allow_internal_secrets:
secrets_data = {
key: value
for key, value in secrets_data.items()
if not self._is_internal_secret_key(key)
}
return mlrun.api.schemas.SecretsData(provider=provider, secrets=secrets_data)

def _is_internal_secret_key(self, key: str):
return key.startswith(self.internal_secrets_key_prefix)

0 comments on commit 3e46b22

Please sign in to comment.