]> granicus.if.org Git - icinga2/commitdiff
HttpServerConnection: Implement CORS support 5443/head
authorNoah Hilverling <noah.hilverling@icinga.com>
Thu, 27 Jul 2017 12:57:34 +0000 (14:57 +0200)
committerMichael Friedrich <michael.friedrich@icinga.com>
Wed, 20 Sep 2017 11:18:29 +0000 (13:18 +0200)
fixes #4326

doc/09-object-types.md
lib/remote/apilistener.ti
lib/remote/httpresponse.cpp
lib/remote/httpresponse.hpp
lib/remote/httpserverconnection.cpp

index 70d0eaaee91485cbbe8555ba85b642527baea205..80810c7c763b6427658ef65bbdf6a8fe57a6f407 100644 (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>
 
index 45e8bd0360fb9a13b7eace5f34e25bfef2b9f7c2..ce91a3f0306ac55dabc66ff487d39f0681ca8f81 100644 (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;
index d8c4e099729fdc619188824f06b679bb003db310..7f77ed3865ee65286516a04bc27a1f11d5659e5f 100644 (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;
        }
index 0bd68629256c43d871f6d8ce4e1e2b53129858ed..da6afd66b97e9c81ce73ced1418fce3fe459da74 100644 (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);
 };
index aac35aaa1af83bd59b7f50a18007a9efa1e3205c..c2538b6eb693d7a6bc52f686b3f05636fda1cb86 100644 (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") {