Philipp Schrader | d0e33a4 | 2022-01-22 21:55:15 -0800 | [diff] [blame^] | 1 | """Starts up Apache to provide HTTPS + LDAP for another web server. |
| 2 | |
| 3 | This script is used by the apache_wrapper() rule as the main entrypoint for its |
| 4 | "executable". This script sets up a minimal Apache environment in a directory |
| 5 | in /tmp. |
| 6 | |
| 7 | Both Apache and the wrapped server binary are started by this script. The |
| 8 | wrapped server should bind to the port specified by the APACHE_WRAPPED_PORT |
| 9 | environment variable. |
| 10 | |
| 11 | See the documentation for apache_wrapper() for more information. |
| 12 | """ |
| 13 | |
| 14 | import argparse |
| 15 | import json |
| 16 | import os |
| 17 | from pathlib import Path |
| 18 | import signal |
| 19 | import subprocess |
| 20 | import sys |
| 21 | import tempfile |
| 22 | |
| 23 | import jinja2 |
| 24 | |
| 25 | DUMMY_CERT_ANSWERS = """\ |
| 26 | US |
| 27 | California |
| 28 | Mountain View |
| 29 | FRC971 |
| 30 | Software |
| 31 | frc971.org |
| 32 | dummy@frc971.org |
| 33 | """ |
| 34 | |
| 35 | def main(argv): |
| 36 | parser = argparse.ArgumentParser() |
| 37 | parser.add_argument("--binary", type=str, required=True) |
| 38 | parser.add_argument("--https_port", type=int, default=7000) |
| 39 | parser.add_argument("--wrapped_port", type=int, default=7500) |
| 40 | parser.add_argument( |
| 41 | "--ldap_info", |
| 42 | type=str, |
| 43 | help="JSON file containing 'ldap_bind_dn', 'ldap_url', and 'ldap_password' entries.", |
| 44 | default="", |
| 45 | ) |
| 46 | args = parser.parse_args(argv[1:]) |
| 47 | |
| 48 | if not args.ldap_info: |
| 49 | args.ldap_info = os.path.join(os.environ["BUILD_WORKSPACE_DIRECTORY"], "ldap.json") |
| 50 | |
| 51 | with open("tools/build_rules/apache_template.conf", "r") as file: |
| 52 | template = jinja2.Template(file.read()) |
| 53 | |
| 54 | with open(args.ldap_info, "r") as file: |
| 55 | substitutions = json.load(file) |
| 56 | |
| 57 | for key in ("ldap_bind_dn", "ldap_url", "ldap_password"): |
| 58 | if key not in substitutions: |
| 59 | raise KeyError(f"The ldap_info JSON file must contain key '{key}'.") |
| 60 | |
| 61 | substitutions.update({ |
| 62 | "https_port": args.https_port, |
| 63 | "wrapped_port": args.wrapped_port, |
| 64 | }) |
| 65 | |
| 66 | config_text = template.render(substitutions) |
| 67 | |
| 68 | with tempfile.TemporaryDirectory() as temp_dir: |
| 69 | temp_dir = Path(temp_dir) |
| 70 | with open(temp_dir / "apache2.conf", "w") as file: |
| 71 | file.write(config_text) |
| 72 | |
| 73 | # Create a directory for error logs and such. |
| 74 | logs_dir = temp_dir / "logs" |
| 75 | os.mkdir(logs_dir) |
| 76 | |
| 77 | print("-" * 60) |
| 78 | print(f"Logs are in {logs_dir}/") |
| 79 | print("-" * 60) |
| 80 | |
| 81 | # Make modules available. |
| 82 | modules_path = Path("external/apache2/usr/lib/apache2/modules") |
| 83 | os.symlink(modules_path.resolve(), temp_dir / "modules") |
| 84 | |
| 85 | # Generate a testing cert. |
| 86 | subprocess.run([ |
| 87 | "openssl", |
| 88 | "req", |
| 89 | "-x509", |
| 90 | "-nodes", |
| 91 | "-days=365", |
| 92 | "-newkey=rsa:2048", |
| 93 | "-keyout=" + str(temp_dir / "apache-selfsigned.key"), |
| 94 | "-out=" + str(temp_dir / "apache-selfsigned.crt"), |
| 95 | ], |
| 96 | check=True, |
| 97 | input=DUMMY_CERT_ANSWERS, |
| 98 | text=True, |
| 99 | ) |
| 100 | |
| 101 | # Start the wrapped binary in the background. |
| 102 | # Tell it via the environment what port to listen on. |
| 103 | env = os.environ.copy() |
| 104 | env["APACHE_WRAPPED_PORT"] = str(args.wrapped_port) |
| 105 | wrapped_binary = subprocess.Popen([args.binary], env=env) |
| 106 | |
| 107 | # Start the apache server. |
| 108 | env = os.environ.copy() |
| 109 | env["LD_LIBRARY_PATH"] = "external/apache2/usr/lib/x86_64-linux-gnu" |
| 110 | try: |
| 111 | subprocess.run( |
| 112 | ["external/apache2/usr/sbin/apache2", "-X", "-d", str(temp_dir)], |
| 113 | check=True, |
| 114 | env=env, |
| 115 | ) |
| 116 | finally: |
| 117 | wrapped_binary.send_signal(signal.SIGINT) |
| 118 | wrapped_binary.wait() |
| 119 | |
| 120 | if __name__ == "__main__": |
| 121 | sys.exit(main(sys.argv)) |