synapse 2.162.0__py311-none-any.whl → 2.163.0__py311-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

synapse/axon.py CHANGED
@@ -593,7 +593,8 @@ class AxonApi(s_cell.CellApi, s_share.Share): # type: ignore
593
593
  await self._reqUserAllowed(('axon', 'del'))
594
594
  return await self.cell.dels(sha256s)
595
595
 
596
- async def wget(self, url, params=None, headers=None, json=None, body=None, method='GET', ssl=True, timeout=None, proxy=None):
596
+ async def wget(self, url, params=None, headers=None, json=None, body=None, method='GET',
597
+ ssl=True, timeout=None, proxy=None, ssl_opts=None):
597
598
  '''
598
599
  Stream a file download directly into the Axon.
599
600
 
@@ -606,11 +607,20 @@ class AxonApi(s_cell.CellApi, s_share.Share): # type: ignore
606
607
  method (str): The HTTP method to use.
607
608
  ssl (bool): Perform SSL verification.
608
609
  timeout (int): The timeout of the request, in seconds.
610
+ ssl_opts (dict): Additional SSL/TLS options.
609
611
 
610
612
  Notes:
611
613
  The response body will be stored, regardless of the response code. The ``ok`` value in the reponse does not
612
614
  reflect that a status code, such as a 404, was encountered when retrieving the URL.
613
615
 
616
+ The ssl_opts dictionary may contain the following values::
617
+
618
+ {
619
+ 'verify': <bool> - Perform SSL/TLS verification. Is overridden by the ssl argument.
620
+ 'client_cert': <str> - PEM encoded full chain certificate for use in mTLS.
621
+ 'client_key': <str> - PEM encoded key for use in mTLS. Alternatively, can be included in client_cert.
622
+ }
623
+
614
624
  The dictionary returned by this may contain the following values::
615
625
 
616
626
  {
@@ -640,18 +650,20 @@ class AxonApi(s_cell.CellApi, s_share.Share): # type: ignore
640
650
  dict: An information dictionary containing the results of the request.
641
651
  '''
642
652
  await self._reqUserAllowed(('axon', 'wget'))
643
- return await self.cell.wget(url, params=params, headers=headers, json=json, body=body, method=method, ssl=ssl,
644
- timeout=timeout, proxy=proxy)
653
+ return await self.cell.wget(url, params=params, headers=headers, json=json, body=body, method=method,
654
+ ssl=ssl, timeout=timeout, proxy=proxy, ssl_opts=ssl_opts)
645
655
 
646
- async def postfiles(self, fields, url, params=None, headers=None, method='POST', ssl=True, timeout=None, proxy=None):
656
+ async def postfiles(self, fields, url, params=None, headers=None, method='POST',
657
+ ssl=True, timeout=None, proxy=None, ssl_opts=None):
647
658
  await self._reqUserAllowed(('axon', 'wput'))
648
- return await self.cell.postfiles(fields, url, params=params, headers=headers,
649
- method=method, ssl=ssl, timeout=timeout, proxy=proxy)
659
+ return await self.cell.postfiles(fields, url, params=params, headers=headers, method=method,
660
+ ssl=ssl, timeout=timeout, proxy=proxy, ssl_opts=ssl_opts)
650
661
 
651
- async def wput(self, sha256, url, params=None, headers=None, method='PUT', ssl=True, timeout=None, proxy=None):
662
+ async def wput(self, sha256, url, params=None, headers=None, method='PUT',
663
+ ssl=True, timeout=None, proxy=None, ssl_opts=None):
652
664
  await self._reqUserAllowed(('axon', 'wput'))
653
- return await self.cell.wput(sha256, url, params=params, headers=headers, method=method, ssl=ssl,
654
- timeout=timeout, proxy=proxy)
665
+ return await self.cell.wput(sha256, url, params=params, headers=headers, method=method,
666
+ ssl=ssl, timeout=timeout, proxy=proxy, ssl_opts=ssl_opts)
655
667
 
656
668
  async def metrics(self):
657
669
  '''
@@ -1434,7 +1446,8 @@ class Axon(s_cell.Cell):
1434
1446
  raise s_exc.BadJsonText(mesg=f'Bad json line encountered while processing {sha256}, ({e})',
1435
1447
  sha256=sha256) from None
1436
1448
 
1437
- async def postfiles(self, fields, url, params=None, headers=None, method='POST', ssl=True, timeout=None, proxy=None):
1449
+ async def postfiles(self, fields, url, params=None, headers=None, method='POST',
1450
+ ssl=True, timeout=None, proxy=None, ssl_opts=None):
1438
1451
  '''
1439
1452
  Send files from the axon as fields in a multipart/form-data HTTP request.
1440
1453
 
@@ -1447,6 +1460,7 @@ class Axon(s_cell.Cell):
1447
1460
  ssl (bool): Perform SSL verification.
1448
1461
  timeout (int): The timeout of the request, in seconds.
1449
1462
  proxy (bool|str|null): Use a specific proxy or disable proxy use.
1463
+ ssl_opts (dict): Additional SSL/TLS options.
1450
1464
 
1451
1465
  Notes:
1452
1466
  The dictionaries in the fields list may contain the following values::
@@ -1460,6 +1474,14 @@ class Axon(s_cell.Cell):
1460
1474
  'content_transfer_encoding': <str> - Optional content-transfer-encoding header for the field.
1461
1475
  }
1462
1476
 
1477
+ The ssl_opts dictionary may contain the following values::
1478
+
1479
+ {
1480
+ 'verify': <bool> - Perform SSL/TLS verification. Is overridden by the ssl argument.
1481
+ 'client_cert': <str> - PEM encoded full chain certificate for use in mTLS.
1482
+ 'client_key': <str> - PEM encoded key for use in mTLS. Alternatively, can be included in client_cert.
1483
+ }
1484
+
1463
1485
  The dictionary returned by this may contain the following values::
1464
1486
 
1465
1487
  {
@@ -1478,20 +1500,12 @@ class Axon(s_cell.Cell):
1478
1500
  if proxy is None:
1479
1501
  proxy = self.conf.get('http:proxy')
1480
1502
 
1481
- cadir = self.conf.get('tls:ca:dir')
1503
+ ssl = self.getCachedSslCtx(opts=ssl_opts, verify=ssl)
1482
1504
 
1483
1505
  connector = None
1484
1506
  if proxy:
1485
1507
  connector = aiohttp_socks.ProxyConnector.from_url(proxy)
1486
1508
 
1487
- if ssl is False:
1488
- pass
1489
- elif cadir:
1490
- ssl = s_common.getSslCtx(cadir)
1491
- else:
1492
- # default aiohttp behavior
1493
- ssl = None
1494
-
1495
1509
  atimeout = aiohttp.ClientTimeout(total=timeout)
1496
1510
 
1497
1511
  async with aiohttp.ClientSession(connector=connector, timeout=atimeout) as sess:
@@ -1556,27 +1570,19 @@ class Axon(s_cell.Cell):
1556
1570
  }
1557
1571
 
1558
1572
  async def wput(self, sha256, url, params=None, headers=None, method='PUT', ssl=True, timeout=None,
1559
- filename=None, filemime=None, proxy=None):
1573
+ filename=None, filemime=None, proxy=None, ssl_opts=None):
1560
1574
  '''
1561
1575
  Stream a blob from the axon as the body of an HTTP request.
1562
1576
  '''
1563
1577
  if proxy is None:
1564
- prox = self.conf.get('http:proxy')
1578
+ proxy = self.conf.get('http:proxy')
1565
1579
 
1566
- cadir = self.conf.get('tls:ca:dir')
1580
+ ssl = self.getCachedSslCtx(opts=ssl_opts, verify=ssl)
1567
1581
 
1568
1582
  connector = None
1569
1583
  if proxy:
1570
1584
  connector = aiohttp_socks.ProxyConnector.from_url(proxy)
1571
1585
 
1572
- if ssl is False:
1573
- pass
1574
- elif cadir:
1575
- ssl = s_common.getSslCtx(cadir)
1576
- else:
1577
- # default aiohttp behavior
1578
- ssl = None
1579
-
1580
1586
  atimeout = aiohttp.ClientTimeout(total=timeout)
1581
1587
 
1582
1588
  async with aiohttp.ClientSession(connector=connector, timeout=atimeout) as sess:
@@ -1638,7 +1644,8 @@ class Axon(s_cell.Cell):
1638
1644
 
1639
1645
  return info
1640
1646
 
1641
- async def wget(self, url, params=None, headers=None, json=None, body=None, method='GET', ssl=True, timeout=None, proxy=None):
1647
+ async def wget(self, url, params=None, headers=None, json=None, body=None, method='GET',
1648
+ ssl=True, timeout=None, proxy=None, ssl_opts=None):
1642
1649
  '''
1643
1650
  Stream a file download directly into the Axon.
1644
1651
 
@@ -1652,11 +1659,20 @@ class Axon(s_cell.Cell):
1652
1659
  ssl (bool): Perform SSL verification.
1653
1660
  timeout (int): The timeout of the request, in seconds.
1654
1661
  proxy (bool|str|null): Use a specific proxy or disable proxy use.
1662
+ ssl_opts (dict): Additional SSL/TLS options.
1655
1663
 
1656
1664
  Notes:
1657
1665
  The response body will be stored, regardless of the response code. The ``ok`` value in the reponse does not
1658
1666
  reflect that a status code, such as a 404, was encountered when retrieving the URL.
1659
1667
 
1668
+ The ssl_opts dictionary may contain the following values::
1669
+
1670
+ {
1671
+ 'verify': <bool> - Perform SSL/TLS verification. Is overridden by the ssl argument.
1672
+ 'client_cert': <str> - PEM encoded full chain certificate for use in mTLS.
1673
+ 'client_key': <str> - PEM encoded key for use in mTLS. Alternatively, can be included in client_cert.
1674
+ }
1675
+
1660
1676
  The dictionary returned by this may contain the following values::
1661
1677
 
1662
1678
  {
@@ -1690,7 +1706,7 @@ class Axon(s_cell.Cell):
1690
1706
  if proxy is None:
1691
1707
  proxy = self.conf.get('http:proxy')
1692
1708
 
1693
- cadir = self.conf.get('tls:ca:dir')
1709
+ ssl = self.getCachedSslCtx(opts=ssl_opts, verify=ssl)
1694
1710
 
1695
1711
  connector = None
1696
1712
  if proxy:
@@ -1698,14 +1714,6 @@ class Axon(s_cell.Cell):
1698
1714
 
1699
1715
  atimeout = aiohttp.ClientTimeout(total=timeout)
1700
1716
 
1701
- if ssl is False:
1702
- pass
1703
- elif cadir:
1704
- ssl = s_common.getSslCtx(cadir)
1705
- else:
1706
- # default aiohttp behavior
1707
- ssl = None
1708
-
1709
1717
  async with aiohttp.ClientSession(connector=connector, timeout=atimeout) as sess:
1710
1718
 
1711
1719
  try:
synapse/cortex.py CHANGED
@@ -3879,6 +3879,10 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
3879
3879
  if proxyurl is not None:
3880
3880
  conf['http:proxy'] = proxyurl
3881
3881
 
3882
+ cadir = self.conf.get('tls:ca:dir')
3883
+ if cadir is not None:
3884
+ conf['tls:ca:dir'] = cadir
3885
+
3882
3886
  self.axon = await s_axon.Axon.anit(path, conf=conf, parent=self)
3883
3887
  self.axoninfo = await self.axon.getCellInfo()
3884
3888
  self.axon.onfini(self.axready.clear)
synapse/lib/cell.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import gc
2
2
  import os
3
+ import ssl
3
4
  import copy
4
5
  import time
5
6
  import fcntl
@@ -12,6 +13,7 @@ import tarfile
12
13
  import argparse
13
14
  import datetime
14
15
  import platform
16
+ import tempfile
15
17
  import functools
16
18
  import contextlib
17
19
  import multiprocessing
@@ -32,6 +34,7 @@ import synapse.lib.boss as s_boss
32
34
  import synapse.lib.coro as s_coro
33
35
  import synapse.lib.hive as s_hive
34
36
  import synapse.lib.link as s_link
37
+ import synapse.lib.cache as s_cache
35
38
  import synapse.lib.const as s_const
36
39
  import synapse.lib.nexus as s_nexus
37
40
  import synapse.lib.queue as s_queue
@@ -58,6 +61,7 @@ import synapse.tools.backup as s_t_backup
58
61
  logger = logging.getLogger(__name__)
59
62
 
60
63
  SLAB_MAP_SIZE = 128 * s_const.mebibyte
64
+ SSLCTX_CACHE_SIZE = 64
61
65
 
62
66
  '''
63
67
  Base classes for the synapse "cell" microservice architecture.
@@ -1151,6 +1155,8 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1151
1155
  self.usermetadb = self.slab.initdb('user:meta') # useriden + <valu> -> dict valu
1152
1156
  self.rolemetadb = self.slab.initdb('role:meta') # roleiden + <valu> -> dict valu
1153
1157
 
1158
+ self._sslctx_cache = s_cache.FixedCache(self._makeCachedSslCtx, size=SSLCTX_CACHE_SIZE)
1159
+
1154
1160
  self.hive = await self._initCellHive()
1155
1161
 
1156
1162
  # self.cellinfo, a HiveDict for general purpose persistent storage
@@ -4342,3 +4348,60 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
4342
4348
  self.slab.delete(key_iden, db=self.apikeydb)
4343
4349
  self.slab.delete(lkey, db=self.usermetadb)
4344
4350
  await asyncio.sleep(0)
4351
+
4352
+ def _makeCachedSslCtx(self, opts):
4353
+
4354
+ opts = dict(opts)
4355
+
4356
+ cadir = self.conf.get('tls:ca:dir')
4357
+
4358
+ if cadir is not None:
4359
+ sslctx = s_common.getSslCtx(cadir, purpose=ssl.Purpose.SERVER_AUTH)
4360
+ else:
4361
+ sslctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
4362
+
4363
+ if not opts['verify']:
4364
+ sslctx.check_hostname = False
4365
+ sslctx.verify_mode = ssl.CERT_NONE
4366
+
4367
+ if not opts['client_cert']:
4368
+ return sslctx
4369
+
4370
+ client_cert = opts['client_cert'].encode()
4371
+
4372
+ if opts['client_key']:
4373
+ client_key = opts['client_key'].encode()
4374
+ else:
4375
+ client_key = None
4376
+ client_key_path = None
4377
+
4378
+ with self.getTempDir() as tmpdir:
4379
+
4380
+ with tempfile.NamedTemporaryFile(dir=tmpdir, mode='wb', delete=False) as fh:
4381
+ fh.write(client_cert)
4382
+ client_cert_path = fh.name
4383
+
4384
+ if client_key:
4385
+ with tempfile.NamedTemporaryFile(dir=tmpdir, mode='wb', delete=False) as fh:
4386
+ fh.write(client_key)
4387
+ client_key_path = fh.name
4388
+
4389
+ try:
4390
+ sslctx.load_cert_chain(client_cert_path, keyfile=client_key_path)
4391
+ except ssl.SSLError as e:
4392
+ raise s_exc.BadArg(mesg=f'Error loading client cert: {str(e)}') from None
4393
+
4394
+ return sslctx
4395
+
4396
+ def getCachedSslCtx(self, opts=None, verify=None):
4397
+
4398
+ if opts is None:
4399
+ opts = {}
4400
+
4401
+ if verify is not None:
4402
+ opts['verify'] = verify
4403
+
4404
+ opts = s_schemas.reqValidSslCtxOpts(opts)
4405
+
4406
+ key = tuple(sorted(opts.items()))
4407
+ return self._sslctx_cache.get(key)
synapse/lib/oauth.py CHANGED
@@ -230,13 +230,7 @@ class OAuthMixin(s_nexus.Pusher):
230
230
 
231
231
  timeout = aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT)
232
232
 
233
- cadir = self.conf.get('tls:ca:dir')
234
- if ssl_verify is False:
235
- ssl = False
236
- elif cadir:
237
- ssl = s_common.getSslCtx(cadir)
238
- else:
239
- ssl = None
233
+ ssl = self.getCachedSslCtx(verify=ssl_verify)
240
234
 
241
235
  async with aiohttp.ClientSession(timeout=timeout) as sess:
242
236
 
synapse/lib/schemas.py CHANGED
@@ -263,3 +263,13 @@ _cellUserApiKeySchema = {
263
263
  ],
264
264
  }
265
265
  reqValidUserApiKeyDef = s_config.getJsValidator(_cellUserApiKeySchema)
266
+
267
+ reqValidSslCtxOpts = s_config.getJsValidator({
268
+ 'type': 'object',
269
+ 'properties': {
270
+ 'verify': {'type': 'boolean', 'default': True},
271
+ 'client_cert': {'type': ['string', 'null'], 'default': None},
272
+ 'client_key': {'type': ['string', 'null'], 'default': None},
273
+ },
274
+ 'additionalProperties': False,
275
+ })
synapse/lib/stormhttp.py CHANGED
@@ -13,6 +13,7 @@ import synapse.common as s_common
13
13
 
14
14
  import synapse.lib.base as s_base
15
15
  import synapse.lib.msgpack as s_msgpack
16
+ import synapse.lib.version as s_version
16
17
  import synapse.lib.stormtypes as s_stormtypes
17
18
 
18
19
  @s_stormtypes.registry.registerType
@@ -87,6 +88,14 @@ class WebSocket(s_base.Base, s_stormtypes.StormType):
87
88
  class LibHttp(s_stormtypes.Lib):
88
89
  '''
89
90
  A Storm Library exposing an HTTP client API.
91
+
92
+ For APIs that accept an ssl_opts argument, the dictionary may contain the following values::
93
+
94
+ {
95
+ 'verify': <bool> - Perform SSL/TLS verification. Is overridden by the ssl_verify argument.
96
+ 'client_cert': <str> - PEM encoded full chain certificate for use in mTLS.
97
+ 'client_key': <str> - PEM encoded key for use in mTLS. Alternatively, can be included in client_cert.
98
+ }
90
99
  '''
91
100
  _storm_locals = (
92
101
  {'name': 'get', 'desc': 'Get the contents of a given URL.',
@@ -105,6 +114,9 @@ class LibHttp(s_stormtypes.Lib):
105
114
  'default': True},
106
115
  {'name': 'proxy', 'type': ['bool', 'null', 'str'],
107
116
  'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
117
+ {'name': 'ssl_opts', 'type': 'dict',
118
+ 'desc': 'Optional SSL/TLS options. See $lib.inet.http help for additional details.',
119
+ 'default': None},
108
120
  ),
109
121
  'returns': {'type': 'inet:http:resp', 'desc': 'The response object.'}}},
110
122
  {'name': 'post', 'desc': 'Post data to a given URL.',
@@ -134,6 +146,9 @@ class LibHttp(s_stormtypes.Lib):
134
146
  'default': None},
135
147
  {'name': 'proxy', 'type': ['bool', 'null', 'str'],
136
148
  'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
149
+ {'name': 'ssl_opts', 'type': 'dict',
150
+ 'desc': 'Optional SSL/TLS options. See $lib.inet.http help for additional details.',
151
+ 'default': None},
137
152
  ),
138
153
  'returns': {'type': 'inet:http:resp', 'desc': 'The response object.'}}},
139
154
  {'name': 'head', 'desc': 'Get the HEAD response for a URL.',
@@ -153,6 +168,9 @@ class LibHttp(s_stormtypes.Lib):
153
168
  'default': False},
154
169
  {'name': 'proxy', 'type': ['bool', 'null', 'str'],
155
170
  'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
171
+ {'name': 'ssl_opts', 'type': 'dict',
172
+ 'desc': 'Optional SSL/TLS options. See $lib.inet.http help for additional details.',
173
+ 'default': None},
156
174
  ),
157
175
  'returns': {'type': 'inet:http:resp', 'desc': 'The response object.'}}},
158
176
  {'name': 'request', 'desc': 'Make an HTTP request using the given HTTP method to the url.',
@@ -183,6 +201,9 @@ class LibHttp(s_stormtypes.Lib):
183
201
  'default': None},
184
202
  {'name': 'proxy', 'type': ['bool', 'null', 'str'],
185
203
  'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
204
+ {'name': 'ssl_opts', 'type': 'dict',
205
+ 'desc': 'Optional SSL/TLS options. See $lib.inet.http help for additional details.',
206
+ 'default': None},
186
207
  ),
187
208
  'returns': {'type': 'inet:http:resp', 'desc': 'The response object.'}
188
209
  }
@@ -201,6 +222,9 @@ class LibHttp(s_stormtypes.Lib):
201
222
  'default': None},
202
223
  {'name': 'proxy', 'type': ['bool', 'null', 'str'],
203
224
  'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
225
+ {'name': 'ssl_opts', 'type': 'dict',
226
+ 'desc': 'Optional SSL/TLS options. See $lib.inet.http help for additional details.',
227
+ 'default': None},
204
228
  ),
205
229
  'returns': {'type': 'inet:http:socket', 'desc': 'A websocket object.'}}},
206
230
  {'name': 'urlencode', 'desc': '''
@@ -290,28 +314,31 @@ class LibHttp(s_stormtypes.Lib):
290
314
  return s_common.httpcodereason(code)
291
315
 
292
316
  async def _httpEasyHead(self, url, headers=None, ssl_verify=True, params=None, timeout=300,
293
- allow_redirects=False, proxy=None):
317
+ allow_redirects=False, proxy=None, ssl_opts=None):
294
318
  return await self._httpRequest('HEAD', url, headers=headers, ssl_verify=ssl_verify, params=params,
295
- timeout=timeout, allow_redirects=allow_redirects, proxy=proxy)
319
+ timeout=timeout, allow_redirects=allow_redirects, proxy=proxy, ssl_opts=ssl_opts)
296
320
 
297
321
  async def _httpEasyGet(self, url, headers=None, ssl_verify=True, params=None, timeout=300,
298
- allow_redirects=True, proxy=None):
322
+ allow_redirects=True, proxy=None, ssl_opts=None):
299
323
  return await self._httpRequest('GET', url, headers=headers, ssl_verify=ssl_verify, params=params,
300
- timeout=timeout, allow_redirects=allow_redirects, proxy=proxy)
324
+ timeout=timeout, allow_redirects=allow_redirects, proxy=proxy, ssl_opts=ssl_opts)
301
325
 
302
326
  async def _httpPost(self, url, headers=None, json=None, body=None, ssl_verify=True,
303
- params=None, timeout=300, allow_redirects=True, fields=None, proxy=None):
327
+ params=None, timeout=300, allow_redirects=True, fields=None, proxy=None, ssl_opts=None):
304
328
  return await self._httpRequest('POST', url, headers=headers, json=json, body=body,
305
329
  ssl_verify=ssl_verify, params=params, timeout=timeout,
306
- allow_redirects=allow_redirects, fields=fields, proxy=proxy)
330
+ allow_redirects=allow_redirects, fields=fields, proxy=proxy, ssl_opts=ssl_opts)
307
331
 
308
- async def inetHttpConnect(self, url, headers=None, ssl_verify=True, timeout=300, params=None, proxy=None):
332
+ async def inetHttpConnect(self, url, headers=None, ssl_verify=True, timeout=300,
333
+ params=None, proxy=None, ssl_opts=None):
309
334
 
310
335
  url = await s_stormtypes.tostr(url)
311
336
  headers = await s_stormtypes.toprim(headers)
312
337
  timeout = await s_stormtypes.toint(timeout, noneok=True)
313
338
  params = await s_stormtypes.toprim(params)
314
339
  proxy = await s_stormtypes.toprim(proxy)
340
+ ssl_verify = await s_stormtypes.tobool(ssl_verify, noneok=True)
341
+ ssl_opts = await s_stormtypes.toprim(ssl_opts)
315
342
 
316
343
  headers = self.strify(headers)
317
344
 
@@ -332,15 +359,7 @@ class LibHttp(s_stormtypes.Lib):
332
359
  if params:
333
360
  kwargs['params'] = params
334
361
 
335
- cadir = self.runt.snap.core.conf.get('tls:ca:dir')
336
-
337
- if ssl_verify is False:
338
- kwargs['ssl'] = False
339
- elif cadir:
340
- kwargs['ssl'] = s_common.getSslCtx(cadir)
341
- else:
342
- # default aiohttp behavior
343
- kwargs['ssl'] = None
362
+ kwargs['ssl'] = self.runt.snap.core.getCachedSslCtx(opts=ssl_opts, verify=ssl_verify)
344
363
 
345
364
  try:
346
365
  sess = await sock.enter_context(aiohttp.ClientSession(connector=connector, timeout=timeout))
@@ -374,7 +393,7 @@ class LibHttp(s_stormtypes.Lib):
374
393
 
375
394
  async def _httpRequest(self, meth, url, headers=None, json=None, body=None,
376
395
  ssl_verify=True, params=None, timeout=300, allow_redirects=True,
377
- fields=None, proxy=None):
396
+ fields=None, proxy=None, ssl_opts=None):
378
397
  meth = await s_stormtypes.tostr(meth)
379
398
  url = await s_stormtypes.tostr(url)
380
399
  json = await s_stormtypes.toprim(json)
@@ -386,6 +405,7 @@ class LibHttp(s_stormtypes.Lib):
386
405
  ssl_verify = await s_stormtypes.tobool(ssl_verify, noneok=True)
387
406
  allow_redirects = await s_stormtypes.tobool(allow_redirects)
388
407
  proxy = await s_stormtypes.toprim(proxy)
408
+ ssl_opts = await s_stormtypes.toprim(ssl_opts)
389
409
 
390
410
  kwargs = {'allow_redirects': allow_redirects}
391
411
  if params:
@@ -399,12 +419,24 @@ class LibHttp(s_stormtypes.Lib):
399
419
  if fields:
400
420
  if any(['sha256' in field for field in fields]):
401
421
  self.runt.confirm(('storm', 'lib', 'axon', 'wput'))
422
+
423
+ kwargs = {}
424
+ axonvers = self.runt.snap.core.axoninfo['synapse']['version']
425
+ if axonvers >= s_stormtypes.AXON_MINVERS_PROXY:
426
+ kwargs['proxy'] = proxy
427
+
428
+ if ssl_opts is not None:
429
+ mesg = f'The ssl_opts argument requires an Axon Synapse version {s_stormtypes.AXON_MINVERS_SSLOPTS}, ' \
430
+ f'but the Axon is running {axonvers}'
431
+ s_version.reqVersion(axonvers, s_stormtypes.AXON_MINVERS_SSLOPTS, mesg=mesg)
432
+ kwargs['ssl_opts'] = ssl_opts
433
+
402
434
  axon = self.runt.snap.core.axon
403
- info = await axon.postfiles(fields, url, headers=headers, params=params,
404
- method=meth, ssl=ssl_verify, timeout=timeout, proxy=proxy)
435
+ info = await axon.postfiles(fields, url, headers=headers, params=params, method=meth,
436
+ ssl=ssl_verify, timeout=timeout, **kwargs)
405
437
  return HttpResp(info)
406
438
 
407
- cadir = self.runt.snap.core.conf.get('tls:ca:dir')
439
+ kwargs['ssl'] = self.runt.snap.core.getCachedSslCtx(opts=ssl_opts, verify=ssl_verify)
408
440
 
409
441
  if proxy is None:
410
442
  proxy = await self.runt.snap.core.getConfOpt('http:proxy')
@@ -413,14 +445,6 @@ class LibHttp(s_stormtypes.Lib):
413
445
  if proxy:
414
446
  connector = aiohttp_socks.ProxyConnector.from_url(proxy)
415
447
 
416
- if ssl_verify is False:
417
- kwargs['ssl'] = False
418
- elif cadir:
419
- kwargs['ssl'] = s_common.getSslCtx(cadir)
420
- else:
421
- # default aiohttp behavior
422
- kwargs['ssl'] = None
423
-
424
448
  timeout = aiohttp.ClientTimeout(total=timeout)
425
449
 
426
450
  async with aiohttp.ClientSession(connector=connector, timeout=timeout) as sess: