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

feat: Configure codejail and run safety check at startup #10

Merged
merged 1 commit into from
Feb 11, 2025

Conversation

timmc-edx
Copy link
Contributor

@timmc-edx timmc-edx commented Feb 6, 2025

  • Initialize codejail at startup
  • Run safety checks at startup, locking out the API if the checks fail

If codejail isn't properly configured, it defaults to running code unsafely. To prevent this from affecting the service, we run a smoke test at startup to check if there's anything just drastically wrong.

If this check does not pass, two things happen:

  • The healthcheck endpoint will never return a 200 OK
  • The code-exec endpoint will refuse with a 500 error

Supporting changes:

  • Define an explicit AppConfig for the api subpackage so that we can hook into the ready() mechanism
  • Wrap safe_exec to prevent codejail eagerly setting UNSAFE=True at module load time. (Not clear why this doesn't affect edx-platform; maybe something to do with app vs. middleware load order.) Issue for fixing this: Import order affects whether code is sandboxed. codejail#16
  • safe_exec wrapper also performs a deepcopy to allow callers to reason about the globals dict more easily.

Other changes:

  • Clean up healthcheck docstring (mostly just trim it down)
  • Lint cleanup

Part of edx/edx-arch-experiments#927


Manual testing performed with changes to the Dockerfile and to devstack (PRs pending), and mostly entailed calling the healthcheck endpoint.

When passing, the startup logs look like this:

edx.devstack.codejail  | 2025-02-05 23:26:03,745 INFO 365 [codejail_service.startup_check] [user None] [ip None] startup_check.py:73 - Startup test 'Basic code execution' passed
edx.devstack.codejail  | 2025-02-05 23:26:03,819 INFO 365 [codejail_service.startup_check] [user None] [ip None] startup_check.py:73 - Startup test 'Block sandbox escape by disk access' passed
edx.devstack.codejail  | 2025-02-05 23:26:03,892 INFO 365 [codejail_service.startup_check] [user None] [ip None] startup_check.py:73 - Startup test 'Block sandbox escape by child process' passed

When codejail is misconfigured:

edx.devstack.codejail  | 2025-02-06 11:47:41,056 WARNING 419 [codejail] [user None] [ip None] safe_exec.py:305 - Using codejail/safe_exec.py:not_safe_exec for None
edx.devstack.codejail  | 2025-02-06 11:47:41,058 INFO 419 [codejail_service.startup_check] [user None] [ip None] startup_check.py:73 - Startup test 'Basic code execution' passed
edx.devstack.codejail  | 2025-02-06 11:47:41,058 WARNING 419 [codejail] [user None] [ip None] safe_exec.py:305 - Using codejail/safe_exec.py:not_safe_exec for None
edx.devstack.codejail  | 2025-02-06 11:47:41,059 ERROR 419 [codejail_service.startup_check] [user None] [ip None] startup_check.py:76 - Startup test 'Block sandbox escape by disk access' failed with: "Expected error, but code ran successfully. Globals: {'ret': ['var', 'home', 'lib64', 'tmp', 'boot', 'media', 'root', 'etc', 'srv', 'proc', 'usr', 'run', 'bin', 'dev', 'opt', 'lib', 'sbin', 'mnt', 'sys', 'edx', '.dockerenv', 'app', 'venv', 'sandbox', 'lib.usr-is-merged']}"
edx.devstack.codejail  | 2025-02-06 11:47:41,059 WARNING 419 [codejail] [user None] [ip None] safe_exec.py:305 - Using codejail/safe_exec.py:not_safe_exec for None
edx.devstack.codejail  | 2025-02-06 11:47:41,061 ERROR 419 [codejail_service.startup_check] [user None] [ip None] startup_check.py:76 - Startup test 'Block sandbox escape by child process' failed with: "Expected error, but code ran successfully. Globals: {'ret': '42\\n'}"

Merge checklist:
Check off if complete or not applicable:

  • Version bumped
  • Changelog record added
  • Documentation updated (not only docstrings)
  • Unit tests added/updated
  • Manual testing instructions provided
  • Noted any: Concerns, dependencies, migration issues, deadlines, tickets

@timmc-edx timmc-edx force-pushed the timmc/safe-startup branch 2 times, most recently from 2123d53 to e1ae885 Compare February 6, 2025 17:55
@timmc-edx timmc-edx marked this pull request as ready for review February 6, 2025 18:12
Copy link

@robrap robrap left a comment

Choose a reason for hiding this comment

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

Thanks.

(responses(math=Exception("Divide by zero")), False),
)
@ddt.unpack
@patch('codejail_service.startup_check.STARTUP_SAFETY_CHECK_OK', None)
Copy link

Choose a reason for hiding this comment

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

I thought this ends up as a parameter on the test? I'm confused about how this works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That surprised me too. But if you set the new parameter here, you don't get an additional argument to the decorated function.

If patch() is used as a decorator and new is omitted, the created mock is passed in as an extra argument to the decorated function.

Copy link

Choose a reason for hiding this comment

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

Which is the new parameter? The None? Maybe I'm just used to patched functions, and that is what is throwing me off.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, this sets codejail_service.startup_check.STARTUP_SAFETY_CHECK_OK to None for the duration of the test and then sets it back to the original value afterwards.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wonder if I should just use the constant "uninitialized" instead of None to make the code and tests clearer. (This wouldn't affect any of the startup check functions, as anything other than True should make it be considered unhealthy.)

Copy link

@robrap robrap left a comment

Choose a reason for hiding this comment

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

Thanks.

- Initialize codejail at startup
- Run safety checks at startup, locking out the API if the checks fail

If codejail isn't properly configured, it defaults to running code
unsafely. To prevent this from affecting the service, we run a smoke test
at startup to check if there's anything just *drastically* wrong.

If this check does not pass, two things happen:

- The healthcheck endpoint will never return a 200 OK
- The code-exec endpoint will refuse with a 500 error

Supporting changes:

- Define an explicit AppConfig for the api subpackage so that we can hook
  into the `ready()` mechanism
- Wrap `safe_exec` to prevent codejail eagerly setting `UNSAFE=True`
  at module load time. (Not clear why this doesn't affect
  edx-platform; maybe something to do with app vs. middleware load
  order.) Filed openedx/codejail#225 for
  possibly fixing this.
- `safe_exec` wrapper also performs a deepcopy to allow callers to
  reason about the globals dict more easily.

Other changes:

- Clean up healthcheck docstring (mostly just trim it down)
- Lint cleanup

Part of edx/edx-arch-experiments#927
@MoisesGSalas
Copy link
Contributor

@timmc-edx, I won't perform any actual review, but I'm assuming you are ready to merge right?

@timmc-edx
Copy link
Contributor Author

Yes, ready to merge -- thank you!

@MoisesGSalas MoisesGSalas merged commit 88277db into openedx:main Feb 11, 2025
7 checks passed
@timmc-edx timmc-edx deleted the timmc/safe-startup branch February 11, 2025 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants