pypsrp 0.9.0rc1__tar.gz → 0.9.0rc2__tar.gz
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-0.9.0rc1 → pypsrp-0.9.0rc2}/CHANGELOG.md +6 -0
- {pypsrp-0.9.0rc1/src/pypsrp.egg-info → pypsrp-0.9.0rc2}/PKG-INFO +4 -3
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/README.md +2 -1
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/lib.sh +0 -6
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/win-setup.ps1 +185 -134
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/pyproject.toml +2 -2
- pypsrp-0.9.0rc2/src/pypsrp/_pool_manager.py +142 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/negotiate.py +2 -2
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/powershell.py +50 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/shell.py +38 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/wsman.py +45 -11
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2/src/pypsrp.egg-info}/PKG-INFO +4 -3
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp.egg-info/SOURCES.txt +5 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp.egg-info/requires.txt +1 -1
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/conftest.py +11 -2
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/_template.yml +1 -0
- pypsrp-0.9.0rc2/tests/tests_pypsrp/responses/test_psrp_is_alive_http_error.yml +12 -0
- pypsrp-0.9.0rc2/tests/tests_pypsrp/responses/test_psrp_is_alive_invalid_selectors.yml +14 -0
- pypsrp-0.9.0rc2/tests/tests_pypsrp/responses/test_psrp_is_alive_other_wsman_error.yml +14 -0
- pypsrp-0.9.0rc2/tests/tests_pypsrp/responses/test_psrp_is_alive_state_disconnected.yml +11 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_integration.py +18 -8
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_powershell.py +67 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_wsman.py +7 -7
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/LICENSE +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/MANIFEST.in +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/JEARole.psrc +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/JEARoleSettings.pssc +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/check-winrm.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/run-ci.sh +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/setup.cfg +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/__init__.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/_utils.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/client.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/complex_objects.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/encryption.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/exceptions.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/host.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/messages.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/pwsh_scripts/__init__.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/pwsh_scripts/copy.ps1 +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/pwsh_scripts/fetch.ps1 +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/py.typed +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/serializer.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp.egg-info/dependency_links.txt +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp.egg-info/top_level.txt +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/__init__.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/data/test_sanitise_clixml_with_error.xml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/data/test_sanitise_clixml_with_no_errors.xml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_expand_vars.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_file.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_file_empty.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_file_failure.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_file_really_large.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_file_warning.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_execute_cmd.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_execute_cmd_environment.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_execute_ps.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_execute_ps_environment.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_execute_ps_failure.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_fetch_file.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_fetch_file_expand_vars.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_fetch_file_fail_dir.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_fetch_file_fail_missing.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_fetch_file_hash_mismatch.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_application_args.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_clear_commands.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_disconnect_runspaces.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_disconnected_commands.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_error_failed.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_get_command_metadata.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_key_exchange_timeout.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_long_running_cmdlet.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_merge_commands.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_multiple_commands.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_multiple_invocations.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_nested_command.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_no_profile.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_open_runspace.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_pshost_methods.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_pshost_raw_ui_mocked_methods.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_pshost_ui_mocked_methods.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_receive_failure.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_reset_runspace_state.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_reset_runspace_state_fail.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_run_protocol_version_2.1.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_run_protocol_version_2.2.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_run_protocol_version_2.3.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_set_runspaces.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_small_msg_size.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_stream_no_output_invocation.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_stream_output_invocation.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_with_history.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_with_input.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_with_jea_configuration.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_with_no_history.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_bad_cmd_id.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_environment.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_extra_opts.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_fail_poll_process.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_no_cmd_shell.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_noprofile.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_open_already_opened.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_operation_timeout.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_send.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_standard.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_stderr_rc.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_unicode.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_wsman_update_envelope_size_150.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_wsman_update_envelope_size_4096.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_wsman_update_envelope_size_500.yml +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_client.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_complex_objects.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_encryption.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_exceptions.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_host.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_messages.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_negotiate.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_serializer.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_shell.py +0 -0
- {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_utils.py +0 -0
|
@@ -5,10 +5,16 @@
|
|
|
5
5
|
* Raised minimum Python version to 3.10
|
|
6
6
|
* Added retry handler for WSMan `Receive` operations that can attempt to recover from a network disconnect during a command operation
|
|
7
7
|
* Added `no_profile` option to `RunspacePool` to skip loading the user profile on the remote shell
|
|
8
|
+
* Added `idle_timeout` option to `RunspacePool` to control the idle timeout, in seconds, of the remote Runspace Pool
|
|
8
9
|
* Made an `__enter__` and `__exit__` method for `PowerShell` to allow it to be used with a `with PowerShell(runspace) as ps:` syntax
|
|
9
10
|
* On `__exit__` the pipeline will be closed using the `TERMINATE` signal to clean up any resources on the server end
|
|
10
11
|
* `ps.close()` can also be called manually to clean up any resources and to prep the pipeline to be run again
|
|
11
12
|
* Added `clear_streams()` onto a `PowerShell` object to clear the output and `streams` values so it is ready for a subsequent run
|
|
13
|
+
* Changed default service name used for Kerberos authentication from `WSMAN` to `host` to match the behaviour on Windows
|
|
14
|
+
* `WSMAN` was used for CredSSP authentication only on the native Windows client while `Negotiate/Kerberos` auth used `host`
|
|
15
|
+
* This should fix errors when authenticating with domain controllers which sometimes fail to register the `WSMAN` service for the host
|
|
16
|
+
* Added `is_alive(timeout=...)` on `RunspacePool` to check if the Runspace Pool is still alive and ready for use on the server
|
|
17
|
+
* Added `certificate_key_password` option to `WSMan` to support loaded client certificate keys that have been encrypted
|
|
12
18
|
|
|
13
19
|
## 0.8.1 - 2022-02-22
|
|
14
20
|
|
|
@@ -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`
|
|
@@ -182,7 +182,8 @@ These are the options that can be used to setup `WSMan`;
|
|
|
182
182
|
* `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`
|
|
183
183
|
* `reconnection_retries`: Number of retries on a connection problem, default is `0`
|
|
184
184
|
* `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`
|
|
185
|
-
* `certificate_key_pem`: The path to the certificate key used in `certificate` authentication
|
|
185
|
+
* `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
|
|
186
|
+
* `certificate_key_password`: The password for `certificate_key_pem` if it is encrypted
|
|
186
187
|
* `certificate_pem`: The path to the certificate used in `certificate` authentication
|
|
187
188
|
* `credssp_auth_mechanism`: The sub-auth mechanism used in CredSSP, default is `auto`, choices are `auto`, `ntlm`, or `kerberos`
|
|
188
189
|
* `credssp_disable_tlsv1_2`: Whether to used CredSSP auth over the insecure TLSv1.0, default is `False`
|
|
@@ -28,12 +28,6 @@ lib::setup::windows_requirements() {
|
|
|
28
28
|
-Password "${PYPSRP_PASSWORD}" \
|
|
29
29
|
-CertPath "${PYPSRP_CERT_DIR}" \
|
|
30
30
|
-InformationAction Continue
|
|
31
|
-
|
|
32
|
-
# FIXME: For some reason cert auth is failing with. Need to figure out what's happening here and unset this
|
|
33
|
-
# pypsrp.exceptions.WSManFaultError: Received a WSManFault message. (Code: 2150859262, Machine: localhost,
|
|
34
|
-
# Reason: The WS-Management service cannot process the operation. An attempt to query mapped credential failed.
|
|
35
|
-
# This will happen if the security context associated with WinRM service has changed since the credential was originally mapped
|
|
36
|
-
unset PYPSRP_CERT_DIR
|
|
37
31
|
}
|
|
38
32
|
|
|
39
33
|
lib::setup::system_requirements() {
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
using namespace System.IO
|
|
2
|
+
using namespace System.Management.Automation
|
|
3
|
+
using namespace System.Runtime.InteropServices
|
|
4
|
+
using namespace System.Security.Cryptography
|
|
5
|
+
using namespace System.Security.Cryptography.X509Certificates
|
|
6
|
+
|
|
1
7
|
[CmdletBinding()]
|
|
2
8
|
param(
|
|
3
9
|
[Parameter(Mandatory)]
|
|
@@ -17,83 +23,6 @@ $ErrorActionPreference = "Stop"
|
|
|
17
23
|
|
|
18
24
|
Write-Information -MessageData "Configuring WinRM for pypsrp tests for $UserName"
|
|
19
25
|
|
|
20
|
-
Function New-LegacySelfSignedCert {
|
|
21
|
-
[CmdletBinding()]
|
|
22
|
-
param (
|
|
23
|
-
[Parameter(Mandatory)]
|
|
24
|
-
[String]
|
|
25
|
-
$Subject,
|
|
26
|
-
|
|
27
|
-
[Parameter(Mandatory)]
|
|
28
|
-
[Int32]
|
|
29
|
-
$ValidDays
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
Write-Information -MessageData "Creating self-signed certificate of CN=$Subject for $ValidDays days"
|
|
33
|
-
$subject_name = New-Object -ComObject X509Enrollment.CX500DistinguishedName
|
|
34
|
-
$subject_name.Encode("CN=$Subject", 0)
|
|
35
|
-
|
|
36
|
-
$private_key = New-Object -ComObject X509Enrollment.CX509PrivateKey
|
|
37
|
-
$private_key.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
|
|
38
|
-
$private_key.KeySpec = 1
|
|
39
|
-
$private_key.Length = 4096
|
|
40
|
-
$private_key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
|
|
41
|
-
$private_key.MachineContext = 1
|
|
42
|
-
$private_key.Create()
|
|
43
|
-
|
|
44
|
-
$server_auth_oid = New-Object -ComObject X509Enrollment.CObjectId
|
|
45
|
-
$server_auth_oid.InitializeFromValue("1.3.6.1.5.5.7.3.1")
|
|
46
|
-
|
|
47
|
-
$ekuoids = New-Object -ComObject X509Enrollment.CObjectIds
|
|
48
|
-
$ekuoids.Add($server_auth_oid)
|
|
49
|
-
|
|
50
|
-
$eku_extension = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage
|
|
51
|
-
$eku_extension.InitializeEncode($ekuoids)
|
|
52
|
-
|
|
53
|
-
$name = @($env:COMPUTERNAME, ([System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname))
|
|
54
|
-
$alt_names = New-Object -ComObject X509Enrollment.CAlternativeNames
|
|
55
|
-
foreach ($name in $name) {
|
|
56
|
-
$alt_name = New-Object -ComObject X509Enrollment.CAlternativeName
|
|
57
|
-
$alt_name.InitializeFromString(0x3, $name)
|
|
58
|
-
$alt_names.Add($alt_name)
|
|
59
|
-
}
|
|
60
|
-
$alt_names_extension = New-Object -ComObject X509Enrollment.CX509ExtensionAlternativeNames
|
|
61
|
-
$alt_names_extension.InitializeEncode($alt_names)
|
|
62
|
-
|
|
63
|
-
$digital_signature = [Security.Cryptography.X509Certificates.X509KeyUsageFlags]::DigitalSignature
|
|
64
|
-
$key_encipherment = [Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyEncipherment
|
|
65
|
-
$key_usage = [int]($digital_signature -bor $key_encipherment)
|
|
66
|
-
$key_usage_extension = New-Object -ComObject X509Enrollment.CX509ExtensionKeyUsage
|
|
67
|
-
$key_usage_extension.InitializeEncode($key_usage)
|
|
68
|
-
$key_usage_extension.Critical = $true
|
|
69
|
-
|
|
70
|
-
$signature_oid = New-Object -ComObject X509Enrollment.CObjectId
|
|
71
|
-
$sha256_oid = New-Object -TypeName Security.Cryptography.Oid -ArgumentList "SHA256"
|
|
72
|
-
$signature_oid.InitializeFromValue($sha256_oid.Value)
|
|
73
|
-
|
|
74
|
-
$certificate = New-Object -ComObject X509Enrollment.CX509CertificateRequestCertificate
|
|
75
|
-
$certificate.InitializeFromPrivateKey(2, $private_key, "")
|
|
76
|
-
$certificate.Subject = $subject_name
|
|
77
|
-
$certificate.Issuer = $certificate.Subject
|
|
78
|
-
$certificate.NotBefore = (Get-Date).AddDays(-1)
|
|
79
|
-
$certificate.NotAfter = $certificate.NotBefore.AddDays($ValidDays)
|
|
80
|
-
$certificate.X509Extensions.Add($key_usage_extension)
|
|
81
|
-
$certificate.X509Extensions.Add($alt_names_extension)
|
|
82
|
-
$certificate.X509Extensions.Add($eku_extension)
|
|
83
|
-
$certificate.SignatureInformation.HashAlgorithm = $signature_oid
|
|
84
|
-
$certificate.Encode()
|
|
85
|
-
|
|
86
|
-
$enrollment = New-Object -ComObject X509Enrollment.CX509Enrollment
|
|
87
|
-
$enrollment.InitializeFromRequest($certificate)
|
|
88
|
-
$certificate_data = $enrollment.CreateRequest(0)
|
|
89
|
-
$enrollment.InstallResponse(2, $certificate_data, 0, "")
|
|
90
|
-
|
|
91
|
-
$parsed_certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
|
|
92
|
-
$parsed_certificate.Import([System.Text.Encoding]::UTF8.GetBytes($certificate_data))
|
|
93
|
-
|
|
94
|
-
return $parsed_certificate
|
|
95
|
-
}
|
|
96
|
-
|
|
97
26
|
function New-WinRMFirewallRule {
|
|
98
27
|
[CmdletBinding()]
|
|
99
28
|
param (
|
|
@@ -148,7 +77,7 @@ function New-WinRMFirewallRule {
|
|
|
148
77
|
try {
|
|
149
78
|
$fw.Rules.Add($rule)
|
|
150
79
|
}
|
|
151
|
-
catch [
|
|
80
|
+
catch [COMException] {
|
|
152
81
|
# E_UNEXPECTED 0x80000FFFF means the rule already exists
|
|
153
82
|
if ($_.Exception.ErrorCode -eq 0x8000FFFF) {
|
|
154
83
|
Write-Information -MessageData "WinRM $Protocol firewall rule already exists, deleting before recreating"
|
|
@@ -166,7 +95,15 @@ function New-WinRMFirewallRule {
|
|
|
166
95
|
|
|
167
96
|
function Reset-WinRMConfig {
|
|
168
97
|
[CmdletBinding()]
|
|
169
|
-
Param(
|
|
98
|
+
Param(
|
|
99
|
+
[Parameter(Mandatory)]
|
|
100
|
+
[X509Certificate2]
|
|
101
|
+
$CACertificate,
|
|
102
|
+
|
|
103
|
+
[Parameter(Mandatory)]
|
|
104
|
+
[String]
|
|
105
|
+
$CtlStore
|
|
106
|
+
)
|
|
170
107
|
|
|
171
108
|
Write-Verbose "Removing all existing WinRM listeners"
|
|
172
109
|
Get-ChildItem -LiteralPath WSMan:\localhost\Listener | Remove-Item -Force -Recurse
|
|
@@ -181,7 +118,15 @@ function Reset-WinRMConfig {
|
|
|
181
118
|
}
|
|
182
119
|
New-WSManInstance -ResourceURI winrm/config/listener -SelectorSet $selectorSet -ValueSet $valueSet > $null
|
|
183
120
|
|
|
184
|
-
$
|
|
121
|
+
$certParams = @{
|
|
122
|
+
CertStoreLocation = 'Cert:\LocalMachine\My'
|
|
123
|
+
DnsName = $env:COMPUTERNAME, 'localhost'
|
|
124
|
+
NotAfter = (Get-Date).AddYears(1)
|
|
125
|
+
Provider = 'Microsoft Software Key Storage Provider'
|
|
126
|
+
Signer = $CACertificate
|
|
127
|
+
Subject = "CN=$env:COMPUTERNAME"
|
|
128
|
+
}
|
|
129
|
+
$certificate = New-SelfSignedCertificate @certParams
|
|
185
130
|
$selectorSet = @{
|
|
186
131
|
Transport = "HTTPS"
|
|
187
132
|
Address = "*"
|
|
@@ -223,37 +168,64 @@ function Reset-WinRMConfig {
|
|
|
223
168
|
PropertyType = "DWord"
|
|
224
169
|
Force = $true
|
|
225
170
|
}
|
|
226
|
-
New-ItemProperty @regInfo
|
|
171
|
+
New-ItemProperty @regInfo > $null
|
|
172
|
+
|
|
173
|
+
Write-Information -MessageData "Configuring WinRM HTTPS binding to use CTL trust store '$CtlStore'"
|
|
174
|
+
$existingBinding = netsh.exe http show sslcert ipport=0.0.0.0:5986 json=enable
|
|
175
|
+
if ($LASTEXITCODE) {
|
|
176
|
+
throw "Failed to get existing WinRM HTTPS binding:`n$existingBinding"
|
|
177
|
+
}
|
|
178
|
+
$binding = $existingBinding | ConvertFrom-Json | Select-Object -ExpandProperty SslCertificateBindings
|
|
179
|
+
|
|
180
|
+
$out = netsh.exe @(
|
|
181
|
+
"http"
|
|
182
|
+
"update"
|
|
183
|
+
"sslcert"
|
|
184
|
+
"ipport=0.0.0.0:5986"
|
|
185
|
+
"appid=$($binding.GuidString)"
|
|
186
|
+
"certhash=$($certificate.Thumbprint)"
|
|
187
|
+
"sslctlstorename=$CtlStore"
|
|
188
|
+
) 2>&1
|
|
189
|
+
if ($LASTEXITCODE) {
|
|
190
|
+
throw "Failed to set sslctlstorename for WinRM binding:`n$out"
|
|
191
|
+
}
|
|
227
192
|
|
|
228
193
|
Write-Information -MessageData "WinRM and PS Remoting have been set up successfully"
|
|
229
194
|
}
|
|
230
195
|
|
|
231
196
|
Function New-CertificateAuthBinding {
|
|
197
|
+
[OutputType([string])]
|
|
232
198
|
[CmdletBinding()]
|
|
233
199
|
Param (
|
|
234
200
|
[String]
|
|
235
201
|
$Name,
|
|
236
202
|
|
|
237
203
|
[String]
|
|
238
|
-
$CertPath
|
|
204
|
+
$CertPath,
|
|
205
|
+
|
|
206
|
+
[X509Certificate2]
|
|
207
|
+
$CACertificate,
|
|
208
|
+
|
|
209
|
+
[PSCredential]
|
|
210
|
+
$Credential
|
|
239
211
|
)
|
|
240
212
|
|
|
241
213
|
Write-Information -MessageData "Generating self signed certificate for authentication of user $Name"
|
|
242
214
|
$certInfo = @{
|
|
215
|
+
CertStoreLocation = "Cert:\CurrentUser\My"
|
|
216
|
+
Provider = 'Microsoft Software Key Storage Provider'
|
|
217
|
+
Signer = $CACertificate
|
|
243
218
|
Subject = "CN=$Name"
|
|
244
|
-
KeyUsage = "DigitalSignature", "KeyEncipherment"
|
|
245
|
-
KeyAlgorithm = "RSA"
|
|
246
|
-
KeyLength = 2048
|
|
247
219
|
TextExtension = @("2.5.29.37={text}1.3.6.1.5.5.7.3.2", "2.5.29.17={text}upn=$Name@localhost")
|
|
248
220
|
Type = "Custom"
|
|
249
|
-
CertStoreLocation = "Cert:\CurrentUser\My"
|
|
250
221
|
}
|
|
251
222
|
$cert = New-SelfSignedCertificate @certInfo
|
|
252
223
|
|
|
253
224
|
Write-Information -MessageData "Exporting private key in a PFX file"
|
|
254
|
-
[
|
|
225
|
+
[File]::WriteAllBytes("$CertPath\cert.pfx", $cert.Export("Pfx"))
|
|
255
226
|
|
|
256
227
|
Write-Information -MessageData "Converting private key to PEM format with openssl"
|
|
228
|
+
$certPassword = $Credential.GetNetworkCredential().Password
|
|
257
229
|
$out = openssl.exe @(
|
|
258
230
|
"pkcs12",
|
|
259
231
|
"-in", "$CertPath\cert.pfx",
|
|
@@ -266,35 +238,46 @@ Function New-CertificateAuthBinding {
|
|
|
266
238
|
if ($LASTEXITCODE) {
|
|
267
239
|
throw "Failed to extract key from PEM:`n$out"
|
|
268
240
|
}
|
|
241
|
+
$out = openssl.exe @(
|
|
242
|
+
"pkcs12",
|
|
243
|
+
"-in", "$CertPath\cert.pfx",
|
|
244
|
+
"-nocerts",
|
|
245
|
+
"-out", "$CertPath\cert_enc_key.pem",
|
|
246
|
+
"-passin", "pass:",
|
|
247
|
+
"-passout", "pass:$certPassword"
|
|
248
|
+
) 2>&1
|
|
249
|
+
if ($LASTEXITCODE) {
|
|
250
|
+
throw "Failed to extract encrypted key from PEM:`n$out"
|
|
251
|
+
}
|
|
269
252
|
Remove-Item -Path "$CertPath\cert.pfx" -Force
|
|
270
253
|
|
|
271
|
-
# WinRM seems to be very picky about the type of cert in the trusted
|
|
254
|
+
# WinRM seems to be very picky about the type of cert in the trusted people store, make sure this is set
|
|
272
255
|
# to the cert and not cert + key.
|
|
273
|
-
$
|
|
256
|
+
$certNoKey = [X509Certificate2]::new($cert.RawData)
|
|
274
257
|
|
|
275
|
-
Write-Information -MessageData "Exporting
|
|
276
|
-
|
|
277
|
-
Remove-Item -Path "$CertPath\cert_key.pem" -Force
|
|
278
|
-
[System.IO.File]::WriteAllLines("$CertPath\cert.pem", @(
|
|
279
|
-
$key_pem
|
|
258
|
+
Write-Information -MessageData "Exporting user certificate PEM"
|
|
259
|
+
[File]::WriteAllLines("$CertPath\cert.pem", @(
|
|
280
260
|
"-----BEGIN CERTIFICATE-----"
|
|
281
|
-
[
|
|
261
|
+
[Convert]::ToBase64String($certNoKey.RawData) -replace ".{64}", "$&`n"
|
|
282
262
|
"-----END CERTIFICATE-----"
|
|
283
263
|
))
|
|
284
264
|
|
|
285
|
-
Write-Information -MessageData "Importing cert into LocalMachine\Root"
|
|
286
|
-
$store = Get-Item -Path Cert:\LocalMachine\Root
|
|
287
|
-
$store.Open("MaxAllowed")
|
|
288
|
-
$store.Add($cert)
|
|
289
|
-
$store.Close()
|
|
290
|
-
|
|
291
265
|
Write-Information -MessageData "Importing cert into LocalMachine\TrustedPeople"
|
|
292
266
|
$store = Get-Item -Path Cert:\LocalMachine\TrustedPeople
|
|
293
|
-
$store.Open(
|
|
294
|
-
$store.Add($
|
|
295
|
-
$store.
|
|
267
|
+
$store.Open([OpenFlags]::ReadWrite)
|
|
268
|
+
$store.Add($certNoKey)
|
|
269
|
+
$store.Dispose()
|
|
270
|
+
|
|
271
|
+
$credBinding = @{
|
|
272
|
+
Credential = $Credential
|
|
273
|
+
Force = $true
|
|
274
|
+
Issuer = $CACertificate.Thumbprint
|
|
275
|
+
Path = "WSMan:\localhost\ClientCertificate"
|
|
276
|
+
Subject = "$Name@localhost"
|
|
277
|
+
}
|
|
278
|
+
New-Item @credBinding > $null
|
|
296
279
|
|
|
297
|
-
$cert
|
|
280
|
+
$cert.Thumbprint
|
|
298
281
|
}
|
|
299
282
|
|
|
300
283
|
Function New-JEAConfiguration {
|
|
@@ -310,12 +293,12 @@ Function New-JEAConfiguration {
|
|
|
310
293
|
$modulePath = Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell\Modules\$Name"
|
|
311
294
|
Write-Information -MessageData "Setting up JEA PowerShell module path at '$modulePath'"
|
|
312
295
|
if (-not (Test-Path -Path $modulePath)) {
|
|
313
|
-
New-Item -Path $modulePath -ItemType Directory
|
|
296
|
+
New-Item -Path $modulePath -ItemType Directory | Out-Null
|
|
314
297
|
}
|
|
315
298
|
|
|
316
299
|
$functionsPath = Join-Path -Path $modulePath -ChildPath "$($Name)Functions.psm1"
|
|
317
300
|
if (-not (Test-Path -Path $functionsPath)) {
|
|
318
|
-
New-Item -Path $functionsPath -ItemType File
|
|
301
|
+
New-Item -Path $functionsPath -ItemType File | Out-Null
|
|
319
302
|
}
|
|
320
303
|
|
|
321
304
|
$manifestPath = Join-Path -Path $modulePath -ChildPath "$($Name).psd1"
|
|
@@ -325,7 +308,7 @@ Function New-JEAConfiguration {
|
|
|
325
308
|
|
|
326
309
|
$rolePath = Join-Path -Path $modulePath -ChildPath "RoleCapabilities"
|
|
327
310
|
if (-not (Test-Path -Path $rolePath)) {
|
|
328
|
-
New-Item -Path $rolePath -ItemType Directory
|
|
311
|
+
New-Item -Path $rolePath -ItemType Directory | Out-Null
|
|
329
312
|
}
|
|
330
313
|
|
|
331
314
|
$jeaRoleSrc = Join-Path -Path $JEAConfigPath -ChildPath "$($Name).psrc"
|
|
@@ -339,43 +322,111 @@ Function New-JEAConfiguration {
|
|
|
339
322
|
}
|
|
340
323
|
}
|
|
341
324
|
|
|
342
|
-
$
|
|
343
|
-
|
|
344
|
-
$
|
|
325
|
+
$caParams = @{
|
|
326
|
+
Extension = @(
|
|
327
|
+
[X509BasicConstraintsExtension]::new($true, $false, 0, $true)
|
|
328
|
+
[X509KeyUsageExtension]::new('KeyCertSign', $true)
|
|
329
|
+
)
|
|
330
|
+
CertStoreLocation = 'Cert:\CurrentUser\My'
|
|
331
|
+
NotAfter = (Get-Date).AddYears(1)
|
|
332
|
+
Provider = 'Microsoft Software Key Storage Provider'
|
|
333
|
+
Subject = 'CN=PyPSRP CA'
|
|
334
|
+
Type = 'Custom'
|
|
335
|
+
}
|
|
336
|
+
Write-Information -MessageData "Creating CA certificate"
|
|
337
|
+
$ca = New-SelfSignedCertificate @caParams
|
|
338
|
+
|
|
339
|
+
$root = Get-Item -LiteralPath Cert:\LocalMachine\Root
|
|
340
|
+
$root.Open([OpenFlags]::ReadWrite)
|
|
341
|
+
$root.Add($ca)
|
|
342
|
+
$root.Dispose()
|
|
343
|
+
|
|
344
|
+
# Setup a specific store for the WinRM CTL as GHA's root store is too large
|
|
345
|
+
# and causes issues with WinRM cert selection during authentication
|
|
346
|
+
$ctlStoreName = 'WinRMTrustedIssuers'
|
|
347
|
+
$ctlStore = [X509Store]::new(
|
|
348
|
+
$ctlStoreName,
|
|
349
|
+
[StoreLocation]::LocalMachine)
|
|
350
|
+
$ctlStore.Open([OpenFlags]::ReadWrite)
|
|
351
|
+
$ctlStore.Add([X509Certificate2]::new($ca.RawData)) # Strip key affinity
|
|
352
|
+
$ctlStore.Dispose()
|
|
345
353
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
Reset-WinRMConfig
|
|
354
|
+
$secPassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
|
|
355
|
+
$userCredential = [PSCredential]::new("$env:COMPUTERNAME\$UserName", $secPassword)
|
|
349
356
|
|
|
350
357
|
$localUser = New-LocalUser -Name $UserName -Password $secPassword -AccountNeverExpires -PasswordNeverExpires
|
|
351
358
|
Add-LocalGroupMember -Group Administrators -Member $localUser
|
|
352
359
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
360
|
+
Enable-PSRemoting -Force
|
|
361
|
+
Start-Service -Name WinRM
|
|
362
|
+
Reset-WinRMConfig -CACertificate $ca -CtlStore $ctlStoreName
|
|
363
|
+
Write-Information -MessageData "Setting up JEA configuration"
|
|
364
|
+
New-JEAConfiguration -Name JEARole -JEAConfigPath $PSScriptRoot
|
|
365
|
+
Register-PSSessionConfiguration -Path "$PSScriptRoot\JEARoleSettings.pssc" -Name JEARole -Force > $null
|
|
366
|
+
Restart-Service -Name winrm
|
|
367
|
+
|
|
368
|
+
# It is important we setup the certificate auth binding after the JEA session is
|
|
369
|
+
# registered. JEA will change the WinRM service account from NetworkService to
|
|
370
|
+
# SYSTEM and cert auth bindings are encrypted based on the service account.
|
|
371
|
+
$clientCertParams = @{
|
|
372
|
+
Name = $UserName
|
|
373
|
+
CertPath = $CertPath
|
|
374
|
+
CACertificate = $ca
|
|
375
|
+
Credential = $userCredential
|
|
364
376
|
}
|
|
365
|
-
New-
|
|
377
|
+
$clientCertificate = New-CertificateAuthBinding @clientCertParams
|
|
366
378
|
|
|
367
|
-
|
|
368
|
-
|
|
379
|
+
# Only remove the CA/Key from CurrentUser\My after all other certs have been generated.
|
|
380
|
+
Remove-Item -LiteralPath "Cert:\CurrentUser\My\$($ca.Thumbprint)" -Force
|
|
369
381
|
|
|
382
|
+
# Do one last restart, I've found that sometimes the service gets into a funky
|
|
383
|
+
# state after all the changes above.
|
|
370
384
|
Restart-Service -Name winrm
|
|
371
385
|
|
|
372
|
-
Write-Information -MessageData "Testing WinRM connection"
|
|
386
|
+
Write-Information -MessageData "Testing WinRM connection over HTTP"
|
|
373
387
|
$invokeParams = @{
|
|
374
388
|
ComputerName = 'localhost'
|
|
375
|
-
ScriptBlock = {
|
|
376
|
-
|
|
389
|
+
ScriptBlock = { [Environment]::UserName }
|
|
390
|
+
}
|
|
391
|
+
$user = Invoke-Command @invokeParams -Credential $userCredential
|
|
392
|
+
if ($user -ne $UserName) {
|
|
393
|
+
throw "WinRM authentication did not return expected user. Expected: $UserName, Actual: $user"
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
# Seems like the HTTPS service can get into a bit of a funk based on our setup
|
|
397
|
+
# We retry a few times to get a successful connection rather than fail immediately.
|
|
398
|
+
Write-Information -MessageData "Testing WinRM connection over HTTPS"
|
|
399
|
+
$attempt = 0
|
|
400
|
+
while ($true) {
|
|
401
|
+
try {
|
|
402
|
+
$user = Invoke-Command @invokeParams -UseSSL -Credential $userCredential
|
|
403
|
+
break
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
if ($attempt -gt 4) {
|
|
407
|
+
throw
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
Write-Information -MessageData "WinRM over HTTPS connection failed - $_`nRetrying in 5 seconds..."
|
|
411
|
+
$attempt++
|
|
412
|
+
|
|
413
|
+
Start-Sleep -Seconds 5
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if ($user -ne $UserName) {
|
|
417
|
+
throw "WinRM authentication over HTTPS did not return expected user. Expected: $UserName, Actual: $user"
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
Write-Information -MessageData "Testing WinRM connection over HTTPS with certificate authentication"
|
|
421
|
+
$user = Invoke-Command @invokeParams -UseSSL -CertificateThumbprint $clientCertificate
|
|
422
|
+
if ($user -ne $UserName) {
|
|
423
|
+
throw "Certificate authentication did not return expected user. Expected: $UserName, Actual: $user"
|
|
377
424
|
}
|
|
378
|
-
Invoke-Command @invokeParams -Credential $userCredential
|
|
379
425
|
|
|
380
|
-
|
|
381
|
-
|
|
426
|
+
Write-Information -MessageData "Testing WinRM connection with JEA"
|
|
427
|
+
$value = Invoke-Command -ComputerName localhost -ScriptBlock {
|
|
428
|
+
Get-Item -Path WSMan:\localhost\Service\AllowUnencrypted
|
|
429
|
+
} -ConfigurationName JEARole -Credential $userCredential
|
|
430
|
+
if (-not $value.Value) {
|
|
431
|
+
throw "JEA WinRM session did not return expected AllowUnencrypted value of True. Actual: $($value.Value)"
|
|
432
|
+
}
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pypsrp"
|
|
9
|
-
version = "0.9.
|
|
9
|
+
version = "0.9.0rc2"
|
|
10
10
|
description = "PowerShell Remoting Protocol and WinRM for Python"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.10"
|
|
@@ -28,7 +28,7 @@ classifiers = [
|
|
|
28
28
|
dependencies = [
|
|
29
29
|
"cryptography >= 3.1", # default_backend made an optional kwarg
|
|
30
30
|
"pyspnego >= 0.7.0, < 1.0.0",
|
|
31
|
-
"requests >= 2.
|
|
31
|
+
"requests >= 2.27.0", # requests official support for 3.10+
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[project.urls]
|
|
@@ -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
|