|
3 | 3 | # that can be found in the COPYING file. |
4 | 4 |
|
5 | 5 | import ssl |
6 | | -import asyncio |
7 | 6 | import functools |
| 7 | +import tempfile |
8 | 8 |
|
| 9 | +import urllib3 |
9 | 10 | import pytest |
10 | | -from grpclib import utils, server |
11 | | -import pytest_asyncio |
12 | | -from cloudvision.api.arista.inventory import v1 as inventory |
13 | | -from cloudvision.api.client import AsyncCVClient |
| 11 | +from cloudvision.api.client import AsyncCVClient, UnableToAuthenticateException |
14 | 12 | from pathlib import Path |
15 | 13 |
|
16 | | -TEST_TOKEN = 'test' |
17 | | -THIS_DIR = Path(__file__).parent |
18 | | -TEST_DIR = THIS_DIR.parent |
19 | | -TEST_DATA_DIR = Path.joinpath(TEST_DIR, "test_data") |
| 14 | +from . import utils |
20 | 15 |
|
| 16 | +pytestmark = [pytest.mark.filterwarnings( |
| 17 | + "ignore:Unverified HTTPS request is being made to host 'localhost'")] |
21 | 18 |
|
22 | | -class MockInventoryService(inventory.DeviceServiceBase): |
23 | 19 |
|
24 | | - async def _call_rpc_handler_server_stream(self, handler, stream, request): |
25 | | - assert stream.metadata['authorization'] == f'Bearer {TEST_TOKEN}' |
26 | | - return await super()._call_rpc_handler_server_stream(handler, stream, request) |
| 20 | +@pytest.fixture |
| 21 | +def tmp_dir_factory(): |
| 22 | + with tempfile.TemporaryDirectory() as td: |
| 23 | + counter = 1 |
| 24 | + td = Path(td) |
27 | 25 |
|
28 | | - async def get_all(self, device_stream_request): |
29 | | - for i in range(3): |
30 | | - yield inventory.DeviceStreamResponse( |
31 | | - value=inventory.Device( |
32 | | - key=inventory.DeviceKey(device_id=f'device-{i}') |
33 | | - ) |
34 | | - ) |
| 26 | + def factory(): |
| 27 | + nonlocal counter |
| 28 | + d = td / str(counter) |
| 29 | + d.mkdir() |
| 30 | + counter += 1 |
| 31 | + return d |
35 | 32 |
|
| 33 | + yield factory |
36 | 34 |
|
37 | | -@pytest_asyncio.fixture |
38 | | -async def grpc_server(unused_tcp_port_factory): |
39 | | - invService = MockInventoryService() |
40 | | - srv = server.Server([invService]) |
41 | 35 |
|
42 | | - context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) |
43 | | - context.load_cert_chain(certfile=Path.joinpath(TEST_DATA_DIR, "cert.pem"), |
44 | | - keyfile=Path.joinpath(TEST_DATA_DIR, "key.pem")) |
45 | | - with utils.graceful_exit([server]): |
46 | | - async with srv: |
47 | | - port = unused_tcp_port_factory() |
48 | | - await srv.start('localhost', port, ssl=context) |
49 | | - yield 'localhost', port |
| 36 | +@pytest.mark.asyncio |
| 37 | +async def test_self_signed(tmp_dir_factory, unused_tcp_port_factory): |
| 38 | + certs = utils.create_self_signed_cert(tmp_dir_factory()) |
| 39 | + async with utils.grpc_server(unused_tcp_port_factory(), certs) as (host, port): |
| 40 | + f = functools.partial(AsyncCVClient.from_token, utils.TEST_TOKEN, host=host, |
| 41 | + port=port) |
| 42 | + await utils.assert_grpc_response(f) |
| 43 | + |
| 44 | + |
| 45 | +@pytest.mark.asyncio |
| 46 | +async def test_self_signed_insecure(tmp_dir_factory, unused_tcp_port_factory): |
| 47 | + certs = utils.create_self_signed_cert(tmp_dir_factory()) |
| 48 | + |
| 49 | + async with utils.grpc_server(unused_tcp_port_factory(), certs) as (host, port): |
| 50 | + f = functools.partial(AsyncCVClient.from_token, utils.TEST_TOKEN, host=host, |
| 51 | + port=port, insecure=True) |
| 52 | + await utils.assert_grpc_response(f) |
| 53 | + |
| 54 | + |
| 55 | +@pytest.mark.asyncio |
| 56 | +async def test_self_signed_insecure_wrong_host(tmp_dir_factory, unused_tcp_port_factory): |
| 57 | + certs = utils.create_self_signed_cert(tmp_dir_factory(), hostname='example.org') |
| 58 | + async with utils.grpc_server(unused_tcp_port_factory(), certs) as (host, port): |
| 59 | + f = functools.partial(AsyncCVClient.from_token, utils.TEST_TOKEN, host=host, |
| 60 | + port=port, insecure=True) |
| 61 | + await utils.assert_grpc_response(f) |
| 62 | + |
| 63 | + |
| 64 | +@pytest.mark.asyncio |
| 65 | +async def test_self_signed_wrong_host(tmp_dir_factory, unused_tcp_port_factory): |
| 66 | + certs = utils.create_self_signed_cert(tmp_dir_factory(), hostname='example.org') |
| 67 | + async with utils.grpc_server(unused_tcp_port_factory(), certs) as (host, port): |
| 68 | + f = functools.partial(AsyncCVClient.from_token, utils.TEST_TOKEN, host=host, |
| 69 | + port=port) |
| 70 | + with pytest.raises(ssl.SSLCertVerificationError): |
| 71 | + await utils.assert_grpc_response(f) |
| 72 | + |
| 73 | + |
| 74 | +@pytest.mark.asyncio |
| 75 | +async def test_ca_cert_provided(tmp_dir_factory, unused_tcp_port_factory): |
| 76 | + certs = utils.create_ca_signed_certs(tmp_dir_factory()) |
| 77 | + async with utils.grpc_server(unused_tcp_port_factory(), certs) as (host, port): |
| 78 | + f = functools.partial(AsyncCVClient.from_token, utils.TEST_TOKEN, host=host, |
| 79 | + port=port, cacert=certs.cacert) |
| 80 | + |
| 81 | + await utils.assert_grpc_response(f) |
| 82 | + |
| 83 | + |
| 84 | +@pytest.mark.asyncio |
| 85 | +async def test_bogus_ca_cert(tmp_dir_factory, unused_tcp_port_factory): |
| 86 | + realCerts = utils.create_ca_signed_certs(tmp_dir_factory()) |
| 87 | + bogusCerts = utils.create_ca_signed_certs(tmp_dir_factory()) |
| 88 | + |
| 89 | + async with utils.grpc_server(unused_tcp_port_factory(), bogusCerts) as (host, port): |
| 90 | + f = functools.partial(AsyncCVClient.from_token, utils.TEST_TOKEN, host=host, |
| 91 | + port=port, cacert=realCerts.cacert) |
| 92 | + with pytest.raises(ssl.SSLCertVerificationError): |
| 93 | + await utils.assert_grpc_response(f) |
50 | 94 |
|
51 | 95 |
|
52 | 96 | @pytest.mark.asyncio |
53 | | -async def test_token_auth(grpc_server): |
54 | | - host, port = grpc_server |
55 | | - callable = functools.partial(AsyncCVClient.from_token, TEST_TOKEN, host=host, port=port) |
56 | | - # Need to run this in executor, otherwise it would block the event loop forever |
57 | | - client = await asyncio.get_running_loop().run_in_executor(None, callable) |
58 | | - with client as channel: |
59 | | - stub = inventory.DeviceServiceStub(channel) |
60 | | - result = [] |
61 | | - async for device in stub.get_all(inventory.DeviceStreamRequest(), timeout=10): |
62 | | - result.append(device) |
63 | | - |
64 | | - assert len(result) == 3 |
65 | | - assert set([dev.value.key.device_id for dev in result]) == \ |
66 | | - {'device-0', 'device-1', 'device-2'} |
| 97 | +async def test_insecure(tmp_dir_factory, unused_tcp_port_factory): |
| 98 | + bogusCerts = utils.create_ca_signed_certs(tmp_dir_factory()) |
| 99 | + |
| 100 | + async with utils.grpc_server(unused_tcp_port_factory(), bogusCerts) as (host, port): |
| 101 | + f = functools.partial(AsyncCVClient.from_token, utils.TEST_TOKEN, host=host, |
| 102 | + port=port, insecure=True) |
| 103 | + await utils.assert_grpc_response(f) |
| 104 | + |
| 105 | + |
| 106 | +@pytest.mark.asyncio |
| 107 | +async def test_user_password_sefl_signed(tmp_dir_factory, unused_tcp_port_factory): |
| 108 | + certs = utils.create_self_signed_cert(tmp_dir_factory()) |
| 109 | + port = unused_tcp_port_factory() |
| 110 | + async with utils.http_server(port=port, certs=certs): |
| 111 | + client = AsyncCVClient.from_user_credentials(username=utils.USERNAME, |
| 112 | + password=utils.PASSWORD, host='localhost', |
| 113 | + port=port) |
| 114 | + assert client.token == utils.TEST_TOKEN |
| 115 | + |
| 116 | + |
| 117 | +@pytest.mark.asyncio |
| 118 | +async def test_user_password_with_ca(tmp_dir_factory, unused_tcp_port_factory): |
| 119 | + certs = utils.create_ca_signed_certs(tmp_dir_factory()) |
| 120 | + port = unused_tcp_port_factory() |
| 121 | + async with utils.http_server(port=port, certs=certs): |
| 122 | + client = AsyncCVClient.from_user_credentials(username=utils.USERNAME, |
| 123 | + password=utils.PASSWORD, host='localhost', |
| 124 | + port=port, cacert=certs.cacert) |
| 125 | + assert client.token == utils.TEST_TOKEN |
| 126 | + |
| 127 | + |
| 128 | +@pytest.mark.asyncio |
| 129 | +async def test_user_password_wrong_ca_cert(tmp_dir_factory, unused_tcp_port_factory): |
| 130 | + realCert = utils.create_ca_signed_certs(tmp_dir_factory()) |
| 131 | + bogusCert = utils.create_ca_signed_certs(tmp_dir_factory()) |
| 132 | + |
| 133 | + port = unused_tcp_port_factory() |
| 134 | + async with utils.http_server(port=port, certs=bogusCert): |
| 135 | + with pytest.raises(UnableToAuthenticateException): |
| 136 | + AsyncCVClient.from_user_credentials(username=utils.USERNAME, |
| 137 | + password=utils.PASSWORD, host='localhost', |
| 138 | + port=port, cacert=realCert.cacert) |
| 139 | + |
| 140 | + |
| 141 | +@pytest.mark.asyncio |
| 142 | +async def test_user_password_wrong_ca_cert_insecure(tmp_dir_factory, unused_tcp_port_factory): |
| 143 | + bogusCert = utils.create_ca_signed_certs(tmp_dir_factory()) |
| 144 | + |
| 145 | + port = unused_tcp_port_factory() |
| 146 | + async with utils.http_server(port=port, certs=bogusCert): |
| 147 | + client = AsyncCVClient.from_user_credentials(username=utils.USERNAME, |
| 148 | + password=utils.PASSWORD, host='localhost', |
| 149 | + port=port, |
| 150 | + insecure=True) |
| 151 | + assert client.token == utils.TEST_TOKEN |
| 152 | + |
| 153 | + |
| 154 | +@pytest.mark.asyncio |
| 155 | +async def test_user_password_wrong_password(tmp_dir_factory, unused_tcp_port_factory): |
| 156 | + certs = utils.create_ca_signed_certs(tmp_dir_factory()) |
| 157 | + |
| 158 | + port = unused_tcp_port_factory() |
| 159 | + async with utils.http_server(port=port, certs=certs): |
| 160 | + with pytest.raises(UnableToAuthenticateException): |
| 161 | + AsyncCVClient.from_user_credentials(username=utils.USERNAME, |
| 162 | + password='wrong', host='localhost', |
| 163 | + port=port, cacert=certs.cacert) |
0 commit comments