Skip to content

Commit

Permalink
Tightens security of the matlab-proxy when token authentication is en…
Browse files Browse the repository at this point in the history
…abled, for multi-user shared system environments.
  • Loading branch information
Prabhakar Kumar committed Sep 22, 2023
1 parent 4faf4b7 commit b446ec2
Show file tree
Hide file tree
Showing 9 changed files with 529 additions and 134 deletions.
4 changes: 2 additions & 2 deletions Advanced-Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ The following table describes all the environment variables that you can set to
| **TMPDIR** or **TMP** | string | `"/path/for/MATLAB/to/use/as/tmp"` | Set either one of these variables to control the temporary folder used by MATLAB. `TMPDIR` takes precedence over `TMP` and if neither variable is set, `/tmp` is the default value used by MATLAB. |
| **MWI_SSL_CERT_FILE** | string | `"/path/to/certificate.pem"` | The certfile string must be the path to a single file in PEM format containing the certificate as well as any number of CA certificates needed to establish the certificate’s authenticity. See [SSL Support](./SECURITY.md#ssl-support) for more information.|
| **MWI_SSL_KEY_FILE** | string | `"/path/to/keyfile.key"` | The keyfile string, if present, must point to a file containing the private key. Otherwise the private key will be taken from certfile as well. |
| **MWI_ENABLE_TOKEN_AUTH** | string | `"True"` | When set to `True`, matlab-proxy will require users to provide the security token to access the proxy. <br />The default value is `False` . See [Token-Based Authentication](./SECURITY.md#token-based-authentication) for more information.|
| **MWI_AUTH_TOKEN** | string (optional) | `"AnyURLSafeToken"` | Optionally, provide a custom `token` for use with `MWI_ENABLE_TOKEN_AUTH`. A token can safely contain any combination of alpha numeric text along with the following permitted characters: `- . _ ~`.<br />When absent matlab-proxy will generate a random URL safe token. |
| **MWI_ENABLE_TOKEN_AUTH** | string | `"True"` | When set to `True`, matlab-proxy will require users to provide the security token to access the proxy. One can optionally set the token using the environment variable `MWI_AUTH_TOKEN`. If `MWI_AUTH_TOKEN` is not specified, then a token will be generated for you. <br />The default value is `False` . See [Token-Based Authentication](./SECURITY.md#token-based-authentication) for more information.|
| **MWI_AUTH_TOKEN** | string (optional) | `"AnyURLSafeToken"` | Specify a custom `token` for matlab-proxy to use with [Token-Based Authentication](./SECURITY.md#token-based-authentication). A token can safely contain any combination of alpha numeric text along with the following permitted characters: `- . _ ~`.<br />When absent matlab-proxy will generate a random URL safe token. |
| **MWI_USE_EXISTING_LICENSE** | string (optional) | `"True"` | When set to True, matlab-proxy will not ask you for additional licensing information and will try to launch an already activated MATLAB on your system PATH.
| **MWI_CUSTOM_MATLAB_ROOT** | string (optional) | `"/path/to/matlab/root/"` | Optionally, provide a custom path to MATLAB root. For more information see [Adding MATLAB to System Path](#adding-matlab-to-system-path) |

Expand Down
5 changes: 5 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ This token can be provided to the server in 2 ways:
<img width="800" src="./img/token_authentication_page.png">
</p>

3. Through a `mwi_auth_token` header. Example:
``` html
mwi_auth_token:abcdef..
```

**NOTE** : Its highly recommended to use this feature along with SSL enabled as shown [here](#use-token-authentication-with-ssl-enabled).

### **Use with auto-generated tokens**
Expand Down
4 changes: 1 addition & 3 deletions gui/src/actionCreators/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,8 @@ export function updateAuthStatus(token){
const options = {
method: 'POST',
headers: {
'Accept': 'application/text',
'Content-Type': 'application/text'
'mwi_auth_token': token
},
body: token
};
const response = await fetchWithTimeout(dispatch, './authenticate_request', options, 15000);
const data = await response.json()
Expand Down
41 changes: 27 additions & 14 deletions matlab_proxy/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
import matlab_proxy
from matlab_proxy import constants, settings, util
from matlab_proxy.app_state import AppState
from matlab_proxy.default_configuration import config
from matlab_proxy.util import list_servers, mwi
from matlab_proxy.util import mwi
from matlab_proxy.util.mwi import environment_variables as mwi_env
from matlab_proxy.util.mwi import token_auth
from matlab_proxy.util.mwi.exceptions import AppError, InvalidTokenError, LicensingError
Expand Down Expand Up @@ -119,6 +118,9 @@ async def create_status_response(app, loadUrl=None):
)


# @token_auth.authenticate_access_decorator
# Explicitly disabling authentication for this end-point,
# because the front end requires this endpoint to be available at all times.
async def get_env_config(req):
"""API Endpoint to get Matlab Web Desktop environment specific configuration.
Expand All @@ -142,6 +144,9 @@ async def get_env_config(req):
return web.json_response(config)


# @token_auth.authenticate_access_decorator
# Explicitly disabling authentication for this end-point,
# because the front end requires this endpoint to be available at all times.
async def get_status(req):
"""API Endpoint to get the generic status of the server, MATLAB and MATLAB Licensing.
Expand All @@ -154,39 +159,39 @@ async def get_status(req):
return await create_status_response(req.app)


# @token_auth.authenticate_access_decorator
# Explicitly disabling authentication for this end-point, as it checks for authenticity internally.
async def authenticate_request(req):
"""API Endpoint to authenticate request to access server
Returns:
JSONResponse: JSONResponse object containing information about authentication status and error if any.
"""
if await token_auth.authenticate_request(req):
logger.debug("!!!!!! REQUEST IS AUTHORIZED !!!!")
authStatus = True
error = None
else:
logger.debug("!!!!!! REQUEST IS NOT AUTHORIZED !!!!")
authStatus = False
error = marshal_error(
is_authenticated = await token_auth.authenticate_request(req)
error = (
None
if is_authenticated
else marshal_error(
InvalidTokenError(
"Token invalid. Please enter a valid token to authenticate"
)
)

)
# If there is an error, state.error is not updated because the client may have set the
# token incorrectly which is not an error raised on the backend.

token = await req.text() if not error else ""
# TODO: @krisctl to remove this.
token = req.app["settings"]["mwi_auth_token"] if is_authenticated else ""

return web.json_response(
{
"authStatus": authStatus,
"authStatus": is_authenticated,
"authToken": token,
"error": error,
}
)


@token_auth.authenticate_access_decorator
async def start_matlab(req):
"""API Endpoint to start MATLAB
Expand All @@ -204,6 +209,7 @@ async def start_matlab(req):
return await create_status_response(req.app)


@token_auth.authenticate_access_decorator
async def stop_matlab(req):
"""API Endpoint to stop MATLAB
Expand All @@ -220,6 +226,7 @@ async def stop_matlab(req):
return await create_status_response(req.app)


@token_auth.authenticate_access_decorator
async def set_licensing_info(req):
"""API Endpoint to set licensing information on the server side.
Expand Down Expand Up @@ -261,6 +268,7 @@ async def set_licensing_info(req):
return await create_status_response(req.app)


@token_auth.authenticate_access_decorator
async def update_entitlement(req):
"""API endpoint to update selected entitlement to start MATLAB with.
Expand Down Expand Up @@ -290,6 +298,7 @@ async def __start_matlab_if_licensed(state):
await state.start_matlab(restart_matlab=True)


@token_auth.authenticate_access_decorator
async def licensing_info_delete(req):
"""API Endpoint to stop MATLAB and remove licensing details.
Expand All @@ -312,6 +321,7 @@ async def licensing_info_delete(req):
return await create_status_response(req.app)


@token_auth.authenticate_access_decorator
async def termination_integration_delete(req):
"""API Endpoint to terminate the Integration and shutdown the server.
Expand Down Expand Up @@ -339,6 +349,8 @@ async def termination_integration_delete(req):
sys.exit(0)


# @token_auth.authenticate_access_decorator
# Explicitly disabling authentication for this end-point, as authenticity is checked by the redirected endpoint.
async def root_redirect(request):
"""API Endpoint to return the root index.html file.
Expand Down Expand Up @@ -421,6 +433,7 @@ def make_static_route_table(app):
return table


@token_auth.authenticate_access_decorator
async def matlab_view(req):
"""API Endpoint which proxies requests to the MATLAB Embedded Connector
Expand Down
14 changes: 13 additions & 1 deletion matlab_proxy/app_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,21 @@ async def get_matlab_state(self):
# If execution reaches here, it implies that:
# 1) MATLAB process has started.
# 2) Embedded connector has not started yet.
# Proceed to query the Embedded Connector about its state.
# matlab-proxy sends a request to itself to the endpoint: /messageservice/json/state
# which the server redirects to the matlab_view() function to handle (which then sends the request to EC)
# As the matlab_view is now a protected endpoint, we need to pass token information through headers.

# Include token information into the headers if authentication is enabled.
headers = (
{self.settings["mwi_auth_token_name"]: self.settings["mwi_auth_token"]}
if self.settings["mwi_is_token_auth_enabled"]
else None
)

embedded_connector_status = await mwi.embedded_connector.request.get_state(
self.settings["mwi_server_url"]
mwi_server_url=self.settings["mwi_server_url"],
headers=headers,
)

# Embedded Connector can be in either "up" or "down" state
Expand Down
23 changes: 19 additions & 4 deletions matlab_proxy/util/mwi/embedded_connector/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

from matlab_proxy.util.mwi.exceptions import EmbeddedConnectorError

from matlab_proxy.util import mwi

logger = mwi.logger.get()

from .helpers import get_data_for_ping_request, get_ping_endpoint


Expand Down Expand Up @@ -39,9 +43,14 @@ async def send_request(url: str, data: dict, method: str, headers: dict = None)

try:
async with aiohttp.ClientSession() as session:
logger.debug(
f"sending request: method={method}, url={url}, data={data}, headers={headers}, "
)

async with session.request(
method=method, url=url, data=data, headers=None, ssl=False
method=method, url=url, data=data, headers=headers, ssl=False
) as resp:
logger.debug(f"response from endpoint{url} and resp={resp}")
if not resp.ok:
# Converting to dict and formatting for printing
data = json.loads(data)
Expand All @@ -55,19 +64,25 @@ async def send_request(url: str, data: dict, method: str, headers: dict = None)
raise err


async def get_state(mwi_server_url):
async def get_state(mwi_server_url, headers=None):
"""Returns the state of MATLAB's Embedded Connector.
Args:
port (int): The port on which the embedded connector is running at
headers: Headers to include with the request
Returns:
str: Either "up" or "down"
"""
data = get_data_for_ping_request()
url = get_ping_endpoint(mwi_server_url)

try:
resp = await send_request(url=url, data=data, method="POST")
resp = await send_request(
url=url,
data=data,
method="POST",
headers=headers,
)

# Additional assert statements to catch any changes in response from embedded connector
# Tested from R2020b to R2023a
Expand Down
Loading

0 comments on commit b446ec2

Please sign in to comment.