{ "cells": [ { "cell_type": "markdown", "id": "331918b2-c03c-4d14-9737-d65b94ed7e73", "metadata": {}, "source": [ "# Add MAAP Secrets for Earthdata Login Credentials\n", "\n", "Earthdata Login credentials are required for downloading files from NASA DAACs. In order for a DPS job to be able to use such credentials in a safe manner, it looks for them in the MAAP Secrets store. Therefore, you must add the necessary secrets before a DPS job that requires them is submitted, otherwise the job will fail due to the missing secrets." ] }, { "cell_type": "code", "execution_count": null, "id": "cbdb9c92-6219-4d58-b9f4-206ee507ce51", "metadata": {}, "outputs": [], "source": [ "from maap.maap import MAAP" ] }, { "cell_type": "code", "execution_count": null, "id": "e2f0c041-646c-47e3-9c9f-4937ed9f7907", "metadata": {}, "outputs": [], "source": [ "def mask(value: str, *, show_last: int = 4) -> str:\n", " \"\"\"Mask a value.\n", " \n", " Parameters\n", " ----------\n", " value:\n", " Value to mask.\n", " show_last:\n", " Number of trailing characters to leave unmasked.\n", " \n", " Returns\n", " -------\n", " Masked value, where all but `show_last` characters of the specified value are\n", " replaced with an asterisk (`*`). If necessary, the value is further left-padded\n", " with asterisks to ensure there are at least as many asterisks as there are\n", " unmasked trailing characters.\n", " \"\"\"\n", "\n", " # Make sure the returned value contains at least the same number of masked\n", " # characters as unmasked characters for a bit of added \"security.\"\n", " n_masked = max(len(value), 2 * show_last) - show_last\n", " \n", " return f\"{'*' * n_masked}{value[-show_last:]}\"\n", "\n", "\n", "def prompt_user(\n", " prompt: str,\n", " *,\n", " value: str | None = None,\n", " sensitive: bool = True,\n", ") -> str | None:\n", " \"\"\"Prompt user for a possibly sensitive input value.\n", "\n", " Parameters\n", " ----------\n", " prompt:\n", " Human-readable prompt to present to the user, which is combined with `value`\n", " when prompting the user.\n", " value:\n", " Current value (if any) of what to prompt the user for, which is combined with\n", " `prompt` when prompting the user, and masked, if `senstive` is `True`.\n", " sensitive:\n", " If `True`, mask the current value (if any) when prompting the user, and also\n", " avoid echoing user input. Otherwise, the current value is left unmasked and\n", " user input is echoed.\n", "\n", " Returns\n", " -------\n", " Value entered by the user at the prompt, or `None` if either the user pressed the\n", " Return or Enter key without supplying any input, or a KeyboardInterrupt occurred.\n", " \"\"\"\n", " from getpass import getpass\n", " from IPython.display import clear_output\n", "\n", " masked_value = mask(value) if value and sensitive else value\n", " get_user_input = getpass if sensitive else input\n", "\n", " try:\n", " return get_user_input(f\"{prompt} [{masked_value}]:\") or None\n", " except KeyboardInterrupt:\n", " clear_output() # Remove prompt from cell output\n", " raise\n", "\n", " \n", "def get_secret(maap: MAAP, name: str) -> str | None:\n", " \"\"\"Get the value of a MAAP secret, or `None` if the secret does not exist.\n", " \n", " Parameters\n", " ----------\n", " maap:\n", " MAAP instance to use for secrets management.\n", " name:\n", " Name of the secret to get the value of.\n", "\n", " Returns\n", " -------\n", " Value of the secret, if it exists; otherwise `None`.\n", " \"\"\"\n", "\n", " # Calling maap.secrets.get_secret returns the value of the requested secret, if it\n", " # exists, but oddly returns an object with an error code if it does not exist,\n", " # rather than simply (and conveniently) returning `None`.\n", " \n", " return value if isinstance(value := maap.secrets.get_secret(name), str) else None\n", "\n", "\n", "def set_secret_interactively(\n", " maap: MAAP,\n", " *,\n", " prompt: str,\n", " name: str,\n", " sensitive: bool = True,\n", ") -> str | None:\n", " \"\"\"Set the value of a secret by prompting the user for a value.\n", "\n", " If the secret already exists, the user prompt will include the existing value,\n", " which will be masked if `sensitive` is `True` (showing only the last 4\n", " characters of the value).\n", "\n", " If an empty input is provided (i.e., either the Enter or Return key is pressed at\n", " the prompt, without any other input, or a KeyboardInterrupt occurs), the secret is\n", " not set and `None` is returned. Otherwise, the secret is set to the user-supplied\n", " value and that value is returned.\n", "\n", " Parameters\n", " ----------\n", " maap:\n", " MAAP instance to use for secrets management.\n", " prompt:\n", " Human-readable prompt to display to user when prompting for input.\n", " name:\n", " Name of the secret.\n", " sensitive:\n", " If `True`, the prompt will mask the current secret value, if there is one.\n", " Further, user input will not be echoed. Otherwise, the prompt will show the\n", " full secret value, if there is one, and user input will be echoed.\n", "\n", " Returns\n", " -------\n", " User-supplied input provided at the prompt, or `None` if the Enter or Return key\n", " was pressed without input.\n", " \"\"\"\n", " if (value := prompt_user(prompt, value=get_secret(maap, name), sensitive=sensitive)):\n", " maap.secrets.add_secret(name, value)\n", " masked_value = mask(value) if sensitive else value\n", " print(f\"The secret {name} was set to {masked_value}.\")\n", " return value\n", "\n", " print(f\"The value for secret {name} was left unchanged.\")" ] }, { "cell_type": "markdown", "id": "97611ae6-837e-4021-9e4e-bee447749d5e", "metadata": {}, "source": [ "## Create MAAP Instance for Managing Secrets" ] }, { "cell_type": "code", "execution_count": null, "id": "10612951-3392-4e32-983e-6e9a85c8c938", "metadata": {}, "outputs": [], "source": [ "maap = MAAP()" ] }, { "cell_type": "markdown", "id": "98d52db0-7187-4aa3-a24a-7f3cbfce63c5", "metadata": {}, "source": [ "## Store Earthdata Login Credentials as MAAP Secrets" ] }, { "cell_type": "code", "execution_count": null, "id": "986a47cb-0586-4f62-827b-6df6fcb609d4", "metadata": {}, "outputs": [], "source": [ "set_secret_interactively(\n", " maap, prompt=\"Earthdata Login username\", name=\"EARTHDATA_USERNAME\", sensitive=False\n", ")\n", "set_secret_interactively(\n", " maap, prompt=\"Earthdata Login password\", name=\"EARTHDATA_PASSWORD\"\n", ");" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.13" } }, "nbformat": 4, "nbformat_minor": 5 }