From 0ce40cecbce00104be54871ce87ca99cef2aa480 Mon Sep 17 00:00:00 2001 From: MichaelMorris Date: Tue, 19 Sep 2023 09:48:04 +0100 Subject: [PATCH] sdc-BE TLS support Signed-off-by: MichaelMorris Issue-ID: SDC-4639 Change-Id: Id97c5331b52650f5da25caff4e9ad0166f39202a --- .../sdc-catalog-be/recipes/BE_4_jetty_Modules.rb | 16 ++++ .../sdc-catalog-be/templates/default/ssl-ini.erb | 27 ++++-- .../sdc/be/components/kafka/KafkaCommonConfig.java | 93 +++++++++++++++++++++ .../sdc/be/components/kafka/SdcKafkaConsumer.java | 45 +--------- .../sdc/be/components/kafka/SdcKafkaProducer.java | 40 +-------- catalog-be/src/main/resources/jetty-ssl.xml | 51 ----------- catalog-be/src/main/resources/keystore/README.txt | 16 ---- .../src/main/resources/keystore/catalogbe.jks | Bin 2201 -> 0 bytes .../src/main/resources/keystore/catalogbe.jks.pwd | 1 - .../java/org/onap/config/api/JettySSLUtils.java | 30 +++++-- 10 files changed, 155 insertions(+), 164 deletions(-) create mode 100644 catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/KafkaCommonConfig.java delete mode 100644 catalog-be/src/main/resources/jetty-ssl.xml delete mode 100644 catalog-be/src/main/resources/keystore/README.txt delete mode 100644 catalog-be/src/main/resources/keystore/catalogbe.jks delete mode 100644 catalog-be/src/main/resources/keystore/catalogbe.jks.pwd diff --git a/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_4_jetty_Modules.rb b/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_4_jetty_Modules.rb index f029fb07fe..172888c773 100644 --- a/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_4_jetty_Modules.rb +++ b/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/recipes/BE_4_jetty_Modules.rb @@ -41,3 +41,19 @@ template "https-ini" do mode "0755" variables :https_port => "#{node['BE'][:https_port]}" end + +template "ssl-ini" do + path "#{ENV['JETTY_BASE']}/start.d/ssl.ini" + source "ssl-ini.erb" + owner "#{ENV['JETTY_USER']}" + group "#{ENV['JETTY_GROUP']}" + mode "0755" + variables ({ + :https_port => "#{node['BE'][:https_port]}" , + :keystore_path => "#{node['BE'][:keystore_path]}" , + :keystore_password => "#{node['BE'][:keystore_password]}" , + :truststore_path => "#{node['BE'][:truststore_path]}" , + :truststore_password => "#{node['BE'][:truststore_password]}" + }) +end + diff --git a/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/templates/default/ssl-ini.erb b/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/templates/default/ssl-ini.erb index 278fdea2ae..d3c8bc187c 100644 --- a/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/templates/default/ssl-ini.erb +++ b/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/templates/default/ssl-ini.erb @@ -42,33 +42,42 @@ jetty.ssl.port=<%= @https_port %> ## See http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html ## Keystore file path (relative to $jetty.base) -jetty.sslContext.keyStorePath=etc/org.onap.sdc.p12 +<% unless @keystore_path.nil? || @keystore_path.strip.empty? -%> +jetty.sslContext.keyStorePath=<%= @keystore_path %> +<% end -%> ## Truststore file path (relative to $jetty.base) -jetty.sslContext.trustStorePath=etc/org.onap.sdc.trust.jks +<% unless @truststore_path.nil? || @truststore_path.strip.empty? -%> +jetty.sslContext.trustStorePath=<%= @truststore_path %> +<% end -%> ## Keystore password -# jetty.sslContext.keyStorePassword=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 -jetty.sslContext.keyStorePassword=<%= @jetty_keystore_pwd %> +<% unless @keystore_password.nil? || @keystore_password.strip.empty? -%> +jetty.sslContext.keyStorePassword=<%= @keystore_password %> +<% end -%> ## Keystore type and provider # jetty.sslContext.keyStoreType=JKS # jetty.sslContext.keyStoreProvider= ## KeyManager password -# jetty.sslContext.keyManagerPassword=OBF:1u2u1wml1z7s1z7a1wnl1u2g -jetty.sslContext.keyManagerPassword=<%= @jetty_keymanager_pwd %> +<% unless @keystore_password.nil? || @keystore_password.strip.empty? -%> +jetty.sslContext.keyManagerPassword=<%= @keystore_password %> +<% end -%> ## Truststore password -# jetty.sslContext.trustStorePassword=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 -jetty.sslContext.trustStorePassword=<%= @jetty_truststore_pwd %> +# tp<%= @truststore_password %>end +# kp<%= @keystore_password %>end +<% unless @truststore_password.nil? || @truststore_password.strip.empty? -%> +jetty.sslContext.trustStorePassword=<%= @truststore_password %> +<% end -%> ## Truststore type and provider # jetty.sslContext.trustStoreType=JKS # jetty.sslContext.trustStoreProvider= ## whether client certificate authentication is required -# jetty.sslContext.needClientAuth=false +jetty.sslContext.needClientAuth=<%= !@truststore_password.nil? && !@truststore_password.strip.empty? %> ## Whether client certificate authentication is desired # jetty.sslContext.wantClientAuth=false diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/KafkaCommonConfig.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/KafkaCommonConfig.java new file mode 100644 index 0000000000..425b681f4b --- /dev/null +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/KafkaCommonConfig.java @@ -0,0 +1,93 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2023 Nordix Foundation. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.openecomp.sdc.be.components.kafka; + +import java.util.Properties; +import java.util.UUID; +import org.apache.kafka.clients.CommonClientConfigs; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.KafkaException; +import org.apache.kafka.common.config.SaslConfigs; +import org.apache.kafka.common.config.SslConfigs; +import org.onap.config.api.JettySSLUtils; +import org.openecomp.sdc.be.config.DistributionEngineConfiguration; + +public class KafkaCommonConfig { + + private final DistributionEngineConfiguration deConfiguration; + + public KafkaCommonConfig(DistributionEngineConfiguration config){ + this.deConfiguration = config; + } + + public Properties getConsumerProperties(){ + Properties props = new Properties(); + setCommonProperties(props); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); + props.put(ConsumerConfig.CLIENT_ID_CONFIG, deConfiguration.getDistributionStatusTopic().getConsumerId() + "-consumer-" + UUID.randomUUID()); + props.put(ConsumerConfig.GROUP_ID_CONFIG, deConfiguration.getDistributionStatusTopic().getConsumerGroup()); + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest"); + return props; + } + + public Properties getProducerProperties(){ + Properties props = new Properties(); + setCommonProperties(props); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); + props.put(ProducerConfig.CLIENT_ID_CONFIG, deConfiguration.getDistributionStatusTopic().getConsumerId() + "-producer-" + UUID.randomUUID()); + + return props; + } + + private void setCommonProperties(Properties props) { + String securityProtocolConfig = System.getenv().getOrDefault("SECURITY_PROTOCOL", "SASL_PLAINTEXT"); + props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocolConfig); + props.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, deConfiguration.getKafkaBootStrapServers()); + + if("SSL".equals(securityProtocolConfig)) { + final JettySSLUtils.JettySslConfig sslConfig = JettySSLUtils.getSSLConfig(); + props.put(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG, sslConfig.getKeystorePath()); + props.put(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG, sslConfig.getKeystorePass()); + props.put(SslConfigs.SSL_KEY_PASSWORD_CONFIG, sslConfig.getKeyStoreManager()); + props.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, ""); + props.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, sslConfig.getTruststorePath()); + props.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, sslConfig.getTruststorePass()); + } else { + props.put(SaslConfigs.SASL_JAAS_CONFIG, getKafkaSaslJaasConfig()); + props.put(SaslConfigs.SASL_MECHANISM, "SCRAM-SHA-512"); + } + } + + /** + * @return The Sasl Jaas Configuration + */ + private String getKafkaSaslJaasConfig() throws KafkaException { + String saslJaasConfFromEnv = System.getenv("SASL_JAAS_CONFIG"); + if(saslJaasConfFromEnv != null) { + return saslJaasConfFromEnv; + } else { + throw new KafkaException("sasl.jaas.config not set for Kafka Consumer"); + } + } + +} diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/SdcKafkaConsumer.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/SdcKafkaConsumer.java index 04df4e1fbe..81da06dc74 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/SdcKafkaConsumer.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/SdcKafkaConsumer.java @@ -25,14 +25,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; -import java.util.UUID; -import org.apache.kafka.clients.CommonClientConfigs; -import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.common.KafkaException; -import org.apache.kafka.common.config.SaslConfigs; import org.openecomp.sdc.be.config.DistributionEngineConfiguration; import org.openecomp.sdc.common.log.wrappers.Logger; @@ -42,30 +38,17 @@ import org.openecomp.sdc.common.log.wrappers.Logger; public class SdcKafkaConsumer { private static final Logger log = Logger.getLogger(SdcKafkaConsumer.class.getName()); - private static final String DEFAULT_SASL_MECHANISM = "SCRAM-SHA-512"; - private final DistributionEngineConfiguration deConfiguration; - private KafkaConsumer kafkaConsumer; + private final KafkaConsumer kafkaConsumer; /** * Constructor setting up the KafkaConsumer from a predefined set of configurations */ public SdcKafkaConsumer(DistributionEngineConfiguration deConfiguration){ log.info("Create SdcKafkaConsumer via constructor"); - Properties properties = new Properties(); + KafkaCommonConfig kafkaCommonConfig = new KafkaCommonConfig(deConfiguration); + Properties properties = kafkaCommonConfig.getConsumerProperties(); this.deConfiguration = deConfiguration; - - properties.put(ConsumerConfig.CLIENT_ID_CONFIG, deConfiguration.getDistributionStatusTopic().getConsumerId()+ "-consumer-" + UUID.randomUUID()); - properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); - properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); - properties.put(ConsumerConfig.GROUP_ID_CONFIG, deConfiguration.getDistributionStatusTopic().getConsumerGroup()); - properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, deConfiguration.getKafkaBootStrapServers()); - properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_PLAINTEXT"); - properties.put(ConsumerConfig.ALLOW_AUTO_CREATE_TOPICS_CONFIG, false); - properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest"); - properties.put(SaslConfigs.SASL_MECHANISM, getKafkaSaslMechanism()); - - properties.put(SaslConfigs.SASL_JAAS_CONFIG, getKafkaSaslJaasConfig()); kafkaConsumer = new KafkaConsumer<>(properties); } @@ -80,28 +63,6 @@ public class SdcKafkaConsumer { this.kafkaConsumer = kafkaConsumer; } - /** - * - * @return the Sasl Jass Config - */ - private String getKafkaSaslJaasConfig() { - String saslJaasConfFromEnv = System.getenv("SASL_JAAS_CONFIG"); - if(saslJaasConfFromEnv != null) { - return saslJaasConfFromEnv; - } else { - throw new KafkaException("sasl.jaas.config not set for Kafka Consumer"); - } - } - - private static String getKafkaSaslMechanism() throws KafkaException { - String saslMechanism = System.getenv("SASL_MECHANISM"); - if(saslMechanism != null) { - return saslMechanism; - } else { - return DEFAULT_SASL_MECHANISM; - } - } - /** * * @param topic Topic in which to subscribe diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/SdcKafkaProducer.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/SdcKafkaProducer.java index 7158357799..5db5165b55 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/SdcKafkaProducer.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/kafka/SdcKafkaProducer.java @@ -21,13 +21,9 @@ package org.openecomp.sdc.be.components.kafka; import com.google.common.annotations.VisibleForTesting; import java.util.Properties; -import java.util.UUID; -import org.apache.kafka.clients.CommonClientConfigs; import org.apache.kafka.clients.producer.KafkaProducer; -import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.KafkaException; -import org.apache.kafka.common.config.SaslConfigs; import org.openecomp.sdc.be.config.DistributionEngineConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,24 +33,16 @@ import org.slf4j.LoggerFactory; */ public class SdcKafkaProducer { private static final Logger log = LoggerFactory.getLogger(SdcKafkaProducer.class.getName()); - private static final String DEFAULT_SASL_MECHANISM = "SCRAM-SHA-512"; - private KafkaProducer kafkaProducer; + private final KafkaProducer kafkaProducer; /** * Constructor setting up the KafkaProducer from a predefined set of configurations */ public SdcKafkaProducer(DistributionEngineConfiguration deConfiguration) { log.info("Create SdcKafkaProducer via constructor"); - Properties properties = new Properties(); - - properties.put(ProducerConfig.CLIENT_ID_CONFIG, deConfiguration.getDistributionStatusTopic().getConsumerId() + "-producer-" + UUID.randomUUID()); - properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); - properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); - properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, deConfiguration.getKafkaBootStrapServers()); - properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_PLAINTEXT"); - properties.put(SaslConfigs.SASL_JAAS_CONFIG, getKafkaSaslJaasConfig()); - properties.put(SaslConfigs.SASL_MECHANISM, getKafkaSaslMechanism()); + KafkaCommonConfig kafkaCommonConfig = new KafkaCommonConfig(deConfiguration); + Properties properties = kafkaCommonConfig.getProducerProperties(); kafkaProducer = new KafkaProducer<>(properties); } @@ -67,31 +55,9 @@ public class SdcKafkaProducer { this.kafkaProducer = kafkaProducer; } - /** - * @return The Sasl Jaas Configuration - */ - private static String getKafkaSaslJaasConfig() throws KafkaException { - String saslJaasConfFromEnv = System.getenv("SASL_JAAS_CONFIG"); - if(saslJaasConfFromEnv != null) { - return saslJaasConfFromEnv; - } else { - throw new KafkaException("sasl.jaas.config not set for Kafka Consumer"); - } - } - - private static String getKafkaSaslMechanism() throws KafkaException { - String saslMechanism = System.getenv("SASL_MECHANISM"); - if(saslMechanism != null) { - return saslMechanism; - } else { - return DEFAULT_SASL_MECHANISM; - } - } - /** * @param message A message to Send * @param topicName The name of the topic to publish to - * @return The status of the send request */ public void send(String message, String topicName) throws KafkaException { ProducerRecord kafkaMessagePayload = new ProducerRecord<>(topicName, "PartitionKey", message); diff --git a/catalog-be/src/main/resources/jetty-ssl.xml b/catalog-be/src/main/resources/jetty-ssl.xml deleted file mode 100644 index 9b375087f1..0000000000 --- a/catalog-be/src/main/resources/jetty-ssl.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - / - - - / - - - - - - - SSL_RSA_WITH_DES_CBC_SHA - SSL_DHE_RSA_WITH_DES_CBC_SHA - SSL_DHE_DSS_WITH_DES_CBC_SHA - SSL_RSA_EXPORT_WITH_RC4_40_MD5 - SSL_RSA_EXPORT_WITH_DES40_CBC_SHA - SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA - SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA - - - - - - - - - - - - - - - - - - - SSLv3 - SSLv2 - - - - - diff --git a/catalog-be/src/main/resources/keystore/README.txt b/catalog-be/src/main/resources/keystore/README.txt deleted file mode 100644 index bbbbd07e27..0000000000 --- a/catalog-be/src/main/resources/keystore/README.txt +++ /dev/null @@ -1,16 +0,0 @@ -keytool -genkeypair -keystore catalogbe.jks -alias catalogbe -keypass Aa123456 -storepass Aa123456 -keyalg RSA -keysize 2048 -validity 3650 -dname "CN=Catalog BE, OU=Development, O=AT&T, L=TLV, C=IL" - - -3650 – 10 years validity -Eyal Sofer – creator -Development – Organization unit -AT&T – Organization -TLV- City -IL – Country code - - -catalogbe.jks – name of keystore -Aa123456 - password - -#In order to generate the password OBF:..., run the following command: -java -cp ../jetty-distribution-9.2.7.v20150116/lib/jetty-http-9.2.7.v20150116.jar:../jetty-distribution-9.2.7.v20150116/lib/jetty-util-9.2.7.v20150116.jar org.eclipse.jetty.util.security.Password Aa123456 \ No newline at end of file diff --git a/catalog-be/src/main/resources/keystore/catalogbe.jks b/catalog-be/src/main/resources/keystore/catalogbe.jks deleted file mode 100644 index 8493a46acaca88b18096dcbce5ed6435812913a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2201 zcmcJP={wX58^>q0jeR{rvL@?qtYhDokYVimmVFFkD0>VBIV6O^X_7Tr%9d;;iBM#X z>6ATLB9bie=seGh^A9{P?)$nv-#7PlfA9PHo@}0Mf4*xJue$d`;*)i>WaFyqELwHmqlYC8(d z6Z5T>lC(+u7VXLg>l{7G@+gtq#Yb;KVh9UZ9)bvpyk^SLct()2w-aUQi z87AM@A1!^skxR6<>kAuInepn`+NcPz0KXB(#i`1@mcu`-0U9pT{-)fuo`kDAOmSFW zOqjRl0f#8!0j1V`BlB;C;Heu#g!he^T^)|-@2Z51_#)8Y5#j+5CjYQ3?KV&l=3$17aH<<<&yFu2ZH0Vv%SF=%k;e00I zo+iHMnR^9Wk0$2bSu@%Lme$6PWYs3pLJ-*aZ20_~nNl0dP;0o{!KNN0Pqx;)t#9x&%!W(rTjyBLqj|@@{21{CORlWant6~Flw=YxEaK&b( zqu$;!54Md`ABl?c{aPTUOr_Z7KhiGOyW2_H3dET`GT|GJ;Q=4>2XyufdQ!_2+MKt! zQ}^owBp9`Oa@E5H(inMr)fT%kg>u_}>GK{7665HYC{NOqNqgodQP^NR%*xMFb~}iM zFm+HtnClLM^w9XNrq45WvVtnwnn5MRB6J$_pPX|iP3#@n!30VSRd4;`NyR(;T`F%_ zMaNYQ=Z@0ASS9r=>6rUFpN6{|f~wm&t)49x1qz=MF3~ljGi=8#Xbx7E75n@93_`|Ls#y9<@3z*iwnd$E*u{ z29Iw3bS&~6v-n}N{E_hGgqJ{_=X)5dlpq5_H&(6!g;F%g3A$$%A4>h@@ALoqNr~u; zRw}g?Cu&(&NiW+r$aEq?RLh^=SW3=AVNK{+6JB;1ceeYvbk{p(NhH{Gt{F&eU}!V( zB2$_nHRKBN(C2dpw=;)}_}Z1*in+CR2L@4`N{8&%*W_M|(7#ObioSL`EvK(WP;zhc zz3=xZKO+`V5u~#66G&R8d{J|I`a5}d=K~F{^#gPG?86oBtbf80?jWlKoCPBE3P6Nj z28a;kJtzbWfxzhXuxtPm4ZS4EgtX5D1|#4gz!r%B=xLy~Fd7cXWpgAW!0;PrIiNP? zc1R`w@tYVJVY)WrHb_o@{Wl^w5C%Tie6S&*!9KWfBnQCuTZM8k=>IpAFX$Np=Xscr zN`QjA{8?94LOJm;$)6!o^?&&P8cPHV|9io+{D%_3EFgdgW`GdEV9-E$e;G7uPOm&Q zGb$|5-S@!f{fi)9tucZcI&E?CptK>+V*yU#*4&@IkJ*~bt??lzCh4%33s`C9l3p>- zte~^(dvm5kX9sbIa_?+Ccw@5-ZdwjSE=6R7pHtkj%bms@L>jzJax3aVN#E&m2oVU6 zj~A(XaQ^e>E%xGseU-x9>kPe7t026p>Cv!*1Z|sj#!w!rgP^c9d(wzE?^GR}DvZ5( z@sFq5HJq%Lwcl+xf};IS0$MWh%$4Kabp)%uQ@0^`tGMZ`o}TQMF@qOIw=!a?{K;X) z+Phjjs6aA{k}P(&aLz4r3ts1*tNAOF1vN?-zJ`Y$(e~@5IbX7kL~K7q3I35e62~k6gqV= zEQET+c$P+sjd4WT&->ih^eJLK!r?K>v)Up;xXzd7ZVn-x8C`OD;7e}thEvekzOLqB zg)^Af=k1Qg?Ym()b@aT))@_tZZ;fm|IZyz+p*Z!}t;`VhN$6Uv&fkuRQ%8_)ZS0Yb zxqa499G_)9cX|GcxM;-`Y4t=Y*1U?S3PaM3t%6!~(P8iq=VNy#%4=1ZMpdKDS{Ylt zK42rBbo@