diff --git a/src/k8s-extension/HISTORY.rst b/src/k8s-extension/HISTORY.rst index b4a73e6015b..243dd8f7307 100644 --- a/src/k8s-extension/HISTORY.rst +++ b/src/k8s-extension/HISTORY.rst @@ -3,6 +3,10 @@ Release History =============== +1.0.4 +++++++++++++++++++ +* microsoft.azureml.kubernetes: Support SSL secret + 1.0.3 ++++++++++++++++++ * Remove identity creation for calls to Microsoft.ResourceConnector diff --git a/src/k8s-extension/azext_k8s_extension/partner_extensions/AzureMLKubernetes.py b/src/k8s-extension/azext_k8s_extension/partner_extensions/AzureMLKubernetes.py index d9c5f5e31f0..3414cf9d803 100644 --- a/src/k8s-extension/azext_k8s_extension/partner_extensions/AzureMLKubernetes.py +++ b/src/k8s-extension/azext_k8s_extension/partner_extensions/AzureMLKubernetes.py @@ -81,6 +81,7 @@ def __init__(self): self.privateEndpointILB = 'privateEndpointILB' self.privateEndpointNodeport = 'privateEndpointNodeport' self.inferenceLoadBalancerHA = 'inferenceLoadBalancerHA' + self.SSL_SECRET = 'sslSecret' # constants for existing AKS to AMLARC migration self.IS_AKS_MIGRATION = 'isAKSMigration' @@ -108,7 +109,7 @@ def Create(self, cmd, client, resource_group_name, cluster_name, name, cluster_t ext_scope = Scope(cluster=scope_cluster, namespace=None) # validate the config - self.__validate_config(configuration_settings, configuration_protected_settings) + self.__validate_config(configuration_settings, configuration_protected_settings, release_namespace) # get the arc's location subscription_id = get_subscription_id(cmd.cli_ctx) @@ -285,7 +286,7 @@ def Update(self, cmd, resource_group_name, cluster_name, auto_upgrade_minor_vers if self.sslKeyPemFile in configuration_protected_settings and \ self.sslCertPemFile in configuration_protected_settings: logger.info(f"Both {self.sslKeyPemFile} and {self.sslCertPemFile} are set, update ssl key.") - self.__set_inference_ssl_from_file(configuration_protected_settings) + self.__set_inference_ssl_from_file(configuration_protected_settings, self.sslCertPemFile, self.sslKeyPemFile) return PatchExtension(auto_upgrade_minor_version=auto_upgrade_minor_version, release_train=release_train, @@ -318,7 +319,7 @@ def __normalize_config(self, configuration_settings, configuration_protected_set logger.warning( 'Internal load balancer only supported on AKS and AKS Engine Clusters.') - def __validate_config(self, configuration_settings, configuration_protected_settings): + def __validate_config(self, configuration_settings, configuration_protected_settings, release_namespace): # perform basic validation of the input config config_keys = configuration_settings.keys() config_protected_keys = configuration_protected_settings.keys() @@ -339,12 +340,12 @@ def __validate_config(self, configuration_settings, configuration_protected_sett if enable_inference: logger.warning("The installed AzureML extension for AML inference is experimental and not covered by customer support. Please use with discretion.") - self.__validate_scoring_fe_settings(configuration_settings, configuration_protected_settings) + self.__validate_scoring_fe_settings(configuration_settings, configuration_protected_settings, release_namespace) self.__set_up_inference_ssl(configuration_settings, configuration_protected_settings) elif not (enable_training or enable_inference): raise InvalidArgumentValueError( - "Please create Microsoft.AzureML.Kubernetes extension, either " - "for Machine Learning training or inference by specifying " + "To create Microsoft.AzureML.Kubernetes extension, either " + "enable Machine Learning training or inference by specifying " f"'--configuration-settings {self.ENABLE_TRAINING}=true' or '--configuration-settings {self.ENABLE_INFERENCE}=true'") configuration_settings[self.ENABLE_TRAINING] = configuration_settings.get(self.ENABLE_TRAINING, enable_training) @@ -353,7 +354,7 @@ def __validate_config(self, configuration_settings, configuration_protected_sett configuration_protected_settings.pop(self.ENABLE_TRAINING, None) configuration_protected_settings.pop(self.ENABLE_INFERENCE, None) - def __validate_scoring_fe_settings(self, configuration_settings, configuration_protected_settings): + def __validate_scoring_fe_settings(self, configuration_settings, configuration_protected_settings, release_namespace): isTestCluster = _get_value_from_config_protected_config( self.inferenceLoadBalancerHA, configuration_settings, configuration_protected_settings) isTestCluster = str(isTestCluster).lower() == 'false' @@ -367,16 +368,20 @@ def __validate_scoring_fe_settings(self, configuration_settings, configuration_p if isAKSMigration: configuration_settings['scoringFe.namespace'] = "default" configuration_settings[self.IS_AKS_MIGRATION] = "true" + sslSecret = _get_value_from_config_protected_config( + self.SSL_SECRET, configuration_settings, configuration_protected_settings) feSslCertFile = configuration_protected_settings.get(self.sslCertPemFile) feSslKeyFile = configuration_protected_settings.get(self.sslKeyPemFile) allowInsecureConnections = _get_value_from_config_protected_config( self.allowInsecureConnections, configuration_settings, configuration_protected_settings) allowInsecureConnections = str(allowInsecureConnections).lower() == 'true' - if (not feSslCertFile or not feSslKeyFile) and not allowInsecureConnections: + sslEnabled = (feSslCertFile and feSslKeyFile) or sslSecret + if not sslEnabled and not allowInsecureConnections: raise InvalidArgumentValueError( - "Provide ssl certificate and key. " - "Otherwise explicitly allow insecure connection by specifying " - "'--configuration-settings allowInsecureConnections=true'") + "To enable HTTPs endpoint, " + "either provide sslCertPemFile and sslKeyPemFile to config protected settings, " + f"or provide sslSecret (kubernetes secret name) containing both ssl cert and ssl key under {release_namespace} namespace. " + "Otherwise, to enable HTTP endpoint, explicitly set allowInsecureConnections=true.") feIsNodePort = _get_value_from_config_protected_config( self.privateEndpointNodeport, configuration_settings, configuration_protected_settings) @@ -395,16 +400,17 @@ def __validate_scoring_fe_settings(self, configuration_settings, configuration_p logger.warning( 'Internal load balancer only supported on AKS and AKS Engine Clusters.') - def __set_inference_ssl_from_file(self, configuration_protected_settings): + def __set_inference_ssl_from_secret(self, configuration_settings, fe_ssl_secret): + configuration_settings['scoringFe.sslSecret'] = fe_ssl_secret + + def __set_inference_ssl_from_file(self, configuration_protected_settings, fe_ssl_cert_file, fe_ssl_key_file): import base64 - feSslCertFile = configuration_protected_settings.get(self.sslCertPemFile) - feSslKeyFile = configuration_protected_settings.get(self.sslKeyPemFile) - with open(feSslCertFile) as f: + with open(fe_ssl_cert_file) as f: cert_data = f.read() cert_data_bytes = cert_data.encode("ascii") ssl_cert = base64.b64encode(cert_data_bytes).decode() configuration_protected_settings['scoringFe.sslCert'] = ssl_cert - with open(feSslKeyFile) as f: + with open(fe_ssl_key_file) as f: key_data = f.read() key_data_bytes = key_data.encode("ascii") ssl_key = base64.b64encode(key_data_bytes).decode() @@ -415,7 +421,16 @@ def __set_up_inference_ssl(self, configuration_settings, configuration_protected self.allowInsecureConnections, configuration_settings, configuration_protected_settings) allowInsecureConnections = str(allowInsecureConnections).lower() == 'true' if not allowInsecureConnections: - self.__set_inference_ssl_from_file(configuration_protected_settings) + fe_ssl_secret = _get_value_from_config_protected_config( + self.SSL_SECRET, configuration_settings, configuration_protected_settings) + fe_ssl_cert_file = configuration_protected_settings.get(self.sslCertPemFile) + fe_ssl_key_file = configuration_protected_settings.get(self.sslKeyPemFile) + + # always take ssl key/cert first, then secret if key/cert file is not provided + if fe_ssl_cert_file and fe_ssl_key_file: + self.__set_inference_ssl_from_file(configuration_protected_settings, fe_ssl_cert_file, fe_ssl_key_file) + else: + self.__set_inference_ssl_from_secret(configuration_settings, fe_ssl_secret) else: logger.warning( 'SSL is not enabled. Allowing insecure connections to the deployed services.') diff --git a/src/k8s-extension/setup.py b/src/k8s-extension/setup.py index c846f00ce76..177316aaf15 100644 --- a/src/k8s-extension/setup.py +++ b/src/k8s-extension/setup.py @@ -33,7 +33,7 @@ # TODO: Add any additional SDK dependencies here DEPENDENCIES = [] -VERSION = "1.0.3" +VERSION = "1.0.4" with open("README.rst", "r", encoding="utf-8") as f: README = f.read()