Import apache2 for testing
Create `971-Robot-Code/ldap.json`. It is a JSON file with the LDAP URL
and password.
{
"ldap_bind_dn": "...",
"ldap_url": "...",
"ldap_password": "..."
}
Run like this:
$ bazel run //build_tests:apache_https_demo
Then you can navigate to `https://localhost:7000`.
If the ports are taken, customize with:
* `--https_port`
* `--wrapped_port`
Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I56432388b774932fc6cf295da52ac8bc9e974cd7
diff --git a/.gitignore b/.gitignore
index 82b963a..8c6aa64 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,10 @@
/bazel-*
/compile_commands.json
+# The `tools/build_rules/apache_runner.py` helper looks for this file by
+# default. We don't want folks to check it in.
+/ldap.json
+
# Hide vagrant's files that unfortunately make it into the source tree when you
# run "vagrant up".
/vm/.vagrant/
diff --git a/WORKSPACE b/WORKSPACE
index c15c957..4aaad5b 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -7,6 +7,10 @@
python_debs = "files",
)
load(
+ "//debian:apache2.bzl",
+ apache2_debs = "files",
+)
+load(
"//debian:patch.bzl",
patch_debs = "files",
)
@@ -82,6 +86,8 @@
generate_repositories_for_debs(ssh_debs)
+generate_repositories_for_debs(apache2_debs)
+
generate_repositories_for_debs(patch_debs)
generate_repositories_for_debs(pandoc_debs)
@@ -490,6 +496,13 @@
)
http_archive(
+ name = "apache2",
+ build_file = "@//debian:apache2.BUILD",
+ sha256 = "98b0ad6d911751ba0aa486429e6278f995e7bbabd928c7d3d44c888fa2bf371b",
+ url = "https://www.frc971.org/Build-Dependencies/apache2.tar.gz",
+)
+
+http_archive(
name = "pandoc",
build_file = "@//debian:pandoc.BUILD",
sha256 = "9f7a7adb3974a1f14715054c349ff3edc2909e920dbe3438fca437a83845f3c4",
diff --git a/build_tests/BUILD b/build_tests/BUILD
index c74407e..ca34de2 100644
--- a/build_tests/BUILD
+++ b/build_tests/BUILD
@@ -1,6 +1,7 @@
load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library")
load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_py_library")
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+load("//tools/build_rules:apache.bzl", "apache_wrapper")
cc_test(
name = "gflags_build_test",
@@ -113,3 +114,15 @@
visibility = ["//visibility:private"],
deps = ["//build_tests/go_greeter"],
)
+
+py_binary(
+ name = "dummy_http_server",
+ srcs = ["dummy_http_server.py"],
+ target_compatible_with = ["@platforms//cpu:x86_64"],
+)
+
+apache_wrapper(
+ name = "apache_https_demo",
+ binary = ":dummy_http_server",
+ target_compatible_with = ["@platforms//cpu:x86_64"],
+)
diff --git a/build_tests/dummy_http_server.py b/build_tests/dummy_http_server.py
new file mode 100644
index 0000000..f65122a
--- /dev/null
+++ b/build_tests/dummy_http_server.py
@@ -0,0 +1,48 @@
+"""This is a dummy server to demonstrate the apache_wrapper() rule.
+
+Don't run this server on its own. Instead run the
+`//build_tests:apache_https_demo` binary.
+
+When you authenticate against Apache via LDAP, this server prints the username
+you authenticated with and the path from the GET request.
+"""
+
+import base64
+from http.server import BaseHTTPRequestHandler, HTTPServer
+import os
+
+def parse_auth(authorization):
+ auth_type, auth_info = authorization.split(" ", maxsplit=1)
+ if auth_type != "Basic":
+ raise ValueError(f"Unknown auth type: {auth_type}")
+ username, _ = base64.b64decode(auth_info).decode().split(":", 1)
+ return username
+
+class Server(BaseHTTPRequestHandler):
+ def _set_response(self):
+ self.send_response(200)
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+
+ def _write(self, text):
+ self.wfile.write(text.encode("utf-8"))
+
+ def do_GET(self):
+ self._set_response()
+ self._write(f"GET request for {self.path}")
+ self._write("<br/>")
+ username = parse_auth(self.headers["Authorization"])
+ self._write(f"Hello, {username}")
+
+def main():
+ port = int(os.environ["APACHE_WRAPPED_PORT"])
+ server_address = ("", port)
+ httpd = HTTPServer(server_address, Server)
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ pass
+ httpd.server_close()
+
+if __name__ == "__main__":
+ main()
diff --git a/debian/BUILD b/debian/BUILD
index 0540a28..76bcf28 100644
--- a/debian/BUILD
+++ b/debian/BUILD
@@ -3,6 +3,10 @@
python_debs = "files",
)
load(
+ ":apache2.bzl",
+ apache2_debs = "files",
+)
+load(
":patch.bzl",
patch_debs = "files",
)
@@ -112,6 +116,26 @@
)
download_packages(
+ name = "download_apache2_packages",
+ excludes = [
+ "libaprutil1-dbd-mysql",
+ "libaprutil1-dbd-odbc",
+ "libaprutil1-dbd-pgsql",
+ "libaprutil1-dbd-freetds",
+ "libstdc++6",
+ "lsb-base",
+ "debconf",
+ "libc6-dev",
+ ],
+ force_includes = [
+ "libaprutil1",
+ ],
+ packages = [
+ "apache2",
+ ],
+)
+
+download_packages(
name = "download_python_deps",
excludes = [
"libblas.so.3",
@@ -253,6 +277,12 @@
)
generate_deb_tarball(
+ name = "apache2",
+ files = apache2_debs,
+ target_compatible_with = ["@platforms//os:linux"],
+)
+
+generate_deb_tarball(
name = "patch",
files = patch_debs,
target_compatible_with = ["@platforms//os:linux"],
diff --git a/debian/apache2.BUILD b/debian/apache2.BUILD
new file mode 100644
index 0000000..7f3f87c
--- /dev/null
+++ b/debian/apache2.BUILD
@@ -0,0 +1,5 @@
+filegroup(
+ name = "all_files",
+ srcs = glob(["**"]),
+ visibility = ["//visibility:public"],
+)
diff --git a/debian/apache2.bzl b/debian/apache2.bzl
new file mode 100644
index 0000000..3d557c9
--- /dev/null
+++ b/debian/apache2.bzl
@@ -0,0 +1,59 @@
+files = {
+ "apache2-bin_2.4.52-1~deb11u2_amd64.deb": "dc0b906cb78fb55238bc1ddda5dac6917cbf1027a4372944e24a1a16aa8c9fdc",
+ "apache2-data_2.4.52-1~deb11u2_all.deb": "8fac5b05668e3d01e97f4ec2e8c52d9b138da0e2109425d6eacbcd005651068e",
+ "apache2-utils_2.4.52-1~deb11u2_amd64.deb": "a164cb0109bfbadb932cbd0a7cf8eeb1b4ed1242bc6e44394f5a343589ddca39",
+ "apache2_2.4.52-1~deb11u2_amd64.deb": "00bd5ca827ea67e0d2a10e6191595b26261f902c66096bd9e2b80fe6b606945a",
+ "debconf_1.5.77_all.deb": "d9ee4dff77aaad12674eed3ccefdcccd332424c9e2ac2ac00a37a1e06c84ab70",
+ "dpkg_1.20.9_amd64.deb": "ac90a4705b36f2d952a4a745ad944b33ff60c5ec69a5f89e4559a89bd0c53e01",
+ "gcc-10-base_10.2.1-6_amd64.deb": "be65535e94f95fbf04b104e8ab36790476f063374430f7dfc6c516cbe2d2cd1e",
+ "init-system-helpers_1.60_all.deb": "43420922c5e3aa747f8854236bf381a35179bba3885b242edb104751dad20644",
+ "libacl1_2.2.53-10_amd64.deb": "aa18d721be8aea50fbdb32cd9a319cb18a3f111ea6ad17399aa4ba9324c8e26a",
+ "libapr1_1.7.0-6+deb11u1_amd64.deb": "b95ce92c2530ba9c3a9af906f6ad27a0894efbefe9f0a2d9f2f91c6bdcdaf874",
+ "libaprutil1-dbd-sqlite3_1.6.1-5_amd64.deb": "18778f0cfc866d6230ab4f14ec3248d214e0f2a6efec5ded409aa6709d2d9255",
+ "libaprutil1-ldap_1.6.1-5_amd64.deb": "902a329ef05b94380ba9f7e413bd53086e096dc20cb0a913a4f346febc1e69e5",
+ "libaprutil1_1.6.1-5_amd64.deb": "d6aa77a54ed7533ea5c00240a677009f1b5d4e85f1607980d51ba35768c2eb39",
+ "libbrotli1_1.0.9-2+b2_amd64.deb": "65ca7d8b03e9dac09c5d544a89dd52d1aeb74f6a19583d32e4ff5f0c77624c24",
+ "libbz2-1.0_1.0.8-4_amd64.deb": "16e27c3ebd97981e70db3733f899963362748f178a62644df69d1f247e741379",
+ "libc6_2.31-13+deb11u2_amd64.deb": "3d9421c3fc0ef0d8ce57c0a149e1f8dbad78aba067f120be9e652af28902e346",
+ "libcrypt1_4.4.18-4_amd64.deb": "f617952df0c57b4ee039448e3941bccd3f97bfff71e9b0f87ca6dae15cb3f5ef",
+ "libcurl4_7.74.0-1.3+deb11u1_amd64.deb": "6f9c494eecc920899bb2c72d1a507a34b3703105778b0b9b9ae9aebdbdffcaab",
+ "libdb5.3_5.3.28+dfsg1-0.8_amd64.deb": "00b9e63e287f45300d4a4f59b6b88e25918443c932ae3e5845d5761ae193c530",
+ "libexpat1_2.2.10-2_amd64.deb": "eda6663f34375a9456c8c701002f1271bc90ac2627b9fb0892474e65eae1b668",
+ "libgcc-s1_10.2.1-6_amd64.deb": "e478f2709d8474165bb664de42e16950c391f30eaa55bc9b3573281d83a29daf",
+ "libgcrypt20_1.8.7-6_amd64.deb": "7a2e0eef8e0c37f03f3a5fcf7102a2e3dc70ba987f696ab71949f9abf36f35ef",
+ "libgdbm-compat4_1.19-2_amd64.deb": "e62caed68b0ffaa03b5fa539d6fdc08c4151f66236d5878949bead0b71b7bb09",
+ "libgdbm6_1.19-2_amd64.deb": "e54cfe4d8b8f209bb7df31a404ce040f7c2f9b1045114a927a7e1061cdf90727",
+ "libgpg-error0_1.38-2_amd64.deb": "16a507fb20cc58b5a524a0dc254a9cb1df02e1ce758a2d8abde0bc4a3c9b7c26",
+ "libicu67_67.1-7_amd64.deb": "2bf5c46254f527865bfd6368e1120908755fa57d83634bd7d316c9b3cfd57303",
+ "libjansson4_2.13.1-1.1_amd64.deb": "7025d03e4ad4177a06692bd8e6edcbdebf7fd3897e5f29b70ae968e17e57f6fa",
+ "liblua5.3-0_5.3.3-1.1+b1_amd64.deb": "98d5a85075a936ff014f639552882914064e46bfbbdc4bc80e80db33a9c77602",
+ "liblz4-1_1.9.3-2_amd64.deb": "79ac6e9ca19c483f2e8effcc3401d723dd9dbb3a4ae324714de802adb21a8117",
+ "liblzma5_5.2.5-2_amd64.deb": "eed58eb6b62208685dbe42a05582e78bd48c8d879d7ae0b61f114265d18824db",
+ "libncurses6_6.2+20201114-2_amd64.deb": "dfe45cb6ab048d1182175df55b007a4a188515c6d764a4dd5a44a0b47b6286a1",
+ "libncursesw6_6.2+20201114-2_amd64.deb": "ee3cd315dfa18865cf888ba6813a552077a4f3d1439dd225e4a0d0fee53aadc2",
+ "libnghttp2-14_1.43.0-1_amd64.deb": "a1a8aae24ced43025c94a9cb0c0eabfb3fc070785de9ee51c9a3a4fe86f0d11e",
+ "libpcre2-8-0_10.36-2_amd64.deb": "d31e4d6c04e847194b36b13ab59c578775aa12f04c48a840922796bd1f5eb32a",
+ "libpcre3_8.39-13_amd64.deb": "48efcf2348967c211cd9408539edf7ec3fa9d800b33041f6511ccaecc1ffa9d0",
+ "libperl5.32_5.32.1-4+deb11u2_amd64.deb": "224cafe65968deb83168113b74dff2d2f13b115a41d99eb209ed3b8f981df0b3",
+ "libprocps8_3.3.17-5_amd64.deb": "0a60017f0229cd4eec95b9f354c68312cc4ca4770ba8c01f545fd9c02b34e8a0",
+ "libpsl5_0.21.0-1.2_amd64.deb": "d716f5b4346ec85bb728f4530abeb1da4a79f696c72d7f774c59ba127c202fa7",
+ "librtmp1_2.4+20151223.gitfa8646d.1-2+b2_amd64.deb": "e1f69020dc2c466e421ec6a58406b643be8b5c382abf0f8989011c1d3df91c87",
+ "libselinux1_3.1-3_amd64.deb": "339f5ede10500c16dd7192d73169c31c4b27ab12130347275f23044ec8c7d897",
+ "libsqlite3-0_3.34.1-3_amd64.deb": "a0b8d3acf4a0483048637637d269be93af48d5c16f6f139f53edd13384ad4686",
+ "libssh2-1_1.9.0-2_amd64.deb": "f730fe45716a206003597819ececeeffe0fff754bdbbd0105425a177aa20a2de",
+ "libssl1.1_1.1.1k-1+deb11u1_amd64.deb": "82e6ded36e4fa4c28dcec00369b36ee242975f4c110f755a970a56d03d410ffb",
+ "libsystemd0_247.3-6_amd64.deb": "8c948d9d97178e6617f549822db2b89e23b1bfa1ee745ffbf0e41b6ee64f8737",
+ "libtinfo6_6.2+20201114-2_amd64.deb": "aeaf942c71ecc0ed081efdead1a1de304dcd513a9fc06791f26992e76986597b",
+ "libuuid1_2.36.1-8_amd64.deb": "94f13f58ac45ae850559e6bfe1a02be72566c66761e628a2599cc85066cb84d3",
+ "libxml2_2.9.10+dfsg-6.7_amd64.deb": "023296a15e1a28607609cb15c7ca0dd8a25160f3e89a0da58368319c7e17d4e0",
+ "libzstd1_1.4.8+dfsg-2.1_amd64.deb": "5dcadfbb743bfa1c1c773bff91c018f835e8e8c821d423d3836f3ab84773507b",
+ "mailcap_3.69_all.deb": "63fa5520f05d2aea5ca23eee95981a5e029608e1186ded4143470c8f84184158",
+ "media-types_4.0.0_all.deb": "f9835dcf3cdbaf163104d4e511c9c4e0f41a56822e147e57f28f749fcbf7d44c",
+ "mime-support_3.66_all.deb": "b964e671e6c47674879a3e54130b6737e8760fbd3da6afcc015faa174af98ba0",
+ "perl-base_5.32.1-4+deb11u2_amd64.deb": "018a3e48e58cbc478d3a4365090fb1daa151769f90f9b45984ec9d056ef96adc",
+ "perl-modules-5.32_5.32.1-4+deb11u2_all.deb": "6fa15be322c3c89ec4a07d704ad58d4a2d1aabf866135a859f6d8d58c59e9df4",
+ "perl_5.32.1-4+deb11u2_amd64.deb": "1cebc4516ed7c240b812c7bdd7e6ea0810f513152717ca17ce139ee0dfbc7b0d",
+ "procps_3.3.17-5_amd64.deb": "ac8edf0517abe09637c36651cb6a59e10948b2879f3af9003b9145b2128a7a08",
+ "tar_1.34+dfsg-1_amd64.deb": "bd8e963c6edcf1c806df97cd73560794c347aa94b9aaaf3b88eea585bb2d2f3c",
+ "zlib1g_1.2.11.dfsg-2_amd64.deb": "af36bf9249db4372a014a21ff2b84cc5a6808e3c878feda652c78e871d5964ad",
+}
diff --git a/debian/download_packages.py b/debian/download_packages.py
index e8caebd..be43120 100755
--- a/debian/download_packages.py
+++ b/debian/download_packages.py
@@ -92,6 +92,9 @@
if package == b'libblas.so.3':
yield b'libblas3'
continue
+ if package == b'debconf-2.0':
+ yield b'debconf'
+ continue
yield package
def download_deps(apt_args, packages, excludes, force_includes):
diff --git a/tools/build_rules/BUILD b/tools/build_rules/BUILD
index cf27ca0..aeb8517 100644
--- a/tools/build_rules/BUILD
+++ b/tools/build_rules/BUILD
@@ -12,3 +12,15 @@
visibility = ["//visibility:public"],
deps = ["@python_jinja2"],
)
+
+py_binary(
+ name = "apache_runner",
+ srcs = ["apache_runner.py"],
+ data = [
+ "apache_template.conf",
+ "@apache2//:all_files",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+ deps = ["@python_jinja2"],
+)
diff --git a/tools/build_rules/apache.bzl b/tools/build_rules/apache.bzl
new file mode 100644
index 0000000..9fb2ef5
--- /dev/null
+++ b/tools/build_rules/apache.bzl
@@ -0,0 +1,82 @@
+def _apache_binary_impl(ctx):
+ binary_path = ctx.attr.binary.files_to_run.executable.short_path
+
+ out = ctx.actions.declare_file(ctx.label.name)
+ ctx.actions.write(out, """\
+#!/bin/bash
+
+exec ./tools/build_rules/apache_runner --binary "{}" "$@"
+""".format(binary_path), is_executable = True)
+
+ # Collect files and runfiles for the tools that we're wrapping.
+ files = depset(transitive = [
+ ctx.attr._apache_runner.files,
+ ctx.attr.binary.files,
+ ])
+
+ runfiles = ctx.attr._apache_runner.default_runfiles
+ runfiles = runfiles.merge(ctx.attr.binary.default_runfiles)
+
+ return [
+ DefaultInfo(
+ executable = out,
+ files = files,
+ runfiles = runfiles,
+ ),
+ ]
+
+apache_wrapper = rule(
+ implementation = _apache_binary_impl,
+ attrs = {
+ "binary": attr.label(
+ mandatory = True,
+ executable = True,
+ cfg = "target",
+ doc = "The binary that we're wrapping with LDAP+HTTPS.",
+ ),
+ "_apache_runner": attr.label(
+ default = "@//tools/build_rules:apache_runner",
+ executable = True,
+ cfg = "target",
+ ),
+ },
+ doc = """\
+This rule wraps another web server and provides LDAP and HTTPS support.
+
+It's not intended to be used in production. It's intended to provide team
+members with a way to test their code in a production-like environment. E.g. to
+test whether your server makes use of LDAP credentials correctly.
+
+Write this as a wrapper around another binary like so:
+
+ apache_wrapper(
+ name = "wrapped_server",
+ binary = "//path/to:server_binary",
+ )
+
+Then you can run Apache and the wrapped binary like so:
+
+ $ bazel run :wrapped_server
+
+The wrapped binary can find the port that Apache is wrapping via the
+APACHE_WRAPPED_PORT environment variable.
+
+This rule assumes that you have a file at the root of the workspace called
+"ldap.json". You can customize this path with the `--ldap_info` argument. The
+JSON file has to have these three entries in it:
+
+ {
+ "ldap_bind_dn": "...",
+ "ldap_url": "...",
+ "ldap_password": "..."
+ }
+
+where the "..." values are replaced with the information to connect to an LDAP
+server. If you want to connect to our FRC971 LDAP server, please contact a
+Software mentor. Or ask on the `#coding` Slack channel.
+
+If the default ports of 7000 and 7500 are already taken, you can change them via
+the `--https_port` and `--wrapped_port` arguments.
+""",
+ executable = True,
+)
diff --git a/tools/build_rules/apache_runner.py b/tools/build_rules/apache_runner.py
new file mode 100644
index 0000000..3364216
--- /dev/null
+++ b/tools/build_rules/apache_runner.py
@@ -0,0 +1,121 @@
+"""Starts up Apache to provide HTTPS + LDAP for another web server.
+
+This script is used by the apache_wrapper() rule as the main entrypoint for its
+"executable". This script sets up a minimal Apache environment in a directory
+in /tmp.
+
+Both Apache and the wrapped server binary are started by this script. The
+wrapped server should bind to the port specified by the APACHE_WRAPPED_PORT
+environment variable.
+
+See the documentation for apache_wrapper() for more information.
+"""
+
+import argparse
+import json
+import os
+from pathlib import Path
+import signal
+import subprocess
+import sys
+import tempfile
+
+import jinja2
+
+DUMMY_CERT_ANSWERS = """\
+US
+California
+Mountain View
+FRC971
+Software
+frc971.org
+dummy@frc971.org
+"""
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--binary", type=str, required=True)
+ parser.add_argument("--https_port", type=int, default=7000)
+ parser.add_argument("--wrapped_port", type=int, default=7500)
+ parser.add_argument(
+ "--ldap_info",
+ type=str,
+ help="JSON file containing 'ldap_bind_dn', 'ldap_url', and 'ldap_password' entries.",
+ default="",
+ )
+ args = parser.parse_args(argv[1:])
+
+ if not args.ldap_info:
+ args.ldap_info = os.path.join(os.environ["BUILD_WORKSPACE_DIRECTORY"], "ldap.json")
+
+ with open("tools/build_rules/apache_template.conf", "r") as file:
+ template = jinja2.Template(file.read())
+
+ with open(args.ldap_info, "r") as file:
+ substitutions = json.load(file)
+
+ for key in ("ldap_bind_dn", "ldap_url", "ldap_password"):
+ if key not in substitutions:
+ raise KeyError(f"The ldap_info JSON file must contain key '{key}'.")
+
+ substitutions.update({
+ "https_port": args.https_port,
+ "wrapped_port": args.wrapped_port,
+ })
+
+ config_text = template.render(substitutions)
+
+ with tempfile.TemporaryDirectory() as temp_dir:
+ temp_dir = Path(temp_dir)
+ with open(temp_dir / "apache2.conf", "w") as file:
+ file.write(config_text)
+
+ # Create a directory for error logs and such.
+ logs_dir = temp_dir / "logs"
+ os.mkdir(logs_dir)
+
+ print("-" * 60)
+ print(f"Logs are in {logs_dir}/")
+ print("-" * 60)
+
+ # Make modules available.
+ modules_path = Path("external/apache2/usr/lib/apache2/modules")
+ os.symlink(modules_path.resolve(), temp_dir / "modules")
+
+ # Generate a testing cert.
+ subprocess.run([
+ "openssl",
+ "req",
+ "-x509",
+ "-nodes",
+ "-days=365",
+ "-newkey=rsa:2048",
+ "-keyout=" + str(temp_dir / "apache-selfsigned.key"),
+ "-out=" + str(temp_dir / "apache-selfsigned.crt"),
+ ],
+ check=True,
+ input=DUMMY_CERT_ANSWERS,
+ text=True,
+ )
+
+ # Start the wrapped binary in the background.
+ # Tell it via the environment what port to listen on.
+ env = os.environ.copy()
+ env["APACHE_WRAPPED_PORT"] = str(args.wrapped_port)
+ wrapped_binary = subprocess.Popen([args.binary], env=env)
+
+ # Start the apache server.
+ env = os.environ.copy()
+ env["LD_LIBRARY_PATH"] = "external/apache2/usr/lib/x86_64-linux-gnu"
+ try:
+ subprocess.run(
+ ["external/apache2/usr/sbin/apache2", "-X", "-d", str(temp_dir)],
+ check=True,
+ env=env,
+ )
+ finally:
+ wrapped_binary.send_signal(signal.SIGINT)
+ wrapped_binary.wait()
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
diff --git a/tools/build_rules/apache_template.conf b/tools/build_rules/apache_template.conf
new file mode 100644
index 0000000..91a8338
--- /dev/null
+++ b/tools/build_rules/apache_template.conf
@@ -0,0 +1,57 @@
+PidFile logs/httpd.pid
+
+ServerTokens Prod
+UseCanonicalName On
+TraceEnable Off
+
+Listen 127.0.0.1:{{ https_port }}
+
+LoadModule mpm_event_module modules/mod_mpm_event.so
+LoadModule authn_core_module modules/mod_authn_core.so
+LoadModule authz_core_module modules/mod_authz_core.so
+LoadModule authz_user_module modules/mod_authz_user.so
+LoadModule auth_basic_module modules/mod_auth_basic.so
+LoadModule authnz_ldap_module modules/mod_authnz_ldap.so
+LoadModule ldap_module modules/mod_ldap.so
+LoadModule proxy_module modules/mod_proxy.so
+LoadModule proxy_http_module modules/mod_proxy_http.so
+LoadModule ssl_module modules/mod_ssl.so
+
+{% raw %}
+ErrorLogFormat "[%{cu}t] [%-m:%-l] %-a %-L %M"
+LogFormat "%h %l %u [%{%Y-%m-%d %H:%M:%S}t.%{usec_frac}t] \"%r\" %>s %b \
+\"%{Referer}i\" \"%{User-Agent}i\"" combined
+{% endraw %}
+
+LogLevel debug
+ErrorLog logs/error.log
+CustomLog logs/access.log combined
+
+LDAPCacheEntries 1024
+LDAPCacheTTL 600
+LDAPTrustedGlobalCert CA_BASE64 "apache-selfsigned.crt"
+LDAPTrustedMode STARTTLS
+LDAPLibraryDebug 7
+LDAPVerifyServerCert OFF
+
+<VirtualHost *:{{ https_port }}>
+ ServerName localhost
+ ServerAdmin root@localhost
+
+ SSLEngine on
+ SSLProxyEngine On
+ SSLCertificateFile apache-selfsigned.crt
+ SSLCertificateKeyFile apache-selfsigned.key
+
+ ProxyPass "/" http://localhost:{{ wrapped_port }}/
+
+ <Location />
+ AuthName "Enter your Robotics 971 credentials"
+ AuthType Basic
+ AuthBasicProvider ldap
+ AuthLDAPBindDN "{{ ldap_bind_dn }}"
+ AuthLDAPBindPassword {{ ldap_password }}
+ AuthLDAPURL "{{ ldap_url }}"
+ Require valid-user
+ </Location>
+</VirtualHost>