1 # ============LICENSE_START=======================================================
2 # Copyright (C) 2019 Nordix Foundation.
3 # ================================================================================
4 # extended by highstreet technologies GmbH (c) 2020
5 # Copyright (c) 2021 Nokia Intellectual Property.
6 # ================================================================================
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 # SPDX-License-Identifier: Apache-2.0
20 # ============LICENSE_END=========================================================
35 odl_home = os.environ['ODL_HOME']
36 log_directory = odl_home + '/data/log/'
37 log_file = log_directory + 'installCerts.log'
38 with open(os.path.join(log_directory, 'installCerts.log'), 'w') as fp:
40 log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
41 if not os.path.exists(log_directory):
42 os.makedirs(log_directory)
43 logging.basicConfig(filename=log_file,level=logging.DEBUG,filemode='w',format=log_format)
44 print ('Start cert provisioning. Log file: ' + log_file);
47 if "ODL_CERT_DIR" in os.environ:
48 Path = os.environ['ODL_CERT_DIR']
52 username = os.environ['ODL_ADMIN_USERNAME']
53 password = os.environ['ODL_ADMIN_PASSWORD']
54 newpassword = os.environ.get('ODL_ADMIN_NEWPASSWORD')
59 postKeystore= "/rests/operations/netconf-keystore:add-keystore-entry"
60 postPrivateKey= "/rests/operations/netconf-keystore:add-private-key"
61 postTrustedCertificate= "/rests/operations/netconf-keystore:add-trusted-certificate"
63 truststore_pass_file = Path + '/truststore.pass'
64 truststore_file = Path + '/truststore.jks'
66 keystore_pass_file = Path + '/keystore.pass'
67 keystore_file = Path + '/keystore.jks'
69 jks_files = [truststore_pass_file, keystore_pass_file, keystore_file, truststore_file]
71 envOdlFeaturesBoot='ODL_FEATURES_BOOT'
72 # Strategy sli-api is default
74 certreadyUrl="/rests/operations/SLI-API:healthcheck"
76 if "SDNRWT" in os.environ:
77 sdnrWt = os.environ['SDNRWT']
80 certreadyUrl="/rests/data/network-topology:network-topology"
81 logging.info('ODL ready strategy with command %s and url %s', certreadyCmd, certreadyUrl)
84 cred_string = username + ":" + password
85 headers = {'Authorization':'Basic %s' % base64.b64encode(cred_string.encode()).decode(),
86 'X-FromAppId': 'csit-sdnc',
87 'X-TransactionId': 'csit-sdnc',
88 'Accept':"application/json",
89 'Content-type':"application/yang-data+json"}
91 def readFile(folder, file):
92 key = open(Path + "/" + folder + "/" + file, "r")
95 fileRead = "\n".join(fileRead.splitlines()[1:-1])
98 def readTrustedCertificate(folder, file):
102 key = open(folder + "/" + file, "r")
103 lines = key.readlines()
105 if not "BEGIN CERTIFICATE" in line and not "END CERTIFICATE" in line and startCa:
107 elif "BEGIN CERTIFICATE" in line:
109 elif "END CERTIFICATE" in line:
111 listCert.append(caPem)
115 def makeKeystoreKey(clientKey, count):
116 odl_private_key = "ODL_private_key_%d" %count
118 json_keystore_key='{{\"input\": {{ \"key-credential\": {{\"key-id\": \"{odl_private_key}\", \"private-key\" : ' \
119 '\"{clientKey}\",\"passphrase\" : \"\"}}}}}}'.format(
120 odl_private_key=odl_private_key,
123 return json_keystore_key
125 def makePrivateKey(clientKey, clientCrt, certList, count):
128 for cert in certList:
129 caPem += '\"%s\",' % cert
130 caPem = caPem.rsplit(',', 1)[0]
131 odl_private_key="ODL_private_key_%d" %count
133 json_private_key='{{\"input\": {{ \"private-key\":{{\"name\": \"{odl_private_key}\", \"data\" : ' \
134 '\"{clientKey}\",\"certificate-chain\":[\"{clientCrt}\",{caPem}]}}}}}}'.format(
135 odl_private_key=odl_private_key,
140 return json_private_key
142 def makeTrustedCertificate(certList, count):
144 json_cert_format = ""
145 for cert in certList:
146 cert_name = "xNF_CA_certificate_%d_%d" %(count, number)
147 json_cert_format += '{{\"name\": \"{trusted_name}\",\"certificate\":\"{cert}\"}},\n'.format(
148 trusted_name=cert_name,
152 json_cert_format = json_cert_format.rsplit(',', 1)[0]
153 json_trusted_cert='{{\"input\": {{ \"trusted-certificate\": [{certificates}]}}}}'.format(
154 certificates=json_cert_format)
155 return json_trusted_cert
158 def makeRestconfPost(conn, json_file, apiCall):
159 req = conn.request("POST", apiCall, json_file, headers=headers)
160 res = conn.getresponse()
162 if res.status != 200:
163 logging.error("Error here, response back wasnt 200: Response was : %d , %s" % (res.status, res.reason))
165 logging.debug("Response :%s Reason :%s ",res.status, res.reason)
167 def extractZipFiles(zipFileList, count):
168 for zipFolder in zipFileList:
169 with zipfile.ZipFile(Path + "/" + zipFolder.strip(),"r") as zip_ref:
170 zip_ref.extractall(Path)
171 folder = zipFolder.rsplit(".")[0]
172 processFiles(folder, count)
174 def processFiles(folder, count):
175 logging.info('Process folder: %d %s', count, folder)
176 for file in os.listdir(Path + "/" + folder):
177 if os.path.isfile(Path + "/" + folder + "/" + file.strip()):
179 clientKey = readFile(folder, file.strip())
180 elif "trustedCertificate" in file:
181 certList = readTrustedCertificate(Path + "/" + folder, file.strip())
183 clientCrt = readFile(folder, file.strip())
185 logging.error("Could not find file %s" % file.strip())
186 shutil.rmtree(Path + "/" + folder)
187 post_content(clientKey, clientCrt, certList, count)
189 def post_content(clientKey, clientCrt, certList, count):
190 logging.info('Post content: %d', count)
191 conn = http.client.HTTPConnection("localhost",odl_port)
194 json_keystore_key = makeKeystoreKey(clientKey, count)
195 logging.debug("Posting private key in to ODL keystore")
196 makeRestconfPost(conn, json_keystore_key, postKeystore)
199 json_trusted_cert = makeTrustedCertificate(certList, count)
200 logging.debug("Posting trusted cert list in to ODL")
201 makeRestconfPost(conn, json_trusted_cert, postTrustedCertificate)
203 if clientKey and clientCrt and certList:
204 json_private_key = makePrivateKey(clientKey, clientCrt, certList, count)
205 logging.debug("Posting the cert in to ODL")
206 makeRestconfPost(conn, json_private_key, postPrivateKey)
209 def makeHealthcheckCall(headers, timePassed):
211 # WAIT 10 minutes maximum and test every 30 seconds if HealthCheck API is returning 200
212 while timePassed < TIMEOUT:
214 conn = http.client.HTTPConnection("localhost",odl_port)
215 req = conn.request(certreadyCmd, certreadyUrl,headers=headers)
216 res = conn.getresponse()
218 httpStatus = res.status
219 if httpStatus == 200:
220 logging.debug("Healthcheck Passed in %d seconds." %timePassed)
224 logging.debug("Sleep: %d seconds before testing if Healthcheck worked. Total wait time up now is: %d seconds. Timeout is: %d seconds. Problem code was: %d" %(INTERVAL, timePassed, TIMEOUT, httpStatus))
226 logging.error("Cannot execute REST call. Sleep: %d seconds before testing if Healthcheck worked. Total wait time up now is: %d seconds. Timeout is: %d seconds." %(INTERVAL, timePassed, TIMEOUT))
227 timePassed = timeIncrement(timePassed)
229 if timePassed > TIMEOUT:
230 logging.error("TIME OUT: Healthcheck not passed in %d seconds... Could cause problems for testing activities..." %TIMEOUT)
235 def timeIncrement(timePassed):
237 timePassed = timePassed + INTERVAL
241 def get_pass(file_name):
243 with open(file_name, 'r') as file_obj:
244 password = file_obj.read().strip()
245 return "'{}'".format(password)
246 except Exception as e:
247 logging.error("Error occurred while fetching password : %s", e)
251 for file in os.listdir(Path):
252 if os.path.isfile(Path + '/' + file):
253 logging.debug("Cleaning up the file %s", Path + '/'+ file)
254 os.remove(Path + '/'+ file)
257 def jks_to_p12(file, password):
258 """Converts jks format into p12"""
263 if (file.endswith('.jks')):
264 p12_file = file.replace('.jks', '.p12')
265 jks_cmd = 'keytool -importkeystore -srckeystore {src_file} -destkeystore {dest_file} -srcstoretype JKS -srcstorepass {src_pass} -deststoretype PKCS12 -deststorepass {dest_pass}'.format(src_file=file, dest_file=p12_file, src_pass=password, dest_pass=password)
266 logging.debug("Converting %s into p12 format", file)
270 except Exception as e:
271 logging.error("Error occurred while converting jks to p12 format : %s", e)
274 def make_cert_chain(cert_chain, pattern):
277 cert_chain = cert_chain.decode('utf-8')
278 matches = re.findall(pattern, cert_chain, re.DOTALL | re.MULTILINE)
280 cert_list.append(cert.strip())
283 logging.debug(" Certificate Chain empty: %s " % cert_chain)
286 def process_jks_files(count):
288 logging.info("Processing JKS files found in %s directory " % Path)
290 if all([os.path.isfile(f) for f in jks_files]):
291 keystore_pass = get_pass(keystore_pass_file)
292 keystore_file_p12 = jks_to_p12(keystore_file, keystore_pass)
294 client_key_cmd = 'openssl pkcs12 -in {src_file} -nocerts -nodes -passin pass:{src_pass}'.format(
295 src_file=keystore_file_p12, src_pass=keystore_pass)
296 client_crt_cmd = 'openssl pkcs12 -in {src_file} -clcerts -nokeys -passin pass:{src_pass}'.format(
297 src_file=keystore_file_p12, src_pass=keystore_pass)
299 truststore_pass = get_pass(truststore_pass_file)
300 truststore_p12 = jks_to_p12(truststore_file, truststore_pass)
302 trust_cert_cmd = 'openssl pkcs12 -in {src_file} -cacerts -nokeys -passin pass:{src_pass} '.format(
303 src_file=truststore_p12, src_pass=truststore_pass)
305 key_pattern = r'(?<=-----BEGIN PRIVATE KEY-----).*?(?=-----END PRIVATE KEY-----)'
306 client_key = subprocess.check_output(client_key_cmd, shell=True)
308 client_key = make_cert_chain(client_key, key_pattern)[0]
309 logging.debug("Key Ok")
311 cert_pattern = r'(?<=-----BEGIN CERTIFICATE-----).*?(?=-----END CERTIFICATE-----)'
312 client_cert = subprocess.check_output(client_crt_cmd, shell=True)
314 client_cert = make_cert_chain(client_cert, cert_pattern)[0]
315 logging.debug("Client Cert Ok")
317 ca_cert = subprocess.check_output(trust_cert_cmd, shell=True)
319 ca_cert_list = make_cert_chain(ca_cert, cert_pattern)
320 logging.debug("CA Cert Ok")
322 if client_key and client_cert and ca_cert:
323 post_content(client_key, client_cert, ca_cert_list, count)
325 logging.debug("No JKS files found in %s directory" % Path)
326 except subprocess.CalledProcessError as err:
327 print("CalledProcessError Execution of OpenSSL command failed: %s" % err)
328 except Exception as e:
329 logging.error("UnExpected Error while processing JKS files at {0}, Caused by: {1}".format(Path, e))
331 def replaceAdminPassword(username, password, newpassword):
332 if newpassword is None:
333 logging.info('Not to replace password for user %s', username)
335 logging.info('Replace password for user %s', username)
337 jsondata = '{\"password\": \"{newpassword}\"}'.format(newpassword=newpassword)
338 url = '/auth/v1/users/{username}@sdn'.format(username=username)
339 loggin.info("Url %s data $s", url, jsondata)
340 conn = http.client.HTTPConnection("localhost",odl_port)
341 req = conn.request("PUT", url, jsondata, headers=headers)
342 res = conn.getresponse()
344 httpStatus = res.status
345 if httpStatus == 200:
346 logging.debug("New password provided successfully for user %s", username)
348 logging.debug("Password change was not possible. Problem code was: %d", httpStatus)
350 logging.error("Cannot execute REST call to set password.")
353 def readCertProperties():
355 This function searches for manually copied zip file
356 containing certificates. This is required as part
357 of backward compatibility.
358 If not foud, it searches for jks certificates.
360 connected = makeHealthcheckCall(headers, timePassed)
361 logging.info('Connected status: %s', connected)
363 replaceAdminPassword(username, password, newpassword)
365 if os.path.isfile(Path + "/certs.properties"):
366 with open(Path + "/certs.properties", "r") as f:
368 if not "*****" in line:
369 zipFileList.append(line)
371 extractZipFiles(zipFileList, count)
375 logging.debug("No certs.properties/zip files exist at: " + Path)
376 logging.info("Processing any available jks/p12 files under cert directory")
377 process_jks_files(count)
381 logging.info('Cert installation ending')