Merge SDNC startup and certificate install scripts
[sdnc/oam.git] / installation / sdnc / src / main / scripts / installCerts.py
1 # ============LICENSE_START=======================================================
2 #  Copyright (C) 2019 Nordix Foundation.
3 # ================================================================================
4 #  extended by highstreet technologies GmbH (c) 2020
5 # ================================================================================
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 #      http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18 # SPDX-License-Identifier: Apache-2.0
19 # ============LICENSE_END=========================================================
20 #
21
22
23 # coding=utf-8
24 import os
25 import re
26 import http.client
27 import base64
28 import time
29 import zipfile
30 import shutil
31 import subprocess
32 import logging
33
34 odl_home = os.environ['ODL_HOME']
35 log_directory = odl_home + '/data/log/'
36 log_file = log_directory + 'installCerts.log'
37 with open(os.path.join(log_directory, 'installCerts.log'), 'w') as fp:
38     pass
39 log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
40 if not os.path.exists(log_directory):
41     os.makedirs(log_directory)
42 logging.basicConfig(filename=log_file,level=logging.DEBUG,filemode='w',format=log_format)
43 print ('Start cert provisioning. Log file: ' + log_file);
44
45 Path = "/tmp"
46 if "ODL_CERT_DIR" in os.environ:
47     Path = os.environ['ODL_CERT_DIR']
48
49 zipFileList = []
50
51 username = os.environ['ODL_ADMIN_USERNAME']
52 password = os.environ['ODL_ADMIN_PASSWORD']
53 newpassword = os.environ.get('ODL_ADMIN_NEWPASSWORD')
54 TIMEOUT=1000
55 INTERVAL=30
56 timePassed=0
57
58 postKeystore= "/rests/operations/netconf-keystore:add-keystore-entry"
59 postPrivateKey= "/rests/operations/netconf-keystore:add-private-key"
60 postTrustedCertificate= "/rests/operations/netconf-keystore:add-trusted-certificate"
61
62 truststore_pass_file = Path + '/truststore.pass'
63 truststore_file = Path + '/truststore.jks'
64
65 keystore_pass_file = Path + '/keystore.pass'
66 keystore_file = Path + '/keystore.jks'
67
68 jks_files = [truststore_pass_file, keystore_pass_file, keystore_file, truststore_file]
69
70 envOdlFeaturesBoot='ODL_FEATURES_BOOT'
71 # Strategy sli-api is default
72 certreadyCmd="POST"
73 certreadyUrl="/rests/operations/SLI-API:healthcheck"
74
75 if "SDNRWT" in os.environ: 
76     sdnrWt = os.environ['SDNRWT']
77     if sdnrWt == "true":
78         certreadyCmd="GET"
79         certreadyUrl="/rests/data/network-topology:network-topology"
80 logging.info('ODL ready strategy with command %s and url %s', certreadyCmd, certreadyUrl)
81
82 odl_port = 8181
83 cred_string = username + ":" + password
84 headers = {'Authorization':'Basic %s' % base64.b64encode(cred_string.encode()).decode(),
85            'X-FromAppId': 'csit-sdnc',
86            'X-TransactionId': 'csit-sdnc',
87            'Accept':"application/json",
88            'Content-type':"application/yang-data+json"}
89
90 def readFile(folder, file):
91     key = open(Path + "/" + folder + "/" + file, "r")
92     fileRead = key.read()
93     key.close()
94     fileRead = "\n".join(fileRead.splitlines()[1:-1])
95     return fileRead
96
97 def readTrustedCertificate(folder, file):
98     listCert = list()
99     caPem = ""
100     startCa = False
101     key = open(folder + "/" + file, "r")
102     lines = key.readlines()
103     for line in lines:
104         if not "BEGIN CERTIFICATE" in line and not "END CERTIFICATE" in line and startCa:
105             caPem += line
106         elif "BEGIN CERTIFICATE" in line:
107             startCa = True
108         elif "END CERTIFICATE" in line:
109             startCa = False
110             listCert.append(caPem)
111             caPem = ""
112     return listCert
113
114 def makeKeystoreKey(clientKey, count):
115     odl_private_key = "ODL_private_key_%d" %count
116
117     json_keystore_key='{{\"input\": {{ \"key-credential\": {{\"key-id\": \"{odl_private_key}\", \"private-key\" : ' \
118                       '\"{clientKey}\",\"passphrase\" : \"\"}}}}}}'.format(
119         odl_private_key=odl_private_key,
120         clientKey=clientKey)
121
122     return json_keystore_key
123
124 def makePrivateKey(clientKey, clientCrt, certList, count):
125     caPem = ""
126     if certList:
127         for cert in certList:
128             caPem += '\"%s\",' % cert
129         caPem = caPem.rsplit(',', 1)[0]
130     odl_private_key="ODL_private_key_%d" %count
131
132     json_private_key='{{\"input\": {{ \"private-key\":{{\"name\": \"{odl_private_key}\", \"data\" : ' \
133                      '\"{clientKey}\",\"certificate-chain\":[\"{clientCrt}\",{caPem}]}}}}}}'.format(
134         odl_private_key=odl_private_key,
135         clientKey=clientKey,
136         clientCrt=clientCrt,
137         caPem=caPem)
138
139     return json_private_key
140
141 def makeTrustedCertificate(certList, count):
142     number = 0
143     json_cert_format = ""
144     for cert in certList:
145         cert_name = "xNF_CA_certificate_%d_%d" %(count, number)
146         json_cert_format += '{{\"name\": \"{trusted_name}\",\"certificate\":\"{cert}\"}},\n'.format(
147             trusted_name=cert_name,
148             cert=cert.strip())
149         number += 1
150
151     json_cert_format = json_cert_format.rsplit(',', 1)[0]
152     json_trusted_cert='{{\"input\": {{ \"trusted-certificate\": [{certificates}]}}}}'.format(
153         certificates=json_cert_format)
154     return json_trusted_cert
155
156
157 def makeRestconfPost(conn, json_file, apiCall):
158     req = conn.request("POST", apiCall, json_file, headers=headers)
159     res = conn.getresponse()
160     res.read()
161     if res.status != 200:
162         logging.error("Error here, response back wasnt 200: Response was : %d , %s" % (res.status, res.reason))
163     else:
164         logging.debug("Response :%s Reason :%s ",res.status, res.reason)
165
166 def extractZipFiles(zipFileList, count):
167     for zipFolder in zipFileList:
168         with zipfile.ZipFile(Path + "/" + zipFolder.strip(),"r") as zip_ref:
169             zip_ref.extractall(Path)
170         folder = zipFolder.rsplit(".")[0]
171         processFiles(folder, count)
172
173 def processFiles(folder, count):
174     logging.info('Process folder: %d %s', count, folder)
175     for file in os.listdir(Path + "/" + folder):
176         if os.path.isfile(Path + "/" + folder + "/" + file.strip()):
177             if ".key" in file:
178                 clientKey = readFile(folder, file.strip())
179             elif "trustedCertificate" in file:
180                 certList = readTrustedCertificate(Path + "/" + folder, file.strip())
181             elif ".crt" in file:
182                 clientCrt = readFile(folder, file.strip())
183         else:
184             logging.error("Could not find file %s" % file.strip())
185     shutil.rmtree(Path + "/" + folder)
186     post_content(clientKey, clientCrt, certList, count)
187
188 def post_content(clientKey, clientCrt, certList, count):
189     logging.info('Post content: %d', count)
190     conn = http.client.HTTPConnection("localhost",odl_port)
191
192     if clientKey:
193         json_keystore_key = makeKeystoreKey(clientKey, count)
194         logging.debug("Posting private key in to ODL keystore")
195         makeRestconfPost(conn, json_keystore_key, postKeystore)
196
197     if certList:
198         json_trusted_cert = makeTrustedCertificate(certList, count)
199         logging.debug("Posting trusted cert list in to ODL")
200         makeRestconfPost(conn, json_trusted_cert, postTrustedCertificate)
201
202     if clientKey and clientCrt and certList:
203         json_private_key = makePrivateKey(clientKey, clientCrt, certList, count)
204         logging.debug("Posting the cert in to ODL")
205         makeRestconfPost(conn, json_private_key, postPrivateKey)
206
207
208 def makeHealthcheckCall(headers, timePassed):
209     connected = False
210     # WAIT 10 minutes maximum and test every 30 seconds if HealthCheck API is returning 200
211     while timePassed < TIMEOUT:
212         try:
213             conn = http.client.HTTPConnection("localhost",odl_port)
214             req = conn.request(certreadyCmd, certreadyUrl,headers=headers)
215             res = conn.getresponse()
216             res.read()
217             httpStatus = res.status
218             if httpStatus == 200:
219                 logging.debug("Healthcheck Passed in %d seconds." %timePassed)
220                 connected = True
221                 break
222             else:
223                 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))
224         except:
225             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))
226         timePassed = timeIncrement(timePassed)
227
228     if timePassed > TIMEOUT:
229         logging.error("TIME OUT: Healthcheck not passed in  %d seconds... Could cause problems for testing activities..." %TIMEOUT)
230
231     return connected
232
233
234 def timeIncrement(timePassed):
235     time.sleep(INTERVAL)
236     timePassed = timePassed + INTERVAL
237     return timePassed
238
239
240 def get_pass(file_name):
241     try:
242         with open(file_name, 'r') as file_obj:
243             password = file_obj.read().strip()
244         return "'{}'".format(password)
245     except Exception as e:
246         logging.error("Error occurred while fetching password : %s", e)
247         exit()
248
249 def cleanup():
250     for file in os.listdir(Path):
251         if os.path.isfile(Path + '/' + file):
252             logging.debug("Cleaning up the file %s", Path + '/'+ file)
253             os.remove(Path + '/'+ file)
254
255
256 def jks_to_p12(file, password):
257     """Converts jks format into p12"""
258     try:
259         certList = []
260         key = None
261         cert = None
262         if (file.endswith('.jks')):
263              p12_file = file.replace('.jks', '.p12')
264              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)
265              logging.debug("Converting %s into p12 format", file)
266              os.system(jks_cmd)
267              file = p12_file
268              return file
269     except Exception as e:
270         logging.error("Error occurred while converting jks to p12 format : %s", e)
271
272
273 def make_cert_chain(cert_chain, pattern):
274     cert_list = []
275     if cert_chain:
276         matches = re.findall(pattern, cert_chain, re.DOTALL | re.MULTILINE)
277         for cert in matches:
278             cert_list.append(cert.strip())
279         return cert_list
280     else:
281         logging.debug(" Certificate Chain empty: %s " % cert_chain)
282
283
284 def process_jks_files(count):
285     ca_cert_list = []
286     logging.info("Processing JKS files found in %s directory " % Path)
287     try:
288         if all([os.path.isfile(f) for f in jks_files]):
289             keystore_pass = get_pass(keystore_pass_file)
290             keystore_file_p12 = jks_to_p12(keystore_file, keystore_pass)
291
292             client_key_cmd = 'openssl pkcs12 -in {src_file} -nocerts -nodes -passin pass:{src_pass}'.format(
293                 src_file=keystore_file_p12, src_pass=keystore_pass)
294             client_crt_cmd = 'openssl pkcs12 -in {src_file} -clcerts -nokeys  -passin pass:{src_pass}'.format(
295                 src_file=keystore_file_p12, src_pass=keystore_pass)
296
297             truststore_pass = get_pass(truststore_pass_file)
298             truststore_p12 = jks_to_p12(truststore_file, truststore_pass)
299
300             trust_cert_cmd = 'openssl pkcs12 -in {src_file} -cacerts -nokeys -passin pass:{src_pass} '.format(
301                 src_file=truststore_p12, src_pass=truststore_pass)
302
303             key_pattern = r'(?<=-----BEGIN PRIVATE KEY-----).*?(?=-----END PRIVATE KEY-----)'
304             client_key = subprocess.check_output(client_key_cmd, shell=True)
305             if client_key:
306                 client_key = make_cert_chain(client_key, key_pattern)[0]
307                 logging.debug("Key Ok")
308
309             cert_pattern = r'(?<=-----BEGIN CERTIFICATE-----).*?(?=-----END CERTIFICATE-----)'
310             client_cert = subprocess.check_output(client_crt_cmd, shell=True)
311             if client_cert:
312                 client_cert = make_cert_chain(client_cert, cert_pattern)[0]
313                 logging.debug("Client Cert Ok")
314
315             ca_cert = subprocess.check_output(trust_cert_cmd, shell=True)
316             if ca_cert:
317                 ca_cert_list = make_cert_chain(ca_cert, cert_pattern)
318                 logging.debug("CA Cert Ok")
319
320             if client_key and client_cert and ca_cert:
321                 post_content(client_key, client_cert, ca_cert_list, count)
322         else:
323             logging.debug("No JKS files found in %s directory" % Path)
324     except subprocess.CalledProcessError as err:
325         print("CalledProcessError Execution of OpenSSL command failed: %s" % err)
326     except Exception as e:
327         logging.error("UnExpected Error while processing JKS files at {0}, Caused by: {1}".format(Path, e))
328
329 def replaceAdminPassword(username, password, newpassword):
330     if newpassword is None:
331         logging.info('Not to replace password for user %s', username)
332     else:
333         logging.info('Replace password for user %s', username)
334         try:
335             jsondata = '{\"password\": \"{newpassword}\"}'.format(newpassword=newpassword)
336             url = '/auth/v1/users/{username}@sdn'.format(username=username)
337             loggin.info("Url %s data $s", url, jsondata)
338             conn = http.client.HTTPConnection("localhost",odl_port)
339             req = conn.request("PUT", url, jsondata, headers=headers)
340             res = conn.getresponse()
341             res.read()
342             httpStatus = res.status
343             if httpStatus == 200:
344                 logging.debug("New password provided successfully for user %s", username)
345             else:
346                 logging.debug("Password change was not possible. Problem code was: %d", httpStatus)
347         except:
348             logging.error("Cannot execute REST call to set password.")
349
350
351 def readCertProperties():
352     '''
353     This function searches for manually copied zip file
354     containing certificates. This is required as part
355     of backward compatibility.
356     If not foud, it searches for jks certificates.
357     '''
358     connected = makeHealthcheckCall(headers, timePassed)
359     logging.info('Connected status: %s', connected)
360     if connected:
361         replaceAdminPassword(username, password, newpassword)
362         count = 0
363         if os.path.isfile(Path + "/certs.properties"):
364             with open(Path + "/certs.properties", "r") as f:
365                 for line in f:
366                     if not "*****" in line:
367                         zipFileList.append(line)
368                     else:
369                         extractZipFiles(zipFileList, count)
370                         count += 1
371                         del zipFileList[:]
372         else:
373             logging.debug("No certs.properties/zip files exist at: " + Path)
374             logging.info("Processing any  available jks/p12 files under cert directory")
375             process_jks_files(count)
376
377
378 readCertProperties()
379 logging.info('Cert installation ending')