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

Cleanup NEST Server environment variable handling #2942

Merged
merged 126 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 121 commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
678b0ec
Add env EXEC_SCRIPT
babsey Oct 25, 2022
50ffba0
Cleanup nest-server
babsey Oct 25, 2022
1b2f5a1
Add changes for v3.4
babsey Oct 25, 2022
bb72edc
Set Origins in CORS
babsey Oct 27, 2022
886ee90
Add cross origin for /
babsey Oct 27, 2022
9f40046
Extend cors_origins
babsey Oct 27, 2022
00eb651
Display Python `logging`, even while server is active.
Helveg Oct 27, 2022
6a5591b
Apply CORS headers
Helveg Oct 27, 2022
d6f87f8
forgot 1
Helveg Oct 27, 2022
ab2e525
Vanishing application and authentication :)
Helveg Oct 27, 2022
b8417b8
Allowed authentication to be disabled.
Helveg Oct 27, 2022
a0c7774
restored nest import
Helveg Oct 27, 2022
ba35053
Improved comments
Helveg Oct 27, 2022
d6d013f
reverted more things I did to test locally.
Helveg Oct 27, 2022
1d27bc9
moved handler for pep8, use bool util
Helveg Oct 28, 2022
11ea078
pep8 fixes
Helveg Oct 28, 2022
0445157
abort --> flask.abort
Helveg Oct 28, 2022
b510def
fixed unauthorized error when no auth header is given
Helveg Oct 28, 2022
352f65c
Add env EXEC_SCRIPT
babsey Oct 25, 2022
cb7a4ab
Cleanup nest-server
babsey Oct 25, 2022
8fdffd6
Add changes for v3.4
babsey Oct 25, 2022
740bc8f
Set Origins in CORS
babsey Oct 27, 2022
f9d0b56
Add cross origin for /
babsey Oct 27, 2022
b29c2b2
Extend cors_origins
babsey Oct 27, 2022
31c0951
Apply CORS headers
Helveg Oct 27, 2022
c7f09ab
forgot 1
Helveg Oct 27, 2022
84e8224
Vanishing application and authentication :)
Helveg Oct 27, 2022
21b408f
Allowed authentication to be disabled.
Helveg Oct 27, 2022
3abc6a9
restored nest import
Helveg Oct 27, 2022
9bf1ac5
Improved comments
Helveg Oct 27, 2022
68ed1cf
reverted more things I did to test locally.
Helveg Oct 27, 2022
cb1abfc
moved handler for pep8, use bool util
Helveg Oct 28, 2022
98240d4
pep8 fixes
Helveg Oct 28, 2022
6a88b55
abort --> flask.abort
Helveg Oct 28, 2022
b4ee911
fixed unauthorized error when no auth header is given
Helveg Oct 28, 2022
98012d1
Rename env variables and notify before startup
babsey Oct 28, 2022
dbee4cc
Update notification
babsey Oct 28, 2022
2d7e0c9
Add changes in release notes
babsey Oct 28, 2022
8b3ca53
Set only 1 CORS_origin as default
babsey Oct 28, 2022
63d9c92
Add option to apply user-custom token
babsey Jul 18, 2023
d410f28
Resolve merge conflicts
babsey Jul 18, 2023
d568fdb
Add cross origin for /
babsey Oct 27, 2022
9bdba04
Add changes notes for nest server
babsey Jul 18, 2023
04c9420
Fix correct sytax for custom token
babsey Jul 18, 2023
63f6749
Do not auth for OPTIONS method
babsey Jul 19, 2023
46cf50c
Undo doc for v3.4
babsey Jul 19, 2023
94b2024
Use black
babsey Jul 19, 2023
0d4e954
Remove cors_origins()
babsey Jul 19, 2023
c01da1a
Update changes
babsey Jul 19, 2023
b920b4d
Fix pylint
babsey Jul 19, 2023
65bd3af
Apply black
babsey Jul 19, 2023
ae2ea1c
try to fix black
babsey Jul 19, 2023
b28bdee
Apply black
babsey Jul 19, 2023
3a0c0cc
Add env EXEC_SCRIPT
babsey Oct 25, 2022
d21a4f5
Cleanup nest-server
babsey Oct 25, 2022
2e088e5
Add changes for v3.4
babsey Oct 25, 2022
9fad24c
Set Origins in CORS
babsey Oct 27, 2022
7c8a3aa
Add cross origin for /
babsey Oct 27, 2022
9b93d91
Extend cors_origins
babsey Oct 27, 2022
754f3b6
Display Python `logging`, even while server is active.
Helveg Oct 27, 2022
2e34bab
Apply CORS headers
Helveg Oct 27, 2022
dc55bce
forgot 1
Helveg Oct 27, 2022
14a961c
Vanishing application and authentication :)
Helveg Oct 27, 2022
639acb2
Allowed authentication to be disabled.
Helveg Oct 27, 2022
06b6381
restored nest import
Helveg Oct 27, 2022
5a3aaec
Improved comments
Helveg Oct 27, 2022
01ed58e
reverted more things I did to test locally.
Helveg Oct 27, 2022
3d957df
moved handler for pep8, use bool util
Helveg Oct 28, 2022
8acbb78
pep8 fixes
Helveg Oct 28, 2022
2321955
abort --> flask.abort
Helveg Oct 28, 2022
45bb9e4
fixed unauthorized error when no auth header is given
Helveg Oct 28, 2022
4dc0115
Add env EXEC_SCRIPT
babsey Oct 25, 2022
ae850dd
Cleanup nest-server
babsey Oct 25, 2022
02ffe29
Add changes for v3.4
babsey Oct 25, 2022
e7fed2d
Set Origins in CORS
babsey Oct 27, 2022
49813b4
Add cross origin for /
babsey Oct 27, 2022
32db740
Extend cors_origins
babsey Oct 27, 2022
4dec831
Apply CORS headers
Helveg Oct 27, 2022
42558c2
forgot 1
Helveg Oct 27, 2022
29bba91
Vanishing application and authentication :)
Helveg Oct 27, 2022
660b221
Allowed authentication to be disabled.
Helveg Oct 27, 2022
b1f73a7
restored nest import
Helveg Oct 27, 2022
d654b42
Improved comments
Helveg Oct 27, 2022
45332d9
reverted more things I did to test locally.
Helveg Oct 27, 2022
9c900dc
moved handler for pep8, use bool util
Helveg Oct 28, 2022
b659570
pep8 fixes
Helveg Oct 28, 2022
5d00eac
abort --> flask.abort
Helveg Oct 28, 2022
685c072
fixed unauthorized error when no auth header is given
Helveg Oct 28, 2022
a33759f
Rename env variables and notify before startup
babsey Oct 28, 2022
2a80113
Update notification
babsey Oct 28, 2022
7377778
Add changes in release notes
babsey Oct 28, 2022
6a8f3e6
Set only 1 CORS_origin as default
babsey Oct 28, 2022
4327088
Add option to apply user-custom token
babsey Jul 18, 2023
e38128b
Resolve merge conflicts
babsey Jul 18, 2023
54581f1
Add cross origin for /
babsey Oct 27, 2022
84d5580
Add changes notes for nest server
babsey Jul 18, 2023
b6792d5
Fix correct sytax for custom token
babsey Jul 18, 2023
776af77
Do not auth for OPTIONS method
babsey Jul 19, 2023
11a3d38
Undo doc for v3.4
babsey Jul 19, 2023
ee00564
Use black
babsey Jul 19, 2023
0642be2
Remove cors_origins()
babsey Jul 19, 2023
0a1d504
Update changes
babsey Jul 19, 2023
ede355f
Fix pylint
babsey Jul 19, 2023
795a5e6
Apply black
babsey Jul 19, 2023
d8eefb5
try to fix black
babsey Jul 19, 2023
ec863dc
Apply black
babsey Jul 19, 2023
b7dce27
Merge branch 'nest-server-cleanup' of github.com:babsey/nest-simulato…
babsey Sep 13, 2023
58b8b5e
Fix import modules
babsey Sep 13, 2023
feaefa3
Move changes in v3.6
babsey Sep 13, 2023
99e0eba
Rename Auth Bearer to NESTServerAuth
babsey Sep 14, 2023
86e7629
Rename Auth Bearer to NESTServerAuth
babsey Sep 14, 2023
803302e
Run pre-commit
babsey Sep 14, 2023
558907b
Fix error
babsey Sep 14, 2023
4b404c7
Better instruction for access token
babsey Sep 14, 2023
d8bc5fe
Apply suggestions from code review
terhorstd Sep 15, 2023
357d2be
move text to server guide
jessica-mitchell Sep 15, 2023
18fa050
Merge pull request #6 from jessica-mitchell/nest-server-cleanup
babsey Sep 15, 2023
ec16326
Update doc/htmldoc/whats_new/v3.6/index.rst
jessica-mitchell Sep 15, 2023
10b0991
Remove hmac
babsey Sep 19, 2023
dbda366
Merge branch 'master' into nest-server-cleanup
terhorstd Sep 19, 2023
5bf20c0
run isort
terhorstd Sep 19, 2023
4d23b0d
Apply suggestions from code review
babsey Sep 20, 2023
429faaf
Update doc/htmldoc/connect_nest/nest_server.rst
terhorstd Sep 20, 2023
28c0da8
Update doc/htmldoc/connect_nest/nest_server.rst
jougs Sep 20, 2023
9eab72f
Update doc/htmldoc/connect_nest/nest_server.rst
jougs Sep 20, 2023
c6a9329
Update doc/htmldoc/connect_nest/nest_server.rst
jougs Sep 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/nest-server
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ start() {
if [ "${DAEMON}" -eq 0 ]; then
echo "Use CTRL + C to stop this service."
if [ "${STDOUT}" -eq 1 ]; then
echo "-------------------------------------------------"
echo "-----------------------------------------------------"
fi
fi

Expand Down
19 changes: 19 additions & 0 deletions doc/htmldoc/connect_nest/nest_server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,25 @@ As an alternative to a native installation, NEST Server is available
from the NEST Docker image. Please check out the corresponding
:ref:`installation instructions <docker>` for more details.

.. _sec_server_vars:

Set environment variables for security options
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To use NEST Server, there are several environment variables users need to set in their environment.

* Requests require NESTServerAuth tokens. By default, the authentication is on (``NEST_SERVER_DISABLE_AUTH=0``).
* NEST Server generates a token automatically, but it can also take custom tokens (``NEST_SERVER_ACCESS_TOKEN='alongaccesstoken'``).
* The `CORS <https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS>`_ origins are restricted. By default, the only allowed CORS origin is ``http://localhost``
(``NEST_SERVER_CORS_ORIGINS=http://localhost``).
* Only API calls are enabled. By default, the exec call is disabled (``NEST_SERVER_ENABLE_EXEC_CALL=0``).
* The code execution is restricted. By default, the restriction is activated (``NEST_SERVER_DISABLE_RESTRICTION=0``).
* For security reasons the exec call in NEST Server accepts only modules from NEST_SERVER_MODULES.

For example:

``NEST_SERVER_MODULES='import nest; import numpy as np; from numpy import random'``

terhorstd marked this conversation as resolved.
Show resolved Hide resolved
Run NEST Server
~~~~~~~~~~~~~~~

Expand Down
7 changes: 7 additions & 0 deletions doc/htmldoc/whats_new/v3.6/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,10 @@ the property `volume_transmitter` of the synapse's common properties:
| ) | ) |
| | |
+--------------------------------------------------+--------------------------------------------------+


Changes in NEST Server
----------------------

We improved the security in NEST Server. Now to use NEST Server, users can modify the security options.
See :ref:`section on setting these varialbles <sec_server_vars>` in our NEST Server guide.
156 changes: 139 additions & 17 deletions pynest/nest/server/hl_api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,41 @@
import importlib
import inspect
import io
import logging
import os
import sys
import time
import traceback
from copy import deepcopy

import flask
import nest
import RestrictedPython
from flask import Flask, jsonify, request
from flask_cors import CORS, cross_origin
from flask.logging import default_handler
from flask_cors import CORS
from werkzeug.exceptions import abort
from werkzeug.wrappers import Response

MODULES = os.environ.get("NEST_SERVER_MODULES", "nest").split(",")
RESTRICTION_OFF = bool(os.environ.get("NEST_SERVER_RESTRICTION_OFF", False))
EXCEPTION_ERROR_STATUS = 400
# This ensures that the logging information shows up in the console running the server,
# even when Flask's event loop is running.
root = logging.getLogger()
root.addHandler(default_handler)


def get_boolean_environ(env_key, default_value="false"):
env_value = os.environ.get(env_key, default_value)
return env_value.lower() in ["yes", "true", "t", "1"]

if RESTRICTION_OFF:
msg = "NEST Server runs without a RestrictedPython trusted environment."
print(f"***\n*** WARNING: {msg}\n***")

_default_origins = "http://localhost"
ACCESS_TOKEN = os.environ.get("NEST_SERVER_ACCESS_TOKEN", "")
AUTH_DISABLED = get_boolean_environ("NEST_SERVER_DISABLE_AUTH")
CORS_ORIGINS = os.environ.get("NEST_SERVER_CORS_ORIGINS", _default_origins).split(",")
EXEC_CALL_ENABLED = get_boolean_environ("NEST_SERVER_ENABLE_EXEC_CALL")
MODULES = os.environ.get("NEST_SERVER_MODULES", "import nest")
RESTRICTION_DISABLED = get_boolean_environ("NEST_SERVER_DISABLE_RESTRICTION")
EXCEPTION_ERROR_STATUS = 400

__all__ = [
"app",
Expand All @@ -54,11 +68,116 @@
]

app = Flask(__name__)
CORS(app)
# Inform client-side user agents that they should not attempt to call our server from any
# non-whitelisted domain.
CORS(app, origins=CORS_ORIGINS, methods=["GET", "POST"])

mpi_comm = None


def _check_security():
"""
Checks the security level of the NEST Server instance.
"""

msg = []
if AUTH_DISABLED:
msg.append("AUTH:\tThe authorization is disabled.")
babsey marked this conversation as resolved.
Show resolved Hide resolved
if "*" in CORS_ORIGINS:
msg.append("CORS:\tAllowed origins is not restricted.")
babsey marked this conversation as resolved.
Show resolved Hide resolved
if EXEC_CALL_ENABLED:
msg.append("EXEC CALL:\tAny code scripts can be executed!")
babsey marked this conversation as resolved.
Show resolved Hide resolved
if RESTRICTION_DISABLED:
msg.append("RESTRICTION: Code scripts will be executed without a restricted environment.")
babsey marked this conversation as resolved.
Show resolved Hide resolved

if len(msg) > 0:
print(
"WARNING: You chose to disable important access restrictions!\n"
" This allows other computers to execute code on this machine as the current user!\n"
" Be sure you understand the implications of these settings and take"
" appropriate measures to protect your runtime environment!"
)
print("\n - ".join([" "] + msg) + "\n")


@app.before_request
def _setup_auth():
"""
Authentication function that generates and validates the NESTServerAuth header with a
bearer token.

Cleans up references to itself and the running `app` from this module, as it may be
accessible when the code execution sandbox fails.
"""
try:
# Import the modules inside of the auth function, so that if they fail the auth
# returns a forbidden error.
import gc # noqa
import hashlib # noqa
import inspect # noqa
import time # noqa

# Find our reference to the current function in the garbage collector.
frame = inspect.currentframe()
code = frame.f_code
globs = frame.f_globals
functype = type(lambda: 0)
funcs = []
for func in gc.get_referrers(code):
if type(func) is functype:
if getattr(func, "__code__", None) is code:
if getattr(func, "__globals__", None) is globs:
funcs.append(func)
if len(funcs) > 1:
return ("Unauthorized", 403)
self = funcs[0]

# Use the salted hash (unless `PYTHONHASHSEED` is fixed) of the location of this
# function in the Python heap and the current timestamp to create a SHA512 hash.
if not hasattr(self, "_hash"):
if ACCESS_TOKEN:
self._hash = ACCESS_TOKEN
else:
hasher = hashlib.sha512()
hasher.update(str(hash(id(self))).encode("utf-8"))
hasher.update(str(time.perf_counter()).encode("utf-8"))
self._hash = hasher.hexdigest()[:48]
if not AUTH_DISABLED:
print(f" Access token to NEST Server: {self._hash}")
print(" Add this to the headers: {'NESTServerAuth': '<access_token>'}\n")

if request.method == "OPTIONS":
return

# The first time we hit the line below is when below the function definition we
# call `setup_auth` without any Flask request existing yet, so the function errors
# and exits here after generating and storing the auth hash.
auth = request.headers.get("NESTServerAuth", None)
# We continue here the next time this function is called, before the Flask app
# handles the first request. At that point we also remove this module's reference
# to the running app.
try:
del globals()["app"]
except KeyError:
pass
# Things get more straightforward here: Every time a request is handled, compare
# the NESTServerAuth header to the hash, with a constant-time algorithm to avoid
# timing attacks.
if not (AUTH_DISABLED or auth == self._hash):
return ("Unauthorized", 403)
# DON'T LINT! Intentional bare except clause! Even `KeyboardInterrupt` and
# `SystemExit` exceptions should not bypass authentication!
except Exception: # noqa
return ("Unauthorized", 403)


print(80 * "*")
_check_security()
_setup_auth()
del _setup_auth
print(80 * "*")


@app.route("/", methods=["GET"])
def index():
return jsonify(
Expand All @@ -76,7 +195,7 @@ def do_exec(args, kwargs):

locals_ = dict()
response = dict()
if RESTRICTION_OFF:
if RESTRICTION_DISABLED:
with Capturing() as stdout:
globals_ = globals().copy()
globals_.update(get_modules_from_env())
Expand Down Expand Up @@ -104,7 +223,7 @@ def do_exec(args, kwargs):
except Exception as e:
for line in traceback.format_exception(*sys.exc_info()):
print(line, flush=True)
abort(Response(str(e), EXCEPTION_ERROR_STATUS))
flask.abort(EXCEPTION_ERROR_STATUS, str(e))


def log(call_name, msg):
Expand Down Expand Up @@ -159,13 +278,18 @@ def do_call(call_name, args=[], kwargs={}):


@app.route("/exec", methods=["GET", "POST"])
@cross_origin()
def route_exec():
"""Route to execute script in Python."""

args, kwargs = get_arguments(request)
response = do_call("exec", args, kwargs)
return jsonify(response)
if EXEC_CALL_ENABLED:
args, kwargs = get_arguments(request)
response = do_call("exec", args, kwargs)
return jsonify(response)
else:
flask.abort(
403,
"The route `/exec` has been disabled. Please contact the server administrator.",
)


# --------------------------
Expand All @@ -178,14 +302,12 @@ def route_exec():


@app.route("/api", methods=["GET"])
@cross_origin()
def route_api():
"""Route to list call functions in NEST."""
return jsonify(nest_calls)


@app.route("/api/<call>", methods=["GET", "POST"])
@cross_origin()
def route_api_call(call):
"""Route to call function in NEST."""
print(f"\n{'='*40}\n", flush=True)
Expand Down Expand Up @@ -281,7 +403,7 @@ def func_wrapper(call, args, kwargs):
except Exception as e:
for line in traceback.format_exception(*sys.exc_info()):
print(line, flush=True)
abort(Response(str(e), EXCEPTION_ERROR_STATUS))
flask.abort(EXCEPTION_ERROR_STATUS, str(e))

return func_wrapper

Expand Down
Loading