Skip to content

Commit

Permalink
Merge branch 'tickets/DM-45049'
Browse files Browse the repository at this point in the history
  • Loading branch information
iagaponenko committed Jul 18, 2024
2 parents 687c882 + d47dea3 commit 35d9599
Show file tree
Hide file tree
Showing 55 changed files with 1,092 additions and 514 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-http "
"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: 5 additions & 3 deletions src/czar/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
add_library(czar OBJECT)

target_sources(czar PRIVATE
ChttpModule.cc
Czar.cc
HttpCzarIngestModule.cc
HttpCzarSvc.cc
HttpCzarQueryModule.cc
HttpModule.cc
HttpCzarSvc.cc
HttpMonitorModule.cc
HttpSvc.cc
MessageTable.cc
QhttpModule.cc
)

target_include_directories(czar PRIVATE
Expand All @@ -23,6 +24,7 @@ target_link_libraries(czar PUBLIC
util
log
XrdSsiLib
cpp-httplib
)

function(CZAR_UTILS)
Expand Down Expand Up @@ -51,4 +53,4 @@ endfunction()

czar_utils(
qserv-czar-http
)
)
67 changes: 67 additions & 0 deletions src/czar/ChttpModule.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* LSST Data Management System
*
* This product includes software developed by the
* LSST Project (http://www.lsst.org/).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the LSST License Statement and
* the GNU General Public License along with this program. If not,
* see <http://www.lsstcorp.org/LegalNotices/>.
*/

// Class header
#include "czar/ChttpModule.h"

// System headers
#include <stdexcept>

// Qserv headers
#include "cconfig/CzarConfig.h"
#include "http/Exceptions.h"
#include "http/RequestBodyJSON.h"
#include "http/RequestQuery.h"

using namespace std;

namespace lsst::qserv::czar {

ChttpModule::ChttpModule(string const& context, httplib::Request const& req, httplib::Response& resp)
: http::ChttpModule(cconfig::CzarConfig::instance()->replicationAuthKey(),
cconfig::CzarConfig::instance()->replicationAdminAuthKey(), req, resp),
_context(context) {}

string ChttpModule::context() const { return _context; }

void ChttpModule::enforceCzarName(string const& func) const {
string const czarNameAttrName = "czar";
string czarName;
if (method() == "GET") {
if (!query().has(czarNameAttrName)) {
throw http::Error(func, "No Czar identifier was provided in the request query.");
}
czarName = query().requiredString(czarNameAttrName);
} else {
if (!body().has(czarNameAttrName)) {
throw http::Error(func, "No Czar identifier was provided in the request body.");
}
czarName = body().required<string>(czarNameAttrName);
}
string const expectedCzarName = cconfig::CzarConfig::instance()->name();
if (expectedCzarName != czarName) {
string const msg = "Requested Czar identifier '" + czarName + "' does not match the one '" +
expectedCzarName + "' of the current Czar.";
throw http::Error(func, msg);
}
}

} // namespace lsst::qserv::czar
69 changes: 69 additions & 0 deletions src/czar/ChttpModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* LSST Data Management System
*
* This product includes software developed by the
* LSST Project (http://www.lsst.org/).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the LSST License Statement and
* the GNU General Public License along with this program. If not,
* see <http://www.lsstcorp.org/LegalNotices/>.
*/
#ifndef LSST_QSERV_CZAR_CHTTPMODULE_H
#define LSST_QSERV_CZAR_CHTTPMODULE_H

// System headers
#include <string>

// Qserv headers
#include "http/ChttpModule.h"

// Forward declarations
namespace httplib {
class Request;
class Response;
} // namespace httplib

// This header declarations
namespace lsst::qserv::czar {

/**
* Class ChttpModule is an intermediate base class of the Qserv Czar modules.
*/
class ChttpModule : public http::ChttpModule {
public:
ChttpModule() = delete;
ChttpModule(ChttpModule const&) = delete;
ChttpModule& operator=(ChttpModule const&) = delete;

virtual ~ChttpModule() = default;

protected:
ChttpModule(std::string const& context, httplib::Request const& req, httplib::Response& resp);

virtual std::string context() const final;

/**
* Check if Czar identifier is present in a request and if so then the identifier
* is the same as the one of the current Czar. Throw an exception in case of mismatch.
* @param func The name of the calling context (it's used for error reporting).
* @throws std::invalid_argument If the dentifiers didn't match.
*/
void enforceCzarName(std::string const& func) const;

private:
std::string const _context;
};

} // namespace lsst::qserv::czar

#endif // LSST_QSERV_CZAR_CHTTPMODULE_H
15 changes: 6 additions & 9 deletions src/czar/HttpCzarIngestModule.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@
#include "http/BinaryEncoding.h"
#include "http/Exceptions.h"
#include "http/MetaModule.h"
#include "http/RequestBody.h"
#include "qhttp/Request.h"
#include "http/RequestBodyJSON.h"
#include "qhttp/Status.h"

using namespace std;
Expand Down Expand Up @@ -106,18 +105,16 @@ void setProtocolFields(json& data) {
namespace lsst::qserv::czar {

void HttpCzarIngestModule::process(asio::io_service& io_service, string const& context,
shared_ptr<qhttp::Request> const& req,
shared_ptr<qhttp::Response> const& resp, string const& subModuleName,
http::AuthType const authType) {
httplib::Request const& req, httplib::Response& resp,
string const& subModuleName, http::AuthType const authType) {
HttpCzarIngestModule module(io_service, context, req, resp);
module.execute(subModuleName, authType);
}

HttpCzarIngestModule::HttpCzarIngestModule(asio::io_service& io_service, string const& context,
shared_ptr<qhttp::Request> const& req,
shared_ptr<qhttp::Response> const& resp)
: http::ModuleBase(cconfig::CzarConfig::instance()->replicationAuthKey(),
cconfig::CzarConfig::instance()->replicationAdminAuthKey(), req, resp),
httplib::Request const& req, httplib::Response& resp)
: http::ChttpModule(cconfig::CzarConfig::instance()->replicationAuthKey(),
cconfig::CzarConfig::instance()->replicationAdminAuthKey(), req, resp),
_io_service(io_service),
_context(context),
_registryBaseUrl("http://" + cconfig::CzarConfig::instance()->replicationRegistryHost() + ":" +
Expand Down
Loading

0 comments on commit 35d9599

Please sign in to comment.