Backstory

Back when macOS 10.14 Mojave was released, Apple made a change to the Apache web-server that ships with macOS, so that it wouldn't load any modules that aren't signed by either Apple or a paid developer account. This issue affected not only Passenger users, but also users of other 3rd party modules such as php.

Error version 1 (for unsigned module):

httpd: Syntax error on line 545 of /private/etc/apache2/httpd.conf: Syntax error on line 1 of /private/etc/apache2/other/passenger.conf: Cannot load /usr/local/opt/passenger-enterprise/libexec/buildout/apache2/mod_passenger.so into server: dlopen(/usr/local/opt/passenger-enterprise/libexec/buildout/apache2/mod_passenger.so, 10): no suitable image found.  Did find:
    /usr/local/opt/passenger-enterprise/libexec/buildout/apache2/mod_passenger.so: code signature in (/usr/local/opt/passenger-enterprise/libexec/buildout/apache2/mod_passenger.so) not valid for use in process using Library Validation: mapped file has no cdhash, completely unsigned? Code has to be at least ad-hoc signed.

/usr/local/opt/passenger-enterprise/libexec/buildout/apache2/mod_passenger.so: stat() failed with errno=22

Ad hoc signing the module, like the initial Apache error told me to, resulted in a different error telling me that the signature was not sufficient for loading into Apache.

Error version 2 for ad-hoc signed module:

httpd: Syntax error on line 545 of /private/etc/apache2/httpd.conf: Syntax error on line 1 of /private/etc/apache2/other/passenger.conf: Cannot load /usr/local/opt/passenger-enterprise/libexec/buildout/apache2/mod_passenger.so into server: dlopen(/usr/local/opt/passenger-enterprise/libexec/buildout/apache2/mod_passenger.so, 10): no suitable image found.  Did find:
    /usr/local/opt/passenger-enterprise/libexec/buildout/apache2/mod_passenger.so: code signature in (/usr/local/opt/passenger-enterprise/libexec/buildout/apache2/mod_passenger.so) not valid for use in process using Library Validation: mapped file has no Team ID and is not a platform binary (signed with custom identity or adhoc?)

/usr/local/opt/passenger-enterprise/libexec/buildout/apache2/mod_passenger.so: stat() failed with errno=22

To get a Team ID, you need a paid developer account which is a non-starter for most web developers so I filed a radar with Apple and in macOS 10.14.4 this change was reverted.

Contemporary

Fast forward to the release of Big Sur, where I noticed that Apache had a new message for me when I load Passenger:

AH06665: No code signing authority for module at /usr/local/opt/passenger/libexec/buildout/apache2/mod_passenger.so specified in LoadModule directive. Proceeding with loading process, but this will be an error condition in a future version of macOS.

Which is already better than the last approach because Apple is giving advance warning to users before things stop working. Looking at the /etc/apache2/httpd.conf config file that ships with macOS Big Sur, there's also a big clue as to how Apple expects developers to deal with this warning; in the module loading section of the file, this example is given:
# LoadModule my_custom_module /usr/local/libexec/mod_my_custom.so "My Signature Authority"

The "My Signature Authority" part at the end is new to Big Sur, and looks like we can specify which signing authority we want to allow for loading this Apache module. Which means it might not need to be from a paid developer account.

So to test my hypothesis the first thing we need is a code signing authority. Unfortunately Apple's directions for creating one are very sparse, and I also encountered a lot of random errors while I did so. So while I normally use GnuPG or OpenSSL to work with keys and certificates, in this case I just used the graphical tool.

Here's what worked for me in the end.

  1. Open Keychain Access.app Keychain-Access-Icon
  2. In the Keychain Access Menu under the Certificate Assistant menu item, choose Open… [1]Menus
  3. Click Continue to get to the first page of optionsStep-1
  4. Select Create a Certificate Authority (CA)Step-2
  5. Click Continue to get to the next page of options
  6. Choose a name for your new CAStep-3
  7. Choose the email for use with this CA
  8. Set the User Certificate dropdown to Code Signing
  9. Click Continue to get to the next page of options
  10. Click Continue to get to the next page of options[2]Step-4
  11. Fill in the rest of the fields for the CA Certificate[3]Step-5
  12. Click Continue to get to the next page of options
  13. Click Continue to get to the next page of optionsStep-6
  14. Click Continue to get to the next page of optionsStep-7
  15. Click Continue to get to the next page of optionsStep-8
  16. Click Continue to get to the next page of optionsStep-9
  17. Ensure that Code Signing is checked in the list of capabilitiesStep-10
  18. Click Continue to get to the next page of options
  19. Ensure that Code Signing is the only capability checked in the list of capabilities, and that This extension is critical is checkedStep-11
  20. Click Continue to get to the next page of options
  21. Click Continue to get to the next page of optionsStep-12
  22. Click Continue to get to the next page of optionsStep-13
  23. Click Continue to get to the next page of optionsStep-14
  24. Click Continue to get to the next page of optionsStep-15
  25. Click Create to create your Certificate AuthorityStep-16
  26. You can close this windowStep-17
  27. Right click your CA in the main Keychain Access window and choose Get InfoStep-27
  28. Open the Trust disclosure triangleStep-28
  29. Set the topmost trust setting to Always TrustStep-29
  30. Close the window (you will be prompted to authenticate)Step-18

You now have your own Certificate Authority which can issue code signing certificates, progress! Now let's use it do just that and create a code signing certificate.

  1. Open the Certificate assistant again to the What would you like to do? step (steps #2 & #3 above)
  2. Choose Create a certificate for yourselfStep-30
  3. Click Continue to get to the next page of options
  4. Choose a name for the certificateStep-31-1
  5. Set the Identity Type to Leaf
  6. Set the Certificate Type to Code Signing
  7. Click Create to start the creation process
  8. Click Continue in the popup to create the certificate[4]Step-32
  9. Click Create to use the CA we created earlier to sign our new certificateStep-33-1
  10. Make note of the Common Name field in this window, as we'll need that value laterStep-34-1
  11. Close the window

We now have a code signing certificate that is trusted by our mac. The next step is to sign the Apache module. This requires using the Terminal app to run the command described below.
In this command the -s flag is followed by the cert's Common Name we took note of in step #10. And the --keychain flag is followed by the path to the login keychain for your user, which will likely be the same as the example. And the last argument is the path to the module we are signing, in this case the Passenger module.

codesign -s "Camden Jared Narzt" --keychain ~/Library/Keychains/login.keychain-db /usr/local/opt/passenger/libexec/buildout/apache2/mod_passenger.so

Then edit the apache config file you use to load the module; in my case: /etc/apache2/other/passenger.conf.

Add the same Common Name you used in the codesign command in double quotes to the LoadModule line like so:

LoadModule passenger_module /usr/local/opt/passenger/libexec/buildout/apache2/mod_passenger.so "Camden Jared Narzt"

Checking the config with apachectl -t we see a new message:

AH06662: Allowing module loading process to continue for module at /usr/local/opt/passenger/libexec/buildout/apache2/mod_passenger.so because module signature matches authority "Camden Jared Narzt" specified in LoadModule directive

Indicating that our system considers the module to be validly signed and will load it without issue going forward.

Conclusion

Now you might wonder why we don't sign the module for you?

  1. For this to work you must have the code signing certificate which signed the module in your keychain, and we don't distribute a code signing certificate yet[5], nor does our mac build box have the ability to sign modules at this time.
  2. You may need the Certificate Authority certificate too in order to trust the code signing certificate, and we don't want the responsibility of being a root authority on your system.
  3. The most common method of installing Passenger on macOS is via Homebrew, which means we don't have the opportunity to sign the module, and Homebrew doesn't currently use or distribute a code signing certificate either.

Footnotes

  1. Even though there are more direct shortcuts to what we want to do in this sub-menu, they can trigger weird issues due to some kind of contextual behaviour bug where Certificate Assistant tries to use a random key from your system to generate the Certificate Authority Back
  2. You shouldn't need to extend the Validity Period here, as long as the certificate is valid when the signature is made it should keep working Back
  3. If this isn't your first Certificate Authority, you may be asked to choose a previous CA to cross sign your new CA Back
  4. We don't care that others won't trust this certificate, we set our mac to trust it back when we trusted the certificate authority in step #30 Back
  5. We do distribute a code signing key, however that is insufficient for this process Back