8135f6008bcc7f5973e1f1056df22ec50d4581d5
[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 httplib
24 import base64
25 import time
26 import zipfile
27 import shutil
28 import subprocess
29 import logging
30
31 log_file = '/opt/opendaylight/data/log/installCerts.log'
32 log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
33 logging.basicConfig(filename=log_file,level=logging.DEBUG,filemode='w',format=log_format)
34
35 Path = os.environ['ODL_CERT_DIR']
36
37 zipFileList = []
38
39 username = os.environ['ODL_ADMIN_USERNAME']
40 password = os.environ['ODL_ADMIN_PASSWORD']
41 TIMEOUT=1000
42 INTERVAL=30
43 timePassed=0
44
45 postKeystore= "/restconf/operations/netconf-keystore:add-keystore-entry"
46 postPrivateKey= "/restconf/operations/netconf-keystore:add-private-key"
47 postTrustedCertificate= "/restconf/operations/netconf-keystore:add-trusted-certificate"
48
49 truststore_pass_file = Path + '/truststore.pass'
50 truststore_file = Path + '/truststore.jks'
51
52 keystore_pass_file = Path + '/keystore.pass'
53 keystore_file = Path + '/keystore.jks'
54
55 jks_files = [truststore_pass_file, keystore_pass_file, keystore_file, truststore_file]
56
57 odl_port = 8181
58 headers = {'Authorization':'Basic %s' % base64.b64encode(username + ":" + password),
59            'X-FromAppId': 'csit-sdnc',
60            'X-TransactionId': 'csit-sdnc',
61            'Accept':"application/json",
62            'Content-type':"application/json"}
63
64
65 def readFile(folder, file):
66     key = open(Path + "/" + folder + "/" + file, "r")
67     fileRead = key.read()
68     key.close()
69     fileRead = "\n".join(fileRead.splitlines()[1:-1])
70     return fileRead
71
72
73 def readTrustedCertificate(folder, file):
74     listCert = list()
75     caPem = ""
76     startCa = False
77     key = open(folder + "/" + file, "r")
78     lines = key.readlines()
79     for line in lines:
80         if not "BEGIN CERTIFICATE" in line and not "END CERTIFICATE" in line and startCa:
81             caPem += line
82         elif "BEGIN CERTIFICATE" in line:
83             startCa = True
84         elif "END CERTIFICATE" in line:
85             startCa = False
86             listCert.append(caPem)
87             caPem = ""
88     return listCert
89
90
91 def makeKeystoreKey(clientKey, count):
92     odl_private_key = "ODL_private_key_%d" %count
93
94     json_keystore_key='{{\"input\": {{ \"key-credential\": {{\"key-id\": \"{odl_private_key}\", \"private-key\" : ' \
95                       '\"{clientKey}\",\"passphrase\" : \"\"}}}}}}'.format(
96         odl_private_key=odl_private_key,
97         clientKey=clientKey)
98
99     return json_keystore_key
100
101
102 def makePrivateKey(clientKey, clientCrt, certList, count):
103     caPem = ""
104     if certList:
105         for cert in certList:
106             caPem += '\"%s\",' % cert
107         caPem = caPem.rsplit(',', 1)[0]
108     odl_private_key="ODL_private_key_%d" %count
109
110     json_private_key='{{\"input\": {{ \"private-key\":{{\"name\": \"{odl_private_key}\", \"data\" : ' \
111                      '\"{clientKey}\",\"certificate-chain\":[\"{clientCrt}\",{caPem}]}}}}}}'.format(
112         odl_private_key=odl_private_key,
113         clientKey=clientKey,
114         clientCrt=clientCrt,
115         caPem=caPem)
116
117     return json_private_key
118
119
120 def makeTrustedCertificate(certList, count):
121     number = 0
122     json_cert_format = ""
123     for cert in certList:
124         cert_name = "xNF_CA_certificate_%d_%d" %(count, number)
125         json_cert_format += '{{\"name\": \"{trusted_name}\",\"certificate\":\"{cert}\"}},\n'.format(
126             trusted_name=cert_name,
127             cert=cert.strip())
128         number += 1
129
130     json_cert_format = json_cert_format.rsplit(',', 1)[0]
131     json_trusted_cert='{{\"input\": {{ \"trusted-certificate\": [{certificates}]}}}}'.format(
132         certificates=json_cert_format)
133     return json_trusted_cert
134
135
136 def makeRestconfPost(conn, json_file, apiCall):
137     req = conn.request("POST", apiCall, json_file, headers=headers)
138     res = conn.getresponse()
139     res.read()
140     if res.status != 200:
141         logging.error("Error here, response back wasnt 200: Response was : %d , %s" % (res.status, res.reason))
142     else:
143         logging.debug("Response :%s Reason :%s ",res.status, res.reason)
144
145
146 def extractZipFiles(zipFileList, count):
147     for zipFolder in zipFileList:
148         with zipfile.ZipFile(Path + "/" + zipFolder.strip(),"r") as zip_ref:
149             zip_ref.extractall(Path)
150         folder = zipFolder.rsplit(".")[0]
151         processFiles(folder, count)
152
153
154 def processFiles(folder, count):
155     for file in os.listdir(Path + "/" + folder):
156         if os.path.isfile(Path + "/" + folder + "/" + file.strip()):
157             if ".key" in file:
158                 clientKey = readFile(folder, file.strip())
159             elif "trustedCertificate" in file:
160                 certList = readTrustedCertificate(Path + "/" + folder, file.strip())
161             elif ".crt" in file:
162                 clientCrt = readFile(folder, file.strip())
163         else:
164             logging.error("Could not find file %s" % file.strip())
165     shutil.rmtree(Path + "/" + folder)
166     post_content(clientKey, clientCrt, certList, count)
167
168
169 def post_content(clientKey, clientCrt, certList, count):
170     conn = httplib.HTTPConnection("localhost",odl_port)
171
172     if clientKey:
173         json_keystore_key = makeKeystoreKey(clientKey, count)
174         logging.debug("Posting private key in to ODL keystore")
175         makeRestconfPost(conn, json_keystore_key, postKeystore)
176
177     if certList:
178         json_trusted_cert = makeTrustedCertificate(certList, count)
179         logging.debug("Posting trusted cert list in to ODL")
180         makeRestconfPost(conn, json_trusted_cert, postTrustedCertificate)
181
182     if clientKey and clientCrt and certList:
183         json_private_key = makePrivateKey(clientKey, clientCrt, certList, count)
184         logging.debug("Posting the cert in to ODL")
185         makeRestconfPost(conn, json_private_key, postPrivateKey)
186
187
188 def makeHealthcheckCall(headers, timePassed):
189     connected = False
190     # WAIT 10 minutes maximum and test every 30 seconds if HealthCheck API is returning 200
191     while timePassed < TIMEOUT:
192         try:
193             conn = httplib.HTTPConnection("localhost",odl_port)
194             req = conn.request("POST", "/restconf/operations/SLI-API:healthcheck",headers=headers)
195             res = conn.getresponse()
196             res.read()
197             if res.status == 200:
198                 logging.debug("Healthcheck Passed in %d seconds." %timePassed)
199                 connected = True
200                 break
201             else:
202                 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))
203         except:
204             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))
205         timePassed = timeIncrement(timePassed)
206
207     if timePassed > TIMEOUT:
208         logging.error("TIME OUT: Healthcheck not passed in  %d seconds... Could cause problems for testing activities..." %TIMEOUT)
209     return connected
210
211
212 def timeIncrement(timePassed):
213     time.sleep(INTERVAL)
214     timePassed = timePassed + INTERVAL
215     return timePassed
216
217
218 def get_pass(file_name):
219     try:
220         with open(file_name , 'r') as file_obj:
221             password = file_obj.read().strip()
222         return password
223     except Exception as e:
224         logging.error("Error occurred while fetching password : %s", e)
225         exit()
226
227
228 def cleanup():
229     for file in os.listdir(Path):
230         if os.path.isfile(Path + '/' + file):
231             logging.debug("Cleaning up the file %s", Path + '/'+ file)
232             os.remove(Path + '/'+ file)
233
234
235 def jks_to_p12(file, password):
236     """Converts jks format into p12"""
237     try:
238         p12_file = file.replace('.jks', '.p12')
239         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)
240         logging.debug("Converting %s into p12 format", file)
241         os.system(jks_cmd)
242         file = p12_file
243         return file
244     except Exception as e:
245         logging.error("Error occurred while converting jks to p12 format : %s", e)
246
247
248 def extract_content():
249     """Extracts client key, certificates, CA certificates."""
250     try:
251         certList = []
252         key = None
253         cert = None
254
255         truststore_pass = get_pass(truststore_pass_file)
256         truststore_file_p12 = jks_to_p12(truststore_file, truststore_pass)
257
258         keystore_pass = get_pass(keystore_pass_file)
259         keystore_file_p12 = jks_to_p12(keystore_file, keystore_pass)
260
261         clcrt_cmd = 'openssl pkcs12 -in {src_file} -clcerts -nokeys  -passin pass:{src_pass}'.format(src_file=keystore_file_p12, src_pass=keystore_pass)
262
263         clkey_cmd = 'openssl pkcs12 -in {src_file}  -nocerts -nodes -passin pass:{src_pass}'.format(src_file=keystore_file_p12, src_pass=keystore_pass)
264         trust_file = truststore_file_p12.split('/')[2] + '.trust'
265
266         trustCerts_cmd = 'openssl pkcs12 -in {src_file} -out {out_file} -cacerts -nokeys -passin pass:{src_pass} '.format(src_file=truststore_file_p12, out_file=Path + '/' + trust_file, src_pass=truststore_pass)
267
268         result_key = subprocess.check_output(clkey_cmd , shell=True)
269         if result_key:
270             key = result_key.split('-----BEGIN PRIVATE KEY-----', 1)[1].lstrip().split('-----END PRIVATE KEY-----')[0]
271             logging.debug("key ok")
272
273         os.system(trustCerts_cmd)
274         if os.path.exists(Path + '/' + trust_file):
275             certList = readTrustedCertificate(Path, trust_file)
276             logging.debug("certList ok")
277
278         result_crt = subprocess.check_output(clcrt_cmd , shell=True)
279         if result_crt:
280             cert = result_crt.split('-----BEGIN CERTIFICATE-----', 1)[1].lstrip().split('-----END CERTIFICATE-----')[0]
281             logging.debug("cert ok")
282
283         if key and cert and certList:
284             post_content(key, cert, certList, 0)
285         else:
286             logging.debug("Exiting. Key, cert or key are missing")
287             return
288
289     except Exception as e:
290         logging.error("Error occurred while processing the file: %s", e)
291
292
293 def look_for_jks_files():
294     if all([os.path.isfile(f) for f in jks_files]):
295         extract_content()
296         cleanup()
297     else:
298         logging.debug("Some of the files are missing")
299         return
300
301
302 def readCertProperties():
303     '''
304     This function searches for manually copied zip file
305     containing certificates. This is required as part
306     of backward compatibility.
307     If not foud, it searches for jks certificates.
308     '''
309     connected = makeHealthcheckCall(headers, timePassed)
310
311     if connected:
312         count = 0
313         if os.path.isfile(Path + "/certs.properties"):
314             with open(Path + "/certs.properties", "r") as f:
315                 for line in f:
316                     if not "*****" in line:
317                         zipFileList.append(line)
318                     else:
319                         extractZipFiles(zipFileList, count)
320                         count += 1
321                         del zipFileList[:]
322         else:
323             logging.debug("No zipfiles present in folder " + Path)
324
325         logging.info("Looking for jks files in folder " + Path)
326         look_for_jks_files()
327
328
329 readCertProperties()