Skip to content

Commit 11201d7

Browse files
Merge pull request #978 from netenglabs/feature/cert-verify
New feature cert verify
2 parents b79ffaa + 6f125c8 commit 11201d7

File tree

9 files changed

+228
-24
lines changed

9 files changed

+228
-24
lines changed

docs/config_file.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ coalescer:
5353
| rest.logsize | maximum size of the REST logfile in bytes | 10000000 | no |
5454
| rest.log-stdout | log everything on the standard output instead of a file | False | no |
5555
| rest.no-https | if True, the REST server doesn't use SSL. Highly discouraged in production. | False | no |
56+
| rest.cert-verify | if False the certificate will not be verified.<br>If True the certificate will be verified.<br>If a path, that will be checked as CA. | True | no |
5657
| poller.logging-level | logging level for the poller.<br/> Choices: INFO, WARNING, ERROR | WARNING | no |
5758
| poller.logfile | log file for poller | /tmp/sq-poller.log | no |
5859
| poller.log-stdout | log on standard output instead of file | False | no |

suzieq/engines/rest/engineobj.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
from typing import Type, Dict
1+
import os
22
import urllib
3-
import requests
4-
import urllib3
3+
from typing import Dict, Type
54

65
import pandas as pd
6+
import requests
7+
import urllib3
78

89
from suzieq.engines.base_engine import SqEngineObj
910

@@ -105,7 +106,15 @@ def _get_response(self, verb: str, **kwargs) -> pd.DataFrame:
105106
f'{query_params}')
106107

107108
# pylint: disable=missing-timeout
108-
response = requests.get(url, verify=None)
109+
cert_verify = self.ctxt.cfg.get('rest', {}).get('cert-verify', True)
110+
111+
if isinstance(cert_verify, (str, bool)) is False:
112+
raise TypeError('cert_verify must be a boolean or a string')
113+
if isinstance(cert_verify, str):
114+
if not os.path.exists(os.path.dirname(cert_verify)):
115+
raise ValueError('cert_verify path does not exist')
116+
117+
response = requests.get(url, verify=cert_verify)
109118
if response.status_code != 200:
110119
if response.text:
111120
msg = response.json().get("detail", str(response.status_code))

tests/conftest.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from suzieq.shared.schema import Schema
2424
from suzieq.shared.utils import load_sq_config
2525
from suzieq.sqobjects import get_sqobject, get_tables
26+
import socket
27+
from contextlib import closing
2628

2729
suzieq_cli_path = './suzieq/cli/sq_cli.py'
2830
suzieq_rest_server_path = './suzieq/restServer/sq_rest_server.py'
@@ -286,3 +288,15 @@ def validate_host_shape(df: pd.DataFrame, ns_dict: dict):
286288
if ns in df.namespace.unique():
287289
assert df.query(
288290
f'namespace == "{ns}"').hostname.nunique() == ns_dict[ns]
291+
292+
293+
def get_free_port() -> int:
294+
"""Return a free port
295+
296+
Returns:
297+
int: free port
298+
"""
299+
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
300+
s.bind(('', 0))
301+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
302+
return s.getsockname()[1]

tests/test_cert_CA/ca-cert.pem

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIID7TCCAtWgAwIBAgIUe0bXUvllC1Ocgp1/Njwis+rb+DkwDQYJKoZIhvcNAQEL
3+
BQAwgYQxCzAJBgNVBAYTAklUMQ4wDAYDVQQIDAVJdGFseTENMAsGA1UEBwwEcm9t
4+
ZTENMAsGA1UECgwEUm9vdDENMAsGA1UECwwEUm9vdDEQMA4GA1UEAwwHMC4wLjAu
5+
MDEmMCQGCSqGSIb3DQEJARYXc3RhcmR1c3Qucm9vdEBnbWFpbC5jb20wIBcNMjUw
6+
MjIwMTQ0MzQ4WhgPMzAyNDA2MjMxNDQzNDhaMIGEMQswCQYDVQQGEwJJVDEOMAwG
7+
A1UECAwFSXRhbHkxDTALBgNVBAcMBHJvbWUxDTALBgNVBAoMBFJvb3QxDTALBgNV
8+
BAsMBFJvb3QxEDAOBgNVBAMMBzAuMC4wLjAxJjAkBgkqhkiG9w0BCQEWF3N0YXJk
9+
dXN0LnJvb3RAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
10+
AQEApph5CxSlWFkxR94QKfcOgh9OpzXh8HTn+9vZpDv4t3w/pGUGYysoIWx2B7xo
11+
evzFroGJ6CQEGSG23gyixipP1sd5hmKHbapk+G286X21kOSUYTZ50E+8bZgJkUof
12+
xYlUQUJf+MmHdlwJXEDDH4I0uuXNrddpNTRmsxH6tqBDNhD5SBulzrUfeLba/Mdw
13+
H0+He720e/KNhtGgi1z97uBnb85YcCE+uSi+zhWunLHYHfc61W1B+5CkRWLxD2Ag
14+
V84g0E4O5pCSlwBHwHRiykWbvTIQD1aMVyhSQxbA6U519oIauR7ajbtTWbqJmxe/
15+
2iJnEPXpSj90LH0lWs926E6IKwIDAQABo1MwUTAdBgNVHQ4EFgQUTr1dzVayV602
16+
zsi05K4JBQGe2QgwHwYDVR0jBBgwFoAUTr1dzVayV602zsi05K4JBQGe2QgwDwYD
17+
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAYJYFdPw4MlOJRcMNHSNF
18+
34E5Oh2yRGqUZtrL9F3Acc3UV2BKHRy30w/PCdBLoynaUu9khxmROFFO9mYuolOQ
19+
UP8LoK1lVg1e7JkrQmA53CRrCSeES8dqZvsOdOdhKRmFKYftzefjbk3A+9J1M3wX
20+
IiMlq3lAzj2ts8S9HQLYrhDdwKxbfuw+e0HjcG4r4QbjkSA/A+5KLIolyjuPKHcO
21+
6RADRargWA1kwMxzmNdt0ySHlQ2Lal0fYNdpYlp428eIVRpolsw2SjsC2XZ62Ca+
22+
6+Xnns2MkTnkbaEhTl6erqsql1lVIT/ramHSuZ+/oCcEvSjvXjp+jUAGi5raESi1
23+
Zw==
24+
-----END CERTIFICATE-----

tests/test_cert_CA/server-cert.pem

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDjzCCAncCAQEwDQYJKoZIhvcNAQELBQAwgYQxCzAJBgNVBAYTAklUMQ4wDAYD
3+
VQQIDAVJdGFseTENMAsGA1UEBwwEcm9tZTENMAsGA1UECgwEUm9vdDENMAsGA1UE
4+
CwwEUm9vdDEQMA4GA1UEAwwHMC4wLjAuMDEmMCQGCSqGSIb3DQEJARYXc3RhcmR1
5+
c3Qucm9vdEBnbWFpbC5jb20wIBcNMjUwMjIwMTQ1MDA1WhgPMzAyNDA2MjMxNDUw
6+
MDVaMIGTMQswCQYDVQQGEwJJVDEOMAwGA1UECAwFSXRhbHkxDTALBgNVBAcMBHJv
7+
bWUxGDAWBgNVBAoMD1N0YXJkdXN0IHNlcnZlcjEPMA0GA1UECwwGc2VydmVyMRAw
8+
DgYDVQQDDAcwLjAuMC4wMSgwJgYJKoZIhvcNAQkBFhlzdGFyZHVzdC5zZXJ2ZXJA
9+
Z21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4tzYPsL3
10+
jxpt9zAsHEGvPMZ/3CjklxEXCQKXG2KhJ/RznoW3mBcVRdrGrO2MLgPPM9nZegY1
11+
H8JJ6ZLvVUGO8E1Yjt3LRX3n3/gS2RYXcSSQogoSdmZciJIyouwgbh5++jvN/DjE
12+
8fLuRDjrbdPD94rTFkiodenqsLKT9iHhriJwItuT/F4E+w3ZgVAG/5Hi1wLez+Jk
13+
GdDIY8Hh3D+6FpGmTK3zBEFcYN2QW4oy9rx+IihZfAtvYxpezYh9iRSPxl6PpKY1
14+
81PSBO84Xvf5MblhN8rnKL72YH4Mxpa0OI/As0liug2517meZNrftc7S5hrBbvEC
15+
C12RVeoSPHltDQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCerhsig6eDMNFgRyhh
16+
WTFwKf0RQ/evcAPmPJ3GBzAlPwvkicj0oa0gKga/0Enot0Y7Ookz5sxi+M1Etyl2
17+
vd1ch77/xRpUfEzQRu7l5G0UQ+21CaQJBPDlibLG57dqycfQ0xwEajrxf7RAS8EO
18+
CkasLRps/y7Iq8DeFP5y4Rxs9V/VC0iEQHwqeuqFMgpnS6XpVUPXcMMkmHlUCKcC
19+
5OTSv0JuyymzYAHIstrm91ZzBrb8yuo9t1riOJqGtqvFKuoPpi5VAH8Twtqymqry
20+
KAsysWaoSMI45tC0RUJ+817UbMmi8IE56l+ajOAceuy6oXfjv8nU/lxQ8crJba+1
21+
ck52
22+
-----END CERTIFICATE-----

tests/test_cert_CA/server-key.pem

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDi3Ng+wvePGm33
3+
MCwcQa88xn/cKOSXERcJApcbYqEn9HOehbeYFxVF2sas7YwuA88z2dl6BjUfwknp
4+
ku9VQY7wTViO3ctFfeff+BLZFhdxJJCiChJ2ZlyIkjKi7CBuHn76O838OMTx8u5E
5+
OOtt08P3itMWSKh16eqwspP2IeGuInAi25P8XgT7DdmBUAb/keLXAt7P4mQZ0Mhj
6+
weHcP7oWkaZMrfMEQVxg3ZBbijL2vH4iKFl8C29jGl7NiH2JFI/GXo+kpjXzU9IE
7+
7zhe9/kxuWE3yucovvZgfgzGlrQ4j8CzSWK6DbnXuZ5k2t+1ztLmGsFu8QILXZFV
8+
6hI8eW0NAgMBAAECggEAAX+t7+fsemhML1OOCeebnSh+s0Ac6RC6BFbUJhcz5ZHI
9+
5i/PwBaCqoDenMla07DaiZ3oAN56s3BnnoeGFXdtqP9l0KCq16eUZIwBed8IK4sT
10+
1WSA/8rjr2Umb4+cAarORE3foInEwnYDFAgW8vDVdhAXefv2HTxzxbi2llCUxhGL
11+
Dx7HxqrrhjHAhhLRoa4QNbhPn+wXT2ArS+0rKXs2yzVFewPWZ0eR/n2gvyPi3CKS
12+
puDYzs2ewc8AWW7n6p3ux/DrvP++R9+/UmC+5kjMn5xvQYmPnkUDS24A/BqFPvxh
13+
NLqHZrWy3bmr8Lttq/He3Ol2K5s+ZrSlRgDi5qb+gQKBgQD8k6VThmcUVU4lXw9D
14+
1+cSzxVG0lhDNTnV3g6GMPDEuhI93EsIXUH8p4vkM3jc34ZEXStCyFuHfNYXSGWP
15+
1FHiJQ6NaaA62fF0stv1H+2mQdQqzxkEitEOva9vISnfQzd4t2Alk6WGyNyg2wgQ
16+
JBn2URMUVkp7YjhQ56kLqyvtgQKBgQDl7/refPGnKAuuEiDWy1sOU8SB9Eoc0OD3
17+
PPk5SWY8S/eDzlvI/HO2xhto2KcugUzyXCTeUManxHQfDnpAUq1eSLwKzyXpwZWw
18+
gqrjBRrOsVrkHKfTeH77agmPtXiI+d+8+IqUXpUMkI/bqRHRL4e+MmQj/IKfcIuz
19+
uacDa2AdjQKBgQCtSwvim9N7gu/j+i26CZcUM5rQhZ9jNVCiKQHkFg4Lm/LKGKwu
20+
Z/XPSJFVl+8z8/TmUNpOrrMF6aPmQ5jTLwSjWXN7mN4Doubkf5ckvqxKJt5QJNlw
21+
YWIAcCq+340gDrkvjPldrsiiCow9nSoSEQLzGjsx9+aQcxpagCdexymTgQKBgQCn
22+
FDLhWj6p7LJYATo1ebync3z1xRHZUHo3jOm3k7sjEzw+XUNajv5yEA+4pr0MUM4d
23+
yZDMrjs7iseqDXYNqUXqncVtwUnWSmE/yiLsJThuencGDEByrDrw6wMZlo6IUbEe
24+
+iaQWw3I/H5b6cVVkEj9jlYvw/sSadBJfxx5optLvQKBgF1c6DlLvsqJHKP10RDR
25+
mzjFBG15GYDeN+62EgIrAxFGPGlPMEnm1Tw7Ab69yp/c0gysZ1DmMLp3h94pzmte
26+
RKioxfV48GjGOaIBmezD+SRze3GH6JudmzDZxAbh1qq3ptWtT1Ih2PQB8g0ED7Ww
27+
F+QnzMIIw3wJGJhN87EY6Zh9
28+
-----END PRIVATE KEY-----

tests/unit/engine/rest/test_rest_engine.py

Lines changed: 124 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
1+
import os
2+
import subprocess
13
from dataclasses import dataclass
24
from itertools import combinations
5+
from tempfile import mkstemp
6+
from time import sleep
37
from typing import Dict
48
from urllib.parse import parse_qs, urlparse
59

610
import pytest
11+
import yaml
712
from requests.exceptions import ConnectionError
13+
814
from suzieq.engines.rest.engineobj import SqRestEngine
15+
from suzieq.shared.utils import load_sq_config
16+
from tests.conftest import get_free_port, suzieq_rest_server_path
917

1018

1119
@dataclass
1220
class SqContextMock:
1321
""" SqContext rest parameters mock
1422
"""
23+
cfg: dict
1524
rest_api_key: str
1625
rest_transport: str
1726
rest_server_ip: str
@@ -70,10 +79,10 @@ def req_error(param: str, exp: str, got: str) -> str:
7079

7180
# check query parameters
7281
url_query = parse_qs(url.query)
73-
for query_param, query_value in url_query.items():
74-
assert len(query_value) == 1, \
82+
for query_param, query_values in url_query.items():
83+
assert len(query_values) == 1, \
7584
f'Got more than 1 value for {query_param}'
76-
query_value = query_value[0]
85+
query_value = query_values[0]
7786
if query_param == 'access_token':
7887
# access_token needs a special validation
7988
assert query_value == engine.ctxt.rest_api_key, \
@@ -94,7 +103,7 @@ def req_error(param: str, exp: str, got: str) -> str:
94103
def test_request_params():
95104
"""This test checks if parameters are set correctly in the request
96105
"""
97-
ctxt = SqContextMock('key', 'http', 'rest-ip', 80)
106+
ctxt = SqContextMock({}, 'key', 'http', 'rest-ip', 80)
98107
sqobj = SqObjMock(ctxt, 'default', 'default', 'default',
99108
'default', 'default', 'default', 'default')
100109
engine = SqRestEngine(sqobj)
@@ -109,3 +118,114 @@ def test_request_params():
109118
for sq_params in combinations(testing_params, n_sq_params):
110119
req_params = {p: 'override' for p in sq_params}
111120
validate_args(engine, req_params)
121+
122+
123+
@pytest.mark.rest
124+
@pytest.mark.skipif(not os.environ.get('TEST_SERVER', None),
125+
reason='causes github action hang')
126+
def test_server_cert():
127+
'''Can we can get a valid response with & without certificate'''
128+
129+
# We need to change the port used to avoid conflicts
130+
config = {'data-directory': './tests/data/parquet',
131+
'temp-directory': '/tmp/suzieq',
132+
'logging-level': 'WARNING',
133+
'test_set': 'basic_dual_bgp', # an extra field for testing
134+
'rest': {
135+
'rest-certfile': './tests/test_cert_CA/server-cert.pem',
136+
'rest-keyfile': './tests/test_cert_CA/server-key.pem',
137+
'API_KEY': '496157e6e869ef7f3d6ecb24a6f6d847b224ee4f',
138+
'logging-level': 'WARNING',
139+
'address': '0.0.0.0',
140+
'port': get_free_port(),
141+
'no-https': True,
142+
'log-stdout': True
143+
},
144+
'analyzer': {'timezone': 'GMT'},
145+
}
146+
147+
def create_config(config):
148+
fd, tmpfname = mkstemp(suffix='.yml')
149+
f = os.fdopen(fd, 'w')
150+
f.write(yaml.dump(config))
151+
f.close()
152+
153+
cfgfile = tmpfname
154+
sqcfg = load_sq_config(config_file=cfgfile)
155+
156+
print(f'sqcfg: {sqcfg}')
157+
158+
with open(cfgfile, 'w') as f:
159+
f.write(yaml.safe_dump(sqcfg))
160+
return sqcfg, cfgfile
161+
162+
def open_rest_server(cfgfile):
163+
server_cmd_args = f'{suzieq_rest_server_path} -c {cfgfile}'.split()
164+
# pylint: disable=consider-using-with
165+
proc = subprocess.Popen(server_cmd_args)
166+
sleep(5)
167+
return proc
168+
169+
def make_get_response_request(sqcfg):
170+
ctxt = SqContextMock(
171+
rest_api_key=sqcfg['rest']['API_KEY'],
172+
rest_transport='http' if sqcfg['rest']['no-https'] is True
173+
else 'https',
174+
rest_server_ip=sqcfg['rest']['address'],
175+
rest_server_port=sqcfg['rest']['port'],
176+
cfg={'rest': sqcfg['rest']})
177+
178+
sqobj = SqObjMock(ctxt, '', '', 'default',
179+
'default', 'latest', 'device', 'default')
180+
181+
engine = SqRestEngine(sqobj)
182+
try:
183+
response = engine._get_response('show')
184+
print(f'responsein test: {response}')
185+
return 200
186+
except Exception:
187+
return 400
188+
189+
def close_session(proc, cfgfile):
190+
proc.kill()
191+
os.remove(cfgfile)
192+
193+
# test with http verify None
194+
sqcfg, cfgfile = create_config(config)
195+
proc = open_rest_server(cfgfile)
196+
response = make_get_response_request(sqcfg)
197+
assert response == 200
198+
close_session(proc, cfgfile)
199+
200+
# test with https verify None
201+
config['rest']['no-https'] = False
202+
sqcfg, cfgfile = create_config(config)
203+
proc = open_rest_server(cfgfile)
204+
response = make_get_response_request(sqcfg)
205+
assert response == 400
206+
close_session(proc, cfgfile)
207+
208+
# test with https verify False
209+
config['rest']['cert-verify'] = False
210+
sqcfg, cfgfile = create_config(config)
211+
proc = open_rest_server(cfgfile)
212+
response = make_get_response_request(sqcfg)
213+
assert response == 200
214+
close_session(proc, cfgfile)
215+
216+
# test with https verify True
217+
config['rest']['cert-verify'] = True
218+
sqcfg, cfgfile = create_config(config)
219+
proc = open_rest_server(cfgfile)
220+
response = make_get_response_request(sqcfg)
221+
assert response == 400
222+
close_session(proc, cfgfile)
223+
224+
# test with https verify CA
225+
config['rest']['cert-verify'] =\
226+
'./tests/test_cert_CA/ca-cert.pem'
227+
sqcfg, cfgfile = create_config(config)
228+
proc = open_rest_server(cfgfile)
229+
response = make_get_response_request(sqcfg)
230+
assert response == 200
231+
close_session(proc, cfgfile)

tests/unit/poller/controller/sources/netbox/test_netbox.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
NetboxFaker
1515
from tests.unit.poller.controller.sources.netbox.netbox_rest_server import \
1616
NetboxRestApp
17-
from tests.unit.poller.shared.utils import (get_free_port,
18-
get_src_sample_config)
17+
from tests.unit.poller.shared.utils import (get_src_sample_config)
18+
from tests.conftest import get_free_port
1919

2020
# pylint: disable=redefined-outer-name
2121

tests/unit/poller/shared/utils.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import random
2-
import socket
3-
from contextlib import closing
42
from pathlib import Path
53
from typing import Any, Dict, Tuple
64

@@ -119,15 +117,3 @@ def read_yaml_file(path: str) -> Any:
119117
if not file_path.is_file():
120118
raise RuntimeError(f'Invalid file to read {path}')
121119
return yaml.safe_load(open(file_path, 'r'))
122-
123-
124-
def get_free_port() -> int:
125-
"""Return a free port
126-
127-
Returns:
128-
int: free port
129-
"""
130-
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
131-
s.bind(('', 0))
132-
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
133-
return s.getsockname()[1]

0 commit comments

Comments
 (0)