Skip to content

Commit 26e20a0

Browse files
krisctlPrabhakar Kumar
authored andcommitted
Introducing MWI_ENABLE_SSL to control activation of SSL-based communication with the server.
1 parent 4ea8da6 commit 26e20a0

File tree

8 files changed

+290
-88
lines changed

8 files changed

+290
-88
lines changed

Advanced-Usage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ The following table describes all the environment variables that you can set to
2323
| **MWI_ENABLE_WEB_LOGGING** | string | `"True"` | Set this value to `"True"` to see additional web server logs. |
2424
| **MWI_CUSTOM_HTTP_HEADERS** | string |`'{"Content-Security-Policy": "frame-ancestors *.example.com:*"}'`<br /> OR <br />`"/path/to/your/custom/http-headers.json"` |Specify valid HTTP headers as JSON data in a string format. <br /> Alternatively, specify the full path to the JSON file containing valid HTTP headers instead. These headers are injected into the HTTP response sent to the browser. </br> For more information, see the [Custom HTTP Headers](#custom-http-headers) section.|
2525
| **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. |
26+
| **MWI_ENABLE_SSL** | string | `"False"` | When set to `True`, the values in `MWI_SSL_CERT_FILE & MWI_SSL_KEY_FILE` are used to configure matlab-proxy to use SSL. If you do not provide a CERT and KEY file using these variables, the software generates a self-signed certificate. Defaults to `False`.|
2627
| **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.|
2728
| **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. |
2829
| **MWI_ENABLE_TOKEN_AUTH** | string | `"True"` | When you set the variable to `True`, matlab-proxy requires users to provide the security token to access the proxy. Optionally, set the token using the environment variable `MWI_AUTH_TOKEN`. If you do not specify `MWI_AUTH_TOKEN`, the software generates a token for you. <br />For more information, see [Token-Based Authentication](./SECURITY.md#token-based-authentication) for more information.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Once the `matlab-proxy` package is installed.
9393

9494
Running the above command will print text out on your terminal, which will contain the URL to access MATLAB. For example:
9595
```
96-
MATLAB can be accessed on
96+
Access MATLAB at
9797
http://localhost:44549/matlab/index.html
9898
```
9999

SECURITY.md

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,36 +30,40 @@ The following features are available in `matlab-proxy` to provide secure access
3030
----
3131

3232
## **SSL Support**
33-
Configure `matlab-proxy` to use SSL by providing the desired SSL certificates using the following environment variables at server launch:
34-
The following environment variables must be set to enable `matlab-proxy` to run on SSL:
35-
1. *MWI_SSL_CERT_FILE*
33+
34+
1. *MWI_ENABLE_SSL*
35+
36+
Use the environment variable `MWI_ENABLE_SSL` to configure SSL/TLS support for `matlab-proxy`. To enable SSL/TLS, set `MWI_ENABLE_SSL` to `True`. By default, this uses a self-signed certificate. To use custom SSL certificates instead, specify these files using the following environment variables when you start `matlab-proxy`.
37+
38+
2. *MWI_SSL_CERT_FILE*
3639

3740
A string with the full 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.
3841

3942
2. *MWI_SSL_KEY_FILE*
4043

41-
A string with the full path to a file containing the private key. If absent, the private key must be present in the certfile provided with *MWI_SSL_CERT_FILE*.
44+
A string with the full path to a file containing the private key. If absent, the private key must be present in the cert file provided using `MWI_SSL_CERT_FILE`.
4245

4346
Example:
4447
```bash
45-
# Launch matlab-proxy with SSL enabled
46-
$ env MWI_SSL_CERT_FILE="/path/to/certificate.pem" MWI_SSL_KEY_FILE="/path/to/keyfile.key" matlab-proxy-app
48+
# Start matlab-proxy with SSL enabled
49+
$ env MWI_ENABLE_SSL=True MWI_SSL_CERT_FILE="/path/to/certificate.pem" MWI_SSL_KEY_FILE="/path/to/keyfile.key" matlab-proxy-app
4750

48-
# The access link is presented in the terminal upon startup like follows:
51+
# The access link appears in the terminal at startup:
4952
==================================================================================================
50-
MATLAB can be accessed at:
53+
Access MATLAB at:
5154
https://127.0.0.1:37109
5255
==================================================================================================
5356

5457
# NOTE: The server is running HTTP(S) !
5558

5659
```
5760

61+
5862
----
5963

6064
## **Token-Based Authentication**
6165

62-
`matlab-proxy` is a web server and that allows one to launch and access MATLAB on the machine the server is running on. Anyone with access to the server can access MATLAB and thereby the machine on which its running.
66+
`matlab-proxy` is a web server and that allows one to start and access MATLAB on the machine the server is running on. Anyone with access to the server can access MATLAB and thereby the machine on which its running.
6367

6468
`Token-Based Authentication` is enabled by default and the server will require a token to authenticate access. This token can be provided to the server in a few ways:
6569

@@ -89,12 +93,12 @@ Example:
8993

9094
```bash
9195

92-
# Launch matlab-proxy with Token-Based Authentication enabled by default
96+
# Start matlab-proxy with Token-Based Authentication enabled by default
9397
$ matlab-proxy-app
9498

9599
# The access link is presented in the terminal upon startup like follows:
96100
==================================================================================================
97-
MATLAB can be accessed at:
101+
Access MATLAB at:
98102
http://127.0.0.1:37109?mwi_auth_token=SY78vUw5qyf0JTJzGK4mKJlk_exkzL_SMFJyilbGtNI
99103
==================================================================================================
100104
```
@@ -114,12 +118,12 @@ See [URI Specification RFC3986](https://www.ietf.org/rfc/rfc3986.txt) for more i
114118
Example:
115119
```bash
116120

117-
# Launch matlab-proxy with Token-Based Authentication enabled, and with custom token with a value of "MyCustomSecretToken"
121+
# Start matlab-proxy with Token-Based Authentication enabled, and with custom token with a value of "MyCustomSecretToken"
118122
$ env MWI_ENABLE_TOKEN_AUTH=True MWI_AUTH_TOKEN=MyCustomSecretToken matlab-proxy-app
119123

120124
# The access link is presented in the terminal upon startup like follows:
121125
==================================================================================================
122-
MATLAB can be accessed at:
126+
Access MATLAB at:
123127
http://127.0.0.1:37109?mwi_auth_token=MyCustomSecretToken
124128
==================================================================================================
125129
```
@@ -128,14 +132,14 @@ $ env MWI_ENABLE_TOKEN_AUTH=True MWI_AUTH_TOKEN=MyCustomSecretToken matlab-proxy
128132

129133
It is recommended to enable both `Token-Based Authentication` and `SSL` to secure your access to MATLAB via matlab-proxy. As an example, the following command enables access to MATLAB using HTTPS and token-based authentication
130134

131-
For example, the following command launches the server to deliver content on `HTTPS` along with Token-Based Authentication enabled:
135+
For example, the following command starts the server to deliver content on `HTTPS` along with Token-Based Authentication enabled:
132136
```bash
133-
# Launch matlab-proxy with Token-Based Authentication & SSL enabled with custom token with a value of "asdf"
137+
# Start matlab-proxy with Token-Based Authentication & SSL enabled with custom token with a value of "asdf"
134138
$ env MWI_SSL_CERT_FILE="/path/to/certificate.pem" MWI_SSL_KEY_FILE="/path/to/keyfile.key" MWI_ENABLE_TOKEN_AUTH=True MWI_AUTH_TOKEN=asdf matlab-proxy-app
135139

136140
# The access link is presented in the terminal upon startup like follows:
137141
==================================================================================================
138-
MATLAB can be accessed at:
142+
Access MATLAB at:
139143
https://127.0.0.1:37109?mwi_auth_token=asdf
140144
==================================================================================================
141145

@@ -145,16 +149,16 @@ $ env MWI_SSL_CERT_FILE="/path/to/certificate.pem" MWI_SSL_KEY_FILE="/path/to/ke
145149

146150
### **Token Recovery**
147151

148-
To recover tokens for a previously launched server, you will need access to either:
152+
To recover tokens for a previously started server, you will need access to either:
149153

150-
1. The machine on which the server was launched, while being logged in as the user that launched the server.
151-
2. An authenticated browser session launched for the same user.
154+
1. The machine on which the server was started, while being logged in as the user that started the server.
155+
2. An authenticated browser session started for the same user.
152156

153157
#### **Recover tokens from the machine**
154158

155-
1. Login to the machine on which the servers are running, as the user that launched matlab-proxy.
156-
1. Activate the python environment from which the server was launched.
157-
* This should be the same environment from which the server was launched.
159+
1. Login to the machine on which the servers are running, as the user that started matlab-proxy.
160+
1. Activate the python environment from which the server was started.
161+
* This should be the same environment from which the server was started.
158162
* Run the executable `matlab-proxy-app-list-servers`
159163

160164
Example:
@@ -202,12 +206,12 @@ Example:
202206

203207
```bash
204208

205-
# Launch matlab-proxy with Token-Based Authentication disabled
209+
# Start matlab-proxy with Token-Based Authentication disabled
206210
$ env MWI_ENABLE_TOKEN_AUTH="False" matlab-proxy-app
207211

208212
# The access link is presented in the terminal upon startup like follows:
209213
==================================================================================================
210-
MATLAB can be accessed at:
214+
Access MATLAB at:
211215
http://127.0.0.1:37110
212216
==================================================================================================
213217
```
@@ -217,7 +221,7 @@ $ env MWI_ENABLE_TOKEN_AUTH="False" matlab-proxy-app
217221
* Never share access to your server
218222
Never share URLs from `matlab-proxy` with others. Doing so is equivalent to sharing your user account.
219223

220-
* System administrators who launch `matlab-proxy` for other users, must launch the proxy as the user for whom the server is intended.
224+
* System administrators who start `matlab-proxy` for other users, must start the proxy as the user for whom the server is intended.
221225

222226
----
223227

matlab_proxy/app_state.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ def create_server_info_file(self):
606606
util.prettify(
607607
boundary_filler="=",
608608
text_arr=[
609-
f"MATLAB can be accessed at:",
609+
f"Access MATLAB at:",
610610
self.settings["mwi_server_url"].replace("0.0.0.0", "localhost")
611611
+ mwi_auth_token_str,
612612
],

matlab_proxy/settings.py

Lines changed: 126 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright 2020-2024 The MathWorks, Inc.
22

3+
import datetime
34
import os
45
import shutil
56
import socket
@@ -9,6 +10,11 @@
910
import xml.etree.ElementTree as ET
1011
from pathlib import Path
1112

13+
from cryptography import x509
14+
from cryptography.hazmat.primitives import hashes, serialization
15+
from cryptography.hazmat.primitives.asymmetric import rsa
16+
from cryptography.x509.oid import NameOID
17+
1218
import matlab_proxy
1319
from matlab_proxy import constants
1420
from matlab_proxy.util import mwi, system
@@ -289,10 +295,6 @@ def get_server_settings(config_name):
289295
mwi_auth_token_hash,
290296
) = token_auth.generate_mwi_auth_token_and_hash().values()
291297
mwi_config_folder = get_mwi_config_folder()
292-
ssl_key_file, ssl_cert_file = mwi.validators.validate_ssl_key_and_cert_file(
293-
os.getenv(mwi_env.get_env_name_ssl_key_file(), None),
294-
os.getenv(mwi_env.get_env_name_ssl_cert_file(), None),
295-
)
296298

297299
# log file validation check is already done in logger.py
298300
mwi_log_file = os.getenv(mwi_env.get_env_name_log_file(), None)
@@ -328,9 +330,7 @@ def get_server_settings(config_name):
328330
"mwi_use_existing_license": mwi.validators.validate_use_existing_licensing(
329331
os.getenv(mwi_env.get_env_name_mwi_use_existing_license(), "")
330332
),
331-
"ssl_context": get_ssl_context(
332-
ssl_cert_file=ssl_cert_file, ssl_key_file=ssl_key_file
333-
),
333+
"ssl_context": _validate_ssl_files_and_get_ssl_context(mwi_config_folder),
334334
}
335335

336336

@@ -478,31 +478,127 @@ def get_test_temp_dir():
478478
return test_temp_dir
479479

480480

481-
def get_ssl_context(ssl_cert_file, ssl_key_file):
482-
"""Creates an SSL CONTEXT for use with the TCP Site"""
481+
def _validate_ssl_files_and_get_ssl_context(mwi_config_folder):
482+
"""Creates an SSL CONTEXT for use with the TCP Site.
483+
The certfile string must be the path to a single file in PEM format containing the
484+
certificate as well as any number of CA certificates needed to establish the certificate’s authenticity.
485+
The keyfile string, if present, must point to a file containing the private key in.
486+
Otherwise the private key will be taken from certfile as well.
487+
"""
488+
is_self_signed_certificates = False
489+
env_name_enable_ssl = mwi_env.get_env_name_enable_ssl()
490+
is_ssl_enabled = mwi_env._is_env_set_to_true(env_name_enable_ssl)
491+
env_name_ssl_key_file = mwi_env.get_env_name_ssl_key_file()
492+
env_name_ssl_cert_file = mwi_env.get_env_name_ssl_cert_file()
493+
494+
ssl_key_file, ssl_cert_file = (
495+
os.getenv(env_name_ssl_key_file, None),
496+
os.getenv(env_name_ssl_cert_file, None),
497+
)
483498

484-
# The certfile string must be the path to a single file in PEM format containing the
485-
# certificate as well as any number of CA certificates needed to establish the certificate’s authenticity.
486-
# The keyfile string, if present, must point to a file containing the private key in.
487-
# Otherwise the private key will be taken from certfile as well.
488-
import traceback
499+
# Don't use SSL if the user has explicitly disabled SSL communication or not set the respective env var
500+
if not is_ssl_enabled:
501+
if ssl_cert_file:
502+
logger.warn(
503+
f"Ignoring provided SSL files, as {env_name_enable_ssl} is either unset or set to false"
504+
)
505+
return None
489506

490-
if ssl_cert_file != None:
491-
try:
492-
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
493-
ssl_context.load_cert_chain(ssl_cert_file, ssl_key_file)
494-
logger.debug(f"Using SSL certification!")
495-
except Exception as e:
496-
# Something was wrong with the certificates provided
497-
error_message = "SSL certificates provided are invalid. Aborting..."
498-
logger.error(error_message)
499-
traceback.print_exc()
500-
logger.info("==== Fatal error : ===")
501-
print(e)
502-
# printing stack trace
503-
logger.info("======================")
504-
raise FatalError(error_message)
505-
else:
507+
# Validate that provided SSL files are valid files
508+
ssl_key_file, ssl_cert_file = mwi.validators.validate_ssl_key_and_cert_file(
509+
ssl_key_file, ssl_cert_file
510+
)
511+
512+
if not ssl_cert_file and not ssl_key_file:
513+
logger.debug("Using auto-generated self-signed certificates")
514+
515+
# certs dir under the MWI_CONFIG_FOLDER will hold the self-signed certificates
516+
mwi_certs_dir = mwi_config_folder / "certs"
517+
mwi_certs_dir.mkdir(parents=True, exist_ok=True)
518+
519+
# New certs are generated for every run leading to functionally reliable system, alternative is
520+
# to check for existing certs and have error handling around expired/bad certs.
521+
ssl_cert_file, ssl_key_file = generate_new_self_signed_certs(mwi_certs_dir)
522+
is_self_signed_certificates = True
523+
try:
524+
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
525+
ssl_context.load_cert_chain(ssl_cert_file, ssl_key_file)
526+
logger.debug("Certificate chain was correctly loaded")
527+
except Exception as e:
528+
logger.error(f"Unable to load certificates. Error: {e}")
529+
530+
# Setting to None to use http mode in the event of failing to setup self-signed certificates
506531
ssl_context = None
507532

533+
# Raise a fatal error only in the event of an exception while loading customer-supplied ssl files
534+
if not is_self_signed_certificates:
535+
raise FatalError(e)
536+
508537
return ssl_context
538+
539+
540+
def generate_new_self_signed_certs(mwi_certs_dir):
541+
"""
542+
Generates a new self-signed certificate and corresponding private key, saves them as PEM files in the specified directory.
543+
The certificate is valid for 365 days from the time of creation.
544+
545+
Parameters:
546+
- mwi_certs_dir (Path): A pathlib.Path object representing the directory where the certificate and key files will be saved.
547+
548+
Returns:
549+
- tuple: A tuple containing the file paths (as strings) to the newly created certificate and private key PEM files.
550+
The first element is the path to the certificate file (cert.pem), and the second is the path to the key file (key.pem).
551+
552+
Raises:
553+
- FileNotFoundError: If the mwi_certs_dir does not exist.
554+
- Any other exception that may occur during file writing or certificate generation.
555+
"""
556+
cert_file = priv_key_file = None
557+
try:
558+
# Generate private key
559+
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
560+
561+
# Self-signed certificate
562+
subject = issuer = x509.Name(
563+
[
564+
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
565+
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Massachusetts"),
566+
x509.NameAttribute(NameOID.LOCALITY_NAME, "Natick"),
567+
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "MathWorks Inc."),
568+
x509.NameAttribute(NameOID.COMMON_NAME, "mathworks.com"),
569+
]
570+
)
571+
cert = (
572+
x509.CertificateBuilder()
573+
.subject_name(subject)
574+
.issuer_name(issuer)
575+
.public_key(private_key.public_key())
576+
.serial_number(x509.random_serial_number())
577+
.not_valid_before(datetime.datetime.utcnow())
578+
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
579+
.sign(private_key, hashes.SHA256())
580+
)
581+
582+
# Write private key to file
583+
priv_key_file = mwi_certs_dir / "key.pem"
584+
with open(priv_key_file, "wb") as f:
585+
f.write(
586+
private_key.private_bytes(
587+
encoding=serialization.Encoding.PEM,
588+
format=serialization.PrivateFormat.TraditionalOpenSSL,
589+
encryption_algorithm=serialization.NoEncryption(),
590+
)
591+
)
592+
593+
# Write certificate to file
594+
cert_file = mwi_certs_dir / "cert.pem"
595+
with open(cert_file, "wb") as f:
596+
f.write(cert.public_bytes(serialization.Encoding.PEM))
597+
598+
except Exception as ex:
599+
logger.warn(
600+
f"Failed to generate self-signed certificates, proceeding with non-secure mode! Error: {ex}"
601+
)
602+
cert_file = priv_key_file = None
603+
604+
return cert_file, priv_key_file

0 commit comments

Comments
 (0)