mirror of
https://github.com/Icinga/icinga2.git
synced 2025-07-23 05:34:48 +02:00
Merge pull request #6103 from Icinga/fix/http-security-fixes
HTTP Security fixes
This commit is contained in:
commit
184580f705
@ -208,30 +208,32 @@ The [regex function](18-library-reference.md#global-functions-regex) is availabl
|
|||||||
|
|
||||||
More information about filters can be found in the [filters](12-icinga2-api.md#icinga2-api-filters) chapter.
|
More information about filters can be found in the [filters](12-icinga2-api.md#icinga2-api-filters) chapter.
|
||||||
|
|
||||||
|
Permissions are tied to a maximum HTTP request size to prevent abuse, responses sent by Icinga are not limited.
|
||||||
|
An API user with all permissions ("\*") may send up to 512 MB regardless of the endpoint.
|
||||||
|
|
||||||
Available permissions for specific URL endpoints:
|
Available permissions for specific URL endpoints:
|
||||||
|
|
||||||
Permissions | URL Endpoint | Supports Filters
|
Permissions | URL Endpoint | Supports filters | Max body size in MB
|
||||||
------------------------------|---------------|-----------------
|
------------------------------|---------------|-------------------|---------------------
|
||||||
actions/<action> | /v1/actions | Yes
|
actions/<action> | /v1/actions | Yes | 1
|
||||||
config/query | /v1/config | No
|
config/query | /v1/config | No | 1
|
||||||
config/modify | /v1/config | No
|
config/modify | /v1/config | No | 512
|
||||||
console | /v1/console | No
|
console | /v1/console | No | 512
|
||||||
events/<type> | /v1/events | No
|
events/<type> | /v1/events | No | 1
|
||||||
objects/query/<type> | /v1/objects | Yes
|
objects/query/<type> | /v1/objects | Yes | 1
|
||||||
objects/create/<type> | /v1/objects | No
|
objects/create/<type> | /v1/objects | No | 512
|
||||||
objects/modify/<type> | /v1/objects | Yes
|
objects/modify/<type> | /v1/objects | Yes | 512
|
||||||
objects/delete/<type> | /v1/objects | Yes
|
objects/delete/<type> | /v1/objects | Yes | 512
|
||||||
status/query | /v1/status | Yes
|
status/query | /v1/status | Yes | 1
|
||||||
templates/<type> | /v1/templates | Yes
|
templates/<type> | /v1/templates | Yes | 1
|
||||||
types | /v1/types | Yes
|
types | /v1/types | Yes | 1
|
||||||
variables | /v1/variables | Yes
|
variables | /v1/variables | Yes | 1
|
||||||
|
|
||||||
The required actions or types can be replaced by using a wildcard match ("\*").
|
The required actions or types can be replaced by using a wildcard match ("\*").
|
||||||
|
|
||||||
### Parameters <a id="icinga2-api-parameters"></a>
|
### Parameters <a id="icinga2-api-parameters"></a>
|
||||||
|
|
||||||
Depending on the request method there are two ways of
|
Depending on the request method there are two ways of passing parameters to the request:
|
||||||
passing parameters to the request:
|
|
||||||
|
|
||||||
* JSON object as request body (all request methods other than `GET`)
|
* JSON object as request body (all request methods other than `GET`)
|
||||||
* Query string as URL parameter (all request methods)
|
* Query string as URL parameter (all request methods)
|
||||||
|
@ -60,7 +60,7 @@ void Stream::SignalDataAvailable()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Stream::WaitForData(int timeout)
|
bool Stream::WaitForData()
|
||||||
{
|
{
|
||||||
if (!SupportsWaiting())
|
if (!SupportsWaiting())
|
||||||
BOOST_THROW_EXCEPTION(std::runtime_error("Stream does not support waiting."));
|
BOOST_THROW_EXCEPTION(std::runtime_error("Stream does not support waiting."));
|
||||||
@ -68,10 +68,25 @@ bool Stream::WaitForData(int timeout)
|
|||||||
boost::mutex::scoped_lock lock(m_Mutex);
|
boost::mutex::scoped_lock lock(m_Mutex);
|
||||||
|
|
||||||
while (!IsDataAvailable() && !IsEof())
|
while (!IsDataAvailable() && !IsEof())
|
||||||
if (timeout < 0)
|
m_CV.wait(lock);
|
||||||
m_CV.wait(lock);
|
|
||||||
else
|
return IsDataAvailable() || IsEof();
|
||||||
m_CV.timed_wait(lock, boost::posix_time::milliseconds(timeout * 1000));
|
}
|
||||||
|
|
||||||
|
bool Stream::WaitForData(int timeout)
|
||||||
|
{
|
||||||
|
if (!SupportsWaiting())
|
||||||
|
BOOST_THROW_EXCEPTION(std::runtime_error("Stream does not support waiting."));
|
||||||
|
|
||||||
|
if (timeout < 0)
|
||||||
|
BOOST_THROW_EXCEPTION(std::runtime_error("Timeout can't be negative"));
|
||||||
|
|
||||||
|
boost::system_time const point_of_timeout = boost::get_system_time() + boost::posix_time::seconds(timeout);
|
||||||
|
|
||||||
|
boost::mutex::scoped_lock lock(m_Mutex);
|
||||||
|
|
||||||
|
while (!IsDataAvailable() && !IsEof() && point_of_timeout > boost::get_system_time())
|
||||||
|
m_CV.timed_wait(lock, point_of_timeout);
|
||||||
|
|
||||||
return IsDataAvailable() || IsEof();
|
return IsDataAvailable() || IsEof();
|
||||||
}
|
}
|
||||||
|
@ -122,8 +122,10 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits until data can be read from the stream.
|
* Waits until data can be read from the stream.
|
||||||
|
* Optionally with a timeout.
|
||||||
*/
|
*/
|
||||||
bool WaitForData(int timeout = -1);
|
bool WaitForData();
|
||||||
|
bool WaitForData(int timeout);
|
||||||
|
|
||||||
virtual bool SupportsWaiting() const;
|
virtual bool SupportsWaiting() const;
|
||||||
|
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
# include <poll.h>
|
# include <poll.h>
|
||||||
#endif /* _WIN32 */
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
|
#define TLS_TIMEOUT_SECONDS 10
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
int TlsStream::m_SSLIndex;
|
int TlsStream::m_SSLIndex;
|
||||||
@ -285,8 +287,14 @@ void TlsStream::Handshake()
|
|||||||
m_CurrentAction = TlsActionHandshake;
|
m_CurrentAction = TlsActionHandshake;
|
||||||
ChangeEvents(POLLOUT);
|
ChangeEvents(POLLOUT);
|
||||||
|
|
||||||
while (!m_HandshakeOK && !m_ErrorOccurred && !m_Eof)
|
boost::system_time const timeout = boost::get_system_time() + boost::posix_time::seconds(TLS_TIMEOUT_SECONDS);
|
||||||
m_CV.wait(lock);
|
|
||||||
|
while (!m_HandshakeOK && !m_ErrorOccurred && !m_Eof && timeout > boost::get_system_time())
|
||||||
|
m_CV.timed_wait(lock, timeout);
|
||||||
|
|
||||||
|
// We should _NOT_ (underline, bold, itallic and wordart) throw an exception for a timeout.
|
||||||
|
if (timeout < boost::get_system_time())
|
||||||
|
BOOST_THROW_EXCEPTION(std::runtime_error("Timeout during handshake."));
|
||||||
|
|
||||||
if (m_Eof)
|
if (m_Eof)
|
||||||
BOOST_THROW_EXCEPTION(std::runtime_error("Socket was closed during TLS handshake."));
|
BOOST_THROW_EXCEPTION(std::runtime_error("Socket was closed during TLS handshake."));
|
||||||
|
@ -512,11 +512,17 @@ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const Stri
|
|||||||
JsonRpc::SendMessage(tlsStream, message);
|
JsonRpc::SendMessage(tlsStream, message);
|
||||||
ctype = ClientJsonRpc;
|
ctype = ClientJsonRpc;
|
||||||
} else {
|
} else {
|
||||||
tlsStream->WaitForData(5);
|
tlsStream->WaitForData(10);
|
||||||
|
|
||||||
if (!tlsStream->IsDataAvailable()) {
|
if (!tlsStream->IsDataAvailable()) {
|
||||||
Log(LogWarning, "ApiListener")
|
if (identity.IsEmpty())
|
||||||
<< "No data received on new API connection for identity '" << identity << "'. Ensure that the remote endpoints are properly configured in a cluster setup.";
|
Log(LogInformation, "ApiListener")
|
||||||
|
<< "No data received on new API connection. "
|
||||||
|
<< "Ensure that the remote endpoints are properly configured in a cluster setup.";
|
||||||
|
else
|
||||||
|
Log(LogWarning, "ApiListener")
|
||||||
|
<< "No data received on new API connection for identity '" << identity << "'. "
|
||||||
|
<< "Ensure that the remote endpoints are properly configured in a cluster setup.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "remote/apiuser.hpp"
|
#include "remote/apiuser.hpp"
|
||||||
#include "remote/apiuser-ti.cpp"
|
#include "remote/apiuser-ti.cpp"
|
||||||
#include "base/configtype.hpp"
|
#include "base/configtype.hpp"
|
||||||
|
#include "base/base64.hpp"
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
@ -34,3 +35,31 @@ ApiUser::Ptr ApiUser::GetByClientCN(const String& cn)
|
|||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApiUser::Ptr ApiUser::GetByAuthHeader(const String& auth_header)
|
||||||
|
{
|
||||||
|
String::SizeType pos = auth_header.FindFirstOf(" ");
|
||||||
|
String username, password;
|
||||||
|
|
||||||
|
if (pos != String::NPos && auth_header.SubStr(0, pos) == "Basic") {
|
||||||
|
String credentials_base64 = auth_header.SubStr(pos + 1);
|
||||||
|
String credentials = Base64::Decode(credentials_base64);
|
||||||
|
|
||||||
|
String::SizeType cpos = credentials.FindFirstOf(":");
|
||||||
|
|
||||||
|
if (cpos != String::NPos) {
|
||||||
|
username = credentials.SubStr(0, cpos);
|
||||||
|
password = credentials.SubStr(cpos + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApiUser::Ptr& user = ApiUser::GetByName(username);
|
||||||
|
|
||||||
|
/* Deny authentication if 1) given password is empty 2) configured password does not match. */
|
||||||
|
if (password.IsEmpty())
|
||||||
|
return nullptr;
|
||||||
|
else if (user && user->GetPassword() != password)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
@ -36,6 +36,7 @@ public:
|
|||||||
DECLARE_OBJECTNAME(ApiUser);
|
DECLARE_OBJECTNAME(ApiUser);
|
||||||
|
|
||||||
static ApiUser::Ptr GetByClientCN(const String& cn);
|
static ApiUser::Ptr GetByClientCN(const String& cn);
|
||||||
|
static ApiUser::Ptr GetByAuthHeader(const String& auth_header);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ StreamReadStatus HttpChunkedEncoding::ReadChunkFromStream(const Stream::Ptr& str
|
|||||||
msgbuf << std::hex << line;
|
msgbuf << std::hex << line;
|
||||||
msgbuf >> context.LengthIndicator;
|
msgbuf >> context.LengthIndicator;
|
||||||
|
|
||||||
|
if (context.LengthIndicator < 0)
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("HTTP chunk length must not be negative."));
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamReadContext& scontext = context.StreamContext;
|
StreamReadContext& scontext = context.StreamContext;
|
||||||
|
@ -25,121 +25,149 @@
|
|||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
HttpRequest::HttpRequest(Stream::Ptr stream)
|
HttpRequest::HttpRequest(Stream::Ptr stream)
|
||||||
: Complete(false),
|
: CompleteHeaders(false),
|
||||||
|
CompleteHeaderCheck(false),
|
||||||
|
CompleteBody(false),
|
||||||
ProtocolVersion(HttpVersion11),
|
ProtocolVersion(HttpVersion11),
|
||||||
Headers(new Dictionary()),
|
Headers(new Dictionary()),
|
||||||
m_Stream(std::move(stream)),
|
m_Stream(std::move(stream)),
|
||||||
m_State(HttpRequestStart)
|
m_State(HttpRequestStart)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
bool HttpRequest::Parse(StreamReadContext& src, bool may_wait)
|
bool HttpRequest::ParseHeaders(StreamReadContext& src, bool may_wait)
|
||||||
{
|
{
|
||||||
if (!m_Stream)
|
if (!m_Stream)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (m_State != HttpRequestBody) {
|
if (m_State != HttpRequestStart && m_State != HttpRequestHeaders)
|
||||||
String line;
|
BOOST_THROW_EXCEPTION(std::runtime_error("Invalid HTTP state"));
|
||||||
StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
|
|
||||||
|
String line;
|
||||||
|
StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
|
||||||
|
|
||||||
|
if (srs != StatusNewItem) {
|
||||||
|
if (src.Size > 512)
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Line length for HTTP header exceeded"));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.GetLength() > 512)
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Line length for HTTP header exceeded"));
|
||||||
|
|
||||||
|
if (m_State == HttpRequestStart) {
|
||||||
|
/* ignore trailing new-lines */
|
||||||
|
if (line == "")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
std::vector<String> tokens = line.Split(" ");
|
||||||
|
Log(LogDebug, "HttpRequest")
|
||||||
|
<< "line: " << line << ", tokens: " << tokens.size();
|
||||||
|
if (tokens.size() != 3)
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
|
||||||
|
|
||||||
|
RequestMethod = tokens[0];
|
||||||
|
RequestUrl = new class Url(tokens[1]);
|
||||||
|
|
||||||
|
if (tokens[2] == "HTTP/1.0")
|
||||||
|
ProtocolVersion = HttpVersion10;
|
||||||
|
else if (tokens[2] == "HTTP/1.1") {
|
||||||
|
ProtocolVersion = HttpVersion11;
|
||||||
|
} else
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
|
||||||
|
|
||||||
|
m_State = HttpRequestHeaders;
|
||||||
|
return true;
|
||||||
|
} else { // m_State = HttpRequestHeaders
|
||||||
|
if (line == "") {
|
||||||
|
m_State = HttpRequestBody;
|
||||||
|
CompleteHeaders = true;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (Headers->GetLength() > 128)
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Maximum number of HTTP request headers exceeded"));
|
||||||
|
|
||||||
|
String::SizeType pos = line.FindFirstOf(":");
|
||||||
|
if (pos == String::NPos)
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
|
||||||
|
|
||||||
|
String key = line.SubStr(0, pos).ToLower().Trim();
|
||||||
|
String value = line.SubStr(pos + 1).Trim();
|
||||||
|
Headers->Set(key, value);
|
||||||
|
|
||||||
|
if (key == "x-http-method-override")
|
||||||
|
RequestMethod = value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpRequest::ParseBody(StreamReadContext& src, bool may_wait)
|
||||||
|
{
|
||||||
|
if (!m_Stream)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (m_State != HttpRequestBody)
|
||||||
|
BOOST_THROW_EXCEPTION(std::runtime_error("Invalid HTTP state"));
|
||||||
|
|
||||||
|
/* we're done if the request doesn't contain a message body */
|
||||||
|
if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding")) {
|
||||||
|
CompleteBody = true;
|
||||||
|
return false;
|
||||||
|
} else if (!m_Body)
|
||||||
|
m_Body = new FIFO();
|
||||||
|
|
||||||
|
if (Headers->Get("transfer-encoding") == "chunked") {
|
||||||
|
if (!m_ChunkContext)
|
||||||
|
m_ChunkContext = std::make_shared<ChunkReadContext>(std::ref(src));
|
||||||
|
|
||||||
|
char *data;
|
||||||
|
size_t size;
|
||||||
|
StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
|
||||||
|
|
||||||
if (srs != StatusNewItem)
|
if (srs != StatusNewItem)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (m_State == HttpRequestStart) {
|
m_Body->Write(data, size);
|
||||||
/* ignore trailing new-lines */
|
|
||||||
if (line == "")
|
|
||||||
return true;
|
|
||||||
|
|
||||||
std::vector<String> tokens = line.Split(" ");
|
delete [] data;
|
||||||
Log(LogDebug, "HttpRequest")
|
|
||||||
<< "line: " << line << ", tokens: " << tokens.size();
|
|
||||||
if (tokens.size() != 3)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
|
|
||||||
|
|
||||||
RequestMethod = tokens[0];
|
if (size == 0) {
|
||||||
RequestUrl = new class Url(tokens[1]);
|
CompleteBody = true;
|
||||||
|
return false;
|
||||||
if (tokens[2] == "HTTP/1.0")
|
} else
|
||||||
ProtocolVersion = HttpVersion10;
|
|
||||||
else if (tokens[2] == "HTTP/1.1") {
|
|
||||||
ProtocolVersion = HttpVersion11;
|
|
||||||
} else
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
|
|
||||||
|
|
||||||
m_State = HttpRequestHeaders;
|
|
||||||
} else if (m_State == HttpRequestHeaders) {
|
|
||||||
if (line == "") {
|
|
||||||
m_State = HttpRequestBody;
|
|
||||||
|
|
||||||
/* we're done if the request doesn't contain a message body */
|
|
||||||
if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding"))
|
|
||||||
Complete = true;
|
|
||||||
else
|
|
||||||
m_Body = new FIFO();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
String::SizeType pos = line.FindFirstOf(":");
|
|
||||||
if (pos == String::NPos)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
|
|
||||||
|
|
||||||
String key = line.SubStr(0, pos).ToLower().Trim();
|
|
||||||
String value = line.SubStr(pos + 1).Trim();
|
|
||||||
Headers->Set(key, value);
|
|
||||||
|
|
||||||
if (key == "x-http-method-override")
|
|
||||||
RequestMethod = value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
VERIFY(!"Invalid HTTP request state.");
|
|
||||||
}
|
|
||||||
} else if (m_State == HttpRequestBody) {
|
|
||||||
if (Headers->Get("transfer-encoding") == "chunked") {
|
|
||||||
if (!m_ChunkContext)
|
|
||||||
m_ChunkContext = std::make_shared<ChunkReadContext>(std::ref(src));
|
|
||||||
|
|
||||||
char *data;
|
|
||||||
size_t size;
|
|
||||||
StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
|
|
||||||
|
|
||||||
if (srs != StatusNewItem)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_Body->Write(data, size);
|
|
||||||
|
|
||||||
delete [] data;
|
|
||||||
|
|
||||||
if (size == 0) {
|
|
||||||
Complete = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (src.Eof)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
|
|
||||||
|
|
||||||
if (src.MustRead) {
|
|
||||||
if (!src.FillFromStream(m_Stream, false)) {
|
|
||||||
src.Eof = true;
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
|
|
||||||
}
|
|
||||||
|
|
||||||
src.MustRead = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t length_indicator = Convert::ToLong(Headers->Get("content-length"));
|
|
||||||
|
|
||||||
if (src.Size < length_indicator) {
|
|
||||||
src.MustRead = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Body->Write(src.Buffer, length_indicator);
|
|
||||||
src.DropData(length_indicator);
|
|
||||||
Complete = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (src.Eof)
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
|
||||||
|
|
||||||
|
if (src.MustRead) {
|
||||||
|
if (!src.FillFromStream(m_Stream, false)) {
|
||||||
|
src.Eof = true;
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
|
||||||
|
}
|
||||||
|
|
||||||
|
src.MustRead = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long length_indicator_signed = Convert::ToLong(Headers->Get("content-length"));
|
||||||
|
|
||||||
|
if (length_indicator_signed < 0)
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Content-Length must not be negative."));
|
||||||
|
|
||||||
|
size_t length_indicator = length_indicator_signed;
|
||||||
|
|
||||||
|
if (src.Size < length_indicator) {
|
||||||
|
src.MustRead = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Body->Write(src.Buffer, length_indicator);
|
||||||
|
src.DropData(length_indicator);
|
||||||
|
CompleteBody = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,9 @@ enum HttpRequestState
|
|||||||
struct HttpRequest
|
struct HttpRequest
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
bool Complete;
|
bool CompleteHeaders;
|
||||||
|
bool CompleteHeaderCheck;
|
||||||
|
bool CompleteBody;
|
||||||
|
|
||||||
String RequestMethod;
|
String RequestMethod;
|
||||||
Url::Ptr RequestUrl;
|
Url::Ptr RequestUrl;
|
||||||
@ -62,7 +64,8 @@ public:
|
|||||||
|
|
||||||
HttpRequest(Stream::Ptr stream);
|
HttpRequest(Stream::Ptr stream);
|
||||||
|
|
||||||
bool Parse(StreamReadContext& src, bool may_wait);
|
bool ParseHeaders(StreamReadContext& src, bool may_wait);
|
||||||
|
bool ParseBody(StreamReadContext& src, bool may_wait);
|
||||||
size_t ReadBody(char *data, size_t count);
|
size_t ReadBody(char *data, size_t count);
|
||||||
|
|
||||||
void AddHeader(const String& key, const String& value);
|
void AddHeader(const String& key, const String& value);
|
||||||
|
@ -24,12 +24,13 @@
|
|||||||
#include "remote/apifunction.hpp"
|
#include "remote/apifunction.hpp"
|
||||||
#include "remote/jsonrpc.hpp"
|
#include "remote/jsonrpc.hpp"
|
||||||
#include "base/base64.hpp"
|
#include "base/base64.hpp"
|
||||||
#include "base/configtype.hpp"
|
|
||||||
#include "base/objectlock.hpp"
|
|
||||||
#include "base/utility.hpp"
|
|
||||||
#include "base/logger.hpp"
|
|
||||||
#include "base/exception.hpp"
|
|
||||||
#include "base/convert.hpp"
|
#include "base/convert.hpp"
|
||||||
|
#include "base/configtype.hpp"
|
||||||
|
#include "base/exception.hpp"
|
||||||
|
#include "base/logger.hpp"
|
||||||
|
#include "base/objectlock.hpp"
|
||||||
|
#include "base/timer.hpp"
|
||||||
|
#include "base/utility.hpp"
|
||||||
#include <boost/thread/once.hpp>
|
#include <boost/thread/once.hpp>
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
@ -52,7 +53,7 @@ void HttpServerConnection::StaticInitialize()
|
|||||||
{
|
{
|
||||||
l_HttpServerConnectionTimeoutTimer = new Timer();
|
l_HttpServerConnectionTimeoutTimer = new Timer();
|
||||||
l_HttpServerConnectionTimeoutTimer->OnTimerExpired.connect(std::bind(&HttpServerConnection::TimeoutTimerHandler));
|
l_HttpServerConnectionTimeoutTimer->OnTimerExpired.connect(std::bind(&HttpServerConnection::TimeoutTimerHandler));
|
||||||
l_HttpServerConnectionTimeoutTimer->SetInterval(15);
|
l_HttpServerConnectionTimeoutTimer->SetInterval(5);
|
||||||
l_HttpServerConnectionTimeoutTimer->Start();
|
l_HttpServerConnectionTimeoutTimer->Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +77,12 @@ TlsStream::Ptr HttpServerConnection::GetStream() const
|
|||||||
|
|
||||||
void HttpServerConnection::Disconnect()
|
void HttpServerConnection::Disconnect()
|
||||||
{
|
{
|
||||||
|
boost::mutex::scoped_try_lock lock(m_DataHandlerMutex);
|
||||||
|
if (!lock.owns_lock()) {
|
||||||
|
Log(LogInformation, "HttpServerConnection", "Unable to disconnect Http client, I/O thread busy");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Log(LogDebug, "HttpServerConnection", "Http client disconnected");
|
Log(LogDebug, "HttpServerConnection", "Http client disconnected");
|
||||||
|
|
||||||
ApiListener::Ptr listener = ApiListener::GetInstance();
|
ApiListener::Ptr listener = ApiListener::GetInstance();
|
||||||
@ -89,98 +96,137 @@ void HttpServerConnection::Disconnect()
|
|||||||
|
|
||||||
bool HttpServerConnection::ProcessMessage()
|
bool HttpServerConnection::ProcessMessage()
|
||||||
{
|
{
|
||||||
|
|
||||||
bool res;
|
bool res;
|
||||||
|
HttpResponse response(m_Stream, m_CurrentRequest);
|
||||||
|
|
||||||
try {
|
if (!m_CurrentRequest.CompleteHeaders) {
|
||||||
res = m_CurrentRequest.Parse(m_Context, false);
|
try {
|
||||||
} catch (const std::invalid_argument& ex) {
|
res = m_CurrentRequest.ParseHeaders(m_Context, false);
|
||||||
HttpResponse response(m_Stream, m_CurrentRequest);
|
} catch (const std::invalid_argument& ex) {
|
||||||
response.SetStatus(400, "Bad request");
|
response.SetStatus(400, "Bad Request");
|
||||||
String msg = String("<h1>Bad request</h1><p><pre>") + ex.what() + "</pre></p>";
|
String msg = String("<h1>Bad Request</h1><p><pre>") + ex.what() + "</pre></p>";
|
||||||
response.WriteBody(msg.CStr(), msg.GetLength());
|
response.WriteBody(msg.CStr(), msg.GetLength());
|
||||||
response.Finish();
|
response.Finish();
|
||||||
|
|
||||||
m_Stream->Shutdown();
|
m_CurrentRequest.~HttpRequest();
|
||||||
return false;
|
new (&m_CurrentRequest) HttpRequest(m_Stream);
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
HttpResponse response(m_Stream, m_CurrentRequest);
|
|
||||||
response.SetStatus(400, "Bad request");
|
|
||||||
String msg = "<h1>Bad request</h1><p><pre>" + DiagnosticInformation(ex) + "</pre></p>";
|
|
||||||
response.WriteBody(msg.CStr(), msg.GetLength());
|
|
||||||
response.Finish();
|
|
||||||
|
|
||||||
m_Stream->Shutdown();
|
m_Stream->Shutdown();
|
||||||
return false;
|
|
||||||
|
return false;
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
response.SetStatus(500, "Internal Server Error");
|
||||||
|
String msg = "<h1>Internal Server Error</h1><p><pre>" + DiagnosticInformation(ex) + "</pre></p>";
|
||||||
|
response.WriteBody(msg.CStr(), msg.GetLength());
|
||||||
|
response.Finish();
|
||||||
|
|
||||||
|
m_CurrentRequest.~HttpRequest();
|
||||||
|
new (&m_CurrentRequest) HttpRequest(m_Stream);
|
||||||
|
|
||||||
|
m_Stream->Shutdown();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_CurrentRequest.Complete) {
|
if (!m_CurrentRequest.CompleteHeaderCheck) {
|
||||||
m_RequestQueue.Enqueue(std::bind(&HttpServerConnection::ProcessMessageAsync,
|
m_CurrentRequest.CompleteHeaderCheck = true;
|
||||||
HttpServerConnection::Ptr(this), m_CurrentRequest));
|
if (!ManageHeaders(response)) {
|
||||||
|
m_CurrentRequest.~HttpRequest();
|
||||||
|
new (&m_CurrentRequest) HttpRequest(m_Stream);
|
||||||
|
|
||||||
m_Seen = Utility::GetTime();
|
m_Stream->Shutdown();
|
||||||
m_PendingRequests++;
|
|
||||||
|
|
||||||
m_CurrentRequest.~HttpRequest();
|
return false;
|
||||||
new (&m_CurrentRequest) HttpRequest(m_Stream);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpServerConnection::ProcessMessageAsync(HttpRequest& request)
|
|
||||||
{
|
|
||||||
String auth_header = request.Headers->Get("authorization");
|
|
||||||
|
|
||||||
String::SizeType pos = auth_header.FindFirstOf(" ");
|
|
||||||
String username, password;
|
|
||||||
|
|
||||||
if (pos != String::NPos && auth_header.SubStr(0, pos) == "Basic") {
|
|
||||||
String credentials_base64 = auth_header.SubStr(pos + 1);
|
|
||||||
String credentials = Base64::Decode(credentials_base64);
|
|
||||||
|
|
||||||
String::SizeType cpos = credentials.FindFirstOf(":");
|
|
||||||
|
|
||||||
if (cpos != String::NPos) {
|
|
||||||
username = credentials.SubStr(0, cpos);
|
|
||||||
password = credentials.SubStr(cpos + 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiUser::Ptr user;
|
if (!m_CurrentRequest.CompleteBody) {
|
||||||
|
try {
|
||||||
|
res = m_CurrentRequest.ParseBody(m_Context, false);
|
||||||
|
} catch (const std::invalid_argument& ex) {
|
||||||
|
response.SetStatus(400, "Bad Request");
|
||||||
|
String msg = String("<h1>Bad Request</h1><p><pre>") + ex.what() + "</pre></p>";
|
||||||
|
response.WriteBody(msg.CStr(), msg.GetLength());
|
||||||
|
response.Finish();
|
||||||
|
|
||||||
|
m_CurrentRequest.~HttpRequest();
|
||||||
|
new (&m_CurrentRequest) HttpRequest(m_Stream);
|
||||||
|
|
||||||
|
m_Stream->Shutdown();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
response.SetStatus(500, "Internal Server Error");
|
||||||
|
String msg = "<h1>Internal Server Error</h1><p><pre>" + DiagnosticInformation(ex) + "</pre></p>";
|
||||||
|
response.WriteBody(msg.CStr(), msg.GetLength());
|
||||||
|
response.Finish();
|
||||||
|
|
||||||
|
m_CurrentRequest.~HttpRequest();
|
||||||
|
new (&m_CurrentRequest) HttpRequest(m_Stream);
|
||||||
|
|
||||||
|
m_Stream->Shutdown();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_RequestQueue.Enqueue(std::bind(&HttpServerConnection::ProcessMessageAsync,
|
||||||
|
HttpServerConnection::Ptr(this), m_CurrentRequest, response, m_AuthenticatedUser));
|
||||||
|
|
||||||
|
m_Seen = Utility::GetTime();
|
||||||
|
m_PendingRequests++;
|
||||||
|
|
||||||
|
m_CurrentRequest.~HttpRequest();
|
||||||
|
new (&m_CurrentRequest) HttpRequest(m_Stream);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpServerConnection::ManageHeaders(HttpResponse& response)
|
||||||
|
{
|
||||||
|
static const size_t defaultContentLengthLimit = 1 * 1028 * 1028;
|
||||||
|
static const Dictionary::Ptr specialContentLengthLimits = new Dictionary({
|
||||||
|
{"*", 512 * 1028 * 1028},
|
||||||
|
{"config/modify", 512 * 1028 * 1028},
|
||||||
|
{"console", 512 * 1028 * 1028},
|
||||||
|
{"objects/create", 512 * 1028 * 1028},
|
||||||
|
{"objects/modify", 512 * 1028 * 1028},
|
||||||
|
{"objects/delete", 512 * 1028 * 1028}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (m_CurrentRequest.Headers->Get("expect") == "100-continue") {
|
||||||
|
String continueResponse = "HTTP/1.1 100 Continue\r\n\r\n";
|
||||||
|
m_Stream->Write(continueResponse.CStr(), continueResponse.GetLength());
|
||||||
|
}
|
||||||
|
|
||||||
/* client_cn matched. */
|
/* client_cn matched. */
|
||||||
if (m_ApiUser)
|
if (m_ApiUser)
|
||||||
user = m_ApiUser;
|
m_AuthenticatedUser = m_ApiUser;
|
||||||
else {
|
else
|
||||||
user = ApiUser::GetByName(username);
|
m_AuthenticatedUser = ApiUser::GetByAuthHeader(m_CurrentRequest.Headers->Get("authorization"));
|
||||||
|
|
||||||
/* Deny authentication if 1) given password is empty 2) configured password does not match. */
|
String requestUrl = m_CurrentRequest.RequestUrl->Format();
|
||||||
if (password.IsEmpty())
|
|
||||||
user.reset();
|
|
||||||
else if (user && user->GetPassword() != password)
|
|
||||||
user.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
String requestUrl = request.RequestUrl->Format();
|
Socket::Ptr socket = m_Stream->GetSocket();
|
||||||
|
|
||||||
Log(LogInformation, "HttpServerConnection")
|
Log(LogInformation, "HttpServerConnection")
|
||||||
<< "Request: " << request.RequestMethod << " " << requestUrl
|
<< "Request: " << m_CurrentRequest.RequestMethod << " " << requestUrl
|
||||||
<< " (from " << m_Stream->GetSocket()->GetPeerAddress() << ", user: " << (user ? user->GetName() : "<unauthenticated>") << ")";
|
<< " (from " << (socket ? socket->GetPeerAddress() : "<unkown>")
|
||||||
|
<< ", user: " << (m_AuthenticatedUser ? m_AuthenticatedUser->GetName() : "<unauthenticated>") << ")";
|
||||||
HttpResponse response(m_Stream, request);
|
|
||||||
|
|
||||||
ApiListener::Ptr listener = ApiListener::GetInstance();
|
ApiListener::Ptr listener = ApiListener::GetInstance();
|
||||||
|
|
||||||
if (!listener)
|
if (!listener)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
Array::Ptr headerAllowOrigin = listener->GetAccessControlAllowOrigin();
|
Array::Ptr headerAllowOrigin = listener->GetAccessControlAllowOrigin();
|
||||||
|
|
||||||
if (headerAllowOrigin->GetLength() != 0) {
|
if (headerAllowOrigin->GetLength() != 0) {
|
||||||
String origin = request.Headers->Get("origin");
|
String origin = m_CurrentRequest.Headers->Get("origin");
|
||||||
|
|
||||||
{
|
{
|
||||||
ObjectLock olock(headerAllowOrigin);
|
ObjectLock olock(headerAllowOrigin);
|
||||||
|
|
||||||
@ -193,9 +239,9 @@ void HttpServerConnection::ProcessMessageAsync(HttpRequest& request)
|
|||||||
if (listener->GetAccessControlAllowCredentials())
|
if (listener->GetAccessControlAllowCredentials())
|
||||||
response.AddHeader("Access-Control-Allow-Credentials", "true");
|
response.AddHeader("Access-Control-Allow-Credentials", "true");
|
||||||
|
|
||||||
String accessControlRequestMethodHeader = request.Headers->Get("access-control-request-method");
|
String accessControlRequestMethodHeader = m_CurrentRequest.Headers->Get("access-control-request-method");
|
||||||
|
|
||||||
if (!accessControlRequestMethodHeader.IsEmpty()) {
|
if (m_CurrentRequest.RequestMethod == "OPTIONS" && !accessControlRequestMethodHeader.IsEmpty()) {
|
||||||
response.SetStatus(200, "OK");
|
response.SetStatus(200, "OK");
|
||||||
|
|
||||||
response.AddHeader("Access-Control-Allow-Methods", listener->GetAccessControlAllowMethods());
|
response.AddHeader("Access-Control-Allow-Methods", listener->GetAccessControlAllowMethods());
|
||||||
@ -205,27 +251,27 @@ void HttpServerConnection::ProcessMessageAsync(HttpRequest& request)
|
|||||||
response.WriteBody(msg.CStr(), msg.GetLength());
|
response.WriteBody(msg.CStr(), msg.GetLength());
|
||||||
|
|
||||||
response.Finish();
|
response.Finish();
|
||||||
m_PendingRequests--;
|
return false;
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String accept_header = request.Headers->Get("accept");
|
if (m_CurrentRequest.RequestMethod != "GET" && m_CurrentRequest.Headers->Get("accept") != "application/json") {
|
||||||
|
|
||||||
if (request.RequestMethod != "GET" && accept_header != "application/json") {
|
|
||||||
response.SetStatus(400, "Wrong Accept header");
|
response.SetStatus(400, "Wrong Accept header");
|
||||||
response.AddHeader("Content-Type", "text/html");
|
response.AddHeader("Content-Type", "text/html");
|
||||||
String msg = "<h1>Accept header is missing or not set to 'application/json'.</h1>";
|
String msg = "<h1>Accept header is missing or not set to 'application/json'.</h1>";
|
||||||
response.WriteBody(msg.CStr(), msg.GetLength());
|
response.WriteBody(msg.CStr(), msg.GetLength());
|
||||||
} else if (!user) {
|
response.Finish();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_AuthenticatedUser) {
|
||||||
Log(LogWarning, "HttpServerConnection")
|
Log(LogWarning, "HttpServerConnection")
|
||||||
<< "Unauthorized request: " << request.RequestMethod << " " << requestUrl;
|
<< "Unauthorized request: " << m_CurrentRequest.RequestMethod << " " << requestUrl;
|
||||||
|
|
||||||
response.SetStatus(401, "Unauthorized");
|
response.SetStatus(401, "Unauthorized");
|
||||||
response.AddHeader("WWW-Authenticate", "Basic realm=\"Icinga 2\"");
|
response.AddHeader("WWW-Authenticate", "Basic realm=\"Icinga 2\"");
|
||||||
|
|
||||||
if (request.Headers->Get("accept") == "application/json") {
|
if (m_CurrentRequest.Headers->Get("accept") == "application/json") {
|
||||||
Dictionary::Ptr result = new Dictionary({
|
Dictionary::Ptr result = new Dictionary({
|
||||||
{ "error", 401 },
|
{ "error", 401 },
|
||||||
{ "status", "Unauthorized. Please check your user credentials." }
|
{ "status", "Unauthorized. Please check your user credentials." }
|
||||||
@ -237,32 +283,48 @@ void HttpServerConnection::ProcessMessageAsync(HttpRequest& request)
|
|||||||
String msg = "<h1>Unauthorized. Please check your user credentials.</h1>";
|
String msg = "<h1>Unauthorized. Please check your user credentials.</h1>";
|
||||||
response.WriteBody(msg.CStr(), msg.GetLength());
|
response.WriteBody(msg.CStr(), msg.GetLength());
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
HttpHandler::ProcessRequest(user, request, response);
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
Log(LogCritical, "HttpServerConnection")
|
|
||||||
<< "Unhandled exception while processing Http request: " << DiagnosticInformation(ex);
|
|
||||||
response.SetStatus(503, "Unhandled exception");
|
|
||||||
|
|
||||||
String errorInfo = DiagnosticInformation(ex);
|
response.Finish();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (request.Headers->Get("accept") == "application/json") {
|
size_t maxSize = defaultContentLengthLimit;
|
||||||
Dictionary::Ptr result = new Dictionary({
|
|
||||||
{ "error", 503 },
|
|
||||||
{ "status", errorInfo }
|
|
||||||
});
|
|
||||||
|
|
||||||
HttpUtility::SendJsonBody(response, nullptr, result);
|
Array::Ptr permissions = m_AuthenticatedUser->GetPermissions();
|
||||||
} else {
|
ObjectLock olock(permissions);
|
||||||
response.AddHeader("Content-Type", "text/plain");
|
|
||||||
response.WriteBody(errorInfo.CStr(), errorInfo.GetLength());
|
for (const Value& permission : permissions) {
|
||||||
}
|
std::vector<String> permissionParts = String(permission).Split("/");
|
||||||
}
|
String permissionPath = permissionParts[0] + (permissionParts.size() > 1 ? "/" + permissionParts[1] : "");
|
||||||
|
int size = specialContentLengthLimits->Get(permissionPath);
|
||||||
|
maxSize = size > maxSize ? size : maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t contentLength = m_CurrentRequest.Headers->Get("content-length");
|
||||||
|
|
||||||
|
if (contentLength > maxSize) {
|
||||||
|
response.SetStatus(400, "Bad Request");
|
||||||
|
String msg = String("<h1>Content length exceeded maximum</h1>");
|
||||||
|
response.WriteBody(msg.CStr(), msg.GetLength());
|
||||||
|
response.Finish();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpServerConnection::ProcessMessageAsync(HttpRequest& request, HttpResponse& response, const ApiUser::Ptr& user)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
HttpHandler::ProcessRequest(user, request, response);
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
Log(LogCritical, "HttpServerConnection")
|
||||||
|
<< "Unhandled exception while processing Http request: " << DiagnosticInformation(ex);
|
||||||
|
HttpUtility::SendJsonError(response, nullptr, 503, "Unhandled exception" , DiagnosticInformation(ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Finish();
|
response.Finish();
|
||||||
|
|
||||||
m_PendingRequests--;
|
m_PendingRequests--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@
|
|||||||
#define HTTPSERVERCONNECTION_H
|
#define HTTPSERVERCONNECTION_H
|
||||||
|
|
||||||
#include "remote/httprequest.hpp"
|
#include "remote/httprequest.hpp"
|
||||||
|
#include "remote/httpresponse.hpp"
|
||||||
#include "remote/apiuser.hpp"
|
#include "remote/apiuser.hpp"
|
||||||
#include "base/tlsstream.hpp"
|
#include "base/tlsstream.hpp"
|
||||||
#include "base/timer.hpp"
|
|
||||||
#include "base/workqueue.hpp"
|
#include "base/workqueue.hpp"
|
||||||
|
|
||||||
namespace icinga
|
namespace icinga
|
||||||
@ -51,6 +51,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
ApiUser::Ptr m_ApiUser;
|
ApiUser::Ptr m_ApiUser;
|
||||||
|
ApiUser::Ptr m_AuthenticatedUser;
|
||||||
TlsStream::Ptr m_Stream;
|
TlsStream::Ptr m_Stream;
|
||||||
double m_Seen;
|
double m_Seen;
|
||||||
HttpRequest m_CurrentRequest;
|
HttpRequest m_CurrentRequest;
|
||||||
@ -67,7 +68,9 @@ private:
|
|||||||
static void TimeoutTimerHandler();
|
static void TimeoutTimerHandler();
|
||||||
void CheckLiveness();
|
void CheckLiveness();
|
||||||
|
|
||||||
void ProcessMessageAsync(HttpRequest& request);
|
bool ManageHeaders(HttpResponse& response);
|
||||||
|
|
||||||
|
void ProcessMessageAsync(HttpRequest& request, HttpResponse& response, const ApiUser::Ptr&);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -121,8 +121,10 @@ std::shared_ptr<X509> PkiUtility::FetchCert(const String& host, const String& po
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
stream->Handshake();
|
stream->Handshake();
|
||||||
} catch (...) {
|
} catch (const std::exception& ex) {
|
||||||
|
Log(LogCritical, "pki")
|
||||||
|
<< "Client TLS handshake failed. (" << ex.what() << ")";
|
||||||
|
return std::shared_ptr<X509>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return stream->GetPeerCertificate();
|
return stream->GetPeerCertificate();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user