Skip to content

Conversation

dtrawins
Copy link
Collaborator

@dtrawins dtrawins commented Oct 8, 2025

🛠 Summary

JIRA/Issue if applicable.
Describe the changes.

🧪 Checklist

  • Unit tests added.
  • The documentation updated.
  • Change follows security best practices.
    ``

@dtrawins dtrawins requested a review from atobiszei October 8, 2025 11:08
registerHandler(V3, [this](const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, HttpResponseComponents& response_components, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser) -> Status {
OVMS_PROFILE_FUNCTION();
return processV3(uri, request_components, response, request_body, std::move(serverReaderWriter), std::move(multiPartParser));
return processV3(uri, request_components, response, request_body, std::move(serverReaderWriter), std::move(multiPartParser), api_key);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return processV3(uri, request_components, response, request_body, std::move(serverReaderWriter), std::move(multiPartParser), api_key);
return processV3(uri, request_components, response, request_body, std::move(serverReaderWriter), std::move(multiPartParser), apiKey);

}

Status HttpRestApiHandler::processV3(const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser) {
Status HttpRestApiHandler::processV3(const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser, const std::string& api_key) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Status HttpRestApiHandler::processV3(const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser, const std::string& api_key) {
Status HttpRestApiHandler::processV3(const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser, const std::string& apiKey) {

std::transform(lowercaseKey.begin(), lowercaseKey.end(), lowercaseKey.begin(),
[](unsigned char c){ return std::tolower(c); });
}
if (!api_key.empty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!api_key.empty()) {
if (!apiKey.empty()) {

}
if (!api_key.empty()) {
if (request_components.headers.count("authorization")) {
if (request_components.headers.at("authorization") != "Bearer " + api_key) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (request_components.headers.at("authorization") != "Bearer " + api_key) {
if (request_components.headers.at("authorization") != "Bearer " + apiKey) {

Is exact match safe here? No risk of some additional whitespaces?

if (!api_key.empty()) {
if (request_components.headers.count("authorization")) {
if (request_components.headers.at("authorization") != "Bearer " + api_key) {
SPDLOG_DEBUG("Unauthorized request - invalid API key {} instead of {}", request_components.headers.at("authorization"), api_key);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SPDLOG_DEBUG("Unauthorized request - invalid API key {} instead of {}", request_components.headers.at("authorization"), api_key);
SPDLOG_DEBUG("Unauthorized request - invalid API key {} instead of {}", request_components.headers.at("authorization"), apiKey);

Status processServerMetadataKFSRequest(const HttpRequestComponents& request_components, std::string& response, const std::string& request_body);

Status processV3(const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser);
Status processV3(const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser, const std::string& api_key);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Status processV3(const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser, const std::string& api_key);
Status processV3(const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser, const std::string& apiKey);

Status processV3(const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser, const std::string& api_key);
Status processListModelsRequest(std::string& response);
Status processRetrieveModelRequest(const std::string& name, std::string& response);
const std::string api_key;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const std::string api_key;
const std::string apiKey;

src/server.cpp Outdated

std::string Server::getAPIKey() const {
return apiKey;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}

@mzegla mzegla requested a review from Copilot October 8, 2025 14:10
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds API key authentication support for generative endpoints in the HTTP REST API. The implementation introduces an optional authentication mechanism that validates Bearer tokens against a configured API key.

  • Adds API key configuration parameter and server-level storage
  • Implements Bearer token validation in V3 HTTP endpoints
  • Adds unauthorized status code for invalid authentication attempts

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/status.hpp Adds UNAUTHORIZED status code enum
src/status.cpp Adds status message mapping for unauthorized requests
src/server.hpp Adds API key storage and getter method
src/server.cpp Implements API key initialization and getter
src/http_rest_api_handler.hpp Updates processV3 signature to accept API key parameter
src/http_rest_api_handler.cpp Implements Bearer token validation logic
src/config.hpp Adds API key getter method to Config class
src/config.cpp Implements API key getter method
src/cli_parser.cpp Adds API key command line parameter parsing
src/capi_frontend/server_settings.hpp Adds API key field to server settings

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Status startModules(ovms::Config& config);
void shutdownModules();
std::string getAPIKey() const;
std::string setAPIKey(const std::string& newApiKey);
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setAPIKey method is declared but never implemented. Either implement this method or remove the declaration if API key modification after initialization is not intended.

Suggested change
std::string setAPIKey(const std::string& newApiKey);
std::string setAPIKey(const std::string& newApiKey) {
std::unique_lock<std::shared_mutex> lock(modulesMtx);
std::string oldApiKey = apiKey;
apiKey = newApiKey;
return oldApiKey;
}

Copilot uses AI. Check for mistakes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HttpServerModule has access to ovms::config in HTTPServerModule::start(ovms::Config)
Either extend ovms::createAndStartDrogonHttpServer() with additional parameter or wrap
apiKey together with restBindAddress,restPort, workersCount in helper struct nested inside server settings (HTTPServerConfig?)

Comment on lines 679 to 684
// convert headers to lowercase because http headers are case insensitive
for (const auto& [key, value] : request_components.headers) {
std::string lowercaseKey = key;
std::transform(lowercaseKey.begin(), lowercaseKey.end(), lowercaseKey.begin(),
[](unsigned char c) { return std::tolower(c); });
}
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop converts header keys to lowercase but doesn't store the results anywhere. The lowercaseKey variable is not used, so the header lookup will still be case-sensitive. Either store the lowercase headers in a new map or perform case-insensitive lookup directly.

Copilot uses AI. Check for mistakes.

Comment on lines 686 to 687
if (request_components.headers.count("authorization")) {
if (request_components.headers.at("authorization") != "Bearer " + api_key) {
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header lookup uses exact case 'authorization' but HTTP headers are case-insensitive. This will fail if the client sends 'Authorization' (capitalized). Use case-insensitive header lookup or convert the headers map to lowercase keys.

Copilot uses AI. Check for mistakes.

@dtrawins dtrawins marked this pull request as ready for review October 11, 2025 21:05
docker run --rm -d --user $(id -u):$(id -g) --read-only --tmpfs /tmp -v ${PWD}/models/:/models -p 9178:9178 openvino/model_server:latest \
--model_path /models/resnet/ --model_name resnet --port 9178
```
docker run --rm -d --user $(id -u):$(id -g) --read-only --tmpfs /tmp -v ${PWD}/models/:/models -p 9000:9000 openvino/model_server:latest \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
docker run --rm -d --user $(id -u):$(id -g) --read-only --tmpfs /tmp -v ${PWD}/models/:/models -p 9000:9000 openvino/model_server:latest \
docker run --rm -d --user $(id -u):$(id -g) --read-only --tmpfs /tmp -p 9000:9000 openvino/model_server:latest \

Mount is not needed at all here?

std::string absoluteApiKeyPath = std::filesystem::absolute(apiKeyFile).string();
ofs << "1234";
ofs.close();
::SetUpServer(this->t, this->server, this->port, configPath, 10, absoluteApiKeyPath);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
::SetUpServer(this->t, this->server, this->port, configPath, 10, absoluteApiKeyPath);
randomizeAndEnsureFree(this->port);
::SetUpServer(this->t, this->server, this->port, configPath, 10, absoluteApiKeyPath);

if (file.is_open()) {
std::getline(file, serverSettings.apiKey);
serverSettings.apiKey.erase(serverSettings.apiKey.find_last_not_of(" \n\r\t") + 1);
file.close();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
file.close();

done automatically in destructor.

serverSettings.apiKey.erase(serverSettings.apiKey.find_last_not_of(" \n\r\t") + 1);
file.close();
} else {
throw std::runtime_error("Unable to open API key file: " + api_key_file.string());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just cerr here & exit, instead of throw & instant catch?

if (envApiKey != nullptr) {
serverSettings.apiKey = envApiKey;
}
if (serverSettings.apiKey.empty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we intent to print this message in both cases (empty file with api key & empty env)?

Comment on lines +692 to +704
std::unordered_map<std::string, std::string> lowercaseHeaders;
lowercaseHeaders = toLowerCaseHeaders(request_components.headers);
if (!api_key.empty()) {
if (lowercaseHeaders.count("authorization")) {
if (lowercaseHeaders.at("authorization") != "Bearer " + api_key) {
SPDLOG_DEBUG("Unauthorized request - invalid API key {} instead of {}", lowercaseHeaders.at("authorization"), api_key);
return StatusCode::UNAUTHORIZED;
}
} else {
SPDLOG_DEBUG("Unauthorized request - missing API key");
return StatusCode::UNAUTHORIZED;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap into Status checkIfAuthorized()

lowercaseHeaders = toLowerCaseHeaders(request_components.headers);
if (!api_key.empty()) {
if (lowercaseHeaders.count("authorization")) {
if (lowercaseHeaders.at("authorization") != "Bearer " + api_key) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at() could throw. Use find & check instead

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants