pypsrp 0.9.0rc1__py3-none-any.whl → 0.9.0rc2__py3-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.
- pypsrp/_pool_manager.py +142 -0
- pypsrp/negotiate.py +2 -2
- pypsrp/powershell.py +50 -0
- pypsrp/shell.py +38 -0
- pypsrp/wsman.py +45 -11
- {pypsrp-0.9.0rc1.dist-info → pypsrp-0.9.0rc2.dist-info}/METADATA +4 -3
- {pypsrp-0.9.0rc1.dist-info → pypsrp-0.9.0rc2.dist-info}/RECORD +10 -9
- {pypsrp-0.9.0rc1.dist-info → pypsrp-0.9.0rc2.dist-info}/WHEEL +1 -1
- {pypsrp-0.9.0rc1.dist-info → pypsrp-0.9.0rc2.dist-info}/licenses/LICENSE +0 -0
- {pypsrp-0.9.0rc1.dist-info → pypsrp-0.9.0rc2.dist-info}/top_level.txt +0 -0
pypsrp/_pool_manager.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Copyright: (c) 2026, Jordan Borean (@jborean93) <jborean93@gmail.com>
|
|
2
|
+
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import contextvars
|
|
7
|
+
import functools
|
|
8
|
+
import types
|
|
9
|
+
import typing as t
|
|
10
|
+
|
|
11
|
+
import requests.adapters
|
|
12
|
+
from requests.packages.urllib3.util.retry import Retry
|
|
13
|
+
|
|
14
|
+
T = t.TypeVar("T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class NewConnectionDisabled(Exception):
|
|
18
|
+
"""Raised when new connections are being made but have been disabled in the current context."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DisableNewConnectionsContext:
|
|
22
|
+
"""Context manager to disable new connections from being created."""
|
|
23
|
+
|
|
24
|
+
__slots__ = "_contextvar_token"
|
|
25
|
+
|
|
26
|
+
_contextvar: t.ClassVar[contextvars.ContextVar] = contextvars.ContextVar("NewConnectionContext")
|
|
27
|
+
_contextvar_token: contextvars.Token
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def current(cls) -> DisableNewConnectionsContext | None:
|
|
31
|
+
"""Returns the current context or None if not set."""
|
|
32
|
+
try:
|
|
33
|
+
return cls._contextvar.get()
|
|
34
|
+
except LookupError:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
def __enter__(self) -> DisableNewConnectionsContext:
|
|
38
|
+
self._contextvar_token = self.__class__._contextvar.set(self)
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def __exit__(
|
|
42
|
+
self,
|
|
43
|
+
exc_type: type[BaseException] | None,
|
|
44
|
+
exc_val: BaseException | None,
|
|
45
|
+
exc_tb: types.TracebackType | None,
|
|
46
|
+
) -> bool | None:
|
|
47
|
+
self.__class__._contextvar.reset(self._contextvar_token)
|
|
48
|
+
del self._contextvar_token
|
|
49
|
+
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class HTTPSAdapterWithKeyPassword(requests.adapters.HTTPAdapter):
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
*args: t.Any,
|
|
58
|
+
_pypsrp_key_password: str | None = None,
|
|
59
|
+
**kwargs: t.Any,
|
|
60
|
+
) -> None:
|
|
61
|
+
self.__key_password = _pypsrp_key_password
|
|
62
|
+
super().__init__(*args, **kwargs)
|
|
63
|
+
|
|
64
|
+
def init_poolmanager(
|
|
65
|
+
self,
|
|
66
|
+
connections,
|
|
67
|
+
maxsize,
|
|
68
|
+
block=False,
|
|
69
|
+
**pool_kwargs,
|
|
70
|
+
):
|
|
71
|
+
return super().init_poolmanager(
|
|
72
|
+
connections,
|
|
73
|
+
maxsize,
|
|
74
|
+
block,
|
|
75
|
+
key_password=self.__key_password,
|
|
76
|
+
**pool_kwargs,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _wrap(
|
|
81
|
+
func: t.Callable[..., T],
|
|
82
|
+
*,
|
|
83
|
+
before: t.Callable[[], None] | None = None,
|
|
84
|
+
after: t.Callable[[T], None] | None = None,
|
|
85
|
+
) -> t.Callable[..., T]:
|
|
86
|
+
"""Wraps a function with before and after callables."""
|
|
87
|
+
|
|
88
|
+
@functools.wraps(func)
|
|
89
|
+
def wrapper(*args: t.Any, **kwargs: t.Any) -> T:
|
|
90
|
+
if before:
|
|
91
|
+
before()
|
|
92
|
+
|
|
93
|
+
res = func(*args, **kwargs)
|
|
94
|
+
|
|
95
|
+
if after:
|
|
96
|
+
after(res)
|
|
97
|
+
|
|
98
|
+
return res
|
|
99
|
+
|
|
100
|
+
return wrapper
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _raise_if_connections_disabled():
|
|
104
|
+
if DisableNewConnectionsContext.current() is not None:
|
|
105
|
+
raise NewConnectionDisabled()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _create_new_connection_with_failure(connection):
|
|
109
|
+
connection.connect = _wrap(connection.connect, before=_raise_if_connections_disabled)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _create_connection_pool(pool):
|
|
113
|
+
# This is called in the urllib3 pool when a new connection is needed. We
|
|
114
|
+
# wrap it so we can wrap the connect method when a new connection is
|
|
115
|
+
# created.
|
|
116
|
+
pool.ConnectionCls = _wrap(pool.ConnectionCls, after=_create_new_connection_with_failure)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def create_request_adapter( # type: ignore[no-any-unimported] # requests does not have typing stubs for urllib3
|
|
120
|
+
*,
|
|
121
|
+
max_retries: Retry,
|
|
122
|
+
key_password: str | None = None,
|
|
123
|
+
) -> requests.adapters.HTTPAdapter:
|
|
124
|
+
"""Creates a HTTPAdapter with support for disabling new connections via context."""
|
|
125
|
+
adapter: requests.adapters.HTTPAdapter
|
|
126
|
+
if key_password is not None:
|
|
127
|
+
adapter = HTTPSAdapterWithKeyPassword(
|
|
128
|
+
max_retries=max_retries,
|
|
129
|
+
_pypsrp_key_password=key_password,
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
adapter = requests.adapters.HTTPAdapter(max_retries=max_retries)
|
|
133
|
+
|
|
134
|
+
# pool_classes_by_scheme stores the urllib3 pool types used when creating
|
|
135
|
+
# the connection pool for http/https. We wrap the __init__ method so we can
|
|
136
|
+
# inject our custom ConnectionCls that wraps the connect method when
|
|
137
|
+
# created.
|
|
138
|
+
pool_classes = adapter.poolmanager.pool_classes_by_scheme
|
|
139
|
+
for scheme, pool_cls in list(pool_classes.items()):
|
|
140
|
+
pool_classes[scheme] = _wrap(pool_cls, after=_create_connection_pool)
|
|
141
|
+
|
|
142
|
+
return adapter
|
pypsrp/negotiate.py
CHANGED
|
@@ -36,7 +36,7 @@ class HTTPNegotiateAuth(AuthBase):
|
|
|
36
36
|
password: typing.Optional[str] = None,
|
|
37
37
|
auth_provider: str = "negotiate",
|
|
38
38
|
send_cbt: bool = True,
|
|
39
|
-
service: str = "
|
|
39
|
+
service: str = "host",
|
|
40
40
|
delegate: bool = False,
|
|
41
41
|
hostname_override: typing.Optional[str] = None,
|
|
42
42
|
wrap_required: bool = False,
|
|
@@ -60,7 +60,7 @@ class HTTPNegotiateAuth(AuthBase):
|
|
|
60
60
|
:param send_cbt: Try to bind the channel token (HTTPS only) to the auth
|
|
61
61
|
process, default is True
|
|
62
62
|
:param service: The service part of the SPN to authenticate with,
|
|
63
|
-
defaults to
|
|
63
|
+
defaults to host
|
|
64
64
|
:param delegate: Whether to get an auth token that allows the token to
|
|
65
65
|
be delegated to other servers, this is only used with Kerberos and
|
|
66
66
|
defaults to False
|
pypsrp/powershell.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Copyright: (c) 2018, Jordan Borean (@jborean93) <jborean93@gmail.com>
|
|
2
2
|
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
|
|
3
3
|
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
4
6
|
import base64
|
|
5
7
|
import logging
|
|
6
8
|
import struct
|
|
@@ -11,9 +13,11 @@ import uuid
|
|
|
11
13
|
import warnings
|
|
12
14
|
import xml.etree.ElementTree as ET
|
|
13
15
|
|
|
16
|
+
import requests.exceptions
|
|
14
17
|
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
|
15
18
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
16
19
|
|
|
20
|
+
from pypsrp import _pool_manager
|
|
17
21
|
from pypsrp._utils import version_equal_or_newer
|
|
18
22
|
from pypsrp.complex_objects import (
|
|
19
23
|
ApartmentState,
|
|
@@ -104,6 +108,7 @@ class RunspacePool(object):
|
|
|
104
108
|
session_key_timeout_ms: int = 60000,
|
|
105
109
|
*,
|
|
106
110
|
no_profile: bool = False,
|
|
111
|
+
idle_timeout: int | None = None,
|
|
107
112
|
) -> None:
|
|
108
113
|
"""
|
|
109
114
|
Represents a Runspace pool on a remote host. This pool can contain
|
|
@@ -132,6 +137,9 @@ class RunspacePool(object):
|
|
|
132
137
|
key transfer from the server
|
|
133
138
|
:param no_profile: If True, the user profile will not be loaded and
|
|
134
139
|
will use the machine defaults.
|
|
140
|
+
:param idle_timeout: The idle timeout, in seconds, for the runspace. The
|
|
141
|
+
runspace will be closed if no operations are performed in the time
|
|
142
|
+
specified. If None the server default will be used.
|
|
135
143
|
"""
|
|
136
144
|
log.info("Initialising RunspacePool object for configuration %s" % configuration_name)
|
|
137
145
|
# The below are defined in some way at
|
|
@@ -147,6 +155,7 @@ class RunspacePool(object):
|
|
|
147
155
|
input_streams="stdin pr",
|
|
148
156
|
output_streams="stdout",
|
|
149
157
|
no_profile=no_profile,
|
|
158
|
+
idle_time_out=idle_timeout,
|
|
150
159
|
)
|
|
151
160
|
self.ci_table: typing.Dict = {}
|
|
152
161
|
self.pipelines: typing.Dict[str, "PowerShell"] = {}
|
|
@@ -659,6 +668,47 @@ class RunspacePool(object):
|
|
|
659
668
|
"""
|
|
660
669
|
return self._serializer.serialize(obj, metadata=metadata)
|
|
661
670
|
|
|
671
|
+
def is_alive(
|
|
672
|
+
self,
|
|
673
|
+
*,
|
|
674
|
+
timeout: int | None = None,
|
|
675
|
+
) -> bool:
|
|
676
|
+
"""Checks whether the RunspacePool is still alive.
|
|
677
|
+
|
|
678
|
+
:param timeout: Override the connection timeout defaults for the
|
|
679
|
+
request. The WSMan operation timeout will be set to this value
|
|
680
|
+
and the HTTP connect/read timeouts will be this value + 2 seconds.
|
|
681
|
+
:return: A bool True if the pool is still alive, False otherwise.
|
|
682
|
+
"""
|
|
683
|
+
is_closed = False
|
|
684
|
+
|
|
685
|
+
try:
|
|
686
|
+
# We don't want to try and open a new connection if the socket
|
|
687
|
+
# was closed, we treat it as the pool is not alive.
|
|
688
|
+
with _pool_manager.DisableNewConnectionsContext():
|
|
689
|
+
state = self.shell._get_shell(timeout=timeout)
|
|
690
|
+
|
|
691
|
+
is_closed = state.get("State", "") == "Disconnected"
|
|
692
|
+
except WSManFaultError as exc:
|
|
693
|
+
if exc.code in [
|
|
694
|
+
0x80338029, # ERROR_WSMAN_OPERATION_TIMEDOUT
|
|
695
|
+
0x8033805B, # ERROR_WSMAN_UNEXPECTED_SELECTORS
|
|
696
|
+
]:
|
|
697
|
+
is_closed = True
|
|
698
|
+
else:
|
|
699
|
+
raise
|
|
700
|
+
|
|
701
|
+
except (_pool_manager.NewConnectionDisabled, TimeoutError, requests.exceptions.ConnectionError):
|
|
702
|
+
# If a timeout or connection error occurs, treat the pool as closed.
|
|
703
|
+
is_closed = True
|
|
704
|
+
|
|
705
|
+
if is_closed:
|
|
706
|
+
# Ensures that close() doesn't try and close an already closed pool
|
|
707
|
+
self.state = RunspacePoolState.CLOSED
|
|
708
|
+
return False
|
|
709
|
+
|
|
710
|
+
return True
|
|
711
|
+
|
|
662
712
|
def _receive(
|
|
663
713
|
self,
|
|
664
714
|
id: typing.Optional[str] = None,
|
pypsrp/shell.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Copyright: (c) 2018, Jordan Borean (@jborean93) <jborean93@gmail.com>
|
|
2
2
|
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
|
|
3
3
|
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
4
6
|
import base64
|
|
5
7
|
import logging
|
|
6
8
|
import types
|
|
@@ -315,6 +317,42 @@ class WinRS(object):
|
|
|
315
317
|
ET.SubElement(signal, "{%s}Code" % rsp).text = code
|
|
316
318
|
return self.wsman.signal(self.resource_uri, signal, selector_set=self._selector_set)
|
|
317
319
|
|
|
320
|
+
def _get_shell(
|
|
321
|
+
self,
|
|
322
|
+
*,
|
|
323
|
+
timeout: int | None = None,
|
|
324
|
+
) -> dict[str, str | None]:
|
|
325
|
+
"""Queries the WSMan host for the current shell state.
|
|
326
|
+
|
|
327
|
+
Returns the WSMan GetResponse fields as a dictionary.
|
|
328
|
+
|
|
329
|
+
The timeout parameter can be used to override the default WSMan timeout
|
|
330
|
+
for this operation. If set the HTTP connect and read timeout will be
|
|
331
|
+
set to this value + 2 seconds to ensure the request does not block for
|
|
332
|
+
a longer time.
|
|
333
|
+
|
|
334
|
+
:param timeout: Sets the WSMan operational timeout to this value in
|
|
335
|
+
seconds. Overrides the default timeout set on the WSMan instance.
|
|
336
|
+
:return: A dictionary containing the shell state fields from the raw XML
|
|
337
|
+
response.
|
|
338
|
+
"""
|
|
339
|
+
rsp = NAMESPACES["rsp"]
|
|
340
|
+
|
|
341
|
+
resp = self.wsman.get(
|
|
342
|
+
"http://schemas.microsoft.com/wbem/wsman/1/windows/shell",
|
|
343
|
+
selector_set=self._selector_set,
|
|
344
|
+
timeout=timeout,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
state = {}
|
|
348
|
+
shell_items = resp.find("rsp:Shell", namespaces=NAMESPACES)
|
|
349
|
+
if shell_items is not None:
|
|
350
|
+
for child in shell_items:
|
|
351
|
+
field_name = child.tag.replace(f"{{{rsp}}}", "")
|
|
352
|
+
state[field_name] = child.text
|
|
353
|
+
|
|
354
|
+
return state
|
|
355
|
+
|
|
318
356
|
def _parse_shell_create(
|
|
319
357
|
self,
|
|
320
358
|
response: ET.Element,
|
pypsrp/wsman.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Copyright: (c) 2018, Jordan Borean (@jborean93) <jborean93@gmail.com>
|
|
2
2
|
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
|
|
3
3
|
|
|
4
|
-
from __future__ import
|
|
4
|
+
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import ipaddress
|
|
7
7
|
import logging
|
|
@@ -15,6 +15,7 @@ import xml.etree.ElementTree as ET
|
|
|
15
15
|
import requests
|
|
16
16
|
from requests.packages.urllib3.util.retry import Retry
|
|
17
17
|
|
|
18
|
+
from pypsrp import _pool_manager
|
|
18
19
|
from pypsrp._utils import get_hostname, to_string, to_unicode
|
|
19
20
|
from pypsrp.encryption import WinRMEncryption
|
|
20
21
|
from pypsrp.exceptions import (
|
|
@@ -40,7 +41,7 @@ log = logging.getLogger(__name__)
|
|
|
40
41
|
SUPPORTED_AUTHS = ["basic", "certificate", "credssp", "kerberos", "negotiate", "ntlm"]
|
|
41
42
|
|
|
42
43
|
AUTH_KWARGS: typing.Dict[str, typing.List[str]] = {
|
|
43
|
-
"certificate": ["certificate_key_pem", "certificate_pem"],
|
|
44
|
+
"certificate": ["certificate_key_pem", "certificate_pem", "certificate_key_password"],
|
|
44
45
|
"credssp": ["credssp_auth_mechanism", "credssp_disable_tlsv1_2", "credssp_minimum_version"],
|
|
45
46
|
"negotiate": ["negotiate_delegate", "negotiate_hostname_override", "negotiate_send_cbt", "negotiate_service"],
|
|
46
47
|
}
|
|
@@ -471,6 +472,14 @@ class WSMan(object):
|
|
|
471
472
|
s = NAMESPACES["s"]
|
|
472
473
|
envelope = ET.Element("{%s}Envelope" % s)
|
|
473
474
|
|
|
475
|
+
http_timeout = None
|
|
476
|
+
if timeout:
|
|
477
|
+
# Failsafe if the operation timeout exceeds we don't want the
|
|
478
|
+
# request to hang indefinitely. The HTTP timeout needs to be more
|
|
479
|
+
# than the WSMan timeout as we cannot guarantee a WSMan response
|
|
480
|
+
# until the operation timeout is hit.
|
|
481
|
+
http_timeout = timeout + 2
|
|
482
|
+
|
|
474
483
|
message_id, header = self._create_header(action, resource_uri, option_set, selector_set, timeout)
|
|
475
484
|
message_id = f"uuid:{message_id}"
|
|
476
485
|
envelope.append(header)
|
|
@@ -482,7 +491,11 @@ class WSMan(object):
|
|
|
482
491
|
xml = ET.tostring(envelope, encoding="utf-8", method="xml")
|
|
483
492
|
|
|
484
493
|
try:
|
|
485
|
-
response = self.transport.send(
|
|
494
|
+
response = self.transport.send(
|
|
495
|
+
xml,
|
|
496
|
+
retries_on_read_timeout=retries_on_read_timeout,
|
|
497
|
+
timeout=http_timeout,
|
|
498
|
+
)
|
|
486
499
|
except WinRMTransportError as err:
|
|
487
500
|
try:
|
|
488
501
|
# try and parse the XML and get the WSManFault
|
|
@@ -787,6 +800,7 @@ class _TransportHTTP(object):
|
|
|
787
800
|
|
|
788
801
|
self.certificate_key_pem: typing.Optional[str] = None
|
|
789
802
|
self.certificate_pem: typing.Optional[str] = None
|
|
803
|
+
self.certificate_key_password: typing.Optional[str] = None
|
|
790
804
|
for kwarg_list in AUTH_KWARGS.values():
|
|
791
805
|
for kwarg in kwarg_list:
|
|
792
806
|
setattr(self, kwarg, kwargs.get(kwarg, None))
|
|
@@ -810,6 +824,7 @@ class _TransportHTTP(object):
|
|
|
810
824
|
message: bytes,
|
|
811
825
|
*,
|
|
812
826
|
retries_on_read_timeout: int = 0,
|
|
827
|
+
timeout: int | float | tuple[int | float, int | float] | None = None,
|
|
813
828
|
) -> bytes:
|
|
814
829
|
hostname = get_hostname(self.endpoint)
|
|
815
830
|
if self.session is None:
|
|
@@ -824,9 +839,9 @@ class _TransportHTTP(object):
|
|
|
824
839
|
prep_request = self.session.prepare_request(request)
|
|
825
840
|
|
|
826
841
|
try:
|
|
827
|
-
self._send_request(prep_request)
|
|
842
|
+
self._send_request(prep_request, timeout=timeout)
|
|
828
843
|
except (requests.ReadTimeout, requests.ConnectTimeout) as e:
|
|
829
|
-
log.exception("%s during initial authentication request - attempt %d",
|
|
844
|
+
log.exception("%s during initial authentication request - attempt %d", type(e).__name__, attempt)
|
|
830
845
|
if attempt == retries_on_read_timeout:
|
|
831
846
|
raise
|
|
832
847
|
|
|
@@ -867,9 +882,12 @@ class _TransportHTTP(object):
|
|
|
867
882
|
request = requests.Request("POST", self.endpoint, data=payload, headers=headers)
|
|
868
883
|
prep_request = self.session.prepare_request(request)
|
|
869
884
|
try:
|
|
870
|
-
return self._send_request(
|
|
885
|
+
return self._send_request(
|
|
886
|
+
prep_request,
|
|
887
|
+
timeout=timeout,
|
|
888
|
+
)
|
|
871
889
|
except (requests.ReadTimeout, requests.ConnectTimeout) as e:
|
|
872
|
-
log.exception("%s during WSMan request - attempt %d",
|
|
890
|
+
log.exception("%s during WSMan request - attempt %d", type(e).__name__, attempt)
|
|
873
891
|
if attempt == retries_on_read_timeout:
|
|
874
892
|
raise
|
|
875
893
|
|
|
@@ -880,8 +898,15 @@ class _TransportHTTP(object):
|
|
|
880
898
|
attempt += 1
|
|
881
899
|
time.sleep(self.reconnection_backoff * (2**attempt - 1))
|
|
882
900
|
|
|
883
|
-
def _send_request(
|
|
884
|
-
|
|
901
|
+
def _send_request(
|
|
902
|
+
self,
|
|
903
|
+
request: requests.PreparedRequest,
|
|
904
|
+
timeout: int | float | tuple[int | float, int | float] | None = None,
|
|
905
|
+
) -> bytes:
|
|
906
|
+
response = self.session.send( # type: ignore[union-attr] # This should not happen
|
|
907
|
+
request,
|
|
908
|
+
timeout=timeout if timeout is not None else (self.connection_timeout, self.read_timeout),
|
|
909
|
+
)
|
|
885
910
|
|
|
886
911
|
content_type = response.headers.get("content-type", "")
|
|
887
912
|
if content_type.startswith("multipart/encrypted;") or content_type.startswith("multipart/x-multi-encrypted;"):
|
|
@@ -959,8 +984,14 @@ class _TransportHTTP(object):
|
|
|
959
984
|
del retry_kwargs["status"]
|
|
960
985
|
retries = Retry(**retry_kwargs)
|
|
961
986
|
|
|
962
|
-
session.mount(
|
|
963
|
-
|
|
987
|
+
session.mount(
|
|
988
|
+
"http://",
|
|
989
|
+
_pool_manager.create_request_adapter(max_retries=retries),
|
|
990
|
+
)
|
|
991
|
+
session.mount(
|
|
992
|
+
"https://",
|
|
993
|
+
_pool_manager.create_request_adapter(max_retries=retries, key_password=self.certificate_key_password),
|
|
994
|
+
)
|
|
964
995
|
|
|
965
996
|
# set cert validation config
|
|
966
997
|
session.verify = self.cert_validation
|
|
@@ -998,6 +1029,9 @@ class _TransportHTTP(object):
|
|
|
998
1029
|
if self.ssl is False:
|
|
999
1030
|
raise ValueError("For certificate auth, SSL must be used")
|
|
1000
1031
|
|
|
1032
|
+
# requests does not expose the password through the cert tuple. If set
|
|
1033
|
+
# it'll be passed to urllib3 through the custom adapter created for the
|
|
1034
|
+
# session.
|
|
1001
1035
|
session.cert = (self.certificate_pem, self.certificate_key_pem)
|
|
1002
1036
|
session.headers["Authorization"] = "http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/https/mutual"
|
|
1003
1037
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypsrp
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.0rc2
|
|
4
4
|
Summary: PowerShell Remoting Protocol and WinRM for Python
|
|
5
5
|
Author-email: Jordan Borean <jborean93@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,7 +18,7 @@ Description-Content-Type: text/markdown
|
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: cryptography>=3.1
|
|
20
20
|
Requires-Dist: pyspnego<1.0.0,>=0.7.0
|
|
21
|
-
Requires-Dist: requests>=2.
|
|
21
|
+
Requires-Dist: requests>=2.27.0
|
|
22
22
|
Provides-Extra: credssp
|
|
23
23
|
Requires-Dist: requests-credssp>=2.0.0; extra == "credssp"
|
|
24
24
|
Provides-Extra: kerberos
|
|
@@ -224,7 +224,8 @@ These are the options that can be used to setup `WSMan`;
|
|
|
224
224
|
* `data_locale`: The `wsmv:DataLocale` value to set on each WSMan request. This specifies the format in which numerical data is presented in the response text, default is the value of `locale`
|
|
225
225
|
* `reconnection_retries`: Number of retries on a connection problem, default is `0`
|
|
226
226
|
* `reconnection_backoff`: Number of seconds to backoff in between reconnection attempts (first sleeps X, then sleeps 2*X, 4*X, 8*X, ...), default is `2.0`
|
|
227
|
-
* `certificate_key_pem`: The path to the certificate key used in `certificate` authentication
|
|
227
|
+
* `certificate_key_pem`: The path to the certificate key used in `certificate` authentication. The key can be in either a `PKCS#1` or `PKCS#8` format
|
|
228
|
+
* `certificate_key_password`: The password for `certificate_key_pem` if it is encrypted
|
|
228
229
|
* `certificate_pem`: The path to the certificate used in `certificate` authentication
|
|
229
230
|
* `credssp_auth_mechanism`: The sub-auth mechanism used in CredSSP, default is `auto`, choices are `auto`, `ntlm`, or `kerberos`
|
|
230
231
|
* `credssp_disable_tlsv1_2`: Whether to used CredSSP auth over the insecure TLSv1.0, default is `False`
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
pypsrp/__init__.py,sha256=9aButMuBNIEph0LfHsodNd8uSS7XKZj9IPHJsPd8hjY,958
|
|
2
|
+
pypsrp/_pool_manager.py,sha256=yKUzdzkE4hEo2mqIC-kKPL9uWpIk9mixCQ1AP5rgn_o,4222
|
|
2
3
|
pypsrp/_utils.py,sha256=y9sezXQPl0vXnr1beyZ8xcn-Q_zdg_7cg_lPk9QmjVQ,3540
|
|
3
4
|
pypsrp/client.py,sha256=iJERbEXwwE4wswpqAL0Dcz8ACqLvXUT9Dcl_Qqa3830,13892
|
|
4
5
|
pypsrp/complex_objects.py,sha256=PkJ1F1Byw5RI_Vpfy1KfcMFJdOfioOSsIbi50S_fZ3g,62666
|
|
@@ -6,17 +7,17 @@ pypsrp/encryption.py,sha256=4wOontH-eF8BBxTyHTX9iAF0uIM6m-z3N9poatspP1A,3870
|
|
|
6
7
|
pypsrp/exceptions.py,sha256=SWdQ9UAPOIwr7B4twGqDoXLCEtXFK_5u3PYIVLvEIbA,4034
|
|
7
8
|
pypsrp/host.py,sha256=XQgnkwzsZprNmAr1TPJpEBmj3xRSWFrYQFSIi870aNE,45065
|
|
8
9
|
pypsrp/messages.py,sha256=mCuie1ywOQmkXltISPvrb6ITS6GUA0oSld2jfNLdBf4,34169
|
|
9
|
-
pypsrp/negotiate.py,sha256=
|
|
10
|
-
pypsrp/powershell.py,sha256=
|
|
10
|
+
pypsrp/negotiate.py,sha256=iVX2J-slABamesMajjTfmfqucDS4n5vVTTde_IDqpuE,10951
|
|
11
|
+
pypsrp/powershell.py,sha256=NRDoFtOsYjIrJHMQ6r6vRq6uL2EtPhhem5ZA_bWoDnw,69296
|
|
11
12
|
pypsrp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
13
|
pypsrp/serializer.py,sha256=49rBxfo3fh511mU3uzoK9vFvIOrBtyX3J-CUugbezc0,33767
|
|
13
|
-
pypsrp/shell.py,sha256=
|
|
14
|
-
pypsrp/wsman.py,sha256=
|
|
14
|
+
pypsrp/shell.py,sha256=80mh35c-UXjsy_MFG9J3GGYLYqvk2FppUgrFHkWh6xg,18065
|
|
15
|
+
pypsrp/wsman.py,sha256=KkRg9UDDq1yxY4TKK9mGI-vS6px6ftQDPhmeojjItKg,48728
|
|
15
16
|
pypsrp/pwsh_scripts/__init__.py,sha256=VckRBWWDUdh_LTwPspE2FXSo8TaT50QnZ1aZ-yvxJmU,139
|
|
16
17
|
pypsrp/pwsh_scripts/copy.ps1,sha256=sTy4jcnh0cV5Y0l2-dd1znc-eM_kgE-okk51xZzdIQk,5374
|
|
17
18
|
pypsrp/pwsh_scripts/fetch.ps1,sha256=7gvPxURnksZetYgjWRjbIDIWSJYv2xGXZjRymvNUj6w,1989
|
|
18
|
-
pypsrp-0.9.
|
|
19
|
-
pypsrp-0.9.
|
|
20
|
-
pypsrp-0.9.
|
|
21
|
-
pypsrp-0.9.
|
|
22
|
-
pypsrp-0.9.
|
|
19
|
+
pypsrp-0.9.0rc2.dist-info/licenses/LICENSE,sha256=8uJLgs8Wb_Us_8XaTvOOn7Yf0dwzFi9pcFHVxfgJKoE,1079
|
|
20
|
+
pypsrp-0.9.0rc2.dist-info/METADATA,sha256=_zCveXjKqq1_OiCmjLtge2Zr3v1G-S2EFrF33PTd6QA,23630
|
|
21
|
+
pypsrp-0.9.0rc2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
22
|
+
pypsrp-0.9.0rc2.dist-info/top_level.txt,sha256=9sFQD0PMIG07xbaR4j-1Fq7NaD9kwInn_HTc3Gqznq0,7
|
|
23
|
+
pypsrp-0.9.0rc2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|