blob: 87d7224f48c9c4a3c61214b2a6df0bf790cf5591 [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
Ravago Jones5127ccc2022-07-31 16:32:45 -070037
Philipp Schrader4e661d62022-03-13 22:15:56 -070038def wait_for_server(port: int):
39 """Waits for the server at the specified port to respond to TCP connections."""
40 while True:
41 try:
42 connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
43 connection.connect(("localhost", port))
44 connection.close()
45 break
46 except ConnectionRefusedError:
47 connection.close()
48 time.sleep(0.01)
49
Ravago Jones5127ccc2022-07-31 16:32:45 -070050
Philipp Schraderd0e33a42022-01-22 21:55:15 -080051def main(argv):
Ravago Jones5127ccc2022-07-31 16:32:45 -070052 parser = argparse.ArgumentParser()
53 parser.add_argument("--binary", type=str, required=True)
54 parser.add_argument("--https_port", type=int, default=7000)
55 parser.add_argument("--wrapped_port", type=int, default=7500)
56 parser.add_argument(
57 "--ldap_info",
58 type=str,
59 help=
60 "JSON file containing 'ldap_bind_dn', 'ldap_url', and 'ldap_password' entries.",
61 default="",
Philipp Schraderd0e33a42022-01-22 21:55:15 -080062 )
Ravago Jones5127ccc2022-07-31 16:32:45 -070063 args, unknown_args = parser.parse_known_args(argv[1:])
Philipp Schraderd0e33a42022-01-22 21:55:15 -080064
Ravago Jones5127ccc2022-07-31 16:32:45 -070065 if not args.ldap_info:
66 args.ldap_info = os.path.join(os.environ["BUILD_WORKSPACE_DIRECTORY"],
67 "ldap.json")
Philipp Schraderd0e33a42022-01-22 21:55:15 -080068
Ravago Jones5127ccc2022-07-31 16:32:45 -070069 with open("tools/build_rules/apache_template.conf", "r") as file:
70 template = jinja2.Template(file.read())
Philipp Schrader4e661d62022-03-13 22:15:56 -070071
Ravago Jones5127ccc2022-07-31 16:32:45 -070072 with open(args.ldap_info, "r") as file:
73 substitutions = json.load(file)
Philipp Schrader4e661d62022-03-13 22:15:56 -070074
Ravago Jones5127ccc2022-07-31 16:32:45 -070075 for key in ("ldap_bind_dn", "ldap_url", "ldap_password"):
76 if key not in substitutions:
77 raise KeyError(
78 f"The ldap_info JSON file must contain key '{key}'.")
Philipp Schrader4e661d62022-03-13 22:15:56 -070079
Ravago Jones5127ccc2022-07-31 16:32:45 -070080 substitutions.update({
81 "https_port": args.https_port,
82 "wrapped_port": args.wrapped_port,
83 })
84
85 config_text = template.render(substitutions)
86
87 with tempfile.TemporaryDirectory() as temp_dir:
88 temp_dir = Path(temp_dir)
89 with open(temp_dir / "apache2.conf", "w") as file:
90 file.write(config_text)
91
92 # Create a directory for error logs and such.
93 logs_dir = temp_dir / "logs"
94 os.mkdir(logs_dir)
95
96 print("-" * 60)
97 print(f"Logs are in {logs_dir}/")
98 print("-" * 60)
99
100 # Make modules available.
101 modules_path = Path("external/apache2/usr/lib/apache2/modules")
102 os.symlink(modules_path.resolve(), temp_dir / "modules")
103
104 # Generate a testing cert.
105 subprocess.run(
106 [
107 "openssl",
108 "req",
109 "-x509",
110 "-nodes",
111 "-days=365",
112 "-newkey=rsa:2048",
113 "-keyout=" + str(temp_dir / "apache-selfsigned.key"),
114 "-out=" + str(temp_dir / "apache-selfsigned.crt"),
115 ],
116 check=True,
117 input=DUMMY_CERT_ANSWERS,
118 text=True,
119 )
120
121 # Start the wrapped binary in the background.
122 # Tell it via the environment what port to listen on.
123 env = os.environ.copy()
124 env["APACHE_WRAPPED_PORT"] = str(args.wrapped_port)
125 wrapped_binary = subprocess.Popen([args.binary] + unknown_args,
126 env=env)
127
128 # Start the apache server.
129 env = os.environ.copy()
130 env["LD_LIBRARY_PATH"] = "external/apache2/usr/lib/x86_64-linux-gnu"
131 apache = subprocess.Popen(
132 ["external/apache2/usr/sbin/apache2", "-X", "-d",
133 str(temp_dir)],
134 env=env,
135 )
136
137 wait_for_server(args.https_port)
138 wait_for_server(args.wrapped_port)
139 # Sleep to attempt to get the HTTPS message after the webserver message.
140 time.sleep(1)
141 print(f"Serving HTTPS on port {args.https_port}")
142
143 # Wait until we see a request to shut down.
144 signal.signal(signal.SIGINT, lambda signum, frame: None)
145 signal.signal(signal.SIGTERM, lambda signum, frame: None)
146 signal.pause()
147
148 print("\nShutting down apache and wrapped binary.")
149 apache.terminate()
150 wrapped_binary.terminate()
151 apache.wait()
152 wrapped_binary.wait()
153
Philipp Schraderd0e33a42022-01-22 21:55:15 -0800154
155if __name__ == "__main__":
Ravago Jones5127ccc2022-07-31 16:32:45 -0700156 sys.exit(main(sys.argv))