-
Notifications
You must be signed in to change notification settings - Fork 226
add api-key parameter #3690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
add api-key parameter #3690
Conversation
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!api_key.empty()) { | |
if (!apiKey.empty()) { |
src/http_rest_api_handler.cpp
Outdated
} | ||
if (!api_key.empty()) { | ||
if (request_components.headers.count("authorization")) { | ||
if (request_components.headers.at("authorization") != "Bearer " + api_key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
src/http_rest_api_handler.cpp
Outdated
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const std::string api_key; | |
const std::string apiKey; |
src/server.cpp
Outdated
|
||
std::string Server::getAPIKey() const { | ||
return apiKey; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
} |
There was a problem hiding this 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); |
Copilot
AI
Oct 8, 2025
There was a problem hiding this comment.
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.
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.
There was a problem hiding this comment.
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?)
src/http_rest_api_handler.cpp
Outdated
// 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); }); | ||
} |
Copilot
AI
Oct 8, 2025
There was a problem hiding this comment.
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.
src/http_rest_api_handler.cpp
Outdated
if (request_components.headers.count("authorization")) { | ||
if (request_components.headers.at("authorization") != "Bearer " + api_key) { |
Copilot
AI
Oct 8, 2025
There was a problem hiding this comment.
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.
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 \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
::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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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()); |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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)?
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; | ||
} | ||
} |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
🛠 Summary
JIRA/Issue if applicable.
Describe the changes.
🧪 Checklist
``