Repository: sanic-org/sanic

Issue:
Certificates not created with `mkcert` or `trustme` raise a RuntimeError
The `CertLoader` class in `sanic-org/sanic/sanic/worker/loader.py` checks the creator of the certificate. If the creator is not `mkcert` or `trustme` then it raises a `RuntimeError`. This will prevent Sanic from running with certificates from any other sources.


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/sanic-org/sanic/pull/2578?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) Report
Base: **88.036**% // Head: **88.000**% // Decreases project coverage by **`-0.036%`** :warning:
> Coverage data is based on head [(`7e45890`)](https://codecov.io/gh/sanic-org/sanic/pull/2578?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) compared to base [(`f891995`)](https://codecov.io/gh/sanic-org/sanic/commit/f891995b487f01ff1207afcd241ae359725a8e3c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org).
> Patch coverage: 50.000% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@              Coverage Diff              @@
##              main     #2578       +/-   ##
=============================================
- Coverage   88.036%   88.000%   -0.037%     
=============================================
  Files           71        71               
  Lines         5333      5342        +9     
  Branches       987       894       -93     
=============================================
+ Hits          4695      4701        +6     
- Misses         461       464        +3     
  Partials       177       177               
```


| [Impacted Files](https://codecov.io/gh/sanic-org/sanic/pull/2578?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) | Coverage Δ | |
|---|---|---|
| [sanic/http/tls/creators.py](https://codecov.io/gh/sanic-org/sanic/pull/2578/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org#diff-c2FuaWMvaHR0cC90bHMvY3JlYXRvcnMucHk=) | `92.561% <50.000%> (-3.053%)` | :arrow_down: |
| [sanic/worker/loader.py](https://codecov.io/gh/sanic-org/sanic/pull/2578/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org#diff-c2FuaWMvd29ya2VyL2xvYWRlci5weQ==) | `92.857% <50.000%> (-2.732%)` | :arrow_down: |
| [sanic/server/websockets/impl.py](https://codecov.io/gh/sanic-org/sanic/pull/2578/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org#diff-c2FuaWMvc2VydmVyL3dlYnNvY2tldHMvaW1wbC5weQ==) | `38.061% <0.000%> (+0.709%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/sanic-org/sanic/pull/2578?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org).

ahopkins: This pull request has been mentioned on **Sanic Community Discussion**. There might be relevant details there:

https://community.sanicframework.org/t/unknown-certificate-creator-error/1090/2


Patch:
diff --git a/sanic/worker/loader.py b/sanic/worker/loader.py
index abdcc987..344593db 100644
--- a/sanic/worker/loader.py
+++ b/sanic/worker/loader.py
@@ -5,18 +5,10 @@ import sys
 
 from importlib import import_module
 from pathlib import Path
-from typing import (
-    TYPE_CHECKING,
-    Any,
-    Callable,
-    Dict,
-    Optional,
-    Type,
-    Union,
-    cast,
-)
+from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union, cast
 
-from sanic.http.tls.creators import CertCreator, MkcertCreator, TrustmeCreator
+from sanic.http.tls.context import process_to_context
+from sanic.http.tls.creators import MkcertCreator, TrustmeCreator
 
 
 if TYPE_CHECKING:
@@ -106,21 +98,30 @@ class AppLoader:
 
 
 class CertLoader:
-    _creator_class: Type[CertCreator]
+    _creators = {
+        "mkcert": MkcertCreator,
+        "trustme": TrustmeCreator,
+    }
 
     def __init__(self, ssl_data: Dict[str, Union[str, os.PathLike]]):
-        creator_name = ssl_data.get("creator")
-        if creator_name not in ("mkcert", "trustme"):
+        self._ssl_data = ssl_data
+
+        creator_name = cast(str, ssl_data.get("creator"))
+
+        self._creator_class = self._creators.get(creator_name)
+        if not creator_name:
+            return
+
+        if not self._creator_class:
             raise RuntimeError(f"Unknown certificate creator: {creator_name}")
-        elif creator_name == "mkcert":
-            self._creator_class = MkcertCreator
-        elif creator_name == "trustme":
-            self._creator_class = TrustmeCreator
 
         self._key = ssl_data["key"]
         self._cert = ssl_data["cert"]
         self._localhost = cast(str, ssl_data["localhost"])
 
     def load(self, app: SanicApp):
+        if not self._creator_class:
+            return process_to_context(self._ssl_data)
+
         creator = self._creator_class(app, self._key, self._cert)
         return creator.generate_cert(self._localhost)


Test patch:
diff --git a/tests/test_tls.py b/tests/test_tls.py
index 6c369f92..497b67de 100644
--- a/tests/test_tls.py
+++ b/tests/test_tls.py
@@ -4,6 +4,7 @@ import ssl
 import subprocess
 
 from contextlib import contextmanager
+from multiprocessing import Event
 from pathlib import Path
 from unittest.mock import Mock, patch
 from urllib.parse import urlparse
@@ -636,3 +637,29 @@ def test_sanic_ssl_context_create():
 
     assert sanic_context is context
     assert isinstance(sanic_context, SanicSSLContext)
+
+
+def test_ssl_in_multiprocess_mode(app: Sanic, caplog):
+
+    ssl_dict = {"cert": localhost_cert, "key": localhost_key}
+    event = Event()
+
+    @app.main_process_start
+    async def main_start(app: Sanic):
+        app.shared_ctx.event = event
+
+    @app.after_server_start
+    async def shutdown(app):
+        app.shared_ctx.event.set()
+        app.stop()
+
+    assert not event.is_set()
+    with caplog.at_level(logging.INFO):
+        app.run(ssl=ssl_dict)
+    assert event.is_set()
+
+    assert (
+        "sanic.root",
+        logging.INFO,
+        "Goin' Fast @ https://127.0.0.1:8000",
+    ) in caplog.record_tuples
diff --git a/tests/worker/test_loader.py b/tests/worker/test_loader.py
index 6f953c54..d0d04e9a 100644
--- a/tests/worker/test_loader.py
+++ b/tests/worker/test_loader.py
@@ -86,6 +86,10 @@ def test_input_is_module():
 @patch("sanic.worker.loader.TrustmeCreator")
 @patch("sanic.worker.loader.MkcertCreator")
 def test_cert_loader(MkcertCreator: Mock, TrustmeCreator: Mock, creator: str):
+    CertLoader._creators = {
+        "mkcert": MkcertCreator,
+        "trustme": TrustmeCreator,
+    }
     MkcertCreator.return_value = MkcertCreator
     TrustmeCreator.return_value = TrustmeCreator
     data = {