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

local-run: add option to set service auth token in container environment #3922

Merged
merged 2 commits into from
Jul 22, 2024
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
17 changes: 17 additions & 0 deletions paasta_tools/cli/cmds/local_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from paasta_tools.cli.cmds.cook_image import paasta_cook_image
from paasta_tools.cli.utils import figure_out_service_name
from paasta_tools.cli.utils import get_instance_config
from paasta_tools.cli.utils import get_service_auth_token
from paasta_tools.cli.utils import lazy_choices_completer
from paasta_tools.cli.utils import list_instances
from paasta_tools.cli.utils import pick_random_port
Expand Down Expand Up @@ -506,6 +507,17 @@ def add_subparser(subparsers):
required=False,
default=False,
)
list_parser.add_argument(
"--use-service-auth-token",
help=(
"Acquire service authentication token for the underlying instance,"
" and set it in the container environment"
),
action="store_true",
dest="use_service_auth_token",
required=False,
default=False,
)
list_parser.add_argument(
"--sha",
help=(
Expand Down Expand Up @@ -817,6 +829,7 @@ def run_docker_container(
assume_role_arn="",
use_okta_role=False,
assume_role_aws_account: Optional[str] = None,
use_service_auth_token: bool = False,
):
"""docker-py has issues running a container with a TTY attached, so for
consistency we execute 'docker run' directly in both interactive and
Expand Down Expand Up @@ -906,6 +919,9 @@ def run_docker_container(
)
environment.update(aws_creds)

if use_service_auth_token:
environment["YELP_SVC_AUTHZ_TOKEN"] = get_service_auth_token()

local_run_environment = get_local_run_environment_vars(
instance_config=instance_config, port0=chosen_port, framework=framework
)
Expand Down Expand Up @@ -1251,6 +1267,7 @@ def configure_and_run_docker_container(
assume_role_arn=args.assume_role_arn,
assume_role_aws_account=assume_role_aws_account,
use_okta_role=args.use_okta_role,
use_service_auth_token=args.use_service_auth_token,
)


Expand Down
49 changes: 49 additions & 0 deletions paasta_tools/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
from typing import Tuple

import ephemeral_port_reserve
from botocore.credentials import InstanceMetadataFetcher
from botocore.credentials import InstanceMetadataProvider
from mypy_extensions import NamedArg

from paasta_tools import remote_git
Expand Down Expand Up @@ -76,6 +78,22 @@
from paasta_tools.utils import validate_service_instance
from paasta_tools.vitesscluster_tools import load_vitess_instance_config

try:
from vault_tools.paasta_secret import get_client as get_vault_client
from vault_tools.paasta_secret import get_vault_url
from vault_tools.paasta_secret import get_vault_ca
except ImportError:

def get_vault_client(url: str, capath: str) -> None:
pass

def get_vault_url(ecosystem: str) -> str:
return ""

def get_vault_ca(ecosystem: str) -> str:
return ""


log = logging.getLogger(__name__)


Expand Down Expand Up @@ -1083,3 +1101,34 @@ def get_paasta_oapi_api_clustername(cluster: str, is_eks: bool) -> str:
"eks-" prefix
"""
return f"eks-{cluster}" if is_eks else cluster


def get_current_ecosystem() -> str:
"""Get current ecosystem from host configs, defaults to dev if no config is found"""
try:
with open("/nail/etc/ecosystem") as f:
return f.read().strip()
except IOError:
pass
return "devc"


def get_service_auth_token() -> str:
"""Uses instance profile to authenticate with Vault and generate token for service authentication"""
ecosystem = get_current_ecosystem()
vault_client = get_vault_client(get_vault_url(ecosystem), get_vault_ca(ecosystem))
vault_role = load_system_paasta_config().get_service_auth_vault_role()
metadata_provider = InstanceMetadataProvider(
iam_role_fetcher=InstanceMetadataFetcher(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious: would we need to do any sort of env var clearing for this to work if folks have any AWS_* env vars set (or if they have a default profile or anything like that)

(i've personally run into fun stuff like that when attempting to run things that used only the instance profile)

Copy link
Contributor Author

@piax93 piax93 Jul 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, perfect!

)
danielpops marked this conversation as resolved.
Show resolved Hide resolved
instance_credentials = metadata_provider.load().get_frozen_credentials()
vault_client.auth.aws.iam_login(
instance_credentials.access_key,
instance_credentials.secret_key,
instance_credentials.token,
mount_point="aws-iam",
danielpops marked this conversation as resolved.
Show resolved Hide resolved
role=vault_role,
use_token=True,
)
response = vault_client.secrets.identity.generate_signed_id_token(name=vault_role)
return response["data"]["token"]
4 changes: 4 additions & 0 deletions paasta_tools/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2041,6 +2041,7 @@ class SystemPaastaConfigDict(TypedDict, total=False):
secret_sync_delay_seconds: float
use_multiple_log_readers: Optional[List[str]]
service_auth_token_settings: ProjectedSAVolume
service_auth_vault_role: str
always_authenticating_services: List[str]
mysql_port_mappings: Dict
vitess_images: Dict
Expand Down Expand Up @@ -2756,6 +2757,9 @@ def get_kube_clusters(self) -> Dict:
def get_service_auth_token_volume_config(self) -> ProjectedSAVolume:
return self.config_dict.get("service_auth_token_settings", {})

def get_service_auth_vault_role(self) -> str:
return self.config_dict.get("service_auth_vault_role", "service_authz")

def get_always_authenticating_services(self) -> List[str]:
return self.config_dict.get("always_authenticating_services", [])

Expand Down
8 changes: 8 additions & 0 deletions tests/cli/test_cmds_local_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ def test_configure_and_run_command_uses_cmd_from_config(
args.assume_role_arn = ""
args.assume_pod_identity = False
args.use_okta_role = False
args.use_service_auth_token = False

mock_secret_provider_kwargs = {
"vault_cluster_config": {},
Expand Down Expand Up @@ -436,6 +437,7 @@ def test_configure_and_run_command_uses_cmd_from_config(
assume_pod_identity=False,
assume_role_aws_account=None,
use_okta_role=False,
use_service_auth_token=False,
)


Expand Down Expand Up @@ -470,6 +472,7 @@ def test_configure_and_run_uses_bash_by_default_when_interactive(
args.assume_role_arn = ""
args.assume_pod_identity = False
args.use_okta_role = False
args.use_service_auth_token = False

return_code = configure_and_run_docker_container(
docker_client=mock_docker_client,
Expand Down Expand Up @@ -511,6 +514,7 @@ def test_configure_and_run_uses_bash_by_default_when_interactive(
assume_role_aws_account="dev",
assume_pod_identity=False,
use_okta_role=False,
use_service_auth_token=False,
)


Expand Down Expand Up @@ -551,6 +555,7 @@ def test_configure_and_run_pulls_image_when_asked(
args.assume_role_arn = ""
args.assume_pod_identity = False
args.use_okta_role = False
args.use_service_auth_token = False

return_code = configure_and_run_docker_container(
docker_client=mock_docker_client,
Expand Down Expand Up @@ -594,6 +599,7 @@ def test_configure_and_run_pulls_image_when_asked(
assume_pod_identity=False,
assume_role_aws_account="dev",
use_okta_role=False,
use_service_auth_token=False,
)


Expand Down Expand Up @@ -630,6 +636,7 @@ def test_configure_and_run_docker_container_defaults_to_interactive_instance(
args.assume_role_arn = ""
args.assume_pod_identity = False
args.use_okta_role = False
args.use_service_auth_token = False

mock_config = mock.create_autospec(AdhocJobConfig)
mock_get_default_interactive_config.return_value = mock_config
Expand Down Expand Up @@ -673,6 +680,7 @@ def test_configure_and_run_docker_container_defaults_to_interactive_instance(
assume_pod_identity=False,
assume_role_aws_account="dev",
use_okta_role=False,
use_service_auth_token=False,
)


Expand Down
48 changes: 48 additions & 0 deletions tests/cli/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from paasta_tools.cli import utils
from paasta_tools.cli.utils import extract_tags
from paasta_tools.cli.utils import get_service_auth_token
from paasta_tools.cli.utils import select_k8s_secret_namespace
from paasta_tools.cli.utils import verify_instances
from paasta_tools.kubernetes_tools import KubernetesDeploymentConfig
Expand Down Expand Up @@ -476,3 +477,50 @@ def test_select_k8s_secret_namespace():

namespaces = {"a", "b"}
assert select_k8s_secret_namespace(namespaces) in {"a", "b"}


@patch("paasta_tools.cli.utils.load_system_paasta_config", autospec=True)
@patch("paasta_tools.cli.utils.get_current_ecosystem", autospec=True)
@patch("paasta_tools.cli.utils.InstanceMetadataProvider", autospec=True)
@patch("paasta_tools.cli.utils.InstanceMetadataFetcher", autospec=True)
@patch("paasta_tools.cli.utils.get_vault_client", autospec=True)
@patch("paasta_tools.cli.utils.get_vault_url", autospec=True)
@patch("paasta_tools.cli.utils.get_vault_ca", autospec=True)
def test_get_service_auth_token(
mock_vault_ca,
mock_vault_url,
mock_get_vault_client,
mock_metadata_fetcher,
mock_metadata_provider,
mock_ecosystem,
mock_config,
):
mock_ecosystem.return_value = "dev"
mock_config.return_value.get_service_auth_vault_role.return_value = "foobar"
mock_vault_client = mock_get_vault_client.return_value
mock_vault_client.secrets.identity.generate_signed_id_token.return_value = {
"data": {"token": "sometoken"},
}
assert get_service_auth_token() == "sometoken"
mock_instance_creds = (
mock_metadata_provider.return_value.load.return_value.get_frozen_credentials.return_value
)
mock_metadata_provider.assert_called_once_with(
iam_role_fetcher=mock_metadata_fetcher.return_value
)
mock_vault_url.assert_called_once_with("dev")
mock_vault_ca.assert_called_once_with("dev")
mock_get_vault_client.assert_called_once_with(
mock_vault_url.return_value, mock_vault_ca.return_value
)
mock_vault_client.auth.aws.iam_login.assert_called_once_with(
mock_instance_creds.access_key,
mock_instance_creds.secret_key,
mock_instance_creds.token,
mount_point="aws-iam",
role="foobar",
use_token=True,
)
mock_vault_client.secrets.identity.generate_signed_id_token.assert_called_once_with(
name="foobar"
)
Loading