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