Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Use win apis to run process #58

Merged
merged 10 commits into from
Feb 12, 2024
Merged

Conversation

matta-aws
Copy link
Contributor

@matta-aws matta-aws commented Jan 25, 2024

What was the problem/requirement? (What/Why)

The Windows RunAs (aka Impersonation) and Notify features were being managed via powershell scripts.
a) this involved passing a username and password via command line.
b) it was specifcally using powershell 7 (pwsh.exe), which is not default on Windows.
c) the notify performance was slower

What was the solution? (How)

Remove this powershell layer and use Win apis to start and notify the process.

We use python subprocess.Popen for the non-impersonation case. For impersonation, we derive from Popen and run CreateProcessWithLogonW for our use case. Notify, for impersonation, is now a python script running win apis.

We still use one powershell script to set the env vars and run the command. Here we switch it back to running with powershell 5 rather than 7.

What is the impact of this change?

Performance of running and notifying commands on windows is improved. Also, Powershell 7 is no longer a requirement.

How was this change tested?

Unit tests. These were manually forced to run as another user to test the impersonation behavior.

Was this change documented?

No

Is this a breaking change?

No


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@matta-aws matta-aws requested a review from a team as a code owner January 25, 2024 20:16
@matta-aws matta-aws force-pushed the matta/chore_winapi_proc branch 2 times, most recently from 0dc20ab to 079d359 Compare January 25, 2024 20:21
Copy link
Contributor

@Honglichenn Honglichenn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the Non-Impersonation case is implemented here. We just use subprocess.Popen here.

May I ask if we still plan to use the CreateProcessWithLogonW for the impersonation, which we did the POC before? If so, I think may be CreateProcessA for the non-impersonation is a better choice here to keep the consistent.

In the official doc of CreateProcessA, it mentions:

If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token. To run the new process in the security context of the user represented by the impersonation token, use the CreateProcessAsUser or CreateProcessWithLogonW function.

So I believe that their implementations are similar. Once we can do non-impersonation by using the CreateProcessA, I think this will be the similar in impersonation, which will help us save times investigating how to fetch the return code, logging, signal and exceptions.

@ddneilson ddneilson self-requested a review January 25, 2024 21:29
@ddneilson ddneilson added the security Pull requests that could impact security label Jan 30, 2024
]
CreateProcessWithLogonW.restype = wintypes.BOOL

result = CreateProcessWithLogonW(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add the lpCurrentDirectory arg here and pass a working directory parameter from the Session module? Otherwise, the subprocess will fail to run because the impersonated user won't have permission on the current directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this section. cwd is a Popen arg, and it is now passed along to the CreateProcessWithLogon call. We can look to use that.

@matta-aws matta-aws force-pushed the matta/chore_winapi_proc branch 14 times, most recently from eec2cb6 to 2b58a27 Compare February 1, 2024 19:23
@matta-aws matta-aws changed the title chore: Use win apis to run process [Draft] chore: Use win apis to run process Feb 1, 2024
@matta-aws
Copy link
Contributor Author

the Non-Impersonation case is implemented here. We just use subprocess.Popen here.

May I ask if we still plan to use the CreateProcessWithLogonW for the impersonation, which we did the POC before? If so, I think may be CreateProcessA for the non-impersonation is a better choice here to keep the consistent.

In the official doc of CreateProcessA, it mentions:

If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token. To run the new process in the security context of the user represented by the impersonation token, use the CreateProcessAsUser or CreateProcessWithLogonW function.

So I believe that their implementations are similar. Once we can do non-impersonation by using the CreateProcessA, I think this will be the similar in impersonation, which will help us save times investigating how to fetch the return code, logging, signal and exceptions.

Offline, it was brought up that relying on the subprocess.Popen library to run the non-impersonation case was preferable to writing out own. And, as I ended up deriving from that class to run CreateProcessWithLogon for the impersonation case, it keeps the implementations similar and leverages existing Popen functionality.

@matta-aws matta-aws force-pushed the matta/chore_winapi_proc branch from 2b58a27 to b4b9795 Compare February 1, 2024 21:36
Copy link
Contributor

@Honglichenn Honglichenn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks good to me in general. Leave some minor comments.

src/openjd/sessions/_popen_windows_as_user.py Show resolved Hide resolved
src/openjd/sessions/_popen_windows_as_user.py Show resolved Hide resolved
src/openjd/sessions/_popen_windows_as_user.py Show resolved Hide resolved
src/openjd/sessions/_popen_windows_as_user.py Show resolved Hide resolved
src/openjd/sessions/_popen_windows_as_user.py Show resolved Hide resolved
src/openjd/sessions/_subprocess.py Show resolved Hide resolved
src/openjd/sessions/_subprocess.py Show resolved Hide resolved
src/openjd/sessions/_subprocess.py Show resolved Hide resolved
Copy link
Contributor

@ddneilson ddneilson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also modify the requirements in the README for the required powershell version -- https://github.com/OpenJobDescription/openjd-sessions-for-python?tab=readme-ov-file#compatibility

@matta-aws matta-aws force-pushed the matta/chore_winapi_proc branch from c319473 to 709d390 Compare February 9, 2024 19:12
Signed-off-by: Authement <[email protected]>
@matta-aws matta-aws force-pushed the matta/chore_winapi_proc branch from 709d390 to 06f8f86 Compare February 9, 2024 19:17
Signed-off-by: Authement <[email protected]>
@matta-aws matta-aws force-pushed the matta/chore_winapi_proc branch from 8830cf8 to e2d3e34 Compare February 9, 2024 20:20
src/openjd/sessions/_powershell_generator.py Show resolved Hide resolved
src/openjd/sessions/_runner_base.py Show resolved Hide resolved
src/openjd/sessions/_subprocess.py Show resolved Hide resolved
src/openjd/sessions/_subprocess.py Show resolved Hide resolved
test/openjd/sessions/test_runner_base.py Show resolved Hide resolved
test/openjd/sessions/test_session.py Show resolved Hide resolved
Signed-off-by: Authement <[email protected]>
Copy link
Contributor

@ddneilson ddneilson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on with the tests?! 8 (!!) retries to get the CI to pass?

Looks like two tests in particular:

FAILED test/openjd/sessions/test_runner_base.py::TestScriptRunnerBase::test_run_as_windows_user - RuntimeError: Could not create temp directory within C:\ProgramData\Amazon\OpenJD: [WinError 3] The system cannot find the path specified: 'C:\\ProgramData\\Amazon\\OpenJD\\tmp2vumgeza'
FAILED test/openjd/sessions/test_runner_base.py::TestScriptRunnerBase::test_does_not_inherit_env_vars_windows - RuntimeError: Could not create temp directory within C:\ProgramData\Amazon\OpenJD: [WinError 3] The system cannot find the path specified: 'C:\\ProgramData\\Amazon\\OpenJD\\tmpt6ad3c1_'

Is there a race condition there? It kind of looks like the TempDir() that gets created isn't available on the system right away for some reason. Any thoughts?

@ddneilson
Copy link
Contributor

What's going on with the tests?! 8 (!!) retries to get the CI to pass?

Looks like two tests in particular:

FAILED test/openjd/sessions/test_runner_base.py::TestScriptRunnerBase::test_run_as_windows_user - RuntimeError: Could not create temp directory within C:\ProgramData\Amazon\OpenJD: [WinError 3] The system cannot find the path specified: 'C:\\ProgramData\\Amazon\\OpenJD\\tmp2vumgeza'
FAILED test/openjd/sessions/test_runner_base.py::TestScriptRunnerBase::test_does_not_inherit_env_vars_windows - RuntimeError: Could not create temp directory within C:\ProgramData\Amazon\OpenJD: [WinError 3] The system cannot find the path specified: 'C:\\ProgramData\\Amazon\\OpenJD\\tmpt6ad3c1_'

Is there a race condition there? It kind of looks like the TempDir() that gets created isn't available on the system right away for some reason. Any thoughts?

Interesting -- actions/runner-images#8755

It seems that the C: drive in GitHub actions is super bad performance. Maybe we should put in an env-var for tests that allows us to override where the Session directory is created, and create that on the D: drive in GitHub.

@matta-aws
Copy link
Contributor Author

What's going on with the tests?! 8 (!!) retries to get the CI to pass?

Looks like two tests in particular:

FAILED test/openjd/sessions/test_runner_base.py::TestScriptRunnerBase::test_run_as_windows_user - RuntimeError: Could not create temp directory within C:\ProgramData\Amazon\OpenJD: [WinError 3] The system cannot find the path specified: 'C:\\ProgramData\\Amazon\\OpenJD\\tmp2vumgeza'
FAILED test/openjd/sessions/test_runner_base.py::TestScriptRunnerBase::test_does_not_inherit_env_vars_windows - RuntimeError: Could not create temp directory within C:\ProgramData\Amazon\OpenJD: [WinError 3] The system cannot find the path specified: 'C:\\ProgramData\\Amazon\\OpenJD\\tmpt6ad3c1_'

Is there a race condition there? It kind of looks like the TempDir() that gets created isn't available on the system right away for some reason. Any thoughts?

There is a race condition. "One of the tests creates the openjd directory and doesn’t clean it up. Other test will only succeeded if that test has already run first or if the directory already exists". We will follow up on this separately.

@matta-aws
Copy link
Contributor Author

Interesting -- actions/runner-images#8755

It seems that the C: drive in GitHub actions is super bad performance. Maybe we should put in an env-var for tests that allows us to override where the Session directory is created, and create that on the D: drive in GitHub.

that could explain the longer test runs

@ddneilson
Copy link
Contributor

ddneilson commented Feb 9, 2024

There is a race condition. "One of the tests creates the openjd directory and doesn’t clean it up. Other test will only succeeded if that test has already run first or if the directory already exists". We will follow up on this separately.

Ahh. Okay, yeah, please fix this in a follow-on PR. Re-running the CIs is mildly blood-boiling :-)

@ddneilson ddneilson merged commit 9472ff8 into mainline Feb 12, 2024
15 checks passed
@ddneilson ddneilson deleted the matta/chore_winapi_proc branch February 12, 2024 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
security Pull requests that could impact security
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants