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