Skip to content
Snippets Groups Projects
set-secrets.ipynb 7.84 KiB
Newer Older
Charles Daniels's avatar
Charles Daniels committed
{
 "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",
Charles Daniels's avatar
Charles Daniels committed
    "    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",
Charles Daniels's avatar
Charles Daniels committed
   ]
  }
 ],
 "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
}