HttpServerConnection: Implement CORS support

fixes #4326
This commit is contained in:
Noah Hilverling 2017-07-27 14:57:34 +02:00 committed by Michael Friedrich
parent 835f926b41
commit 94fe1b2292
5 changed files with 81 additions and 20 deletions

View File

@ -43,19 +43,23 @@ object ApiListener "api" {
Configuration Attributes:
Name |Description
--------------------------|--------------------------
cert\_path |**Required.** Path to the public key.
key\_path |**Required.** Path to the private key.
ca\_path |**Required.** Path to the CA certificate file.
ticket\_salt |**Optional.** Private key for auto-signing. **Required** for a signing master instance.
crl\_path |**Optional.** Path to the CRL file.
bind\_host |**Optional.** The IP address the api listener should be bound to. Defaults to `0.0.0.0`.
bind\_port |**Optional.** The port the api listener should be bound to. Defaults to `5665`.
accept\_config |**Optional.** Accept zone configuration. Defaults to `false`.
accept\_commands |**Optional.** Accept remote commands. Defaults to `false`.
cipher\_list |**Optional.** Cipher list that is allowed.
tls\_protocolmin |**Optional.** Minimum TLS protocol version. Must be one of `TLSv1`, `TLSv1.1` or `TLSv1.2`. Defaults to `TLSv1`.
Name |Description
--------------------------------------|--------------------------------------
cert\_path |**Required.** Path to the public key.
key\_path |**Required.** Path to the private key.
ca\_path |**Required.** Path to the CA certificate file.
ticket\_salt |**Optional.** Private key for auto-signing. **Required** for a signing master instance.
crl\_path |**Optional.** Path to the CRL file.
bind\_host |**Optional.** The IP address the api listener should be bound to. Defaults to `0.0.0.0`.
bind\_port |**Optional.** The port the api listener should be bound to. Defaults to `5665`.
accept\_config |**Optional.** Accept zone configuration. Defaults to `false`.
accept\_commands |**Optional.** Accept remote commands. Defaults to `false`.
cipher\_list |**Optional.** Cipher list that is allowed.
tls\_protocolmin |**Optional.** Minimum TLS protocol version. Must be one of `TLSv1`, `TLSv1.1` or `TLSv1.2`. Defaults to `TLSv1`.
access\_control\_allow\_origin |**Optional.** Specifies an array of origin URLs that may access the API. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Origin)
access\_control\_allow\_credentials |**Optional.** Indicates whether or not the actual request can be made using credentials. Defaults to `true`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Credentials)
access\_control\_allow\_headers |**Optional.** Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. Defaults to `Authorization`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers)
access\_control\_allow\_methods |**Optional.** Used in response to a preflight request to indicate which HTTP methods can be used when making the actual request. Defaults to `GET, POST, PUT, DELETE`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods)
## ApiUser <a id="objecttype-apiuser"></a>

View File

@ -49,6 +49,23 @@ class ApiListener : ConfigObject
[config] String ticket_salt;
[config] array(String) access_control_allow_origin {
default {{{ return new Array(); }}}
};
[config] bool access_control_allow_credentials
{
default {{{ return true; }}}
};
[config] String access_control_allow_headers
{
default {{{ return "Authorization"; }}}
};
[config] String access_control_allow_methods
{
default {{{ return "GET, POST, PUT, DELETE"; }}}
};
[state, no_user_modify] Timestamp log_message_timestamp;
[no_user_modify] String identity;

View File

@ -58,13 +58,7 @@ void HttpResponse::SetStatus(int code, const String& message)
void HttpResponse::AddHeader(const String& key, const String& value)
{
if (m_State != HttpResponseHeaders) {
Log(LogWarning, "HttpResponse", "Tried to add header after headers had already been sent.");
return;
}
String header = key + ": " + value + "\r\n";
m_Stream->Write(header.CStr(), header.GetLength());
m_Headers.push_back(key + ": " + value + "\r\n");
}
void HttpResponse::FinishHeaders(void)
@ -74,6 +68,10 @@ void HttpResponse::FinishHeaders(void)
AddHeader("Transfer-Encoding", "chunked");
AddHeader("Server", "Icinga/" + Application::GetAppVersion());
for (const String& header : m_Headers)
m_Stream->Write(header.CStr(), header.GetLength());
m_Stream->Write("\r\n", 2);
m_State = HttpResponseBody;
}

View File

@ -23,6 +23,7 @@
#include "remote/httprequest.hpp"
#include "base/stream.hpp"
#include "base/fifo.hpp"
#include <vector>
namespace icinga
{
@ -70,6 +71,7 @@ private:
const HttpRequest& m_Request;
Stream::Ptr m_Stream;
FIFO::Ptr m_Body;
std::vector<String> m_Headers;
void FinishHeaders(void);
};

View File

@ -171,6 +171,46 @@ void HttpServerConnection::ProcessMessageAsync(HttpRequest& request)
HttpResponse response(m_Stream, request);
ApiListener::Ptr listener = ApiListener::GetInstance();
if (!listener)
return;
Array::Ptr headerAllowOrigin = listener->GetAccessControlAllowOrigin();
if (headerAllowOrigin->GetLength() != 0) {
String origin = request.Headers->Get("origin");
{
ObjectLock olock(headerAllowOrigin);
for (const String& allowedOrigin : headerAllowOrigin) {
if (allowedOrigin == origin)
response.AddHeader("Access-Control-Allow-Origin", origin);
}
}
if (listener->GetAccessControlAllowCredentials())
response.AddHeader("Access-Control-Allow-Credentials", "true");
String accessControlRequestMethodHeader = request.Headers->Get("access-control-request-method");
if (!accessControlRequestMethodHeader.IsEmpty()) {
response.SetStatus(200, "OK");
response.AddHeader("Access-Control-Allow-Methods", listener->GetAccessControlAllowMethods());
response.AddHeader("Access-Control-Allow-Headers", listener->GetAccessControlAllowHeaders());
String msg = "Preflight OK";
response.WriteBody(msg.CStr(), msg.GetLength());
response.Finish();
m_PendingRequests--;
return;
}
}
String accept_header = request.Headers->Get("accept");
if (request.RequestMethod != "GET" && accept_header != "application/json") {