From 56ce685231decf559dd883ee5745d91f629c117a Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Fri, 21 Jun 2024 15:56:19 -0600 Subject: [PATCH 01/23] Add prompt for ssl certificate and key for HAProxy, add to tf variables. Set tls config via settings yaml crts directory. Add instructions for TLS termination to README --- anvil-python/anvil/commands/haproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 444c993..9ea6dce 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -12,7 +12,6 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. - import ipaddress import logging import os.path @@ -130,6 +129,7 @@ def __init__( ) self.preseed = deployment_preseed or {} self.accept_defaults = accept_defaults + self.variables: dict[str, Any] = {"charm_haproxy_config": {}} def get_application_timeout(self) -> int: return HAPROXY_APP_TIMEOUT From 478f5125bcca29a631104aef3b657dc066c5c750 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Wed, 10 Jul 2024 13:57:30 -0600 Subject: [PATCH 02/23] Use separate terraform variable to set services yaml --- anvil-python/anvil/commands/haproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 9ea6dce..4f1442f 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -129,7 +129,7 @@ def __init__( ) self.preseed = deployment_preseed or {} self.accept_defaults = accept_defaults - self.variables: dict[str, Any] = {"charm_haproxy_config": {}} + self.use_tls_termination = False def get_application_timeout(self) -> int: return HAPROXY_APP_TIMEOUT From 3e921df23bf97faf8ef8ccb71d823fdfa2cdc684 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Wed, 10 Jul 2024 14:25:30 -0600 Subject: [PATCH 03/23] Formatting --- anvil-python/anvil/commands/haproxy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 4f1442f..bc0df54 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -12,6 +12,7 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. + import ipaddress import logging import os.path From 8fcfb81eb4287fc16c186cab55c7315a70bed1fd Mon Sep 17 00:00:00 2001 From: Stamatis Katsaounis Date: Mon, 15 Jul 2024 14:29:42 +0300 Subject: [PATCH 04/23] suggestions --- anvil-python/anvil/commands/haproxy.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index bc0df54..9f88f6b 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -130,7 +130,6 @@ def __init__( ) self.preseed = deployment_preseed or {} self.accept_defaults = accept_defaults - self.use_tls_termination = False def get_application_timeout(self) -> int: return HAPROXY_APP_TIMEOUT @@ -168,7 +167,6 @@ def prompt(self, console: Console | None = None) -> None: previous_answers=variables, accept_defaults=self.accept_defaults, ) - cert_filepath = haproxy_config_bank.ssl_cert.ask() variables["ssl_cert"] = cert_filepath key_filepath = haproxy_config_bank.ssl_key.ask() From 6b924f3faa64a73bf2ccb1e26fd95f72b19ace28 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Thu, 1 Aug 2024 14:19:40 -0600 Subject: [PATCH 05/23] Add https redirect, bind agent service to IP address in local network, add acl for access to port 80 --- anvil-python/anvil/commands/haproxy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 9f88f6b..cf95626 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -29,6 +29,7 @@ AddMachineUnitsStep, DeployMachineApplicationStep, ) +from sunbeam.utils import get_local_ip_by_default_route from anvil.jobs.manifest import Manifest from anvil.jobs.steps import RemoveMachineUnitStep From 6318e352d680dd5302ad538e6d53f720f23ca7fd Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Wed, 14 Aug 2024 08:00:54 -0600 Subject: [PATCH 06/23] get management cdirs from database. better error handling around verifying cert/key files --- anvil-python/anvil/commands/haproxy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index cf95626..53e0cfe 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -71,7 +71,6 @@ def validate_key_file(filepath: str) -> None: except PermissionError: raise ValueError(f"Permission denied when trying to read {filepath}") - def validate_virtual_ip(value: str) -> None: """We allow passing an empty IP for virtual_ip""" if value == "": From b89840d6f6a7bd344f351edae800592cb0761eb6 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Mon, 26 Aug 2024 15:47:50 -0600 Subject: [PATCH 07/23] Add configuration option for maas-region to setup TLS Termination on it's end. Change agent-service to agent_service to comply with underscore convention --- anvil-python/anvil/commands/haproxy.py | 1 - cloud/etc/deploy-maas-region/variables.tf | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 53e0cfe..0f03d16 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -29,7 +29,6 @@ AddMachineUnitsStep, DeployMachineApplicationStep, ) -from sunbeam.utils import get_local_ip_by_default_route from anvil.jobs.manifest import Manifest from anvil.jobs.steps import RemoveMachineUnitStep diff --git a/cloud/etc/deploy-maas-region/variables.tf b/cloud/etc/deploy-maas-region/variables.tf index 6d794fa..c782420 100644 --- a/cloud/etc/deploy-maas-region/variables.tf +++ b/cloud/etc/deploy-maas-region/variables.tf @@ -53,3 +53,9 @@ variable "tls_mode" { type = string default = "" } + +variable "tls_mode" { + description = "TLS Mode for MAAS Region charm ('', 'termination', or 'passthrough')" + type = string + default = "" +} \ No newline at end of file From 480cc4d43f271024228f81333909d404b2449ba3 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Tue, 27 Aug 2024 14:02:26 -0600 Subject: [PATCH 08/23] Add question for tls mode --- anvil-python/anvil/commands/haproxy.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 0f03d16..05f6aa7 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -42,6 +42,7 @@ HAPROXY_UNIT_TIMEOUT = ( 1200 # 15 minutes, adding / removing units can take a long time ) +VALID_TLS_MODES = ["termination", "passthrough"] LOG = logging.getLogger(__name__) @@ -79,6 +80,10 @@ def validate_virtual_ip(value: str) -> None: except ValueError as e: raise ValueError(f"{value} is not a valid IP address: {e}") +def validate_tls_mode(value: str) -> None: + if value not in VALID_TLS_MODES: + raise ValueError(f"TLS Mode must be one of {VALID_TLS_MODES}") + def haproxy_questions() -> dict[str, questions.PromptQuestion]: return { @@ -97,6 +102,11 @@ def haproxy_questions() -> dict[str, questions.PromptQuestion]: default_value="", validation_function=validate_key_file, ), + "tls_mode": questions.PromptQuestion( + "TLS termination at HA Proxy (\"termination\"), or passthrough to MAAS (\"passthrough\")?", + default_value="termination", + validation_function=validate_tls_mode, + ), } @@ -153,11 +163,13 @@ def prompt(self, console: Console | None = None) -> None: variables.setdefault("virtual_ip", "") variables.setdefault("ssl_cert", "") variables.setdefault("ssl_key", "") + variables.setdefault("tls_mode", "termination") # Set defaults self.preseed.setdefault("virtual_ip", "") self.preseed.setdefault("ssl_cert", "") self.preseed.setdefault("ssl_key", "") + self.preseed.setdefault("tls_mode", "termination") haproxy_config_bank = questions.QuestionBank( questions=haproxy_questions(), @@ -170,6 +182,10 @@ def prompt(self, console: Console | None = None) -> None: variables["ssl_cert"] = cert_filepath key_filepath = haproxy_config_bank.ssl_key.ask() variables["ssl_key"] = key_filepath + tls_mode = "" + if variables["ssl_cert"] is not None: + tls_mode = haproxy_config_bank.tls_mode.ask() + variables["tls_mode"] = tls_mode virtual_ip = haproxy_config_bank.virtual_ip.ask() variables["virtual_ip"] = virtual_ip @@ -189,13 +205,14 @@ def extra_tfvars(self) -> dict[str, Any]: with open(key_filepath) as key_file: variables["ssl_key_content"] = key_file.read() variables["haproxy_port"] = 443 - variables["haproxy_services_yaml"] = self.get_tls_services_yaml() + variables["haproxy_services_yaml"] = self.get_tls_services_yaml(variables["tls_mode"]) else: variables["haproxy_port"] = 80 # Terraform does not need the content of these answers variables.pop("ssl_cert", None) variables.pop("ssl_key", None) + variables.pop("tls_mode", None) LOG.debug(f"extra tfvars: {variables}") return variables @@ -207,7 +224,7 @@ def get_management_cidrs(self) -> list[str]: ) return answers["bootstrap"]["management_cidr"].split(",") - def get_tls_services_yaml(self) -> str: + def get_tls_services_yaml(self, tls_mode: str) -> str: """Get the HAProxy services.yaml for TLS, inserting the VIP for the frontend bind""" cidrs = self.get_management_cidrs() services: str = ( @@ -219,7 +236,7 @@ def get_tls_services_yaml(self) -> str: - cookie SRVNAME insert - http-request redirect scheme https unless { ssl_fc } server_options: maxconn 100 cookie S{i} check - crts: [DEFAULT] + """ + ("crts: [DEFAULT]" if tls_mode == "termination" else "") + """ - service_name: agent_service service_host: 0.0.0.0 service_port: 80 From ad221dd9f8dd4577b2a2bf6057955c6698b3a984 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Wed, 28 Aug 2024 15:48:51 -0600 Subject: [PATCH 09/23] Pass ssl_cert and ssl_key from haproxy question bank to maas-region charm config --- cloud/etc/deploy-maas-region/main.tf | 4 ++++ cloud/etc/deploy-maas-region/variables.tf | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cloud/etc/deploy-maas-region/main.tf b/cloud/etc/deploy-maas-region/main.tf index 8c734be..24a3e15 100644 --- a/cloud/etc/deploy-maas-region/main.tf +++ b/cloud/etc/deploy-maas-region/main.tf @@ -32,6 +32,8 @@ data "juju_model" "machine_model" { locals { tls_mode = var.tls_mode != "" ? { tls_mode = var.tls_mode } : {} + ssl_cert = var.ssl_cert != "" ? { ssl_cert = var.ssl_cert } : {} + ssl_key = var.ssl_key != "" ? { ssl_key = var.ssl_key } : {} } resource "juju_application" "maas-region" { @@ -48,6 +50,8 @@ resource "juju_application" "maas-region" { config = merge( local.tls_mode, + local.ssl_cert, + local.ssl_key, var.charm_maas_region_config, ) } diff --git a/cloud/etc/deploy-maas-region/variables.tf b/cloud/etc/deploy-maas-region/variables.tf index c782420..c743889 100644 --- a/cloud/etc/deploy-maas-region/variables.tf +++ b/cloud/etc/deploy-maas-region/variables.tf @@ -58,4 +58,16 @@ variable "tls_mode" { description = "TLS Mode for MAAS Region charm ('', 'termination', or 'passthrough')" type = string default = "" -} \ No newline at end of file +} + +variable "ssl_cert" { + description = "Path to SSL certificate for tls_mode=passthrough" + type = string + default = "" +} + +variable "ssl_key" { + description = "Path to SSL private key for tls_mode=passthrough" + type = string + default = "" +} From f46eb288ec9e1adf29ae82c2bb3a912fb0bcf91f Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Wed, 4 Sep 2024 15:50:02 -0600 Subject: [PATCH 10/23] Pass contents of ssl key/cert to maas-region variables instead of filepath. Let maas-region handle creating the files in the proper place --- anvil-python/anvil/commands/haproxy.py | 51 +++++++++++++++++++---- cloud/etc/deploy-maas-region/main.tf | 8 ++-- cloud/etc/deploy-maas-region/variables.tf | 8 ++-- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 05f6aa7..d4f66f0 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -46,9 +46,19 @@ LOG = logging.getLogger(__name__) +<<<<<<< HEAD def validate_cert_file(filepath: str) -> None: if filepath == "": return +======= +def validate_cert_file(filepath: str | None) -> None: + if filepath is None: + # This question is only asked when tls_mode is "termination" or "passthrough" + # so not supplying a file is not an option. + raise ValueError( + "Please provide a certificate file when enabling TLS." + ) +>>>>>>> 9050d1912 (Pass contents of ssl key/cert to maas-region variables instead of filepath. Let maas-region handle creating the files in the proper place) if not os.path.isfile(filepath): raise ValueError(f"{filepath} does not exist") try: @@ -59,9 +69,19 @@ def validate_cert_file(filepath: str) -> None: raise ValueError(f"Permission denied when trying to read {filepath}") +<<<<<<< HEAD def validate_key_file(filepath: str) -> None: if filepath == "": return +======= +def validate_key_file(filepath: str | None) -> None: + if filepath is None: + # This question is only asked when tls_mode is "termination" or "passthrough" + # so not supplying a file is not an option. + raise ValueError( + "Please provide a certificate file when enabling TLS." + ) +>>>>>>> 9050d1912 (Pass contents of ssl key/cert to maas-region variables instead of filepath. Let maas-region handle creating the files in the proper place) if not os.path.isfile(filepath): raise ValueError(f"{filepath} does not exist") try: @@ -80,6 +100,7 @@ def validate_virtual_ip(value: str) -> None: except ValueError as e: raise ValueError(f"{value} is not a valid IP address: {e}") + def validate_tls_mode(value: str) -> None: if value not in VALID_TLS_MODES: raise ValueError(f"TLS Mode must be one of {VALID_TLS_MODES}") @@ -103,8 +124,8 @@ def haproxy_questions() -> dict[str, questions.PromptQuestion]: validation_function=validate_key_file, ), "tls_mode": questions.PromptQuestion( - "TLS termination at HA Proxy (\"termination\"), or passthrough to MAAS (\"passthrough\")?", - default_value="termination", + 'TLS termination at HA Proxy ("termination"), passthrough to MAAS ("passthrough"), or no TLS ("")?', + default_value="", validation_function=validate_tls_mode, ), } @@ -186,6 +207,13 @@ def prompt(self, console: Console | None = None) -> None: if variables["ssl_cert"] is not None: tls_mode = haproxy_config_bank.tls_mode.ask() variables["tls_mode"] = tls_mode + if tls_mode: + cert_filepath = haproxy_config_bank.ssl_cert.ask() + key_filepath = haproxy_config_bank.ssl_key.ask() + with open(cert_filepath) as cert_file: + variables["ssl_cert_content"] = cert_file.read() + with open(key_filepath) as key_file: + variables["ssl_key_content"] = key_file.read() virtual_ip = haproxy_config_bank.virtual_ip.ask() variables["virtual_ip"] = virtual_ip @@ -197,6 +225,7 @@ def extra_tfvars(self) -> dict[str, Any]: self.client, self._HAPROXY_CONFIG ) +<<<<<<< HEAD cert_filepath = variables["ssl_cert"] key_filepath = variables["ssl_key"] if cert_filepath != "" and key_filepath != "": @@ -204,14 +233,17 @@ def extra_tfvars(self) -> dict[str, Any]: variables["ssl_cert_content"] = cert_file.read() with open(key_filepath) as key_file: variables["ssl_key_content"] = key_file.read() +======= + if variables["tls_mode"]: +>>>>>>> 9050d1912 (Pass contents of ssl key/cert to maas-region variables instead of filepath. Let maas-region handle creating the files in the proper place) variables["haproxy_port"] = 443 - variables["haproxy_services_yaml"] = self.get_tls_services_yaml(variables["tls_mode"]) + variables["haproxy_services_yaml"] = self.get_tls_services_yaml( + variables["tls_mode"] + ) else: variables["haproxy_port"] = 80 # Terraform does not need the content of these answers - variables.pop("ssl_cert", None) - variables.pop("ssl_key", None) variables.pop("tls_mode", None) LOG.debug(f"extra tfvars: {variables}") @@ -234,9 +266,12 @@ def get_tls_services_yaml(self, tls_mode: str) -> str: service_options: - balance leastconn - cookie SRVNAME insert - - http-request redirect scheme https unless { ssl_fc } - server_options: maxconn 100 cookie S{i} check - """ + ("crts: [DEFAULT]" if tls_mode == "termination" else "") + """ + - http-request redirect scheme https unless { ssl_fc }""" + + ("\n - mode tcp" if tls_mode == "passthrough" else "") + + """ + server_options: maxconn 100 cookie S{i} check""" + + ("\n crts: [DEFAULT]" if tls_mode == "termination" else "") + + """ - service_name: agent_service service_host: 0.0.0.0 service_port: 80 diff --git a/cloud/etc/deploy-maas-region/main.tf b/cloud/etc/deploy-maas-region/main.tf index 24a3e15..ba94aaf 100644 --- a/cloud/etc/deploy-maas-region/main.tf +++ b/cloud/etc/deploy-maas-region/main.tf @@ -32,8 +32,8 @@ data "juju_model" "machine_model" { locals { tls_mode = var.tls_mode != "" ? { tls_mode = var.tls_mode } : {} - ssl_cert = var.ssl_cert != "" ? { ssl_cert = var.ssl_cert } : {} - ssl_key = var.ssl_key != "" ? { ssl_key = var.ssl_key } : {} + ssl_cert_content = var.ssl_cert_content != "" ? { ssl_cert_content = var.ssl_cert_content } : {} + ssl_key_content = var.ssl_key_content != "" ? { ssl_key_content = var.ssl_key_content } : {} } resource "juju_application" "maas-region" { @@ -50,8 +50,8 @@ resource "juju_application" "maas-region" { config = merge( local.tls_mode, - local.ssl_cert, - local.ssl_key, + local.ssl_cert_content, + local.ssl_key_content, var.charm_maas_region_config, ) } diff --git a/cloud/etc/deploy-maas-region/variables.tf b/cloud/etc/deploy-maas-region/variables.tf index c743889..df24549 100644 --- a/cloud/etc/deploy-maas-region/variables.tf +++ b/cloud/etc/deploy-maas-region/variables.tf @@ -60,14 +60,14 @@ variable "tls_mode" { default = "" } -variable "ssl_cert" { - description = "Path to SSL certificate for tls_mode=passthrough" +variable "ssl_cert_content" { + description = "SSL certificate for tls_mode=passthrough" type = string default = "" } -variable "ssl_key" { - description = "Path to SSL private key for tls_mode=passthrough" +variable "ssl_key_content" { + description = "SSL private key for tls_mode=passthrough" type = string default = "" } From fb2f7dd7a4d1b84d8e001b49f5f4c20062d0be40 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Wed, 4 Sep 2024 16:53:21 -0600 Subject: [PATCH 11/23] Merge cleanup --- anvil-python/anvil/commands/haproxy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index d4f66f0..65debcc 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -43,7 +43,6 @@ 1200 # 15 minutes, adding / removing units can take a long time ) VALID_TLS_MODES = ["termination", "passthrough"] -LOG = logging.getLogger(__name__) <<<<<<< HEAD From d0f7b3ccf584e5e18f532abbf309bb5f2133a2ba Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Wed, 4 Sep 2024 16:55:05 -0600 Subject: [PATCH 12/23] remove duplicate tls_mode variable in maas-region variables.tf --- cloud/etc/deploy-maas-region/variables.tf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cloud/etc/deploy-maas-region/variables.tf b/cloud/etc/deploy-maas-region/variables.tf index df24549..9b1433e 100644 --- a/cloud/etc/deploy-maas-region/variables.tf +++ b/cloud/etc/deploy-maas-region/variables.tf @@ -54,12 +54,6 @@ variable "tls_mode" { default = "" } -variable "tls_mode" { - description = "TLS Mode for MAAS Region charm ('', 'termination', or 'passthrough')" - type = string - default = "" -} - variable "ssl_cert_content" { description = "SSL certificate for tls_mode=passthrough" type = string From 022f1248aa213f668d27e90908d319206eb8a702 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Thu, 5 Sep 2024 07:45:17 -0600 Subject: [PATCH 13/23] revert default values for ssl_cert and ssl_key question answers to empty string instead of None. Make default tls_mode value 'disabled' instead of empty string --- anvil-python/anvil/commands/haproxy.py | 36 +++++------------------ cloud/etc/deploy-maas-region/variables.tf | 2 +- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 65debcc..e245a87 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -42,22 +42,13 @@ HAPROXY_UNIT_TIMEOUT = ( 1200 # 15 minutes, adding / removing units can take a long time ) -VALID_TLS_MODES = ["termination", "passthrough"] +VALID_TLS_MODES = ["termination", "passthrough", "disabled"] + -<<<<<<< HEAD def validate_cert_file(filepath: str) -> None: if filepath == "": return -======= -def validate_cert_file(filepath: str | None) -> None: - if filepath is None: - # This question is only asked when tls_mode is "termination" or "passthrough" - # so not supplying a file is not an option. - raise ValueError( - "Please provide a certificate file when enabling TLS." - ) ->>>>>>> 9050d1912 (Pass contents of ssl key/cert to maas-region variables instead of filepath. Let maas-region handle creating the files in the proper place) if not os.path.isfile(filepath): raise ValueError(f"{filepath} does not exist") try: @@ -68,19 +59,9 @@ def validate_cert_file(filepath: str | None) -> None: raise ValueError(f"Permission denied when trying to read {filepath}") -<<<<<<< HEAD def validate_key_file(filepath: str) -> None: if filepath == "": return -======= -def validate_key_file(filepath: str | None) -> None: - if filepath is None: - # This question is only asked when tls_mode is "termination" or "passthrough" - # so not supplying a file is not an option. - raise ValueError( - "Please provide a certificate file when enabling TLS." - ) ->>>>>>> 9050d1912 (Pass contents of ssl key/cert to maas-region variables instead of filepath. Let maas-region handle creating the files in the proper place) if not os.path.isfile(filepath): raise ValueError(f"{filepath} does not exist") try: @@ -183,13 +164,13 @@ def prompt(self, console: Console | None = None) -> None: variables.setdefault("virtual_ip", "") variables.setdefault("ssl_cert", "") variables.setdefault("ssl_key", "") - variables.setdefault("tls_mode", "termination") + variables.setdefault("tls_mode", "disabled") # Set defaults self.preseed.setdefault("virtual_ip", "") self.preseed.setdefault("ssl_cert", "") self.preseed.setdefault("ssl_key", "") - self.preseed.setdefault("tls_mode", "termination") + self.preseed.setdefault("tls_mode", "disabled") haproxy_config_bank = questions.QuestionBank( questions=haproxy_questions(), @@ -206,7 +187,7 @@ def prompt(self, console: Console | None = None) -> None: if variables["ssl_cert"] is not None: tls_mode = haproxy_config_bank.tls_mode.ask() variables["tls_mode"] = tls_mode - if tls_mode: + if tls_mode != "disabled": cert_filepath = haproxy_config_bank.ssl_cert.ask() key_filepath = haproxy_config_bank.ssl_key.ask() with open(cert_filepath) as cert_file: @@ -224,7 +205,6 @@ def extra_tfvars(self) -> dict[str, Any]: self.client, self._HAPROXY_CONFIG ) -<<<<<<< HEAD cert_filepath = variables["ssl_cert"] key_filepath = variables["ssl_key"] if cert_filepath != "" and key_filepath != "": @@ -232,9 +212,7 @@ def extra_tfvars(self) -> dict[str, Any]: variables["ssl_cert_content"] = cert_file.read() with open(key_filepath) as key_file: variables["ssl_key_content"] = key_file.read() -======= - if variables["tls_mode"]: ->>>>>>> 9050d1912 (Pass contents of ssl key/cert to maas-region variables instead of filepath. Let maas-region handle creating the files in the proper place) + if variables["tls_mode"] != "disabled": variables["haproxy_port"] = 443 variables["haproxy_services_yaml"] = self.get_tls_services_yaml( variables["tls_mode"] @@ -243,7 +221,7 @@ def extra_tfvars(self) -> dict[str, Any]: variables["haproxy_port"] = 80 # Terraform does not need the content of these answers - variables.pop("tls_mode", None) + variables.pop("tls_mode", "disabled") LOG.debug(f"extra tfvars: {variables}") return variables diff --git a/cloud/etc/deploy-maas-region/variables.tf b/cloud/etc/deploy-maas-region/variables.tf index 9b1433e..8c833ac 100644 --- a/cloud/etc/deploy-maas-region/variables.tf +++ b/cloud/etc/deploy-maas-region/variables.tf @@ -49,7 +49,7 @@ variable "enable_haproxy" { } variable "tls_mode" { - description = "TLS Mode for MAAS Region charm ('', 'termination', or 'passthrough')" + description = "TLS Mode for MAAS Region charm ('disabled', 'termination', or 'passthrough')" type = string default = "" } From 9acb0ec37f8b58ce3098451c178eef32660a1482 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Fri, 6 Sep 2024 09:59:44 -0600 Subject: [PATCH 14/23] Save ssl_cert and ssl_key in variables, load content in extra_tfvars --- anvil-python/anvil/commands/haproxy.py | 26 ++++++++++++++-------- anvil-python/anvil/commands/maas_region.py | 16 ++++++++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index e245a87..274c851 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -21,7 +21,7 @@ from rich.console import Console from sunbeam.clusterd.client import Client from sunbeam.commands.juju import BOOTSTRAP_CONFIG_KEY -from sunbeam.commands.terraform import TerraformInitStep +from sunbeam.commands.terraform import TerraformInitStep, TerraformException from sunbeam.jobs import questions from sunbeam.jobs.common import BaseStep, ResultType from sunbeam.jobs.juju import JujuHelper @@ -104,8 +104,8 @@ def haproxy_questions() -> dict[str, questions.PromptQuestion]: validation_function=validate_key_file, ), "tls_mode": questions.PromptQuestion( - 'TLS termination at HA Proxy ("termination"), passthrough to MAAS ("passthrough"), or no TLS ("")?', - default_value="", + 'TLS termination at HA Proxy ("termination"), passthrough to MAAS ("passthrough"), or no TLS ("disabled")?', + default_value="disabled", validation_function=validate_tls_mode, ), } @@ -188,12 +188,8 @@ def prompt(self, console: Console | None = None) -> None: tls_mode = haproxy_config_bank.tls_mode.ask() variables["tls_mode"] = tls_mode if tls_mode != "disabled": - cert_filepath = haproxy_config_bank.ssl_cert.ask() - key_filepath = haproxy_config_bank.ssl_key.ask() - with open(cert_filepath) as cert_file: - variables["ssl_cert_content"] = cert_file.read() - with open(key_filepath) as key_file: - variables["ssl_key_content"] = key_file.read() + variables["ssl_cert"] = haproxy_config_bank.ssl_cert.ask() + variables["ssl_key"] = haproxy_config_bank.ssl_key.ask() virtual_ip = haproxy_config_bank.virtual_ip.ask() variables["virtual_ip"] = virtual_ip @@ -217,11 +213,23 @@ def extra_tfvars(self) -> dict[str, Any]: variables["haproxy_services_yaml"] = self.get_tls_services_yaml( variables["tls_mode"] ) + try: + opening = "certificate" + with open(variables["ssl_cert"]) as cert_file: + variables["ssl_cert_content"] = cert_file.read() + opening = "private key" + with open(variables["ssl_key"]) as key_file: + variables["ssl_key_content"] = key_file.read() + variables.update() + except FileNotFoundError: + raise TerraformException(f"SSL {opening} not found") else: variables["haproxy_port"] = 80 # Terraform does not need the content of these answers variables.pop("tls_mode", "disabled") + variables.pop("ssl_cert", "") + variables.pop("ssl_key", "") LOG.debug(f"extra tfvars: {variables}") return variables diff --git a/anvil-python/anvil/commands/maas_region.py b/anvil-python/anvil/commands/maas_region.py index 210e282..85eb94e 100644 --- a/anvil-python/anvil/commands/maas_region.py +++ b/anvil-python/anvil/commands/maas_region.py @@ -16,7 +16,7 @@ from typing import Any, List from sunbeam.clusterd.client import Client -from sunbeam.commands.terraform import TerraformInitStep +from sunbeam.commands.terraform import TerraformInitStep, TerraformException from sunbeam.jobs import questions from sunbeam.jobs.common import BaseStep from sunbeam.jobs.juju import JujuHelper @@ -76,8 +76,18 @@ def extra_tfvars(self) -> dict[str, Any]: haproxy_vars: dict[str, Any] = questions.load_answers( self.client, HAPROXY_CONFIG_KEY ) - if enable_haproxy and haproxy_vars.get("ssl_cert", "") != "": - variables["tls_mode"] = "termination" + + variables["tls_mode"] = haproxy_vars["tls_mode"] + if variables["tls_mode"] == "passthrough": + try: + opening = "certificate" + with open(variables["ssl_cert"]) as cert_file: + variables["ssl_cert_content"] = cert_file.read() + opening = "private key" + with open(variables["ssl_key"]) as key_file: + variables["ssl_key_content"] = key_file.read() + except FileNotFoundError: + raise TerraformException(f"SSL {opening} not found") return variables From 32c57dbc8256d68cbf4738446a6e71c1c44905f3 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Mon, 9 Sep 2024 13:09:32 -0600 Subject: [PATCH 15/23] Ask tls questions during maas-region init step if haproxy unit is not to be installed. Add ssl_cacert variable --- anvil-python/anvil/commands/haproxy.py | 88 ++++++++++---- anvil-python/anvil/commands/maas_region.py | 114 +++++++++++++++--- .../anvil/commands/upgrades/intra_channel.py | 1 + anvil-python/anvil/provider/local/commands.py | 4 + cloud/etc/deploy-maas-region/main.tf | 2 + cloud/etc/deploy-maas-region/variables.tf | 6 + 6 files changed, 170 insertions(+), 45 deletions(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 274c851..df0a065 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -16,12 +16,12 @@ import ipaddress import logging import os.path -from typing import Any, List +from typing import Any, Callable, List from rich.console import Console from sunbeam.clusterd.client import Client from sunbeam.commands.juju import BOOTSTRAP_CONFIG_KEY -from sunbeam.commands.terraform import TerraformInitStep, TerraformException +from sunbeam.commands.terraform import TerraformException, TerraformInitStep from sunbeam.jobs import questions from sunbeam.jobs.common import BaseStep, ResultType from sunbeam.jobs.juju import JujuHelper @@ -42,7 +42,7 @@ HAPROXY_UNIT_TIMEOUT = ( 1200 # 15 minutes, adding / removing units can take a long time ) -VALID_TLS_MODES = ["termination", "passthrough", "disabled"] +HAPROXY_VALID_TLS_MODES = ["termination", "passthrough", "disabled"] @@ -71,6 +71,21 @@ def validate_key_file(filepath: str) -> None: except PermissionError: raise ValueError(f"Permission denied when trying to read {filepath}") + +def validate_cacert_chain(filepath: str) -> None: + if filepath == "": + return + if not os.path.isfile(filepath): + raise ValueError(f"{filepath} does not exist") + try: + with open(filepath) as f: + # TODO: better validation + if "BEGIN" not in f.read(): + raise ValueError("Invalid cacert chain file") + except PermissionError: + raise ValueError(f"Permission denied when trying to read {filepath}") + + def validate_virtual_ip(value: str) -> None: """We allow passing an empty IP for virtual_ip""" if value == "": @@ -81,36 +96,49 @@ def validate_virtual_ip(value: str) -> None: raise ValueError(f"{value} is not a valid IP address: {e}") -def validate_tls_mode(value: str) -> None: - if value not in VALID_TLS_MODES: - raise ValueError(f"TLS Mode must be one of {VALID_TLS_MODES}") +def get_validate_tls_mode_fn(valid_modes: list[str]) -> Callable[[str], None]: + def validate_tls_mode(value: str) -> None: + if value not in valid_modes: + raise ValueError(f"TLS Mode must be one of {valid_modes}") + return validate_tls_mode -def haproxy_questions() -> dict[str, questions.PromptQuestion]: + +def tls_questions(tls_modes: list[str]) -> dict[str, questions.PromptQuestion]: return { - "virtual_ip": questions.PromptQuestion( - "Virtual IP to use for the Cluster in HA", - default_value="", - validation_function=validate_virtual_ip, - ), "ssl_cert": questions.PromptQuestion( - "Path to SSL Certificate for HAProxy (enter nothing to skip TLS)", + "Path to SSL Certificate for HAProxy", default_value="", validation_function=validate_cert_file, ), "ssl_key": questions.PromptQuestion( - "Path to private key for the SSL certificate (enter nothing to skip TLS)", + "Path to private key for the SSL certificate", default_value="", validation_function=validate_key_file, ), + "ssl_cacert": questions.PromptQuestion( + "Path to cacert chain, for use with self-signed ssl certificates (enter nothing to skip)", + default_value="", + validation_function=validate_cacert_chain, + ), "tls_mode": questions.PromptQuestion( 'TLS termination at HA Proxy ("termination"), passthrough to MAAS ("passthrough"), or no TLS ("disabled")?', default_value="disabled", - validation_function=validate_tls_mode, + validation_function=get_validate_tls_mode_fn(tls_modes), ), } +def haproxy_questions() -> dict[str, questions.PromptQuestion]: + return { + "virtual_ip": questions.PromptQuestion( + "Virtual IP to use for the Cluster in HA", + default_value="", + validation_function=validate_virtual_ip, + ) + } + + class DeployHAProxyApplicationStep(DeployMachineApplicationStep): """Deploy HAProxy application using Terraform""" @@ -164,16 +192,20 @@ def prompt(self, console: Console | None = None) -> None: variables.setdefault("virtual_ip", "") variables.setdefault("ssl_cert", "") variables.setdefault("ssl_key", "") + variables.setdefault("ssl_cacert", "") variables.setdefault("tls_mode", "disabled") # Set defaults self.preseed.setdefault("virtual_ip", "") self.preseed.setdefault("ssl_cert", "") self.preseed.setdefault("ssl_key", "") + self.preseed.setdefault("ssl_cacert", "") self.preseed.setdefault("tls_mode", "disabled") + qs = haproxy_questions() + qs.update(tls_questions(HAPROXY_VALID_TLS_MODES)) haproxy_config_bank = questions.QuestionBank( - questions=haproxy_questions(), + questions=qs, console=console, preseed=self.preseed.get("haproxy"), previous_answers=variables, @@ -190,6 +222,8 @@ def prompt(self, console: Console | None = None) -> None: if tls_mode != "disabled": variables["ssl_cert"] = haproxy_config_bank.ssl_cert.ask() variables["ssl_key"] = haproxy_config_bank.ssl_key.ask() + if tls_mode == "passthrough": + variables["ssl_cacert"] = haproxy_config_bank.ssl_cacert.ask() virtual_ip = haproxy_config_bank.virtual_ip.ask() variables["virtual_ip"] = virtual_ip @@ -213,16 +247,17 @@ def extra_tfvars(self) -> dict[str, Any]: variables["haproxy_services_yaml"] = self.get_tls_services_yaml( variables["tls_mode"] ) - try: - opening = "certificate" - with open(variables["ssl_cert"]) as cert_file: - variables["ssl_cert_content"] = cert_file.read() - opening = "private key" - with open(variables["ssl_key"]) as key_file: - variables["ssl_key_content"] = key_file.read() - variables.update() - except FileNotFoundError: - raise TerraformException(f"SSL {opening} not found") + if not variables["ssl_cert"] or not variables["ssl_key"]: + raise TerraformException( + "Both ssl_cert and ssl_key must be provided when enabling TLS" + ) + with open(variables["ssl_cert"]) as cert_file: + variables["ssl_cert_content"] = cert_file.read() + with open(variables["ssl_key"]) as key_file: + variables["ssl_key_content"] = key_file.read() + if variables["ssl_cacert"]: + with open(variables["ssl_cacert"]) as cacert_file: + variables["ssl_cacert_content"] = cacert_file.read() else: variables["haproxy_port"] = 80 @@ -230,6 +265,7 @@ def extra_tfvars(self) -> dict[str, Any]: variables.pop("tls_mode", "disabled") variables.pop("ssl_cert", "") variables.pop("ssl_key", "") + variables.pop("ssl_cacert", "") LOG.debug(f"extra tfvars: {variables}") return variables diff --git a/anvil-python/anvil/commands/maas_region.py b/anvil-python/anvil/commands/maas_region.py index 85eb94e..3f81ddb 100644 --- a/anvil-python/anvil/commands/maas_region.py +++ b/anvil-python/anvil/commands/maas_region.py @@ -13,41 +13,52 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging from typing import Any, List from sunbeam.clusterd.client import Client -from sunbeam.commands.terraform import TerraformInitStep, TerraformException +from sunbeam.commands.terraform import TerraformException, TerraformInitStep from sunbeam.jobs import questions -from sunbeam.jobs.common import BaseStep +from sunbeam.jobs.common import BaseStep, ResultType from sunbeam.jobs.juju import JujuHelper from sunbeam.jobs.steps import ( AddMachineUnitsStep, DeployMachineApplicationStep, ) -from anvil.commands.haproxy import HAPROXY_CONFIG_KEY +from anvil.commands.haproxy import HAPROXY_CONFIG_KEY, tls_questions from anvil.jobs.manifest import Manifest from anvil.jobs.steps import RemoveMachineUnitStep +LOG = logging.getLogger(__name__) + APPLICATION = "maas-region" CONFIG_KEY = "TerraformVarsMaasregionPlan" +MAASREGION_CONFIG_KEY = "TerraformVarsMaasregion" MAASREGION_APP_TIMEOUT = ( 180 # 3 minutes, managing the application should be fast ) MAASREGION_UNIT_TIMEOUT = ( 1200 # 15 minutes, adding / removing units can take a long time ) +# If tls_mode is being asked here, haproxy is not enabled. +# So, TLS termination is not a valid config +MAAS_REGION_VALID_TLS_MODES = ["passthrough", "disabled"] class DeployMAASRegionApplicationStep(DeployMachineApplicationStep): """Deploy MAAS Region application using Terraform""" + _MAASREGION_CONFIG = MAASREGION_CONFIG_KEY + def __init__( self, client: Client, manifest: Manifest, jhelper: JujuHelper, model: str, + deployment_preseed: dict[Any, Any] | None = None, + accept_defaults: bool = False, refresh: bool = False, ): super().__init__( @@ -62,10 +73,55 @@ def __init__( "Deploying MAAS Region", refresh, ) + self.preseed = deployment_preseed or {} + self.accept_defaults = accept_defaults def get_application_timeout(self) -> int: return MAASREGION_APP_TIMEOUT + def has_prompts(self) -> bool: + if self.refresh: + return False + skip_result = self.is_skip() + if skip_result.result_type == ResultType.SKIPPED: + return False + elif self.client.cluster.list_nodes_by_role("haproxy"): + return False + return True + + def prompt(self, console: questions.Console | None = None) -> None: + variables = questions.load_answers( + self.client, self._MAASREGION_CONFIG + ) + variables.setdefault("ssl_cert", "") + variables.setdefault("ssl_key", "") + variables.setdefault("ssl_cacert", "") + variables.setdefault("tls_mode", "disabled") + + self.preseed.setdefault("ssl_cert", "") + self.preseed.setdefault("ssl_key", "") + self.preseed.setdefault("ssl_cacert", "") + self.preseed.setdefault("tls_mode", "disabled") + + maas_region_config_bank = questions.QuestionBank( + questions=tls_questions(MAAS_REGION_VALID_TLS_MODES), + console=console, + preseed=self.preseed.get("maas-region"), + previous_answers=variables, + accept_defaults=self.accept_defaults, + ) + tls_mode = maas_region_config_bank.tls_mode.ask() + variables["tls_mode"] = tls_mode + if tls_mode == "passthrough": + variables["ssl_cert"] = maas_region_config_bank.ssl_cert.ask() + variables["ssl_key"] = maas_region_config_bank.ssl_key.ask() + variables["ssl_cacert"] = maas_region_config_bank.ssl_cacert.ask() + + LOG.debug(variables) + questions.write_answers( + self.client, self._MAASREGION_CONFIG, variables + ) + def extra_tfvars(self) -> dict[str, Any]: enable_haproxy = ( True @@ -73,21 +129,26 @@ def extra_tfvars(self) -> dict[str, Any]: else False ) variables: dict[str, Any] = {"enable_haproxy": enable_haproxy} - haproxy_vars: dict[str, Any] = questions.load_answers( - self.client, HAPROXY_CONFIG_KEY - ) - - variables["tls_mode"] = haproxy_vars["tls_mode"] + answers: dict[str, Any] = {} + if enable_haproxy: + answers = questions.load_answers(self.client, HAPROXY_CONFIG_KEY) + else: + answers = questions.load_answers( + self.client, self._MAASREGION_CONFIG + ) + variables["tls_mode"] = answers["tls_mode"] if variables["tls_mode"] == "passthrough": - try: - opening = "certificate" - with open(variables["ssl_cert"]) as cert_file: - variables["ssl_cert_content"] = cert_file.read() - opening = "private key" - with open(variables["ssl_key"]) as key_file: - variables["ssl_key_content"] = key_file.read() - except FileNotFoundError: - raise TerraformException(f"SSL {opening} not found") + if not answers["ssl_cert"] or not answers["ssl_key"]: + raise TerraformException( + "Both ssl_cert and ssl_key must be provided when enabling TLS" + ) + with open(answers["ssl_cert"]) as cert_file: + variables["ssl_cert_content"] = cert_file.read() + with open(answers["ssl_key"]) as key_file: + variables["ssl_key_content"] = key_file.read() + if answers["ssl_cacert"]: + with open(answers["ssl_cacert"]) as cacert_file: + variables["ssl_cacert_content"] = cacert_file.read() return variables @@ -143,10 +204,19 @@ def maas_region_install_steps( jhelper: JujuHelper, model: str, fqdn: str, + accept_defaults: bool, + preseed: dict[Any, Any], ) -> List[BaseStep]: return [ TerraformInitStep(manifest.get_tfhelper("maas-region-plan")), - DeployMAASRegionApplicationStep(client, manifest, jhelper, model), + DeployMAASRegionApplicationStep( + client, + manifest, + jhelper, + model, + deployment_preseed=preseed, + accept_defaults=accept_defaults, + ), AddMAASRegionUnitsStep(client, fqdn, jhelper, model), ] @@ -156,10 +226,16 @@ def maas_region_upgrade_steps( manifest: Manifest, jhelper: JujuHelper, model: str, + preseed: dict[Any, Any], ) -> List[BaseStep]: return [ TerraformInitStep(manifest.get_tfhelper("maas-region-plan")), DeployMAASRegionApplicationStep( - client, manifest, jhelper, model, refresh=True + client, + manifest, + jhelper, + model, + deployment_preseed=preseed, + refresh=True, ), ] diff --git a/anvil-python/anvil/commands/upgrades/intra_channel.py b/anvil-python/anvil/commands/upgrades/intra_channel.py index 23d1772..534e841 100644 --- a/anvil-python/anvil/commands/upgrades/intra_channel.py +++ b/anvil-python/anvil/commands/upgrades/intra_channel.py @@ -182,6 +182,7 @@ def get_plan(self) -> list[BaseStep]: self.manifest, self.jhelper, self.deployment.infrastructure_model, + self.preseed, ) ) plan.extend( diff --git a/anvil-python/anvil/provider/local/commands.py b/anvil-python/anvil/provider/local/commands.py index e129ad6..141b2b0 100644 --- a/anvil-python/anvil/provider/local/commands.py +++ b/anvil-python/anvil/provider/local/commands.py @@ -305,6 +305,8 @@ def bootstrap( jhelper, deployment.infrastructure_model, fqdn, + accept_defaults, + preseed, ) ) if is_agent_node: @@ -495,6 +497,8 @@ def join( jhelper, deployment.infrastructure_model, name, + accept_defaults, + preseed, ) ) plan2.append( diff --git a/cloud/etc/deploy-maas-region/main.tf b/cloud/etc/deploy-maas-region/main.tf index ba94aaf..4e28f9f 100644 --- a/cloud/etc/deploy-maas-region/main.tf +++ b/cloud/etc/deploy-maas-region/main.tf @@ -34,6 +34,7 @@ locals { tls_mode = var.tls_mode != "" ? { tls_mode = var.tls_mode } : {} ssl_cert_content = var.ssl_cert_content != "" ? { ssl_cert_content = var.ssl_cert_content } : {} ssl_key_content = var.ssl_key_content != "" ? { ssl_key_content = var.ssl_key_content } : {} + ssl_cacert_content = var.ssl_cacert_content != "" ? { ssl_cacert_content = var.ssl_cacert_content } : {} } resource "juju_application" "maas-region" { @@ -52,6 +53,7 @@ resource "juju_application" "maas-region" { local.tls_mode, local.ssl_cert_content, local.ssl_key_content, + local.ssl_cacert_content, var.charm_maas_region_config, ) } diff --git a/cloud/etc/deploy-maas-region/variables.tf b/cloud/etc/deploy-maas-region/variables.tf index 8c833ac..b775d5a 100644 --- a/cloud/etc/deploy-maas-region/variables.tf +++ b/cloud/etc/deploy-maas-region/variables.tf @@ -65,3 +65,9 @@ variable "ssl_key_content" { type = string default = "" } + +variable "ssl_cacert_content" { + description = "CA Cert chain for self-signed certificates, requires tls_mode=passthrough" + type = string + default = "" +} From c925753f1c37ce9cdb5454bf6b4a985e01a299a1 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Mon, 9 Sep 2024 16:37:29 -0600 Subject: [PATCH 16/23] Allow empty string for ssl cert/key, question prompt for tls mode displays valid tls modes based on roles --- anvil-python/anvil/commands/haproxy.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index df0a065..9c0f298 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -78,10 +78,9 @@ def validate_cacert_chain(filepath: str) -> None: if not os.path.isfile(filepath): raise ValueError(f"{filepath} does not exist") try: + # just make sure we can open the file with open(filepath) as f: - # TODO: better validation - if "BEGIN" not in f.read(): - raise ValueError("Invalid cacert chain file") + pass except PermissionError: raise ValueError(f"Permission denied when trying to read {filepath}") @@ -122,7 +121,7 @@ def tls_questions(tls_modes: list[str]) -> dict[str, questions.PromptQuestion]: validation_function=validate_cacert_chain, ), "tls_mode": questions.PromptQuestion( - 'TLS termination at HA Proxy ("termination"), passthrough to MAAS ("passthrough"), or no TLS ("disabled")?', + f"TLS mode: {tls_modes}?", default_value="disabled", validation_function=get_validate_tls_mode_fn(tls_modes), ), From 907c7d7c16f0ab341555c6876e68dfb31cb27b7c Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Tue, 10 Sep 2024 07:24:59 -0600 Subject: [PATCH 17/23] lint --- anvil-python/anvil/commands/haproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 9c0f298..3b745ef 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -79,7 +79,7 @@ def validate_cacert_chain(filepath: str) -> None: raise ValueError(f"{filepath} does not exist") try: # just make sure we can open the file - with open(filepath) as f: + with open(filepath): pass except PermissionError: raise ValueError(f"Permission denied when trying to read {filepath}") From fd8e31797237cd8935a9530408f5497f28180399 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Thu, 26 Sep 2024 07:35:35 -0600 Subject: [PATCH 18/23] check file existence in try/catch block instead of using os.path.is_file --- anvil-python/anvil/commands/haproxy.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 3b745ef..37a8aeb 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -15,7 +15,6 @@ import ipaddress import logging -import os.path from typing import Any, Callable, List from rich.console import Console @@ -49,12 +48,12 @@ def validate_cert_file(filepath: str) -> None: if filepath == "": return - if not os.path.isfile(filepath): - raise ValueError(f"{filepath} does not exist") try: with open(filepath) as f: if "BEGIN CERTIFICATE" not in f.read(): raise ValueError("Invalid certificate file") + except FileNotFoundError: + raise ValueError(f"{filepath} does not exist") except PermissionError: raise ValueError(f"Permission denied when trying to read {filepath}") @@ -62,12 +61,12 @@ def validate_cert_file(filepath: str) -> None: def validate_key_file(filepath: str) -> None: if filepath == "": return - if not os.path.isfile(filepath): - raise ValueError(f"{filepath} does not exist") try: with open(filepath) as f: if "BEGIN PRIVATE KEY" not in f.read(): raise ValueError("Invalid key file") + except FileNotFoundError: + raise ValueError(f"{filepath} does not exist") except PermissionError: raise ValueError(f"Permission denied when trying to read {filepath}") @@ -75,12 +74,12 @@ def validate_key_file(filepath: str) -> None: def validate_cacert_chain(filepath: str) -> None: if filepath == "": return - if not os.path.isfile(filepath): - raise ValueError(f"{filepath} does not exist") try: # just make sure we can open the file with open(filepath): pass + except FileNotFoundError: + raise ValueError(f"{filepath} does not exist") except PermissionError: raise ValueError(f"Permission denied when trying to read {filepath}") From cc11be725a568bb35aadc8fbcaf8166cd28315ea Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Thu, 3 Oct 2024 10:29:04 -0600 Subject: [PATCH 19/23] add back in basic validation for CA cert chain. Include tls questions in preseed and manifest, and create section for maas-region in preseed/manifest. do not pass CA cert to ha proxy charm variables --- anvil-python/anvil/commands/haproxy.py | 11 +++---- .../anvil/provider/local/deployment.py | 29 +++++++++++++++++-- anvil-python/anvil/versions.py | 2 +- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index 37a8aeb..f13a335 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -75,9 +75,9 @@ def validate_cacert_chain(filepath: str) -> None: if filepath == "": return try: - # just make sure we can open the file - with open(filepath): - pass + with open(filepath) as f: + if "BEGIN CERTIFICATE" not in f.read(): + raise ValueError("Invalid CA certificate file") except FileNotFoundError: raise ValueError(f"{filepath} does not exist") except PermissionError: @@ -115,7 +115,7 @@ def tls_questions(tls_modes: list[str]) -> dict[str, questions.PromptQuestion]: validation_function=validate_key_file, ), "ssl_cacert": questions.PromptQuestion( - "Path to cacert chain, for use with self-signed ssl certificates (enter nothing to skip)", + "Path to CA cert chain, for use with self-signed SSL certificates (enter nothing to skip)", default_value="", validation_function=validate_cacert_chain, ), @@ -253,9 +253,6 @@ def extra_tfvars(self) -> dict[str, Any]: variables["ssl_cert_content"] = cert_file.read() with open(variables["ssl_key"]) as key_file: variables["ssl_key_content"] = key_file.read() - if variables["ssl_cacert"]: - with open(variables["ssl_cacert"]) as cacert_file: - variables["ssl_cacert_content"] = cacert_file.read() else: variables["haproxy_port"] = 80 diff --git a/anvil-python/anvil/provider/local/deployment.py b/anvil-python/anvil/provider/local/deployment.py index 1336e24..d9a4608 100644 --- a/anvil-python/anvil/provider/local/deployment.py +++ b/anvil-python/anvil/provider/local/deployment.py @@ -26,7 +26,16 @@ LocalDeployment as SunbeamLocalDeployment, ) -from anvil.commands.haproxy import HAPROXY_CONFIG_KEY, haproxy_questions +from anvil.commands.haproxy import ( + HAPROXY_CONFIG_KEY, + HAPROXY_VALID_TLS_MODES, + haproxy_questions, + tls_questions, +) +from anvil.commands.maas_region import ( + MAAS_REGION_VALID_TLS_MODES, + MAASREGION_CONFIG_KEY, +) from anvil.commands.postgresql import ( POSTGRESQL_CONFIG_KEY, postgresql_questions, @@ -77,8 +86,10 @@ def generate_preseed(self, console: Console) -> str: variables = load_answers(client, HAPROXY_CONFIG_KEY) except ClusterServiceUnavailableException: variables = {} + qs = haproxy_questions() + qs.update(tls_questions(HAPROXY_VALID_TLS_MODES)) haproxy_config_bank = QuestionBank( - questions=haproxy_questions(), + questions=qs, console=console, previous_answers=variables, ) @@ -86,5 +97,19 @@ def generate_preseed(self, console: Console) -> str: show_questions(haproxy_config_bank, section="haproxy") ) + # MAAS region questions + try: + variables = load_answers(client, MAASREGION_CONFIG_KEY) + except ClusterServiceUnavailableException: + variables = {} + maas_region_config_bank = QuestionBank( + questions=tls_questions(MAAS_REGION_VALID_TLS_MODES), + console=console, + previous_answers=variables, + ) + preseed_content.extend( + show_questions(maas_region_config_bank, section="maas-region") + ) + preseed_content_final = "\n".join(preseed_content) return preseed_content_final diff --git a/anvil-python/anvil/versions.py b/anvil-python/anvil/versions.py index d8a6f5e..f11a11f 100644 --- a/anvil-python/anvil/versions.py +++ b/anvil-python/anvil/versions.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -MAAS_REGION_CHANNEL = "3.5/edge" +MAAS_REGION_CHANNEL = "latest/edge/wyatt-test" MAAS_AGENT_CHANNEL = "3.5/edge" POSTGRESQL_CHANNEL = "14/stable" HAPROXY_CHANNEL = "latest/stable" From 08863249f7224fb7252cc7f53baed1c9241fcb07 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Thu, 3 Oct 2024 11:08:26 -0600 Subject: [PATCH 20/23] Cleanup --- anvil-python/anvil/commands/haproxy.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index f13a335..c9ca489 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -44,7 +44,6 @@ HAPROXY_VALID_TLS_MODES = ["termination", "passthrough", "disabled"] - def validate_cert_file(filepath: str) -> None: if filepath == "": return @@ -209,13 +208,8 @@ def prompt(self, console: Console | None = None) -> None: previous_answers=variables, accept_defaults=self.accept_defaults, ) - cert_filepath = haproxy_config_bank.ssl_cert.ask() - variables["ssl_cert"] = cert_filepath - key_filepath = haproxy_config_bank.ssl_key.ask() - variables["ssl_key"] = key_filepath - tls_mode = "" - if variables["ssl_cert"] is not None: - tls_mode = haproxy_config_bank.tls_mode.ask() + + tls_mode = haproxy_config_bank.tls_mode.ask() variables["tls_mode"] = tls_mode if tls_mode != "disabled": variables["ssl_cert"] = haproxy_config_bank.ssl_cert.ask() @@ -233,13 +227,6 @@ def extra_tfvars(self) -> dict[str, Any]: self.client, self._HAPROXY_CONFIG ) - cert_filepath = variables["ssl_cert"] - key_filepath = variables["ssl_key"] - if cert_filepath != "" and key_filepath != "": - with open(cert_filepath) as cert_file: - variables["ssl_cert_content"] = cert_file.read() - with open(key_filepath) as key_file: - variables["ssl_key_content"] = key_file.read() if variables["tls_mode"] != "disabled": variables["haproxy_port"] = 443 variables["haproxy_services_yaml"] = self.get_tls_services_yaml( From e626f2a8ccc4988f376889eae10c9f41789d6d75 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Thu, 3 Oct 2024 13:42:06 -0600 Subject: [PATCH 21/23] Revert maas-region version --- anvil-python/anvil/versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anvil-python/anvil/versions.py b/anvil-python/anvil/versions.py index f11a11f..d8a6f5e 100644 --- a/anvil-python/anvil/versions.py +++ b/anvil-python/anvil/versions.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -MAAS_REGION_CHANNEL = "latest/edge/wyatt-test" +MAAS_REGION_CHANNEL = "3.5/edge" MAAS_AGENT_CHANNEL = "3.5/edge" POSTGRESQL_CHANNEL = "14/stable" HAPROXY_CHANNEL = "latest/stable" From 55982a53a9ac45d489a9ed60c786a9640261be48 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Wed, 13 Nov 2024 07:30:24 -0700 Subject: [PATCH 22/23] fixing default value of tls_mode in maas-region variables.tf --- cloud/etc/deploy-maas-region/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/etc/deploy-maas-region/variables.tf b/cloud/etc/deploy-maas-region/variables.tf index b775d5a..7b8f81e 100644 --- a/cloud/etc/deploy-maas-region/variables.tf +++ b/cloud/etc/deploy-maas-region/variables.tf @@ -51,7 +51,7 @@ variable "enable_haproxy" { variable "tls_mode" { description = "TLS Mode for MAAS Region charm ('disabled', 'termination', or 'passthrough')" type = string - default = "" + default = "disabled" } variable "ssl_cert_content" { From 7da708ea75df6c64a5b85589a3fc7da3d7d28f97 Mon Sep 17 00:00:00 2001 From: Wyatt Rees Date: Thu, 14 Nov 2024 08:21:31 -0700 Subject: [PATCH 23/23] remove question mark from question --- anvil-python/anvil/commands/haproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anvil-python/anvil/commands/haproxy.py b/anvil-python/anvil/commands/haproxy.py index c9ca489..36f0af9 100644 --- a/anvil-python/anvil/commands/haproxy.py +++ b/anvil-python/anvil/commands/haproxy.py @@ -119,7 +119,7 @@ def tls_questions(tls_modes: list[str]) -> dict[str, questions.PromptQuestion]: validation_function=validate_cacert_chain, ), "tls_mode": questions.PromptQuestion( - f"TLS mode: {tls_modes}?", + f"TLS mode: {tls_modes}", default_value="disabled", validation_function=get_validate_tls_mode_fn(tls_modes), ),