Merge pull request #6512 from Icinga/feature/sni-environment

Refactor environment for API connections
This commit is contained in:
Michael Friedrich 2018-08-10 13:15:48 +02:00 committed by GitHub
commit 060a1ebbd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 109 additions and 18 deletions

View File

@ -116,8 +116,8 @@ you still need a [Host](09-object-types.md#objecttype-host) object.
In case you are using the CLI commands later, you don't have to write
this configuration from scratch in a text editor.
The [ApiListener](09-object-types.md#objecttype-apilistener)
object is used to load the SSL certificates and specify restrictions, e.g.
The [ApiListener](09-object-types.md#objecttype-apilistener) object is
used to load the TLS certificates and specify restrictions, e.g.
for accepting configuration commands.
It is also used for the [Icinga 2 REST API](12-icinga2-api.md#icinga2-api) which shares
@ -2793,3 +2793,36 @@ Add the global zone `global-templates` in case it did not exist.
global = true
}
EOF
## Using Multiple Environments <a id="distributed-monitoring-environments"></a>
In some cases it can be desired to run multiple Icinga instances on the same host.
Two potential scenarios include:
* Different versions of the same monitoring configuration (e.g. production and testing)
* Disparate sets of checks for entirely unrelated monitoring environments (e.g. infrastructure and applications)
The configuration is done with the global constants `ApiBindHost` and `ApiBindPort`
or the `bind_host` and `bind_port` attributes of the
[ApiListener](09-object-types.md#objecttype-apilistener) object.
The environment must be set with the global constant `Environment` or as object attribute
of the [IcingaApplication](#objecttype-icingaapplication) object.
In any case the constant is default value for the attribute and the direct configuration in the objects
have more precedence. The constants have been added to allow the values being set from the CLI on startup.
When Icinga establishes a TLS connection to another cluster instance it automatically uses the [SNI extension](https://en.wikipedia.org/wiki/Server_Name_Indication)
to signal which endpoint it is attempting to connect to. On its own this can already be used to position multiple
Icinga instances behind a load balancer.
SNI example: `icinga2-client1.localdomain`
However, if the environment is configured to `production`, Icinga appends the environment name to the SNI hostname like this:
SNI example with environment: `icinga2-client1.localdomain:production`
Middleware like loadbalancers or TLS proxies can read the SNI header and route the connection to the appropriate target.
I.e., it uses a single externally-visible TCP port (usually 5665) and forwards connections to one or more Icinga
instances which are bound to a local TCP port. It does so by inspecting the environment name that is sent as part of the
SNI extension.

View File

@ -67,6 +67,7 @@ Configuration Attributes:
access\_control\_allow\_credentials | Boolean | **Deprecated.** 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 | String | **Deprecated.** 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 | String | **Deprecated.** 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)
environment | String | **Optional.** Used as suffix in TLS SNI extension name; default from constant `ApiEnvironment`, which is empty.
The attributes `access_control_allow_credentials`, `access_control_allow_headers` and `access_control_allow_methods`
are controlled by Icinga 2 and are not changeable by config any more.
@ -816,6 +817,7 @@ Configuration Attributes:
enable\_service\_checks | Boolean | **Optional.** Whether active service checks are globally enabled. Defaults to true.
enable\_perfdata | Boolean | **Optional.** Whether performance data processing is globally enabled. Defaults to true.
vars | Dictionary | **Optional.** A dictionary containing custom attributes that are available globally.
environment | String | **Optional.** Specify the Icinga environment. This overrides the `Environment` constant specified in the configuration or on the CLI with `--define`. Defaults to empty.
## IdoMySqlConnection <a id="objecttype-idomysqlconnection"></a>

View File

@ -41,6 +41,7 @@ has been removed and this setting has no effect.
New [Icinga constants](17-language-reference.md#icinga-constants) have been added in this release.
* `Environment` for specifying the Icinga environment. Defaults to not set.
* `ApiBindHost` and `ApiBindPort` to allow overriding the default ApiListener values. This will be used for an Icinga addon only.
## Upgrading to v2.9 <a id="upgrading-to-2-9"></a>

View File

@ -407,10 +407,10 @@ Constant | Description
--------------------|-------------------
Vars |**Read-write.** Contains a dictionary with global custom attributes. Not set by default.
NodeName |**Read-write.** Contains the cluster node name. Set to the local hostname by default.
Environment |**Read-write.** The name of the Icinga environment. Included in the SNI host name for outbound connections. Not set by default.
RunAsUser |**Read-write.** Defines the user the Icinga 2 daemon is running as. Set in the Icinga 2 sysconfig.
RunAsGroup |**Read-write.** Defines the group the Icinga 2 daemon is running as. Set in the Icinga 2 sysconfig.
MaxConcurrentChecks |**Read-write.** The number of max checks run simultaneously. Defaults to `512`.
Environment |**Read-write.** The name of the Icinga environment. Included in the SNI host name when making outbound connections. Defaults to `production`.
ApiBindHost |**Read-write.** Overrides the default value for the ApiListener `bind_host` attribute. Not set by default.
ApiBindPort |**Read-write.** Overrides the default value for the ApiListener `bind_port` attribute. Not set by default.
@ -454,7 +454,6 @@ SysconfDir |**Read-only.** Contains the path of the sysconf directory.
LocalStateDir |**Read-only.** Contains the path of the local state directory. Defaults to `PrefixDir + "/var"`.
RunDir |**Read-only.** Contains the path of the run directory. Defaults to `LocalStateDir + "/run"`.
Advanced runtime constants. Please only use them if advised by support or developers.
Variable | Description

View File

@ -297,8 +297,6 @@ static int Main()
Application::DeclareConst("Concurrency", std::thread::hardware_concurrency());
Application::DeclareConst("MaxConcurrentChecks", Application::GetDefaultMaxConcurrentChecks());
ScriptGlobal::Set("Environment", "production");
ScriptGlobal::Set("AttachDebugger", false);
ScriptGlobal::Set("PlatformKernel", Utility::GetPlatformKernel());

View File

@ -27,7 +27,7 @@ mkclass_target(sysloglogger.ti sysloglogger-ti.cpp sysloglogger-ti.hpp)
set(base_SOURCES
i2-base.hpp
application.cpp application.hpp application-ti.hpp application-version.cpp
application.cpp application.hpp application-ti.hpp application-version.cpp application-environment.cpp
array.cpp array.hpp array-script.cpp
base64.cpp base64.hpp
boolean.cpp boolean.hpp boolean-script.cpp

View File

@ -0,0 +1,34 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#include "base/application.hpp"
#include "base/scriptglobal.hpp"
using namespace icinga;
String Application::GetAppEnvironment()
{
Value defaultValue = Empty;
return ScriptGlobal::Get("Environment", &defaultValue);
}
void Application::SetAppEnvironment(const String& name)
{
ScriptGlobal::Set("Environment", name);
}

View File

@ -107,6 +107,9 @@ public:
static String GetAppVersion();
static String GetAppSpecVersion();
static String GetAppEnvironment();
static void SetAppEnvironment(const String& name);
static double GetStartTime();
static void SetStartTime(double ts);

View File

@ -79,10 +79,10 @@ void IcingaApplication::StatsFunc(const Dictionary::Ptr& status, const Array::Pt
{ "enable_host_checks", icingaapplication->GetEnableHostChecks() },
{ "enable_service_checks", icingaapplication->GetEnableServiceChecks() },
{ "enable_perfdata", icingaapplication->GetEnablePerfdata() },
{ "environment", icingaapplication->GetEnvironment() },
{ "pid", Utility::GetPid() },
{ "program_start", Application::GetStartTime() },
{ "version", Application::GetAppVersion() },
{ "environment", ScriptGlobal::Get("Environment", &Empty) }
{ "version", Application::GetAppVersion() }
}));
}
@ -294,6 +294,16 @@ String IcingaApplication::GetNodeName() const
return ScriptGlobal::Get("NodeName");
}
String IcingaApplication::GetEnvironment() const
{
return Application::GetAppEnvironment();
}
void IcingaApplication::SetEnvironment(const String& value, bool suppress_events, const Value& cookie)
{
Application::SetAppEnvironment(value);
}
void IcingaApplication::ValidateVars(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
{
MacroProcessor::ValidateCustomVars(this, lvalue());

View File

@ -50,6 +50,9 @@ public:
String GetNodeName() const;
String GetEnvironment() const override;
void SetEnvironment(const String& value, bool suppress_events = false, const Value& cookie = Empty) override;
void ValidateVars(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils) override;
private:

View File

@ -26,6 +26,12 @@ namespace icinga
class IcingaApplication : Application
{
[config, no_storage, virtual] String environment {
get;
set;
default {{{ return Application::GetAppEnvironment(); }}}
};
[config] bool enable_notifications {
default {{{ return true; }}}
};

View File

@ -388,16 +388,10 @@ void ApiListener::AddConnection(const Endpoint::Ptr& endpoint)
TcpSocket::Ptr client = new TcpSocket();
String serverName = endpoint->GetName();
String env = ScriptGlobal::Get("Environment", &Empty);
if (env != "" && env != "production")
serverName += ":" + env;
try {
endpoint->SetConnecting(true);
client->Connect(host, port);
NewClientHandler(client, serverName, RoleClient);
NewClientHandler(client, endpoint->GetName(), RoleClient);
endpoint->SetConnecting(false);
} catch (const std::exception& ex) {
endpoint->SetConnecting(false);
@ -447,10 +441,17 @@ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const Stri
TlsStream::Ptr tlsStream;
String environmentName = Application::GetAppEnvironment();
String serverName = hostname;
if (!environmentName.IsEmpty())
serverName += ":" + environmentName;
{
ObjectLock olock(this);
try {
tlsStream = new TlsStream(client, hostname, role, m_SSLContext);
tlsStream = new TlsStream(client, serverName, role, m_SSLContext);
} catch (const std::exception&) {
Log(LogCritical, "ApiListener")
<< "Cannot create TLS stream from client connection (" << conninfo << ")";

View File

@ -568,6 +568,7 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&)
<< "{" << std::endl;
for (const Field& field : klass.Fields) {
if (!field.PureSetAccessor)
m_Impl << "\t" << "Set" << field.GetFriendlyName() << "(" << "GetDefault" << field.GetFriendlyName() << "(), true);" << std::endl;
}