paasta-tools 1.21.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- k8s_itests/__init__.py +0 -0
- k8s_itests/test_autoscaling.py +23 -0
- k8s_itests/utils.py +38 -0
- paasta_tools/__init__.py +20 -0
- paasta_tools/adhoc_tools.py +142 -0
- paasta_tools/api/__init__.py +13 -0
- paasta_tools/api/api.py +330 -0
- paasta_tools/api/api_docs/swagger.json +2323 -0
- paasta_tools/api/client.py +106 -0
- paasta_tools/api/settings.py +33 -0
- paasta_tools/api/tweens/__init__.py +6 -0
- paasta_tools/api/tweens/auth.py +125 -0
- paasta_tools/api/tweens/profiling.py +108 -0
- paasta_tools/api/tweens/request_logger.py +124 -0
- paasta_tools/api/views/__init__.py +13 -0
- paasta_tools/api/views/autoscaler.py +100 -0
- paasta_tools/api/views/exception.py +45 -0
- paasta_tools/api/views/flink.py +73 -0
- paasta_tools/api/views/instance.py +395 -0
- paasta_tools/api/views/pause_autoscaler.py +71 -0
- paasta_tools/api/views/remote_run.py +113 -0
- paasta_tools/api/views/resources.py +76 -0
- paasta_tools/api/views/service.py +35 -0
- paasta_tools/api/views/version.py +25 -0
- paasta_tools/apply_external_resources.py +79 -0
- paasta_tools/async_utils.py +109 -0
- paasta_tools/autoscaling/__init__.py +0 -0
- paasta_tools/autoscaling/autoscaling_service_lib.py +57 -0
- paasta_tools/autoscaling/forecasting.py +106 -0
- paasta_tools/autoscaling/max_all_k8s_services.py +41 -0
- paasta_tools/autoscaling/pause_service_autoscaler.py +77 -0
- paasta_tools/autoscaling/utils.py +52 -0
- paasta_tools/bounce_lib.py +184 -0
- paasta_tools/broadcast_log_to_services.py +62 -0
- paasta_tools/cassandracluster_tools.py +210 -0
- paasta_tools/check_autoscaler_max_instances.py +212 -0
- paasta_tools/check_cassandracluster_services_replication.py +35 -0
- paasta_tools/check_flink_services_health.py +203 -0
- paasta_tools/check_kubernetes_api.py +57 -0
- paasta_tools/check_kubernetes_services_replication.py +141 -0
- paasta_tools/check_oom_events.py +244 -0
- paasta_tools/check_services_replication_tools.py +324 -0
- paasta_tools/check_spark_jobs.py +234 -0
- paasta_tools/cleanup_kubernetes_cr.py +138 -0
- paasta_tools/cleanup_kubernetes_crd.py +145 -0
- paasta_tools/cleanup_kubernetes_jobs.py +344 -0
- paasta_tools/cleanup_tron_namespaces.py +96 -0
- paasta_tools/cli/__init__.py +13 -0
- paasta_tools/cli/authentication.py +85 -0
- paasta_tools/cli/cli.py +260 -0
- paasta_tools/cli/cmds/__init__.py +13 -0
- paasta_tools/cli/cmds/autoscale.py +143 -0
- paasta_tools/cli/cmds/check.py +334 -0
- paasta_tools/cli/cmds/cook_image.py +147 -0
- paasta_tools/cli/cmds/get_docker_image.py +76 -0
- paasta_tools/cli/cmds/get_image_version.py +172 -0
- paasta_tools/cli/cmds/get_latest_deployment.py +93 -0
- paasta_tools/cli/cmds/info.py +155 -0
- paasta_tools/cli/cmds/itest.py +117 -0
- paasta_tools/cli/cmds/list.py +66 -0
- paasta_tools/cli/cmds/list_clusters.py +42 -0
- paasta_tools/cli/cmds/list_deploy_queue.py +171 -0
- paasta_tools/cli/cmds/list_namespaces.py +84 -0
- paasta_tools/cli/cmds/local_run.py +1396 -0
- paasta_tools/cli/cmds/logs.py +1601 -0
- paasta_tools/cli/cmds/mark_for_deployment.py +1988 -0
- paasta_tools/cli/cmds/mesh_status.py +174 -0
- paasta_tools/cli/cmds/pause_service_autoscaler.py +107 -0
- paasta_tools/cli/cmds/push_to_registry.py +275 -0
- paasta_tools/cli/cmds/remote_run.py +252 -0
- paasta_tools/cli/cmds/rollback.py +347 -0
- paasta_tools/cli/cmds/secret.py +549 -0
- paasta_tools/cli/cmds/security_check.py +59 -0
- paasta_tools/cli/cmds/spark_run.py +1400 -0
- paasta_tools/cli/cmds/start_stop_restart.py +401 -0
- paasta_tools/cli/cmds/status.py +2302 -0
- paasta_tools/cli/cmds/validate.py +1012 -0
- paasta_tools/cli/cmds/wait_for_deployment.py +275 -0
- paasta_tools/cli/fsm/__init__.py +13 -0
- paasta_tools/cli/fsm/autosuggest.py +82 -0
- paasta_tools/cli/fsm/template/README.md +8 -0
- paasta_tools/cli/fsm/template/cookiecutter.json +7 -0
- paasta_tools/cli/fsm/template/{{cookiecutter.service}}/kubernetes-PROD.yaml +91 -0
- paasta_tools/cli/fsm/template/{{cookiecutter.service}}/monitoring.yaml +20 -0
- paasta_tools/cli/fsm/template/{{cookiecutter.service}}/service.yaml +8 -0
- paasta_tools/cli/fsm/template/{{cookiecutter.service}}/smartstack.yaml +6 -0
- paasta_tools/cli/fsm_cmd.py +121 -0
- paasta_tools/cli/paasta_tabcomplete.sh +23 -0
- paasta_tools/cli/schemas/adhoc_schema.json +199 -0
- paasta_tools/cli/schemas/autoscaling_schema.json +91 -0
- paasta_tools/cli/schemas/autotuned_defaults/cassandracluster_schema.json +37 -0
- paasta_tools/cli/schemas/autotuned_defaults/kubernetes_schema.json +89 -0
- paasta_tools/cli/schemas/deploy_schema.json +173 -0
- paasta_tools/cli/schemas/eks_schema.json +970 -0
- paasta_tools/cli/schemas/kubernetes_schema.json +970 -0
- paasta_tools/cli/schemas/rollback_schema.json +160 -0
- paasta_tools/cli/schemas/service_schema.json +25 -0
- paasta_tools/cli/schemas/smartstack_schema.json +322 -0
- paasta_tools/cli/schemas/tron_schema.json +699 -0
- paasta_tools/cli/utils.py +1118 -0
- paasta_tools/clusterman.py +21 -0
- paasta_tools/config_utils.py +385 -0
- paasta_tools/contrib/__init__.py +0 -0
- paasta_tools/contrib/bounce_log_latency_parser.py +68 -0
- paasta_tools/contrib/check_manual_oapi_changes.sh +24 -0
- paasta_tools/contrib/check_orphans.py +306 -0
- paasta_tools/contrib/create_dynamodb_table.py +35 -0
- paasta_tools/contrib/create_paasta_playground.py +105 -0
- paasta_tools/contrib/emit_allocated_cpu_metrics.py +50 -0
- paasta_tools/contrib/get_running_task_allocation.py +346 -0
- paasta_tools/contrib/habitat_fixer.py +86 -0
- paasta_tools/contrib/ide_helper.py +316 -0
- paasta_tools/contrib/is_pod_healthy_in_proxy.py +139 -0
- paasta_tools/contrib/is_pod_healthy_in_smartstack.py +50 -0
- paasta_tools/contrib/kill_bad_containers.py +109 -0
- paasta_tools/contrib/mass-deploy-tag.sh +44 -0
- paasta_tools/contrib/mock_patch_checker.py +86 -0
- paasta_tools/contrib/paasta_update_soa_memcpu.py +520 -0
- paasta_tools/contrib/render_template.py +129 -0
- paasta_tools/contrib/rightsizer_soaconfigs_update.py +348 -0
- paasta_tools/contrib/service_shard_remove.py +157 -0
- paasta_tools/contrib/service_shard_update.py +373 -0
- paasta_tools/contrib/shared_ip_check.py +77 -0
- paasta_tools/contrib/timeouts_metrics_prom.py +64 -0
- paasta_tools/delete_kubernetes_deployments.py +89 -0
- paasta_tools/deployment_utils.py +44 -0
- paasta_tools/docker_wrapper.py +234 -0
- paasta_tools/docker_wrapper_imports.py +13 -0
- paasta_tools/drain_lib.py +351 -0
- paasta_tools/dump_locally_running_services.py +71 -0
- paasta_tools/eks_tools.py +119 -0
- paasta_tools/envoy_tools.py +373 -0
- paasta_tools/firewall.py +504 -0
- paasta_tools/firewall_logging.py +154 -0
- paasta_tools/firewall_update.py +172 -0
- paasta_tools/flink_tools.py +345 -0
- paasta_tools/flinkeks_tools.py +90 -0
- paasta_tools/frameworks/__init__.py +0 -0
- paasta_tools/frameworks/adhoc_scheduler.py +71 -0
- paasta_tools/frameworks/constraints.py +87 -0
- paasta_tools/frameworks/native_scheduler.py +652 -0
- paasta_tools/frameworks/native_service_config.py +301 -0
- paasta_tools/frameworks/task_store.py +245 -0
- paasta_tools/generate_all_deployments +9 -0
- paasta_tools/generate_authenticating_services.py +94 -0
- paasta_tools/generate_deployments_for_service.py +255 -0
- paasta_tools/generate_services_file.py +114 -0
- paasta_tools/generate_services_yaml.py +30 -0
- paasta_tools/hacheck.py +76 -0
- paasta_tools/instance/__init__.py +0 -0
- paasta_tools/instance/hpa_metrics_parser.py +122 -0
- paasta_tools/instance/kubernetes.py +1362 -0
- paasta_tools/iptables.py +240 -0
- paasta_tools/kafkacluster_tools.py +143 -0
- paasta_tools/kubernetes/__init__.py +0 -0
- paasta_tools/kubernetes/application/__init__.py +0 -0
- paasta_tools/kubernetes/application/controller_wrappers.py +476 -0
- paasta_tools/kubernetes/application/tools.py +90 -0
- paasta_tools/kubernetes/bin/__init__.py +0 -0
- paasta_tools/kubernetes/bin/kubernetes_remove_evicted_pods.py +164 -0
- paasta_tools/kubernetes/bin/paasta_cleanup_remote_run_resources.py +135 -0
- paasta_tools/kubernetes/bin/paasta_cleanup_stale_nodes.py +181 -0
- paasta_tools/kubernetes/bin/paasta_secrets_sync.py +758 -0
- paasta_tools/kubernetes/remote_run.py +558 -0
- paasta_tools/kubernetes_tools.py +4679 -0
- paasta_tools/list_kubernetes_service_instances.py +128 -0
- paasta_tools/list_tron_namespaces.py +60 -0
- paasta_tools/long_running_service_tools.py +678 -0
- paasta_tools/mac_address.py +44 -0
- paasta_tools/marathon_dashboard.py +0 -0
- paasta_tools/mesos/__init__.py +0 -0
- paasta_tools/mesos/cfg.py +46 -0
- paasta_tools/mesos/cluster.py +60 -0
- paasta_tools/mesos/exceptions.py +59 -0
- paasta_tools/mesos/framework.py +77 -0
- paasta_tools/mesos/log.py +48 -0
- paasta_tools/mesos/master.py +306 -0
- paasta_tools/mesos/mesos_file.py +169 -0
- paasta_tools/mesos/parallel.py +52 -0
- paasta_tools/mesos/slave.py +115 -0
- paasta_tools/mesos/task.py +94 -0
- paasta_tools/mesos/util.py +69 -0
- paasta_tools/mesos/zookeeper.py +37 -0
- paasta_tools/mesos_maintenance.py +848 -0
- paasta_tools/mesos_tools.py +1051 -0
- paasta_tools/metrics/__init__.py +0 -0
- paasta_tools/metrics/metastatus_lib.py +1110 -0
- paasta_tools/metrics/metrics_lib.py +217 -0
- paasta_tools/monitoring/__init__.py +13 -0
- paasta_tools/monitoring/check_k8s_api_performance.py +110 -0
- paasta_tools/monitoring_tools.py +652 -0
- paasta_tools/monkrelaycluster_tools.py +146 -0
- paasta_tools/nrtsearchservice_tools.py +143 -0
- paasta_tools/nrtsearchserviceeks_tools.py +68 -0
- paasta_tools/oom_logger.py +321 -0
- paasta_tools/paasta_deploy_tron_jobs +3 -0
- paasta_tools/paasta_execute_docker_command.py +123 -0
- paasta_tools/paasta_native_serviceinit.py +21 -0
- paasta_tools/paasta_service_config_loader.py +201 -0
- paasta_tools/paastaapi/__init__.py +29 -0
- paasta_tools/paastaapi/api/__init__.py +3 -0
- paasta_tools/paastaapi/api/autoscaler_api.py +302 -0
- paasta_tools/paastaapi/api/default_api.py +569 -0
- paasta_tools/paastaapi/api/remote_run_api.py +604 -0
- paasta_tools/paastaapi/api/resources_api.py +157 -0
- paasta_tools/paastaapi/api/service_api.py +1736 -0
- paasta_tools/paastaapi/api_client.py +818 -0
- paasta_tools/paastaapi/apis/__init__.py +22 -0
- paasta_tools/paastaapi/configuration.py +455 -0
- paasta_tools/paastaapi/exceptions.py +137 -0
- paasta_tools/paastaapi/model/__init__.py +5 -0
- paasta_tools/paastaapi/model/adhoc_launch_history.py +176 -0
- paasta_tools/paastaapi/model/autoscaler_count_msg.py +176 -0
- paasta_tools/paastaapi/model/deploy_queue.py +178 -0
- paasta_tools/paastaapi/model/deploy_queue_service_instance.py +194 -0
- paasta_tools/paastaapi/model/envoy_backend.py +185 -0
- paasta_tools/paastaapi/model/envoy_location.py +184 -0
- paasta_tools/paastaapi/model/envoy_status.py +181 -0
- paasta_tools/paastaapi/model/flink_cluster_overview.py +188 -0
- paasta_tools/paastaapi/model/flink_config.py +173 -0
- paasta_tools/paastaapi/model/flink_job.py +186 -0
- paasta_tools/paastaapi/model/flink_job_details.py +192 -0
- paasta_tools/paastaapi/model/flink_jobs.py +175 -0
- paasta_tools/paastaapi/model/float_and_error.py +173 -0
- paasta_tools/paastaapi/model/hpa_metric.py +176 -0
- paasta_tools/paastaapi/model/inline_object.py +170 -0
- paasta_tools/paastaapi/model/inline_response200.py +170 -0
- paasta_tools/paastaapi/model/inline_response2001.py +170 -0
- paasta_tools/paastaapi/model/instance_bounce_status.py +200 -0
- paasta_tools/paastaapi/model/instance_mesh_status.py +186 -0
- paasta_tools/paastaapi/model/instance_status.py +220 -0
- paasta_tools/paastaapi/model/instance_status_adhoc.py +187 -0
- paasta_tools/paastaapi/model/instance_status_cassandracluster.py +173 -0
- paasta_tools/paastaapi/model/instance_status_flink.py +173 -0
- paasta_tools/paastaapi/model/instance_status_kafkacluster.py +173 -0
- paasta_tools/paastaapi/model/instance_status_kubernetes.py +263 -0
- paasta_tools/paastaapi/model/instance_status_kubernetes_autoscaling_status.py +187 -0
- paasta_tools/paastaapi/model/instance_status_kubernetes_v2.py +197 -0
- paasta_tools/paastaapi/model/instance_status_tron.py +204 -0
- paasta_tools/paastaapi/model/instance_tasks.py +182 -0
- paasta_tools/paastaapi/model/integer_and_error.py +173 -0
- paasta_tools/paastaapi/model/kubernetes_container.py +178 -0
- paasta_tools/paastaapi/model/kubernetes_container_v2.py +219 -0
- paasta_tools/paastaapi/model/kubernetes_healthcheck.py +176 -0
- paasta_tools/paastaapi/model/kubernetes_pod.py +201 -0
- paasta_tools/paastaapi/model/kubernetes_pod_event.py +176 -0
- paasta_tools/paastaapi/model/kubernetes_pod_v2.py +213 -0
- paasta_tools/paastaapi/model/kubernetes_replica_set.py +185 -0
- paasta_tools/paastaapi/model/kubernetes_version.py +202 -0
- paasta_tools/paastaapi/model/remote_run_outcome.py +189 -0
- paasta_tools/paastaapi/model/remote_run_start.py +185 -0
- paasta_tools/paastaapi/model/remote_run_stop.py +176 -0
- paasta_tools/paastaapi/model/remote_run_token.py +173 -0
- paasta_tools/paastaapi/model/resource.py +187 -0
- paasta_tools/paastaapi/model/resource_item.py +187 -0
- paasta_tools/paastaapi/model/resource_value.py +176 -0
- paasta_tools/paastaapi/model/smartstack_backend.py +191 -0
- paasta_tools/paastaapi/model/smartstack_location.py +181 -0
- paasta_tools/paastaapi/model/smartstack_status.py +181 -0
- paasta_tools/paastaapi/model/task_tail_lines.py +176 -0
- paasta_tools/paastaapi/model_utils.py +1879 -0
- paasta_tools/paastaapi/models/__init__.py +62 -0
- paasta_tools/paastaapi/rest.py +287 -0
- paasta_tools/prune_completed_pods.py +220 -0
- paasta_tools/puppet_service_tools.py +59 -0
- paasta_tools/py.typed +1 -0
- paasta_tools/remote_git.py +127 -0
- paasta_tools/run-paasta-api-in-dev-mode.py +57 -0
- paasta_tools/run-paasta-api-playground.py +51 -0
- paasta_tools/secret_providers/__init__.py +66 -0
- paasta_tools/secret_providers/vault.py +214 -0
- paasta_tools/secret_tools.py +277 -0
- paasta_tools/setup_istio_mesh.py +353 -0
- paasta_tools/setup_kubernetes_cr.py +412 -0
- paasta_tools/setup_kubernetes_crd.py +138 -0
- paasta_tools/setup_kubernetes_internal_crd.py +154 -0
- paasta_tools/setup_kubernetes_job.py +353 -0
- paasta_tools/setup_prometheus_adapter_config.py +1028 -0
- paasta_tools/setup_tron_namespace.py +248 -0
- paasta_tools/slack.py +75 -0
- paasta_tools/smartstack_tools.py +676 -0
- paasta_tools/spark_tools.py +283 -0
- paasta_tools/synapse_srv_namespaces_fact.py +42 -0
- paasta_tools/tron/__init__.py +0 -0
- paasta_tools/tron/client.py +158 -0
- paasta_tools/tron/tron_command_context.py +194 -0
- paasta_tools/tron/tron_timeutils.py +101 -0
- paasta_tools/tron_tools.py +1448 -0
- paasta_tools/utils.py +4307 -0
- paasta_tools/yaml_tools.py +44 -0
- paasta_tools-1.21.3.data/scripts/apply_external_resources.py +79 -0
- paasta_tools-1.21.3.data/scripts/bounce_log_latency_parser.py +68 -0
- paasta_tools-1.21.3.data/scripts/check_autoscaler_max_instances.py +212 -0
- paasta_tools-1.21.3.data/scripts/check_cassandracluster_services_replication.py +35 -0
- paasta_tools-1.21.3.data/scripts/check_flink_services_health.py +203 -0
- paasta_tools-1.21.3.data/scripts/check_kubernetes_api.py +57 -0
- paasta_tools-1.21.3.data/scripts/check_kubernetes_services_replication.py +141 -0
- paasta_tools-1.21.3.data/scripts/check_manual_oapi_changes.sh +24 -0
- paasta_tools-1.21.3.data/scripts/check_oom_events.py +244 -0
- paasta_tools-1.21.3.data/scripts/check_orphans.py +306 -0
- paasta_tools-1.21.3.data/scripts/check_spark_jobs.py +234 -0
- paasta_tools-1.21.3.data/scripts/cleanup_kubernetes_cr.py +138 -0
- paasta_tools-1.21.3.data/scripts/cleanup_kubernetes_crd.py +145 -0
- paasta_tools-1.21.3.data/scripts/cleanup_kubernetes_jobs.py +344 -0
- paasta_tools-1.21.3.data/scripts/create_dynamodb_table.py +35 -0
- paasta_tools-1.21.3.data/scripts/create_paasta_playground.py +105 -0
- paasta_tools-1.21.3.data/scripts/delete_kubernetes_deployments.py +89 -0
- paasta_tools-1.21.3.data/scripts/emit_allocated_cpu_metrics.py +50 -0
- paasta_tools-1.21.3.data/scripts/generate_all_deployments +9 -0
- paasta_tools-1.21.3.data/scripts/generate_authenticating_services.py +94 -0
- paasta_tools-1.21.3.data/scripts/generate_deployments_for_service.py +255 -0
- paasta_tools-1.21.3.data/scripts/generate_services_file.py +114 -0
- paasta_tools-1.21.3.data/scripts/generate_services_yaml.py +30 -0
- paasta_tools-1.21.3.data/scripts/get_running_task_allocation.py +346 -0
- paasta_tools-1.21.3.data/scripts/habitat_fixer.py +86 -0
- paasta_tools-1.21.3.data/scripts/ide_helper.py +316 -0
- paasta_tools-1.21.3.data/scripts/is_pod_healthy_in_proxy.py +139 -0
- paasta_tools-1.21.3.data/scripts/is_pod_healthy_in_smartstack.py +50 -0
- paasta_tools-1.21.3.data/scripts/kill_bad_containers.py +109 -0
- paasta_tools-1.21.3.data/scripts/kubernetes_remove_evicted_pods.py +164 -0
- paasta_tools-1.21.3.data/scripts/mass-deploy-tag.sh +44 -0
- paasta_tools-1.21.3.data/scripts/mock_patch_checker.py +86 -0
- paasta_tools-1.21.3.data/scripts/paasta_cleanup_remote_run_resources.py +135 -0
- paasta_tools-1.21.3.data/scripts/paasta_cleanup_stale_nodes.py +181 -0
- paasta_tools-1.21.3.data/scripts/paasta_deploy_tron_jobs +3 -0
- paasta_tools-1.21.3.data/scripts/paasta_execute_docker_command.py +123 -0
- paasta_tools-1.21.3.data/scripts/paasta_secrets_sync.py +758 -0
- paasta_tools-1.21.3.data/scripts/paasta_tabcomplete.sh +23 -0
- paasta_tools-1.21.3.data/scripts/paasta_update_soa_memcpu.py +520 -0
- paasta_tools-1.21.3.data/scripts/render_template.py +129 -0
- paasta_tools-1.21.3.data/scripts/rightsizer_soaconfigs_update.py +348 -0
- paasta_tools-1.21.3.data/scripts/service_shard_remove.py +157 -0
- paasta_tools-1.21.3.data/scripts/service_shard_update.py +373 -0
- paasta_tools-1.21.3.data/scripts/setup_istio_mesh.py +353 -0
- paasta_tools-1.21.3.data/scripts/setup_kubernetes_cr.py +412 -0
- paasta_tools-1.21.3.data/scripts/setup_kubernetes_crd.py +138 -0
- paasta_tools-1.21.3.data/scripts/setup_kubernetes_internal_crd.py +154 -0
- paasta_tools-1.21.3.data/scripts/setup_kubernetes_job.py +353 -0
- paasta_tools-1.21.3.data/scripts/setup_prometheus_adapter_config.py +1028 -0
- paasta_tools-1.21.3.data/scripts/shared_ip_check.py +77 -0
- paasta_tools-1.21.3.data/scripts/synapse_srv_namespaces_fact.py +42 -0
- paasta_tools-1.21.3.data/scripts/timeouts_metrics_prom.py +64 -0
- paasta_tools-1.21.3.dist-info/LICENSE +201 -0
- paasta_tools-1.21.3.dist-info/METADATA +74 -0
- paasta_tools-1.21.3.dist-info/RECORD +348 -0
- paasta_tools-1.21.3.dist-info/WHEEL +5 -0
- paasta_tools-1.21.3.dist-info/entry_points.txt +20 -0
- paasta_tools-1.21.3.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# Copyright 2015-2019 Yelp Inc.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import argparse
|
|
16
|
+
import base64
|
|
17
|
+
import contextlib
|
|
18
|
+
import hashlib
|
|
19
|
+
import json
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
from collections import defaultdict
|
|
25
|
+
from functools import partial
|
|
26
|
+
from typing import Callable
|
|
27
|
+
from typing import Dict
|
|
28
|
+
from typing import Generator
|
|
29
|
+
from typing import List
|
|
30
|
+
from typing import Mapping
|
|
31
|
+
from typing import Optional
|
|
32
|
+
from typing import Set
|
|
33
|
+
from typing import Tuple
|
|
34
|
+
|
|
35
|
+
from kubernetes.client.rest import ApiException
|
|
36
|
+
from typing_extensions import Literal
|
|
37
|
+
|
|
38
|
+
from paasta_tools.eks_tools import EksDeploymentConfig
|
|
39
|
+
from paasta_tools.kubernetes_tools import create_secret
|
|
40
|
+
from paasta_tools.kubernetes_tools import create_secret_signature
|
|
41
|
+
from paasta_tools.kubernetes_tools import ensure_namespace
|
|
42
|
+
from paasta_tools.kubernetes_tools import get_paasta_secret_name
|
|
43
|
+
from paasta_tools.kubernetes_tools import get_paasta_secret_signature_name
|
|
44
|
+
from paasta_tools.kubernetes_tools import get_secret_signature
|
|
45
|
+
from paasta_tools.kubernetes_tools import get_vault_key_secret_name
|
|
46
|
+
from paasta_tools.kubernetes_tools import KubeClient
|
|
47
|
+
from paasta_tools.kubernetes_tools import KubernetesDeploymentConfig
|
|
48
|
+
from paasta_tools.kubernetes_tools import sanitise_kubernetes_name
|
|
49
|
+
from paasta_tools.kubernetes_tools import update_secret
|
|
50
|
+
from paasta_tools.kubernetes_tools import update_secret_signature
|
|
51
|
+
from paasta_tools.metrics import metrics_lib
|
|
52
|
+
from paasta_tools.paasta_service_config_loader import PaastaServiceConfigLoader
|
|
53
|
+
from paasta_tools.secret_tools import get_secret_name_from_ref
|
|
54
|
+
from paasta_tools.secret_tools import get_secret_provider
|
|
55
|
+
from paasta_tools.utils import DEFAULT_SOA_DIR
|
|
56
|
+
from paasta_tools.utils import DEFAULT_VAULT_TOKEN_FILE
|
|
57
|
+
from paasta_tools.utils import get_service_instance_list
|
|
58
|
+
from paasta_tools.utils import INSTANCE_TYPE_TO_K8S_NAMESPACE
|
|
59
|
+
from paasta_tools.utils import INSTANCE_TYPES
|
|
60
|
+
from paasta_tools.utils import load_system_paasta_config
|
|
61
|
+
from paasta_tools.utils import PAASTA_K8S_INSTANCE_TYPES
|
|
62
|
+
from paasta_tools.utils import SHARED_SECRETS_K8S_NAMESPACES
|
|
63
|
+
|
|
64
|
+
log = logging.getLogger(__name__)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
K8S_INSTANCE_TYPE_CLASSES = (
|
|
68
|
+
KubernetesDeploymentConfig,
|
|
69
|
+
EksDeploymentConfig,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def parse_args() -> argparse.Namespace:
|
|
74
|
+
parser = argparse.ArgumentParser(description="Sync paasta secrets into k8s")
|
|
75
|
+
parser.add_argument(
|
|
76
|
+
"service_list",
|
|
77
|
+
nargs="+",
|
|
78
|
+
help="The list of services to sync secrets for",
|
|
79
|
+
metavar="SERVICE",
|
|
80
|
+
)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"-c",
|
|
83
|
+
"--cluster",
|
|
84
|
+
dest="cluster",
|
|
85
|
+
metavar="CLUSTER",
|
|
86
|
+
default=None,
|
|
87
|
+
help="Kubernetes cluster name",
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"-d",
|
|
91
|
+
"--soa-dir",
|
|
92
|
+
dest="soa_dir",
|
|
93
|
+
metavar="SOA_DIR",
|
|
94
|
+
default=DEFAULT_SOA_DIR,
|
|
95
|
+
help="define a different soa config directory",
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"-n",
|
|
99
|
+
"--namespace",
|
|
100
|
+
dest="namespace",
|
|
101
|
+
help="Overwrite destination namespace for secrets",
|
|
102
|
+
)
|
|
103
|
+
parser.add_argument(
|
|
104
|
+
"-t",
|
|
105
|
+
"--vault-token-file",
|
|
106
|
+
dest="vault_token_file",
|
|
107
|
+
default=DEFAULT_VAULT_TOKEN_FILE,
|
|
108
|
+
help="Define a different vault token file location",
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"-v", "--verbose", action="store_true", dest="verbose", default=False
|
|
112
|
+
)
|
|
113
|
+
parser.add_argument(
|
|
114
|
+
"--secret-type",
|
|
115
|
+
choices=[
|
|
116
|
+
"all",
|
|
117
|
+
"paasta-secret",
|
|
118
|
+
"boto-key",
|
|
119
|
+
"crypto-key",
|
|
120
|
+
"datastore-credentials",
|
|
121
|
+
],
|
|
122
|
+
default="all",
|
|
123
|
+
type=str,
|
|
124
|
+
help="Define which type of secret to add/update. Default is 'all' (which does not include datastore-credentials)",
|
|
125
|
+
)
|
|
126
|
+
args = parser.parse_args()
|
|
127
|
+
return args
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@contextlib.contextmanager
|
|
131
|
+
def set_temporary_environment_variables(
|
|
132
|
+
environ: Mapping[str, str]
|
|
133
|
+
) -> Generator[None, None, None]:
|
|
134
|
+
"""
|
|
135
|
+
*Note the return value means "yields None, takes None, and when finished, returns None"*
|
|
136
|
+
|
|
137
|
+
Modifies the os.environ variable then yields this temporary state. Resets it when finished.
|
|
138
|
+
|
|
139
|
+
:param environ: Environment variables to set
|
|
140
|
+
"""
|
|
141
|
+
old_environ = dict(os.environ) # ensure we're storing a copy
|
|
142
|
+
os.environ.update(environ)
|
|
143
|
+
try:
|
|
144
|
+
yield
|
|
145
|
+
finally:
|
|
146
|
+
os.environ.clear()
|
|
147
|
+
os.environ.update(old_environ)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def main() -> None:
|
|
151
|
+
args = parse_args()
|
|
152
|
+
if args.verbose:
|
|
153
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
154
|
+
else:
|
|
155
|
+
logging.basicConfig(level=logging.WARNING)
|
|
156
|
+
|
|
157
|
+
system_paasta_config = load_system_paasta_config()
|
|
158
|
+
if args.cluster:
|
|
159
|
+
cluster = args.cluster
|
|
160
|
+
else:
|
|
161
|
+
cluster = system_paasta_config.get_cluster()
|
|
162
|
+
|
|
163
|
+
timer = metrics_lib.system_timer(
|
|
164
|
+
dimensions=dict(
|
|
165
|
+
cluster=cluster,
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
timer.start()
|
|
170
|
+
secret_provider_name = system_paasta_config.get_secret_provider_name()
|
|
171
|
+
vault_cluster_config = system_paasta_config.get_vault_cluster_config()
|
|
172
|
+
kube_client = KubeClient()
|
|
173
|
+
services_to_k8s_namespaces_to_allowlist = (
|
|
174
|
+
get_services_to_k8s_namespaces_to_allowlist(
|
|
175
|
+
service_list=args.service_list,
|
|
176
|
+
cluster=cluster,
|
|
177
|
+
soa_dir=args.soa_dir,
|
|
178
|
+
kube_client=kube_client,
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
result = sync_all_secrets(
|
|
183
|
+
kube_client=kube_client,
|
|
184
|
+
cluster=cluster,
|
|
185
|
+
services_to_k8s_namespaces_to_allowlist=services_to_k8s_namespaces_to_allowlist,
|
|
186
|
+
secret_provider_name=secret_provider_name,
|
|
187
|
+
vault_cluster_config=vault_cluster_config,
|
|
188
|
+
soa_dir=args.soa_dir,
|
|
189
|
+
vault_token_file=args.vault_token_file,
|
|
190
|
+
overwrite_namespace=args.namespace,
|
|
191
|
+
secret_type=args.secret_type,
|
|
192
|
+
)
|
|
193
|
+
exit_code = 0 if result else 1
|
|
194
|
+
|
|
195
|
+
timer.stop(tmp_dimensions={"result": exit_code})
|
|
196
|
+
logging.info(
|
|
197
|
+
f"Stopping timer for {cluster} with result {exit_code}: {timer()}ms elapsed"
|
|
198
|
+
)
|
|
199
|
+
sys.exit(exit_code)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def get_services_to_k8s_namespaces_to_allowlist(
|
|
203
|
+
service_list: List[str], cluster: str, soa_dir: str, kube_client: KubeClient
|
|
204
|
+
) -> Dict[
|
|
205
|
+
str, # service
|
|
206
|
+
Dict[
|
|
207
|
+
str, # namespace
|
|
208
|
+
Optional[Set[str]], # allowlist of secret names, None means allow all.
|
|
209
|
+
],
|
|
210
|
+
]:
|
|
211
|
+
"""
|
|
212
|
+
Generate a mapping of service -> namespace -> allowlist of secrets, e.g.
|
|
213
|
+
|
|
214
|
+
{
|
|
215
|
+
"yelp-main": {
|
|
216
|
+
"paasta": {"secret1", "secret2"},
|
|
217
|
+
"paastasvc-yelp-main": {"secret1", "secret3"},
|
|
218
|
+
"paasta-flinks": None,
|
|
219
|
+
},
|
|
220
|
+
"_shared": {
|
|
221
|
+
"paasta": {"sharedsecret1"},
|
|
222
|
+
"paastasvc-yelp-main": {"sharedsecret1", "sharedsecret2"},
|
|
223
|
+
"paasta-flinks": None,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
This mapping is used by sync_all_secrets / sync_secrets:
|
|
228
|
+
sync_secrets will only sync secrets into a namespace if the allowlist is None or contains that secret's name.
|
|
229
|
+
"""
|
|
230
|
+
services_to_k8s_namespaces_to_allowlist: Dict[
|
|
231
|
+
str, Dict[str, Optional[Set[str]]]
|
|
232
|
+
] = defaultdict(dict)
|
|
233
|
+
|
|
234
|
+
for service in service_list:
|
|
235
|
+
if service == "_shared":
|
|
236
|
+
# _shared is handled specially for each service.
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
config_loader = PaastaServiceConfigLoader(service, soa_dir)
|
|
240
|
+
for instance_type_class in K8S_INSTANCE_TYPE_CLASSES:
|
|
241
|
+
for service_instance_config in config_loader.instance_configs(
|
|
242
|
+
cluster=cluster, instance_type_class=instance_type_class
|
|
243
|
+
):
|
|
244
|
+
secrets_used, shared_secrets_used = get_secrets_used_by_instance(
|
|
245
|
+
service_instance_config
|
|
246
|
+
)
|
|
247
|
+
allowlist = services_to_k8s_namespaces_to_allowlist[service].setdefault(
|
|
248
|
+
service_instance_config.get_namespace(),
|
|
249
|
+
set(),
|
|
250
|
+
)
|
|
251
|
+
if allowlist is not None:
|
|
252
|
+
allowlist.update(secrets_used)
|
|
253
|
+
|
|
254
|
+
if "_shared" in service_list:
|
|
255
|
+
shared_allowlist = services_to_k8s_namespaces_to_allowlist[
|
|
256
|
+
"_shared"
|
|
257
|
+
].setdefault(
|
|
258
|
+
service_instance_config.get_namespace(),
|
|
259
|
+
set(),
|
|
260
|
+
)
|
|
261
|
+
if shared_allowlist is not None:
|
|
262
|
+
shared_allowlist.update(shared_secrets_used)
|
|
263
|
+
|
|
264
|
+
for instance_type in INSTANCE_TYPES:
|
|
265
|
+
if instance_type in PAASTA_K8S_INSTANCE_TYPES:
|
|
266
|
+
continue # handled above.
|
|
267
|
+
|
|
268
|
+
instances = get_service_instance_list(
|
|
269
|
+
service=service,
|
|
270
|
+
instance_type=instance_type,
|
|
271
|
+
cluster=cluster,
|
|
272
|
+
soa_dir=soa_dir,
|
|
273
|
+
)
|
|
274
|
+
if instances:
|
|
275
|
+
# Currently, all instance types besides kubernetes use one big namespace, defined in
|
|
276
|
+
# INSTANCE_TYPE_TO_K8S_NAMESPACE. Sync all shared secrets and all secrets belonging to any service
|
|
277
|
+
# which uses that instance type.
|
|
278
|
+
|
|
279
|
+
services_to_k8s_namespaces_to_allowlist[service][
|
|
280
|
+
INSTANCE_TYPE_TO_K8S_NAMESPACE[instance_type]
|
|
281
|
+
] = None
|
|
282
|
+
if "_shared" in service_list:
|
|
283
|
+
services_to_k8s_namespaces_to_allowlist["_shared"][
|
|
284
|
+
INSTANCE_TYPE_TO_K8S_NAMESPACE[instance_type]
|
|
285
|
+
] = None
|
|
286
|
+
|
|
287
|
+
return dict(services_to_k8s_namespaces_to_allowlist)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def get_secrets_used_by_instance(
|
|
291
|
+
service_instance_config: KubernetesDeploymentConfig,
|
|
292
|
+
) -> Tuple[Set[str], Set[str]]:
|
|
293
|
+
(
|
|
294
|
+
secret_env_vars,
|
|
295
|
+
shared_secret_env_vars,
|
|
296
|
+
) = service_instance_config.get_env_vars_that_use_secrets()
|
|
297
|
+
|
|
298
|
+
secrets_used = {get_secret_name_from_ref(v) for v in secret_env_vars.values()}
|
|
299
|
+
shared_secrets_used = {
|
|
300
|
+
get_secret_name_from_ref(v) for v in shared_secret_env_vars.values()
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for secret_volume in service_instance_config.get_secret_volumes():
|
|
304
|
+
# currently, only per-service secrets are supported for secret_volumes.
|
|
305
|
+
secrets_used.add(secret_volume["secret_name"])
|
|
306
|
+
|
|
307
|
+
return secrets_used, shared_secrets_used
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def sync_all_secrets(
|
|
311
|
+
kube_client: KubeClient,
|
|
312
|
+
cluster: str,
|
|
313
|
+
services_to_k8s_namespaces_to_allowlist: Dict[str, Dict[str, Set[str]]],
|
|
314
|
+
secret_provider_name: str,
|
|
315
|
+
vault_cluster_config: Dict[str, str],
|
|
316
|
+
soa_dir: str,
|
|
317
|
+
vault_token_file: str,
|
|
318
|
+
secret_type: Literal[
|
|
319
|
+
"all", "paasta-secret", "crypto-key", "boto-key", "datastore-credentials"
|
|
320
|
+
] = "all",
|
|
321
|
+
overwrite_namespace: Optional[str] = None,
|
|
322
|
+
) -> bool:
|
|
323
|
+
results = []
|
|
324
|
+
|
|
325
|
+
for (
|
|
326
|
+
service,
|
|
327
|
+
namespaces_to_allowlist,
|
|
328
|
+
) in services_to_k8s_namespaces_to_allowlist.items():
|
|
329
|
+
sync_service_secrets: Dict[str, List[Callable]] = defaultdict(list)
|
|
330
|
+
|
|
331
|
+
if overwrite_namespace:
|
|
332
|
+
namespaces_to_allowlist = {
|
|
333
|
+
overwrite_namespace: None
|
|
334
|
+
if overwrite_namespace in SHARED_SECRETS_K8S_NAMESPACES
|
|
335
|
+
else namespaces_to_allowlist.get(overwrite_namespace, set()),
|
|
336
|
+
}
|
|
337
|
+
for namespace, secret_allowlist in namespaces_to_allowlist.items():
|
|
338
|
+
ensure_namespace(kube_client, namespace)
|
|
339
|
+
sync_service_secrets["paasta-secret"].append(
|
|
340
|
+
partial(
|
|
341
|
+
sync_secrets,
|
|
342
|
+
kube_client=kube_client,
|
|
343
|
+
cluster=cluster,
|
|
344
|
+
service=service,
|
|
345
|
+
secret_provider_name=secret_provider_name,
|
|
346
|
+
vault_cluster_config=vault_cluster_config,
|
|
347
|
+
soa_dir=soa_dir,
|
|
348
|
+
namespace=namespace,
|
|
349
|
+
vault_token_file=vault_token_file,
|
|
350
|
+
secret_allowlist=secret_allowlist,
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
sync_service_secrets["boto-key"].append(
|
|
354
|
+
partial(
|
|
355
|
+
sync_boto_secrets,
|
|
356
|
+
kube_client=kube_client,
|
|
357
|
+
cluster=cluster,
|
|
358
|
+
service=service,
|
|
359
|
+
soa_dir=soa_dir,
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
sync_service_secrets["crypto-key"].append(
|
|
363
|
+
partial(
|
|
364
|
+
sync_crypto_secrets,
|
|
365
|
+
kube_client=kube_client,
|
|
366
|
+
cluster=cluster,
|
|
367
|
+
service=service,
|
|
368
|
+
secret_provider_name=secret_provider_name,
|
|
369
|
+
vault_cluster_config=vault_cluster_config,
|
|
370
|
+
soa_dir=soa_dir,
|
|
371
|
+
vault_token_file=vault_token_file,
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
sync_service_secrets["datastore-credentials"].append(
|
|
376
|
+
partial(
|
|
377
|
+
sync_datastore_credentials,
|
|
378
|
+
kube_client=kube_client,
|
|
379
|
+
cluster=cluster,
|
|
380
|
+
service=service,
|
|
381
|
+
secret_provider_name=secret_provider_name,
|
|
382
|
+
vault_cluster_config=vault_cluster_config,
|
|
383
|
+
soa_dir=soa_dir,
|
|
384
|
+
vault_token_file=vault_token_file,
|
|
385
|
+
overwrite_namespace=overwrite_namespace,
|
|
386
|
+
)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
if secret_type == "all":
|
|
390
|
+
results.append(
|
|
391
|
+
all(sync() for sync in sync_service_secrets["paasta-secret"])
|
|
392
|
+
)
|
|
393
|
+
results.append(all(sync() for sync in sync_service_secrets["boto-key"]))
|
|
394
|
+
results.append(all(sync() for sync in sync_service_secrets["crypto-key"]))
|
|
395
|
+
# note that since datastore-credentials are in a different vault, they're not synced as part of 'all'
|
|
396
|
+
else:
|
|
397
|
+
results.append(all(sync() for sync in sync_service_secrets[secret_type]))
|
|
398
|
+
|
|
399
|
+
return all(results)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def sync_secrets(
|
|
403
|
+
kube_client: KubeClient,
|
|
404
|
+
cluster: str,
|
|
405
|
+
service: str,
|
|
406
|
+
secret_provider_name: str,
|
|
407
|
+
vault_cluster_config: Dict[str, str],
|
|
408
|
+
soa_dir: str,
|
|
409
|
+
namespace: str,
|
|
410
|
+
vault_token_file: str,
|
|
411
|
+
secret_allowlist: Optional[Set[str]],
|
|
412
|
+
) -> bool:
|
|
413
|
+
secret_dir = os.path.join(soa_dir, service, "secrets")
|
|
414
|
+
secret_provider_kwargs = {
|
|
415
|
+
"vault_cluster_config": vault_cluster_config,
|
|
416
|
+
# TODO: make vault-tools support k8s auth method so we don't have to
|
|
417
|
+
# mount a token in.
|
|
418
|
+
"vault_auth_method": "token",
|
|
419
|
+
"vault_token_file": vault_token_file,
|
|
420
|
+
}
|
|
421
|
+
secret_provider = get_secret_provider(
|
|
422
|
+
secret_provider_name=secret_provider_name,
|
|
423
|
+
soa_dir=soa_dir,
|
|
424
|
+
service_name=service,
|
|
425
|
+
cluster_names=[cluster],
|
|
426
|
+
secret_provider_kwargs=secret_provider_kwargs,
|
|
427
|
+
)
|
|
428
|
+
if not os.path.isdir(secret_dir):
|
|
429
|
+
log.debug(f"No secrets dir for {service}")
|
|
430
|
+
return True
|
|
431
|
+
|
|
432
|
+
with os.scandir(secret_dir) as secret_file_paths:
|
|
433
|
+
for secret_file_path in secret_file_paths:
|
|
434
|
+
if secret_file_path.path.endswith("json"):
|
|
435
|
+
secret = secret_file_path.name.replace(".json", "")
|
|
436
|
+
if secret_allowlist is not None:
|
|
437
|
+
if secret not in secret_allowlist:
|
|
438
|
+
log.debug(
|
|
439
|
+
f"Skipping {service}.{secret} in {namespace} because it's not in in secret_allowlist"
|
|
440
|
+
)
|
|
441
|
+
continue
|
|
442
|
+
|
|
443
|
+
with open(secret_file_path, "r") as secret_file:
|
|
444
|
+
secret_signature = secret_provider.get_secret_signature_from_data(
|
|
445
|
+
json.load(secret_file)
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
if secret_signature:
|
|
449
|
+
create_or_update_k8s_secret(
|
|
450
|
+
service=service,
|
|
451
|
+
signature_name=get_paasta_secret_signature_name(
|
|
452
|
+
namespace, service, sanitise_kubernetes_name(secret)
|
|
453
|
+
),
|
|
454
|
+
secret_name=get_paasta_secret_name(
|
|
455
|
+
namespace, service, sanitise_kubernetes_name(secret)
|
|
456
|
+
),
|
|
457
|
+
get_secret_data=(
|
|
458
|
+
lambda: {
|
|
459
|
+
secret: base64.b64encode(
|
|
460
|
+
# If signatures does not match, it'll sys.exit(1)
|
|
461
|
+
secret_provider.decrypt_secret_raw(secret)
|
|
462
|
+
).decode("utf-8")
|
|
463
|
+
}
|
|
464
|
+
),
|
|
465
|
+
secret_signature=secret_signature,
|
|
466
|
+
kube_client=kube_client,
|
|
467
|
+
namespace=namespace,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
return True
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def sync_datastore_credentials(
|
|
474
|
+
kube_client: KubeClient,
|
|
475
|
+
cluster: str,
|
|
476
|
+
service: str,
|
|
477
|
+
secret_provider_name: str,
|
|
478
|
+
vault_cluster_config: Dict[str, str],
|
|
479
|
+
soa_dir: str,
|
|
480
|
+
vault_token_file: str,
|
|
481
|
+
overwrite_namespace: Optional[str] = None,
|
|
482
|
+
) -> bool:
|
|
483
|
+
"""
|
|
484
|
+
Map all the passwords requested for this service-instance to a single Kubernetes Secret store.
|
|
485
|
+
Volume mounts will then map the associated secrets to their associated mount paths.
|
|
486
|
+
"""
|
|
487
|
+
config_loader = PaastaServiceConfigLoader(service=service, soa_dir=soa_dir)
|
|
488
|
+
system_paasta_config = load_system_paasta_config()
|
|
489
|
+
datastore_credentials_vault_overrides = (
|
|
490
|
+
system_paasta_config.get_datastore_credentials_vault_overrides()
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
for instance_type_class in K8S_INSTANCE_TYPE_CLASSES:
|
|
494
|
+
for instance_config in config_loader.instance_configs(
|
|
495
|
+
cluster=cluster, instance_type_class=instance_type_class
|
|
496
|
+
):
|
|
497
|
+
namespace = (
|
|
498
|
+
overwrite_namespace
|
|
499
|
+
if overwrite_namespace is not None
|
|
500
|
+
else instance_config.get_namespace()
|
|
501
|
+
)
|
|
502
|
+
datastore_credentials = instance_config.get_datastore_credentials()
|
|
503
|
+
with set_temporary_environment_variables(
|
|
504
|
+
datastore_credentials_vault_overrides
|
|
505
|
+
):
|
|
506
|
+
# expects VAULT_ADDR_OVERRIDE, VAULT_CA_OVERRIDE, and VAULT_TOKEN_OVERRIDE to be set
|
|
507
|
+
# in order to use a custom vault shard. overriden temporarily in this context
|
|
508
|
+
provider = get_secret_provider(
|
|
509
|
+
secret_provider_name=secret_provider_name,
|
|
510
|
+
soa_dir=soa_dir,
|
|
511
|
+
service_name=service,
|
|
512
|
+
cluster_names=[cluster],
|
|
513
|
+
# overridden by env variables but still needed here for spec validation
|
|
514
|
+
secret_provider_kwargs={
|
|
515
|
+
"vault_cluster_config": vault_cluster_config,
|
|
516
|
+
"vault_auth_method": "token",
|
|
517
|
+
"vault_token_file": vault_token_file,
|
|
518
|
+
},
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
secret_data = {}
|
|
522
|
+
for datastore, credentials in datastore_credentials.items():
|
|
523
|
+
# mypy loses type hints on '.items' and throws false positives. unfortunately have to type: ignore
|
|
524
|
+
# https://github.com/python/mypy/issues/7178
|
|
525
|
+
for credential in credentials: # type: ignore
|
|
526
|
+
vault_path = f"secrets/datastore/{datastore}/{credential}"
|
|
527
|
+
secrets = provider.get_data_from_vault_path(vault_path)
|
|
528
|
+
if not secrets:
|
|
529
|
+
# no secrets found at this path. skip syncing
|
|
530
|
+
log.debug(
|
|
531
|
+
f"Warning: no secrets found at requested path {vault_path}."
|
|
532
|
+
)
|
|
533
|
+
continue
|
|
534
|
+
|
|
535
|
+
# decrypt and save in secret_data
|
|
536
|
+
vault_key_path = get_vault_key_secret_name(vault_path)
|
|
537
|
+
|
|
538
|
+
# kubernetes expects data to be base64 encoded binary in utf-8 when put into secret maps
|
|
539
|
+
# may look like:
|
|
540
|
+
# {'master': {'passwd': '****', 'user': 'v-approle-mysql-serv-nVcYexH95A2'}, 'reporting': {'passwd': '****', 'user': 'v-approle-mysql-serv-GgCpRIh9Ut7'}, 'slave': {'passwd': '****', 'user': 'v-approle-mysql-serv-PzjPwqNMbqu'}
|
|
541
|
+
secret_data[vault_key_path] = base64.b64encode(
|
|
542
|
+
json.dumps(secrets).encode("utf-8")
|
|
543
|
+
).decode("utf-8")
|
|
544
|
+
|
|
545
|
+
create_or_update_k8s_secret(
|
|
546
|
+
service=service,
|
|
547
|
+
signature_name=instance_config.get_datastore_credentials_signature_name(),
|
|
548
|
+
secret_name=instance_config.get_datastore_credentials_secret_name(),
|
|
549
|
+
get_secret_data=(lambda: secret_data),
|
|
550
|
+
secret_signature=_get_dict_signature(secret_data),
|
|
551
|
+
kube_client=kube_client,
|
|
552
|
+
namespace=namespace,
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
return True
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def sync_crypto_secrets(
|
|
559
|
+
kube_client: KubeClient,
|
|
560
|
+
cluster: str,
|
|
561
|
+
service: str,
|
|
562
|
+
secret_provider_name: str,
|
|
563
|
+
vault_cluster_config: Dict[str, str],
|
|
564
|
+
soa_dir: str,
|
|
565
|
+
vault_token_file: str,
|
|
566
|
+
) -> bool:
|
|
567
|
+
"""
|
|
568
|
+
For each key-name in `crypto_key`,
|
|
569
|
+
1. Fetch all versions of the key-name from Vault superregion mapped from cluster, e.g. `kubestage` maps to `devc` Vault server.
|
|
570
|
+
2. Create K8s secret from JSON blob containing all key versions.
|
|
571
|
+
3. Create signatures as K8s configmap based on JSON blob hash.
|
|
572
|
+
|
|
573
|
+
So each replica of a service instance gets the same key, thereby reducing requests to Vault API as we only talk to vault during secret syncing
|
|
574
|
+
"""
|
|
575
|
+
config_loader = PaastaServiceConfigLoader(service=service, soa_dir=soa_dir)
|
|
576
|
+
for instance_type_class in K8S_INSTANCE_TYPE_CLASSES:
|
|
577
|
+
for instance_config in config_loader.instance_configs(
|
|
578
|
+
cluster=cluster, instance_type_class=instance_type_class
|
|
579
|
+
):
|
|
580
|
+
crypto_keys = instance_config.get_crypto_keys_from_config()
|
|
581
|
+
if not crypto_keys:
|
|
582
|
+
continue
|
|
583
|
+
secret_data = {}
|
|
584
|
+
provider = get_secret_provider(
|
|
585
|
+
secret_provider_name=secret_provider_name,
|
|
586
|
+
soa_dir=soa_dir,
|
|
587
|
+
service_name=service,
|
|
588
|
+
cluster_names=[cluster],
|
|
589
|
+
secret_provider_kwargs={
|
|
590
|
+
"vault_cluster_config": vault_cluster_config,
|
|
591
|
+
"vault_auth_method": "token",
|
|
592
|
+
"vault_token_file": vault_token_file,
|
|
593
|
+
},
|
|
594
|
+
)
|
|
595
|
+
for key in crypto_keys:
|
|
596
|
+
key_versions = provider.get_key_versions(key)
|
|
597
|
+
if not key_versions:
|
|
598
|
+
log.error(
|
|
599
|
+
f"No key versions found for {key} on {instance_config.get_sanitised_deployment_name()}"
|
|
600
|
+
)
|
|
601
|
+
continue
|
|
602
|
+
|
|
603
|
+
secret_data[get_vault_key_secret_name(key)] = base64.b64encode(
|
|
604
|
+
json.dumps(key_versions).encode("utf-8")
|
|
605
|
+
).decode("utf-8")
|
|
606
|
+
|
|
607
|
+
if not secret_data:
|
|
608
|
+
continue
|
|
609
|
+
|
|
610
|
+
create_or_update_k8s_secret(
|
|
611
|
+
service=service,
|
|
612
|
+
signature_name=instance_config.get_crypto_secret_signature_name(),
|
|
613
|
+
# the secret name here must match the secret name given in the secret volume config,
|
|
614
|
+
# i.e. `kubernetes.client.V1SecretVolumeSource`'s `secret_name` must match below
|
|
615
|
+
secret_name=instance_config.get_crypto_secret_name(),
|
|
616
|
+
get_secret_data=(lambda: secret_data),
|
|
617
|
+
secret_signature=_get_dict_signature(secret_data),
|
|
618
|
+
kube_client=kube_client,
|
|
619
|
+
namespace=instance_config.get_namespace(),
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
return True
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def sync_boto_secrets(
|
|
626
|
+
kube_client: KubeClient,
|
|
627
|
+
cluster: str,
|
|
628
|
+
service: str,
|
|
629
|
+
soa_dir: str,
|
|
630
|
+
) -> bool:
|
|
631
|
+
config_loader = PaastaServiceConfigLoader(service=service, soa_dir=soa_dir)
|
|
632
|
+
for instance_type_class in K8S_INSTANCE_TYPE_CLASSES:
|
|
633
|
+
for instance_config in config_loader.instance_configs(
|
|
634
|
+
cluster=cluster, instance_type_class=instance_type_class
|
|
635
|
+
):
|
|
636
|
+
boto_keys = instance_config.config_dict.get("boto_keys", [])
|
|
637
|
+
if not boto_keys:
|
|
638
|
+
continue
|
|
639
|
+
boto_keys.sort()
|
|
640
|
+
secret_data = {}
|
|
641
|
+
for key in boto_keys:
|
|
642
|
+
for filetype in ["sh", "yaml", "json", "cfg"]:
|
|
643
|
+
this_key = key + "." + filetype
|
|
644
|
+
sanitised_key = this_key.replace(".", "-").replace("_", "--")
|
|
645
|
+
try:
|
|
646
|
+
with open(f"/etc/boto_cfg_private/{this_key}") as f:
|
|
647
|
+
secret_data[sanitised_key] = base64.b64encode(
|
|
648
|
+
f.read().encode("utf-8")
|
|
649
|
+
).decode("utf-8")
|
|
650
|
+
except IOError:
|
|
651
|
+
log.warning(
|
|
652
|
+
f"Boto key {this_key} required for {service} could not be found."
|
|
653
|
+
)
|
|
654
|
+
secret_data[sanitised_key] = base64.b64encode(
|
|
655
|
+
"This user no longer exists. Remove it from boto_keys.".encode(
|
|
656
|
+
"utf-8"
|
|
657
|
+
)
|
|
658
|
+
).decode("utf-8")
|
|
659
|
+
|
|
660
|
+
if not secret_data:
|
|
661
|
+
continue
|
|
662
|
+
|
|
663
|
+
create_or_update_k8s_secret(
|
|
664
|
+
service=service,
|
|
665
|
+
signature_name=instance_config.get_boto_secret_signature_name(),
|
|
666
|
+
secret_name=instance_config.get_boto_secret_name(),
|
|
667
|
+
get_secret_data=(lambda: secret_data),
|
|
668
|
+
secret_signature=_get_dict_signature(secret_data),
|
|
669
|
+
kube_client=kube_client,
|
|
670
|
+
namespace=instance_config.get_namespace(),
|
|
671
|
+
)
|
|
672
|
+
return True
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def _get_dict_signature(data: Dict[str, str]) -> str:
|
|
676
|
+
return hashlib.sha1(
|
|
677
|
+
"|".join(f"{key}:{value}" for key, value in data.items()).encode("utf-8")
|
|
678
|
+
).hexdigest()
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def create_or_update_k8s_secret(
|
|
682
|
+
service: str,
|
|
683
|
+
secret_name: str,
|
|
684
|
+
signature_name: str,
|
|
685
|
+
get_secret_data: Callable[[], Dict[str, str]],
|
|
686
|
+
secret_signature: str,
|
|
687
|
+
kube_client: KubeClient,
|
|
688
|
+
namespace: str,
|
|
689
|
+
) -> None:
|
|
690
|
+
"""
|
|
691
|
+
:param get_secret_data: is a function to postpone fetching data in order to reduce service load, e.g. Vault API
|
|
692
|
+
"""
|
|
693
|
+
# In order to prevent slamming the k8s API, add some artificial delay here
|
|
694
|
+
delay = load_system_paasta_config().get_secret_sync_delay_seconds()
|
|
695
|
+
if delay:
|
|
696
|
+
time.sleep(delay)
|
|
697
|
+
|
|
698
|
+
kubernetes_signature = get_secret_signature(
|
|
699
|
+
kube_client=kube_client,
|
|
700
|
+
signature_name=signature_name,
|
|
701
|
+
namespace=namespace,
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
if not kubernetes_signature:
|
|
705
|
+
log.info(f"{secret_name} for {service} in {namespace} not found, creating")
|
|
706
|
+
try:
|
|
707
|
+
create_secret(
|
|
708
|
+
kube_client=kube_client,
|
|
709
|
+
service_name=service,
|
|
710
|
+
secret_name=secret_name,
|
|
711
|
+
secret_data=get_secret_data(),
|
|
712
|
+
namespace=namespace,
|
|
713
|
+
)
|
|
714
|
+
except ApiException as e:
|
|
715
|
+
if e.status == 409:
|
|
716
|
+
log.warning(
|
|
717
|
+
f"Secret {secret_name} for {service} already exists in {namespace} but no signature found. Updating secret and signature."
|
|
718
|
+
)
|
|
719
|
+
update_secret(
|
|
720
|
+
kube_client=kube_client,
|
|
721
|
+
secret_name=secret_name,
|
|
722
|
+
secret_data=get_secret_data(),
|
|
723
|
+
service_name=service,
|
|
724
|
+
namespace=namespace,
|
|
725
|
+
)
|
|
726
|
+
else:
|
|
727
|
+
raise
|
|
728
|
+
create_secret_signature(
|
|
729
|
+
kube_client=kube_client,
|
|
730
|
+
service_name=service,
|
|
731
|
+
signature_name=signature_name,
|
|
732
|
+
secret_signature=secret_signature,
|
|
733
|
+
namespace=namespace,
|
|
734
|
+
)
|
|
735
|
+
elif secret_signature != kubernetes_signature:
|
|
736
|
+
log.info(
|
|
737
|
+
f"{secret_name} for {service} in {namespace} needs updating as signature changed"
|
|
738
|
+
)
|
|
739
|
+
update_secret(
|
|
740
|
+
kube_client=kube_client,
|
|
741
|
+
secret_name=secret_name,
|
|
742
|
+
secret_data=get_secret_data(),
|
|
743
|
+
service_name=service,
|
|
744
|
+
namespace=namespace,
|
|
745
|
+
)
|
|
746
|
+
update_secret_signature(
|
|
747
|
+
kube_client=kube_client,
|
|
748
|
+
service_name=service,
|
|
749
|
+
signature_name=signature_name,
|
|
750
|
+
secret_signature=secret_signature,
|
|
751
|
+
namespace=namespace,
|
|
752
|
+
)
|
|
753
|
+
else:
|
|
754
|
+
log.info(f"{secret_name} for {service} in {namespace} up to date")
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
if __name__ == "__main__":
|
|
758
|
+
main()
|