2 # COPYRIGHT NOTICE STARTS HERE
4 # Copyright 2019 Samsung Electronics Co., Ltd.
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
10 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 # COPYRIGHT NOTICE ENDS HERE
20 # Check all node ports exposed outside of kubernetes cluster looking for plain http and https.
21 # Check all ingress controller services exposed outside of the kubernetes cluster
22 # looking for plain http and https. This script looks for K8S NodePorts and ingress services declared
23 # in the K8S cluster configurations and check if service is alive or not.
24 # Automatic detect nodeport or ingress protocol HTTP or HTTPS it also detect if particular service uses HTTPS
25 # with self signed certificate (HTTPU).
26 # Verbose option retrives HTTP header and prints it for each service
30 # pip3 install kubernetes
31 # pip3 install colorama
34 # This script should be run on the local machine which has network access to the onap K8S cluster.
35 # It requires k8s cluster config file on local machine.
38 # Display exposed nodeport and ingress resources declared in the K8S cluster without scanning:
39 # check_for_ingress_and_nodeports.py
40 # Scan declared nodeports:
41 # check_for_ingress_and_nodeports.py --scan-nodeport
42 # Scan declared exposed ingress resources:
43 # check_for_ingress_and_nodeports.py --scan-ingress
45 from kubernetes import client, config
53 from colorama import Fore
57 """ List all nodeports """
58 def list_nodeports(v1):
60 svc = v1.list_namespaced_service(K8S_NAMESPACE)
63 ports = [ j.node_port for j in i.spec.ports if j.node_port ]
65 ret[i.metadata.name] = ports
68 # Class enum for returning current http mode
71 HTTPU = 1 #Unsafe https
76 #Read the ingress controller http and https ports from the kubernetes cluster
77 def find_ingress_ports(v1):
78 svc = v1.list_namespaced_service(K8S_INGRESS_NS)
81 for item in svc.items:
82 if item.metadata.name == K8S_INGRESS_NS:
83 for pinfo in item.spec.ports:
84 if pinfo and pinfo.name == 'http':
85 http_port = pinfo.node_port
86 elif pinfo and pinfo.name == 'https':
87 https_port = pinfo.node_port
89 return http_port,https_port
92 # List all ingress devices
93 def list_ingress(xv1b):
94 SSL_ANNOTATION = 'nginx.ingress.kubernetes.io/ssl-redirect'
95 inglist = xv1b.list_namespaced_ingress(K8S_NAMESPACE)
97 for ing in inglist.items:
98 svc_name = ing.metadata.labels['app']
100 annotations = ing.metadata.annotations
101 for host in ing.spec.rules:
102 arr.append(host.host)
103 if (SSL_ANNOTATION in annotations) and annotations[SSL_ANNOTATION]=="true":
104 smode = ScanMode.HTTPS
105 else: smode = ScanMode.HTTP
106 svc_list[svc_name] = [ arr, smode ]
110 def scan_single_port(host,port,scanmode):
111 ssl_unverified = ssl._create_unverified_context()
112 if scanmode==ScanMode.HTTP:
113 conn = http.client.HTTPConnection(host,port,timeout=10)
114 elif scanmode==ScanMode.HTTPS:
115 conn = http.client.HTTPSConnection(host,port,timeout=10)
116 elif scanmode==ScanMode.HTTPU:
117 conn = http.client.HTTPSConnection(host,port,timeout=10,context=ssl_unverified)
121 conn.request("GET","/")
122 outstr = conn.getresponse()
123 except http.client.BadStatusLine as exc:
124 outstr = "Non HTTP proto" + str(exc)
126 except ConnectionRefusedError as exc:
127 outstr = "Connection refused" + str(exc)
129 except ConnectionResetError as exc:
130 outstr = "Connection reset" + str(exc)
132 except socket.timeout as exc:
133 outstr = "Connection timeout" + str(exc)
135 except ssl.SSLError as exc:
136 outstr = "SSL error" + str(exc)
138 except OSError as exc:
139 outstr = "OS error" + str(exc)
142 return retstatus,outstr
145 def scan_portn(port):
146 host = urllib.parse.urlsplit(v1c.host).hostname
147 for mode in ScanMode:
148 retstatus, out = scan_single_port(host,port,mode)
150 result = port, mode, out.getcode(), out.read().decode('utf-8'),mode
153 result = port, retstatus, out, None,mode
157 def scan_port(host, http, https, mode):
158 if mode==ScanMode.HTTP:
159 retstatus, out = scan_single_port(host,http,ScanMode.HTTP)
161 return host, ScanMode.HTTP, out.getcode(), out.read().decode('utf-8'), mode
163 return host, retstatus, out, None, mode
164 elif mode==ScanMode.HTTPS:
165 retstatus, out = scan_single_port(host,https,ScanMode.HTTPS)
167 return host, ScanMode.HTTPS, out.getcode(), out.read().decode('utf-8'), mode
169 retstatus, out = scan_single_port(host,https,ScanMode.HTTPU)
171 return host, ScanMode.HTTPU, out.getcode(), out.read().decode('utf-8'), mode
173 return host, retstatus, out, None, mode
176 # Visualise scan result
177 def console_visualisation(cname, name, retstatus, httpcode, out, mode, httpcodes = None):
178 if httpcodes is None: httpcodes=[]
179 print(Fore.YELLOW,end='')
180 print( cname,name, end='\t',sep='\t')
181 if isinstance(retstatus,ScanMode):
182 if httpcode in httpcodes: estr = Fore.RED + '[ERROR '
183 else: estr = Fore.GREEN + '[OK '
184 print( estr, retstatus, str(httpcode)+ ']'+Fore.RESET,end='')
185 if VERBOSE: print( '\t',str(out) )
188 if not out: out = str(retstatus)
189 print( Fore.RED, '[ERROR ' +str(mode) +']', Fore.RESET,'\t', str(out))
191 # Visualize compare results
192 def console_compare_visualisation(cname,d1,d2):
193 print(Fore.YELLOW,end='')
194 print(cname, end='\t',sep='\t')
196 print(Fore.RED + '[ERROR] '+ Fore.RESET)
198 print('\tCode:',d1[0],'!=',d2[0])
200 print('\t******** Response #1 ********\n',d1[1])
201 print('\t******** Response #2 ********\n',d2[1])
203 print(Fore.GREEN + '[OK ',d1[0],']', Fore.RESET,sep='')
204 if VERBOSE and d1[1]:
209 def check_onap_ports():
210 print("Scanning onap NodePorts")
211 check_list = list_nodeports(v1)
213 print(Fore.RED + 'Unable to find any declared node port in the K8S cluster', Fore.RESET)
214 for k,v in check_list.items():
216 console_visualisation(k,*scan_portn(port) )
219 def check_onap_ingress():
220 print("Scanning onap ingress services")
221 ihttp,ihttps = find_ingress_ports(v1)
222 check_list = list_ingress(v1b)
224 print(Fore.RED+ 'Unable to find any declared ingress service in the K8S cluster', Fore.RESET)
225 for k,v in check_list.items():
227 console_visualisation(k,*scan_port(host,ihttp,ihttps,v[1]),httpcodes=[404])
229 #Print onap all ingress ports and node ports
231 ihttp,ihttps = find_ingress_ports(v1)
232 host = urllib.parse.urlsplit(v1c.host).hostname
233 print( 'Cluster IP' + Fore.YELLOW, host, Fore.RESET )
234 print('Ingress ' + Fore.RED + 'HTTP' + Fore.RESET + ' port:',Fore.YELLOW, ihttp, Fore.RESET)
235 print('Ingress ' + Fore.RED + 'HTTPS' + Fore.RESET + ' port:',Fore.YELLOW, ihttps, Fore.RESET)
236 print(Fore.YELLOW+"Onap NodePorts list:",Fore.RESET)
237 check_list = list_nodeports(v1)
238 for name,ports in check_list.items():
239 print(Fore.GREEN, name,Fore.RESET,":", *ports)
240 print(Fore.YELLOW+"Onap ingress controler services list:",Fore.RESET)
241 check_list = list_ingress(v1b)
242 for name,hosts in check_list.items():
243 print(Fore.GREEN, name + Fore.RESET,":", *hosts[0], Fore.RED+':', hosts[1],Fore.RESET)
245 #Scan and compare nodeports and ingress check for results
246 def compare_nodeports_and_ingress():
247 ihttp,ihttps = find_ingress_ports(v1)
248 print('Scanning nodeport services ...')
249 check_list = list_nodeports(v1)
251 print(Fore.RED + 'Unable to find any declared node port in the K8S cluster', Fore.RESET)
253 for k,v in check_list.items():
255 nodeport_results = scan_portn(port)
256 if isinstance(nodeport_results[1],ScanMode) and nodeport_results[2] != 404:
257 valid_results[k] = nodeport_results
258 if VERBOSE: console_visualisation(k,*nodeport_results)
259 check_list = list_ingress(v1b)
261 print(Fore.RED+ 'Unable to find any declared ingress service in the K8S cluster', Fore.RESET)
262 print('Scanning ingress services ...')
263 ing_valid_results = {}
264 for k,v in check_list.items():
266 ingress_results = scan_port(host,ihttp,ihttps,v[1])
267 if isinstance(ingress_results[1],ScanMode) and ingress_results[2]!=404:
268 ing_valid_results[k] = ingress_results
269 if VERBOSE: console_visualisation(k,*ingress_results,httpcodes=[404])
270 ks1 = set(valid_results.keys())
271 ks2 = set(ing_valid_results.keys())
272 diff_keys = (ks1 - ks2) | (ks2 - ks1)
273 common_keys = ks1 & ks2
274 if VERBOSE and diff_keys:
275 print(Fore.BLUE + '[WARNING] Non matching nodes and ingress list:')
276 for key in diff_keys: print(key,sep='\t')
277 print(Fore.RESET + 'Please check is it correct.')
278 print('Matching ingress and nodeport host scan results:')
279 for scan_key in common_keys:
280 s1 = valid_results[scan_key][2:4]
281 s2 = ing_valid_results[scan_key][2:4]
283 if s1!=s2: ++num_failures
284 console_compare_visualisation(scan_key,s1,s2)
287 def kube_config_exists(conf):
289 assert path.exists(conf)
290 except AssertionError:
291 raise argparse.ArgumentTypeError(f'Fatal! K8S config {conf} does not exist')
295 if __name__ == "__main__":
297 parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
298 command_group = parser.add_mutually_exclusive_group()
299 command_group.add_argument("--scan-nodeport",
300 default=False, action='store_true',
301 help='Scan onap for node services'
303 command_group.add_argument("--scan-ingress",
304 default=False, action='store_true',
305 help='Scan onap for ingress services'
307 command_group.add_argument("--scan-and-compare",
308 default=False, action='store_true',
309 help='Scan nodeports and ingress and compare results'
311 parser.add_argument( "--namespace",
312 default='onap', action='store',
313 help = 'kubernetes onap namespace'
315 parser.add_argument( "--ingress-namespace",
316 default='ingress-nginx', action='store',
317 help = 'kubernetes ingress namespace'
319 parser.add_argument( "--conf",
320 default='~/.kube/config', action='store',
321 help = 'kubernetes config file',
322 type = kube_config_exists
324 parser.add_argument("--verbose",
325 default=False, action='store_true',
326 help='Verbose output'
328 args = parser.parse_args()
329 K8S_NAMESPACE = args.namespace
330 K8S_INGRESS_NS = args.ingress_namespace
331 VERBOSE = args.verbose
332 config.load_kube_config(config_file=args.conf)
333 v1 = client.CoreV1Api()
334 v1b = client.ExtensionsV1beta1Api()
335 v1c = client.Configuration()
336 if args.scan_nodeport: check_onap_ports()
337 elif args.scan_ingress: check_onap_ingress()
338 elif args.scan_and_compare: sys.exit(compare_nodeports_and_ingress())
339 else: onap_list_all()