Update scripts to use python3
[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 http.client
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= "/rests/operations/netconf-keystore:add-keystore-entry"
51 postPrivateKey= "/rests/operations/netconf-keystore:add-private-key"
52 postTrustedCertificate= "/rests/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 cred_string = username + ":" + password
64 headers = {'Authorization':'Basic %s' % base64.b64encode(cred_string.encode()).decode(),
65            'X-FromAppId': 'csit-sdnc',
66            'X-TransactionId': 'csit-sdnc',
67            'Accept':"application/json",
68            'Content-type':"application/yang-data+json"}
69
70
71 def readFile(folder, file):
72     key = open(Path + "/" + folder + "/" + file, "r")
73     fileRead = key.read()
74     key.close()
75     fileRead = "\n".join(fileRead.splitlines()[1:-1])
76     return fileRead
77
78
79 def readTrustedCertificate(folder, file):
80     listCert = list()
81     caPem = ""
82     startCa = False
83     key = open(folder + "/" + file, "r")
84     lines = key.readlines()
85     for line in lines:
86         if not "BEGIN CERTIFICATE" in line and not "END CERTIFICATE" in line and startCa:
87             caPem += line
88         elif "BEGIN CERTIFICATE" in line:
89             startCa = True
90         elif "END CERTIFICATE" in line:
91             startCa = False
92             listCert.append(caPem)
93             caPem = ""
94     return listCert
95
96
97 def makeKeystoreKey(clientKey, count):
98     odl_private_key = "ODL_private_key_%d" %count
99
100     json_keystore_key='{{\"input\": {{ \"key-credential\": {{\"key-id\": \"{odl_private_key}\", \"private-key\" : ' \
101                       '\"{clientKey}\",\"passphrase\" : \"\"}}}}}}'.format(
102         odl_private_key=odl_private_key,
103         clientKey=clientKey)
104
105     return json_keystore_key
106
107
108 def makePrivateKey(clientKey, clientCrt, certList, count):
109     caPem = ""
110     if certList:
111         for cert in certList:
112             caPem += '\"%s\",' % cert
113         caPem = caPem.rsplit(',', 1)[0]
114     odl_private_key="ODL_private_key_%d" %count
115
116     json_private_key='{{\"input\": {{ \"private-key\":{{\"name\": \"{odl_private_key}\", \"data\" : ' \
117                      '\"{clientKey}\",\"certificate-chain\":[\"{clientCrt}\",{caPem}]}}}}}}'.format(
118         odl_private_key=odl_private_key,
119         clientKey=clientKey,
120         clientCrt=clientCrt,
121         caPem=caPem)
122
123     return json_private_key
124
125
126 def makeTrustedCertificate(certList, count):
127     number = 0
128     json_cert_format = ""
129     for cert in certList:
130         cert_name = "xNF_CA_certificate_%d_%d" %(count, number)
131         json_cert_format += '{{\"name\": \"{trusted_name}\",\"certificate\":\"{cert}\"}},\n'.format(
132             trusted_name=cert_name,
133             cert=cert.strip())
134         number += 1
135
136     json_cert_format = json_cert_format.rsplit(',', 1)[0]
137     json_trusted_cert='{{\"input\": {{ \"trusted-certificate\": [{certificates}]}}}}'.format(
138         certificates=json_cert_format)
139     return json_trusted_cert
140
141
142 def makeRestconfPost(conn, json_file, apiCall):
143     req = conn.request("POST", apiCall, json_file, headers=headers)
144     res = conn.getresponse()
145     res.read()
146     if res.status != 200:
147         logging.error("Error here, response back wasnt 200: Response was : %d , %s" % (res.status, res.reason))
148     else:
149         logging.debug("Response :%s Reason :%s ",res.status, res.reason)
150
151
152 def extractZipFiles(zipFileList, count):
153     for zipFolder in zipFileList:
154         with zipfile.ZipFile(Path + "/" + zipFolder.strip(),"r") as zip_ref:
155             zip_ref.extractall(Path)
156         folder = zipFolder.rsplit(".")[0]
157         processFiles(folder, count)
158
159
160 def processFiles(folder, count):
161     for file in os.listdir(Path + "/" + folder):
162         if os.path.isfile(Path + "/" + folder + "/" + file.strip()):
163             if ".key" in file:
164                 clientKey = readFile(folder, file.strip())
165             elif "trustedCertificate" in file:
166                 certList = readTrustedCertificate(Path + "/" + folder, file.strip())
167             elif ".crt" in file:
168                 clientCrt = readFile(folder, file.strip())
169         else:
170             logging.error("Could not find file %s" % file.strip())
171     shutil.rmtree(Path + "/" + folder)
172     post_content(clientKey, clientCrt, certList, count)
173
174
175 def post_content(clientKey, clientCrt, certList, count):
176     conn = http.client.HTTPConnection("localhost",odl_port)
177
178     if clientKey:
179         json_keystore_key = makeKeystoreKey(clientKey, count)
180         logging.debug("Posting private key in to ODL keystore")
181         makeRestconfPost(conn, json_keystore_key, postKeystore)
182
183     if certList:
184         json_trusted_cert = makeTrustedCertificate(certList, count)
185         logging.debug("Posting trusted cert list in to ODL")
186         makeRestconfPost(conn, json_trusted_cert, postTrustedCertificate)
187
188     if clientKey and clientCrt and certList:
189         json_private_key = makePrivateKey(clientKey, clientCrt, certList, count)
190         logging.debug("Posting the cert in to ODL")
191         makeRestconfPost(conn, json_private_key, postPrivateKey)
192
193
194 def makeHealthcheckCall(headers, timePassed):
195     connected = False
196     # WAIT 10 minutes maximum and test every 30 seconds if HealthCheck API is returning 200
197     while timePassed < TIMEOUT:
198         try:
199             conn = http.client.HTTPConnection("localhost",odl_port)
200             req = conn.request("POST", "/rests/operations/SLI-API:healthcheck",headers=headers)
201             res = conn.getresponse()
202             res.read()
203             if res.status == 200:
204                 logging.debug("Healthcheck Passed in %d seconds." %timePassed)
205                 connected = True
206                 break
207             else:
208                 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))
209         except:
210             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))
211         timePassed = timeIncrement(timePassed)
212
213     if timePassed > TIMEOUT:
214         logging.error("TIME OUT: Healthcheck not passed in  %d seconds... Could cause problems for testing activities..." %TIMEOUT)
215     return connected
216
217
218 def timeIncrement(timePassed):
219     time.sleep(INTERVAL)
220     timePassed = timePassed + INTERVAL
221     return timePassed
222
223
224 def get_pass(file_name):
225     try:
226         with open(file_name, 'r') as file_obj:
227             password = file_obj.read().strip()
228         return "'{}'".format(password)
229     except Exception as e:
230         logging.error("Error occurred while fetching password : %s", e)
231         exit()
232
233
234 def cleanup():
235     for file in jks_files:
236         if os.path.isfile(file):
237             logging.debug("Cleaning up the file %s", file)
238             os.remove(file)
239
240
241 def jks_to_p12(file, password):
242     """Converts jks format into p12"""
243     try:
244         p12_file = file.replace('.jks', '.p12')
245         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)
246         logging.debug("Converting %s into p12 format", file)
247         os.system(jks_cmd)
248         file = p12_file
249         return file
250     except Exception as e:
251         logging.error("Error occurred while converting jks to p12 format : %s", e)
252
253
254 def make_cert_chain(cert_chain, pattern):
255     cert_list = []
256     if cert_chain:
257         matches = re.findall(pattern, cert_chain, re.DOTALL | re.MULTILINE)
258         for cert in matches:
259             cert_list.append(cert.strip())
260         return cert_list
261     else:
262         logging.debug(" Certificate Chain empty: %s " % cert_chain)
263
264
265 def process_jks_files(count):
266     ca_cert_list = []
267     logging.info("Processing JKS files found in %s directory " % Path)
268     try:
269         if all([os.path.isfile(f) for f in jks_files]):
270             keystore_pass = get_pass(keystore_pass_file)
271             keystore_file_p12 = jks_to_p12(keystore_file, keystore_pass)
272
273             client_key_cmd = 'openssl pkcs12 -in {src_file} -nocerts -nodes -passin pass:{src_pass}'.format(
274                 src_file=keystore_file_p12, src_pass=keystore_pass)
275             client_crt_cmd = 'openssl pkcs12 -in {src_file} -clcerts -nokeys  -passin pass:{src_pass}'.format(
276                 src_file=keystore_file_p12, src_pass=keystore_pass)
277
278             truststore_pass = get_pass(truststore_pass_file)
279             truststore_p12 = jks_to_p12(truststore_file, truststore_pass)
280
281             trust_cert_cmd = 'openssl pkcs12 -in {src_file} -cacerts -nokeys -passin pass:{src_pass} '.format(
282                 src_file=truststore_p12, src_pass=truststore_pass)
283
284             key_pattern = r'(?<=-----BEGIN PRIVATE KEY-----).*?(?=-----END PRIVATE KEY-----)'
285             client_key = subprocess.check_output(client_key_cmd, shell=True)
286             if client_key:
287                 client_key = make_cert_chain(client_key, key_pattern)[0]
288                 logging.debug("Key Ok")
289
290             cert_pattern = r'(?<=-----BEGIN CERTIFICATE-----).*?(?=-----END CERTIFICATE-----)'
291             client_cert = subprocess.check_output(client_crt_cmd, shell=True)
292             if client_cert:
293                 client_cert = make_cert_chain(client_cert, cert_pattern)[0]
294                 logging.debug("Client Cert Ok")
295
296             ca_cert = subprocess.check_output(trust_cert_cmd, shell=True)
297             if ca_cert:
298                 ca_cert_list = make_cert_chain(ca_cert, cert_pattern)
299                 logging.debug("CA Cert Ok")
300
301             if client_key and client_cert and ca_cert:
302                 post_content(client_key, client_cert, ca_cert_list, count)
303         else:
304             logging.debug("No JKS files found in %s directory" % Path)
305     except subprocess.CalledProcessError as err:
306         print("CalledProcessError Execution of OpenSSL command failed: %s" % err)
307     except Exception as e:
308         logging.error("UnExpected Error while processing JKS files at {0}, Caused by: {1}".format(Path, e))
309
310
311 def readCertProperties():
312     '''
313     This function searches for manually copied zip file
314     containing certificates. This is required as part
315     of backward compatibility.
316     If not foud, it searches for jks certificates.
317     '''
318     connected = makeHealthcheckCall(headers, timePassed)
319
320     if connected:
321         count = 0
322         if os.path.isfile(Path + "/certs.properties"):
323             with open(Path + "/certs.properties", "r") as f:
324                 for line in f:
325                     if not "*****" in line:
326                         zipFileList.append(line)
327                     else:
328                         extractZipFiles(zipFileList, count)
329                         count += 1
330                         del zipFileList[:]
331         else:
332             logging.debug("No certs.properties/zip files exist at: " + Path)
333             process_jks_files(count)
334
335
336 readCertProperties()