Repository: certbot/certbot

Issue:
Error about being unable to staple a must staple cert
If the user requests a cert with `--must-staple`, is asking `certbot` to install it, and the installer doesn't support OCSP-stapling, we should error out before requesting the cert with a helpful error message.


Discussion:
This change should be in `certbot.main.run`. If `config.must_staple` is set, after calling `choose_configurator_plugins` you should check if the installer supports OCSP stapling by calling `supported_enchancements` on it which returns a list of strings and seeing if `ocsp-stapling` is included in list. If it's not, we should return a string like "You included --must-staple but the selected installer doesn't support OCSP stapling."

You should also add a unit test in `certbot/tests/main_test.py` which checks for this behavior.
We've made a lot of changes to Certbot since this issue was opened. If you still have this issue with an up-to-date version of Certbot, can you please add a comment letting us know? This helps us to better see what issues are still affecting our users. If there is no further activity, this issue will be automatically closed.

I still think we should do this.

Resolving patch:
diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py
--- a/certbot/certbot/_internal/main.py
+++ b/certbot/certbot/_internal/main.py
@@ -1394,6 +1394,20 @@ def run(config: configuration.NamespaceConfig,
     except errors.PluginSelectionError as e:
         return str(e)
 
+    if config.must_staple and installer and "staple-ocsp" not in installer.supported_enhancements():
+        raise errors.NotSupportedError(
+            "Must-Staple extension requested, but OCSP stapling is not supported by the selected "
+            f"installer ({config.installer})\n\n"
+            "You can either:\n"
+            " * remove the --must-staple option from the command line and obtain a certificate "
+            "without the Must-Staple extension, or;\n"
+            " * use the `certonly` subcommand and manually install the certificate into the  "
+            "intended service (e.g. webserver). You must also then manually enable OCSP stapling, "
+            "as it is required for certificates with the Must-Staple extension to "
+            "function properly.\n"
+            " * choose a different installer plugin (such as --nginx or --apache), if possible."
+        )
+
     # Preflight check for enhancement support by the selected installer
     if not enhancements.are_supported(config, installer):
         raise errors.NotSupportedError("One ore more of the requested enhancements "
diff --git a/certbot/certbot/configuration.py b/certbot/certbot/configuration.py
--- a/certbot/certbot/configuration.py
+++ b/certbot/certbot/configuration.py
@@ -120,7 +120,7 @@ def key_type(self, ktype: str) -> None:
 
     @property
     def must_staple(self) -> bool:
-        """Adds the OCSP Must Staple extension to the certificate.
+        """Adds the OCSP Must-Staple extension to the certificate.
 
         Autoconfigures OCSP Stapling for supported setups
         (Apache version >= 2.3.3 ).
diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py
--- a/certbot/certbot/crypto_util.py
+++ b/certbot/certbot/crypto_util.py
@@ -143,7 +143,7 @@ def generate_csr(privkey: util.Key, names: Union[List[str], Set[str]], path: str
     :type privkey: :class:`certbot.util.Key`
     :param set names: `str` names to include in the CSR
     :param str path: Certificate save directory.
-    :param bool must_staple: If true, include the TLS Feature extension "OCSP Must Staple"
+    :param bool must_staple: If true, include the TLS Feature extension "OCSP Must-Staple"
     :param bool strict_permissions: If true and path exists, an exception is raised if
         the directory doesn't have 0755 permissions or isn't owned by the current user.
 


Test patch:
diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py
--- a/certbot/tests/main_test.py
+++ b/certbot/tests/main_test.py
@@ -197,6 +197,15 @@ def test_cert_success_install_error(self, mock_install_cert):
         # The final success message shouldn't be shown
         self.mock_success_installation.assert_not_called()
 
+    @mock.patch('certbot._internal.main.plug_sel.choose_configurator_plugins')
+    def test_run_must_staple_not_supported(self, mock_choose):
+        mock_choose.return_value = (null.Installer(self.config, "null"), None)
+        plugins = disco.PluginsRegistry.find_all()
+        self.config.must_staple = True
+        self.assertRaises(errors.NotSupportedError,
+                          main.run,
+                          self.config, plugins)
+
 class CertonlyTest(unittest.TestCase):
     """Tests for certbot._internal.main.certonly."""