blob: 3839cb0d571d7e8c309e1e8ac9b01ba7f2e37a23 [file] [log] [blame]
Philipp Schraderd0e33a42022-01-22 21:55:15 -08001"""Starts up Apache to provide HTTPS + LDAP for another web server.
2
3This 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
5in /tmp.
6
7Both Apache and the wrapped server binary are started by this script. The
8wrapped server should bind to the port specified by the APACHE_WRAPPED_PORT
9environment variable.
10
11See the documentation for apache_wrapper() for more information.
12"""
13
14import argparse
15import json
16import os
17from pathlib import Path
18import signal
Philipp Schrader4e661d62022-03-13 22:15:56 -070019import socket
Philipp Schraderd0e33a42022-01-22 21:55:15 -080020import subprocess
21import sys
22import tempfile
Philipp Schrader4e661d62022-03-13 22:15:56 -070023import time
Philipp Schraderd0e33a42022-01-22 21:55:15 -080024
25import jinja2
26
27DUMMY_CERT_ANSWERS = """\
28US
29California
30Mountain View
31FRC971
32Software
33frc971.org
34dummy@frc971.org
35"""
36
Philipp Schrader4e661d62022-03-13 22:15:56 -070037def wait_for_server(port: int):
38 """Waits for the server at the specified port to respond to TCP connections."""
39 while True:
40 try:
41 connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
42 connection.connect(("localhost", port))
43 connection.close()
44 break
45 except ConnectionRefusedError:
46 connection.close()
47 time.sleep(0.01)
48
Philipp Schraderd0e33a42022-01-22 21:55:15 -080049def main(argv):
50 parser = argparse.ArgumentParser()
51 parser.add_argument("--binary", type=str, required=True)
52 parser.add_argument("--https_port", type=int, default=7000)
53 parser.add_argument("--wrapped_port", type=int, default=7500)
54 parser.add_argument(
55 "--ldap_info",
56 type=str,
57 help="JSON file containing 'ldap_bind_dn', 'ldap_url', and 'ldap_password' entries.",
58 default="",
59 )
Philipp Schrader4e661d62022-03-13 22:15:56 -070060 args, unknown_args = parser.parse_known_args(argv[1:])
Philipp Schraderd0e33a42022-01-22 21:55:15 -080061
62 if not args.ldap_info:
63 args.ldap_info = os.path.join(os.environ["BUILD_WORKSPACE_DIRECTORY"], "ldap.json")
64
65 with open("tools/build_rules/apache_template.conf", "r") as file:
66 template = jinja2.Template(file.read())
67
68 with open(args.ldap_info, "r") as file:
69 substitutions = json.load(file)
70
71 for key in ("ldap_bind_dn", "ldap_url", "ldap_password"):
72 if key not in substitutions:
73 raise KeyError(f"The ldap_info JSON file must contain key '{key}'.")
74
75 substitutions.update({
76 "https_port": args.https_port,
77 "wrapped_port": args.wrapped_port,
78 })
79
80 config_text = template.render(substitutions)
81
82 with tempfile.TemporaryDirectory() as temp_dir:
83 temp_dir = Path(temp_dir)
84 with open(temp_dir / "apache2.conf", "w") as file:
85 file.write(config_text)
86
87 # Create a directory for error logs and such.
88 logs_dir = temp_dir / "logs"
89 os.mkdir(logs_dir)
90
91 print("-" * 60)
92 print(f"Logs are in {logs_dir}/")
93 print("-" * 60)
94
95 # Make modules available.
96 modules_path = Path("external/apache2/usr/lib/apache2/modules")
97 os.symlink(modules_path.resolve(), temp_dir / "modules")
98
99 # Generate a testing cert.
100 subprocess.run([
101 "openssl",
102 "req",
103 "-x509",
104 "-nodes",
105 "-days=365",
106 "-newkey=rsa:2048",
107 "-keyout=" + str(temp_dir / "apache-selfsigned.key"),
108 "-out=" + str(temp_dir / "apache-selfsigned.crt"),
109 ],
110 check=True,
111 input=DUMMY_CERT_ANSWERS,
112 text=True,
113 )
114
115 # Start the wrapped binary in the background.
116 # Tell it via the environment what port to listen on.
117 env = os.environ.copy()
118 env["APACHE_WRAPPED_PORT"] = str(args.wrapped_port)
Philipp Schrader4e661d62022-03-13 22:15:56 -0700119 wrapped_binary = subprocess.Popen([args.binary] + unknown_args, env=env)
Philipp Schraderd0e33a42022-01-22 21:55:15 -0800120
121 # Start the apache server.
122 env = os.environ.copy()
123 env["LD_LIBRARY_PATH"] = "external/apache2/usr/lib/x86_64-linux-gnu"
Philipp Schrader4e661d62022-03-13 22:15:56 -0700124 apache = subprocess.Popen(
125 ["external/apache2/usr/sbin/apache2", "-X", "-d", str(temp_dir)],
126 env=env,
127 )
128
129 wait_for_server(args.https_port)
130 wait_for_server(args.wrapped_port)
131 # Sleep to attempt to get the HTTPS message after the webserver message.
132 time.sleep(1)
133 print(f"Serving HTTPS on port {args.https_port}")
134
135 # Wait until we see a request to shut down.
136 signal.signal(signal.SIGINT, lambda signum, frame: None)
137 signal.signal(signal.SIGTERM, lambda signum, frame: None)
138 signal.pause()
139
140 print("\nShutting down apache and wrapped binary.")
141 apache.terminate()
142 wrapped_binary.terminate()
143 apache.wait()
144 wrapped_binary.wait()
Philipp Schraderd0e33a42022-01-22 21:55:15 -0800145
146if __name__ == "__main__":
147 sys.exit(main(sys.argv))