Skip to content

Commit

Permalink
Extended the entry point for Czar HTTP frontend to support SSL/TLS co…
Browse files Browse the repository at this point in the history
…nfig

Switched to the SSL-based REST services for testing.
  • Loading branch information
iagaponenko committed Jul 5, 2024
1 parent 6aa2439 commit f7f56a1
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 10 deletions.
2 changes: 2 additions & 0 deletions admin/local/docker/compose/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ services:
--xrootd-manager czar_xrootd
--http-frontend-port 4048
--http-frontend-threads 4
--http-ssl-cert-file /config-etc/ssl/czar-cert.pem
--http-ssl-private-key-file /config-etc/ssl/czar-key.pem
--log-cfg-file=/config-etc/log/log-czar-proxy.cnf
--repl-instance-id qserv_proj
--repl-auth-key replauthkey
Expand Down
3 changes: 2 additions & 1 deletion admin/tools/docker/base/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,10 @@ WORKDIR /home/qserv

RUN mkdir -p /qserv/data && \
mkdir /config-etc && \
mkdir /config-etc/ssl && \
mkdir -p /qserv/run/tmp && \
mkdir -p /var/run/xrootd && \
chown qserv:qserv /qserv/data /config-etc /qserv/run/tmp /var/run/xrootd
chown qserv:qserv /qserv/data /config-etc /config-etc/ssl /qserv/run/tmp /var/run/xrootd

RUN alternatives --install /usr/bin/python python /usr/bin/python3.9 1
ENV PYTHONPATH "${PYTHONPATH}:/usr/local/python"
Expand Down
2 changes: 1 addition & 1 deletion src/admin/etc/integration_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ reference-db-uri: mysql://qsmaster@integration-test-reference:3306
reference-db-admin-uri: mysql://root:CHANGEME@integration-test-reference:3306
replication-controller-uri: repl://@repl_controller:25081
qserv-uri: qserv://qsmaster@czar_proxy:4040
qserv-http-uri: http://czar_http:4048
qserv-http-uri: https://czar_http:4048
czar-db-admin-uri: mysql://root:CHANGEME@czar_mariadb:3306
# The folder where the itest sources will be mounted in the container:
qserv-testdata-dir: /tmp/qserv/itest_src
Expand Down
24 changes: 22 additions & 2 deletions src/admin/python/lsst/qserv/admin/cli/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,13 @@ class CommandInfo:
"--lua-cpath=/usr/local/lua/qserv/lib/czarProxy.so --defaults-file={{proxy_cfg_path}}",
)),
("czar-http", CommandInfo(
"qserv-czar-http http {{czar_cfg_path}} {{http_frontend_port}} {{http_frontend_threads}} ",
"qserv-czar-https "
"http "
"{{czar_cfg_path}} "
"{{http_frontend_port}} "
"{{http_frontend_threads}} "
"{{http_ssl_cert_file}} "
"{{http_ssl_private_key_file}}",
)),
("cmsd-manager", CommandInfo(
"cmsd -c {{cmsd_manager_cfg_path}} -n manager -I v4",
Expand Down Expand Up @@ -563,6 +569,18 @@ def proxy(ctx: click.Context, **kwargs: Any) -> None:
help="The number of threads for the HTTP server of the frontend. The value of http_frontend_threads is passed"
" as a command-line parameter to the application."
)
@click.option(
"--http-ssl-cert-file",
help="A location of a file containing an SSL/TSL certificate.",
default="/config-etc/ssl/czar-cert.pem",
show_default=True,
)
@click.option(
"--http-ssl-private-key-file",
help="A location of a file containing an SSL/TSL private key.",
default="/config-etc/ssl/czar-key.pem",
show_default=True,
)
@click.option(
"--czar-cfg-file",
help="Path to the czar config file.",
Expand Down Expand Up @@ -595,8 +613,10 @@ def czar_http(ctx: click.Context, **kwargs: Any) -> None:
db_uri=targs["db_uri"],
czar_cfg_file=targs["czar_cfg_file"],
czar_cfg_path=targs["czar_cfg_path"],
cmd=targs["cmd"],
log_cfg_file=targs["log_cfg_file"],
http_ssl_cert_file=targs["http_ssl_cert_file"],
http_ssl_private_key_file=targs["http_ssl_private_key_file"],
cmd=targs["cmd"],
)


Expand Down
33 changes: 33 additions & 0 deletions src/admin/python/lsst/qserv/admin/cli/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import logging
import os
import shlex
import socket
import subprocess
import sys
import time
Expand Down Expand Up @@ -657,6 +658,8 @@ def enter_czar_http(
czar_cfg_file: str,
czar_cfg_path: str,
log_cfg_file: str,
http_ssl_cert_file : str,
http_ssl_private_key_file : str,
cmd: str,
) -> None:
"""Entrypoint script for the proxy container.
Expand All @@ -673,6 +676,10 @@ def enter_czar_http(
Location to render the czar config file.
log_cfg_file : `str`
Location of the log4cxx config file.
http_ssl_cert_file : `str`
The path to the SSL certificate file.
http_ssl_private_key_file : `str`
The path to the SSL private key file.
cmd : `str`
The jinja2 template for the command for this function to execute.
"""
Expand Down Expand Up @@ -700,6 +707,32 @@ def enter_czar_http(

_do_smig_block(qmeta_smig_dir, "qmeta", db_uri)

# check if the SSL certificate and private key files exist and create
# them if they don't.
if not os.path.exists(http_ssl_cert_file) or not os.path.exists(http_ssl_private_key_file):
_log.info("Generating self-signed SSL/TLS certificate %s and private key %s for HTTPS",
http_ssl_cert_file, http_ssl_private_key_file)
country = "US"
state = "California"
loc = "Menlo Park"
org = "SLAC National Accelerator Laboratory"
org_unit = "Rubin Observatory"
hostname = socket.gethostbyaddr(socket.gethostname())[0] # FQDN if available
subj = f"/C={country}/ST={state}/L={loc}/O={org}/OU={org_unit}/CN={hostname}"
openssl_cmd = [
"openssl", "req",
"-x509",
"-newkey", "rsa:4096",
"-out", http_ssl_cert_file,
"-keyout", http_ssl_private_key_file,
"-sha256",
"-days", "365",
"-nodes",
"-subj", subj]
ret = subprocess.run(openssl_cmd, env=dict(os.environ,), cwd="/home/qserv")
if ret.returncode != 0:
raise RuntimeError("Failed to create SSL certificate and private key files.")

env = dict(
os.environ,
LD_PRELOAD=ld_preload,
Expand Down
11 changes: 7 additions & 4 deletions src/admin/python/lsst/qserv/admin/itest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from filecmp import dircmp
import json
import logging
import urllib3
import requests
import os
import re
Expand Down Expand Up @@ -510,7 +511,7 @@ def run_attached_http(self, connection: str, database: str) -> None:

# Submit the query, check and analyze the completion status
svc = str(urljoin(connection, '/query'))
req = requests.post(svc, json={'query': query, 'database': database, 'binary_encoding': 'hex'})
req = requests.post(svc, json={'query': query, 'database': database, 'binary_encoding': 'hex'}, verify=False)
req.raise_for_status()
res = req.json()
if res['success'] == 0:
Expand All @@ -534,7 +535,7 @@ def run_detached_http(self, connection: str, database: str) -> None:

# Submit the query via the async service, check and analyze the completion status
svc = str(urljoin(connection, '/query-async'))
req = requests.post(svc, json={'query': query, 'database': database})
req = requests.post(svc, json={'query': query, 'database': database}, verify=False)
req.raise_for_status()
res = req.json()
if res['success'] == 0:
Expand All @@ -547,7 +548,7 @@ def run_detached_http(self, connection: str, database: str) -> None:
while time.time() < end_time:
# Submit a request to check a status of the query
svc = str(urljoin(connection, f"/query-async/status/{query_id}"))
req = requests.get(svc)
req = requests.get(svc, verify=False)
req.raise_for_status()
res = req.json()
if res['success'] == 0:
Expand All @@ -561,7 +562,7 @@ def run_detached_http(self, connection: str, database: str) -> None:

# Make another request to pull the result set
svc = str(urljoin(connection, f"/query-async/result/{query_id}?binary_encoding=hex"))
req = requests.get(svc)
req = requests.get(svc, verify=False)
req.raise_for_status()
res = req.json()
if res['success'] == 0:
Expand Down Expand Up @@ -747,6 +748,8 @@ def __init__(
self.db_name,
skip_numbers,
)
# Supress the warning about the self-signed certificate
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def run(self) -> None:
"""Run the test queries in a test case.
Expand Down
8 changes: 6 additions & 2 deletions src/czar/qserv-czar-http.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace czar = lsst::qserv::czar;
namespace qserv = lsst::qserv;

namespace {
string const usage = "Usage: <czar-name> <config> <port> <threads>";
string const usage = "Usage: <czar-name> <config> <port> <threads> <ssl-cert-file> <ssl-private-key-file>";
}

int main(int argc, char* argv[]) {
Expand All @@ -49,7 +49,9 @@ int main(int argc, char* argv[]) {
// - the port number (0 value would result in allocating the first available port)
// - the number of service threads (0 value would assume the number of host machine's
// hardware threads)
if (argc != 5) {
// - a location of the SSL/TSL certificate for the secure connections
// - a location of the SSL/TSL private key
if (argc != 7) {
cerr << __func__ << ": insufficient number of the command-line parameters\n" << ::usage << endl;
return 1;
}
Expand All @@ -71,6 +73,8 @@ int main(int argc, char* argv[]) {
cerr << __func__ << ": failed to parse command line parameters\n" << ::usage << endl;
return 1;
}
string const sslCertFile = argv[nextArg++];
string const sslPrivateKeyFile = argv[nextArg++];
try {
auto const czar = czar::Czar::createCzar(configFilePath, czarName);
auto const svc = czar::HttpCzarSvc::create(port, numThreads);
Expand Down

0 comments on commit f7f56a1

Please sign in to comment.