From 6403ae8b5359cd16af5087be22d0857271d7ae9c Mon Sep 17 00:00:00 2001 From: matjaz99 Date: Tue, 20 Dec 2022 01:16:35 +0100 Subject: [PATCH] basic auth for providers --- CHANGELOG.md | 4 +++ README.md | 30 +++++++++++++++++-- .../alertmonitor/data/DAO.java | 4 +-- .../model/prometheus/PrometheusApiClient.java | 17 +++++++++-- .../providers/AbstractDataProvider.java | 18 +++++------ .../providers/PrometheusDataProvider.java | 11 +++---- .../providers/PrometheusSyncTask.java | 12 ++++---- .../alertmonitor/util/HttpClientFactory.java | 26 ++++++++++++---- .../alertmonitor/util/TaskManager.java | 2 +- src/main/webapp/WEB-INF/version.txt | 2 +- src/main/webapp/providers/providers.xhtml | 4 +-- 11 files changed, 93 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6879d09..cd35d06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.4-SNAPSHOT + +* [FEATURE] Http client supports basic authentication for connection to providers + ## 2.4.3-SNAPSHOT * [CHANGE] Wording *psync* is everywhere replaced with *sync* diff --git a/README.md b/README.md index 3b1cbd5..1fcc632 100644 --- a/README.md +++ b/README.md @@ -126,10 +126,17 @@ receivers: Since version 3, Alertmonitor supports Data Providers. Data providers are sources that will send data to Alertmonitor. It is possible to define more Prometheus servers or some other sources. +Alertmonitor supports the following data providers: +- Prometheus +- Eventlogger + Data Providers are configured in `providers.yml` configuration file. -Each Data Provider has assigned one URI endpoint. All requests received on that endpoint will be processed -by corresponding Data Provider. +If no providers are defined, there still exists a single data provider (called `.default`) that is constructed from +the environment variables and is bound to the `/alertmonitor/webhook` URI endpoint. All requests received on that +endpoint will be processed by `.default` data provider process. + +> All URI endpoints **must** start with `/alertmonitor/webhook`. > It is not recommended to configure two or more providers to send events to the same URI endpoint! @@ -222,7 +229,7 @@ to behaviour of the application, while other variables may be used for other pur (such as logger configuration or custom environment variable substitution). > With environment variables, only `.default` data provider (Prometheus) can be configured. Other providers -are configured in `providers.yml`. +are configured in `providers.yml` (see Data Providers). A list of supported environment variables: @@ -242,6 +249,23 @@ A list of supported environment variables: | ALERTMONITOR_MONGODB_ENABLED | Enable or disable storing data to MongoDB. If disabled, data is stored in memory only. Default: false | | ALERTMONITOR_MONGODB_CONNECTION_STRING | The connection string for MongoDB (username, password and host). | +### Security + +#### Secure HTTPS protocol + +Alertmonitor supports `http` and `https` protocols for communication with data providers. + +In case of `https`, no certificate validity is checked. + +#### Basic authentication + +Http client in Alertmonitor supports basic authentication when connecting to the data provider (only for https clients). + +Currently, username and password can only be provided via `SERVER` variable using syntax: + +`https://username:password@hostname:port` + + ### Environment variable substitution Prometheus doesn't support substitution of environment variables in alert rules. Alertmonitor does that for you. diff --git a/src/main/java/si/matjazcerkvenik/alertmonitor/data/DAO.java b/src/main/java/si/matjazcerkvenik/alertmonitor/data/DAO.java index f4e654d..d9acc1b 100644 --- a/src/main/java/si/matjazcerkvenik/alertmonitor/data/DAO.java +++ b/src/main/java/si/matjazcerkvenik/alertmonitor/data/DAO.java @@ -47,7 +47,7 @@ private DAO() { } else if (pc.getType().equalsIgnoreCase("eventlogger")) { dp = new EventloggerDataProvider(); } else { - logger.warn("DAO: unknown provider type: " + pc.getType()); + logger.warn("DAO: unknown data provider type: " + pc.getType()); } if (dp != null) { dp.setProviderConfig(pc); @@ -57,7 +57,7 @@ private DAO() { } } // create default provider if not configured - if (!dataProviders.containsKey(AmProps.ALERTMONITOR_DEFAULT_WEBHOOK_URI)) { + if (!dataProviders.containsKey(".default")) { ProviderConfig defaultPC = AmProps.generateProviderConfigFromEnvs(); AbstractDataProvider defaultDP = new PrometheusDataProvider(); defaultDP.setProviderConfig(defaultPC); diff --git a/src/main/java/si/matjazcerkvenik/alertmonitor/model/prometheus/PrometheusApiClient.java b/src/main/java/si/matjazcerkvenik/alertmonitor/model/prometheus/PrometheusApiClient.java index e03781a..a6f2722 100644 --- a/src/main/java/si/matjazcerkvenik/alertmonitor/model/prometheus/PrometheusApiClient.java +++ b/src/main/java/si/matjazcerkvenik/alertmonitor/model/prometheus/PrometheusApiClient.java @@ -53,6 +53,8 @@ public class PrometheusApiClient { /** Name of this client - provider name */ private String name = ".default"; private String server; + private String basicAuthUsername; + private String basicAuthPassword; private boolean secureClient = false; private int connectTimeout = 10; private int readTimeout = 120; @@ -60,7 +62,18 @@ public class PrometheusApiClient { public PrometheusApiClient(AbstractDataProvider dataProvider) { this.dataProvider = dataProvider; name = dataProvider.getProviderConfig().getName(); - server = dataProvider.getProviderConfig().getParam(PrometheusDataProvider.DP_PARAM_KEY_SERVER); + String srv = dataProvider.getProviderConfig().getParam(PrometheusDataProvider.DP_PARAM_KEY_SERVER); + // https://username:password@hostname:port + if (srv.contains("@")) { + String[] sArray1 = srv.split("//"); + String[] sArray2 = sArray1[1].split("@"); + String[] sArray3 = sArray2[0].split(":"); + basicAuthUsername = sArray3[0]; + basicAuthPassword = sArray3[1]; + server = sArray1[0] + "//" + sArray2[1]; + } else { + server = srv; + } secureClient = server.startsWith("https"); connectTimeout = Integer.parseInt(dataProvider.getProviderConfig().getParam(PrometheusDataProvider.DP_PARAM_KEY_CLIENT_CONNECT_TIMEOUT_SEC)); readTimeout = Integer.parseInt(dataProvider.getProviderConfig().getParam(PrometheusDataProvider.DP_PARAM_KEY_CLIENT_READ_TIMEOUT_SEC)); @@ -258,7 +271,7 @@ private String execute(Request request) throws PrometheusApiException { try { // TODO new env var connect timeout - OkHttpClient httpClient = HttpClientFactory.instantiateHttpClient(secureClient, connectTimeout, readTimeout); + OkHttpClient httpClient = HttpClientFactory.instantiateHttpClient(secureClient, connectTimeout, readTimeout, basicAuthUsername, basicAuthPassword); logger.info("PrometheusApi[" + name + "]: request[" + requestCount + "] " + request.method().toUpperCase() + " " + request.url().toString()); Response response = httpClient.newCall(request).execute(); diff --git a/src/main/java/si/matjazcerkvenik/alertmonitor/providers/AbstractDataProvider.java b/src/main/java/si/matjazcerkvenik/alertmonitor/providers/AbstractDataProvider.java index 35ace79..2e462c8 100644 --- a/src/main/java/si/matjazcerkvenik/alertmonitor/providers/AbstractDataProvider.java +++ b/src/main/java/si/matjazcerkvenik/alertmonitor/providers/AbstractDataProvider.java @@ -119,10 +119,10 @@ public boolean synchronizeAlerts(List alertList, boolean sync) { for (DEvent e : alertList) { if (activeAlerts.containsKey(e.getCorrelationId())) { - logger.info("SYNC: Alert exists: {uid=" + e.getUid() + ", cid=" + e.getCorrelationId() + ", alertname=" + e.getAlertname() + ", instance=" + e.getInstance() + "}"); + logger.info("SYNC[" + providerConfig.getName() + "]: alert exists: {uid=" + e.getUid() + ", cid=" + e.getCorrelationId() + ", alertname=" + e.getAlertname() + ", instance=" + e.getInstance() + "}"); activeAlerts.get(e.getCorrelationId()).setToBeDeleted(false); } else { - logger.info("SYNC: New alert: {uid=" + e.getUid() + ", cid=" + e.getCorrelationId() + ", alertname=" + e.getAlertname() + ", instance=" + e.getInstance() + "}"); + logger.info("SYNC[" + providerConfig.getName() + "]: new alert: {uid=" + e.getUid() + ", cid=" + e.getCorrelationId() + ", alertname=" + e.getAlertname() + ", instance=" + e.getInstance() + "}"); e.setFirstTimestamp(e.getTimestamp()); e.setLastTimestamp(e.getTimestamp()); addActiveAlert(e); @@ -139,14 +139,14 @@ public boolean synchronizeAlerts(List alertList, boolean sync) { // remove active alerts which were not received (toBeDeleted=true) for (String cid : cidToDelete) { - logger.info("SYNC: Removing active alert: {cid=" + cid + "}"); + logger.info("SYNC[" + providerConfig.getName() + "]: removing active alert: {cid=" + cid + "}"); removeActiveAlert(activeAlerts.get(cid)); } addToJournal(newAlerts); - logger.info("SYNC: total sync alerts count: " + alertList.size()); - logger.info("SYNC: new alerts count: " + newAlertsCount); - logger.info("SYNC: alerts to be deleted: " + cidToDelete.size()); + logger.info("SYNC[" + providerConfig.getName() + "]: total sync alerts count: " + alertList.size()); + logger.info("SYNC[" + providerConfig.getName() + "]: new alerts count: " + newAlertsCount); + logger.info("SYNC[" + providerConfig.getName() + "]: alerts to be deleted: " + cidToDelete.size()); return true; } @@ -162,17 +162,17 @@ public boolean synchronizeAlerts(List alertList, boolean sync) { if (activeAlerts.containsKey(e.getCorrelationId())) { if (e.getSeverity().equalsIgnoreCase(DSeverity.CLEAR)) { removeActiveAlert(activeAlerts.get(e.getCorrelationId())); - logger.info("SYNC: clear alert: uid=" + e.getUid() + ", cid=" + e.getCorrelationId() + ", alertName: " + e.getAlertname()); + logger.info("SYNC[" + providerConfig.getName() + "]: clear alert: uid=" + e.getUid() + ", cid=" + e.getCorrelationId() + ", alertName: " + e.getAlertname()); } else { updateActiveAlert(e); - logger.info("SYNC: updating alert: uid=" + e.getUid() + ", counter=" + e.getCounter() + ", cid=" + e.getCorrelationId() + ", alertName: " + e.getAlertname()); + logger.info("SYNC[" + providerConfig.getName() + "]: updating alert: uid=" + e.getUid() + ", counter=" + e.getCounter() + ", cid=" + e.getCorrelationId() + ", alertName: " + e.getAlertname()); } } else { if (!e.getSeverity().equalsIgnoreCase(DSeverity.CLEAR)) { e.setFirstTimestamp(e.getTimestamp()); e.setLastTimestamp(e.getTimestamp()); addActiveAlert(e); - logger.info("SYNC: new alert: uid=" + e.getUid() + ", cid=" + e.getCorrelationId() + ", alertName: " + e.getAlertname()); + logger.info("SYNC[" + providerConfig.getName() + "]: new alert: uid=" + e.getUid() + ", cid=" + e.getCorrelationId() + ", alertName: " + e.getAlertname()); } } diff --git a/src/main/java/si/matjazcerkvenik/alertmonitor/providers/PrometheusDataProvider.java b/src/main/java/si/matjazcerkvenik/alertmonitor/providers/PrometheusDataProvider.java index 3716933..3f3fc67 100644 --- a/src/main/java/si/matjazcerkvenik/alertmonitor/providers/PrometheusDataProvider.java +++ b/src/main/java/si/matjazcerkvenik/alertmonitor/providers/PrometheusDataProvider.java @@ -194,18 +194,19 @@ public void restartSyncTimer() { stopSyncTimer(); // TODO handle exception - Integer i = Integer.parseInt(providerConfig.getParam(DP_PARAM_KEY_SYNC_INTERVAL_SEC)); - if (i == 0) { + Integer interval = Integer.parseInt(providerConfig.getParam(DP_PARAM_KEY_SYNC_INTERVAL_SEC)); + AmMetrics.alertmonitor_sync_interval_seconds.labels(providerConfig.getName()).set(interval); + if (interval == 0) { LogFactory.getLogger().info("Sync is disabled"); } // start resync timer if (prometheusSyncTask == null) { - LogFactory.getLogger().info("Start periodic sync task with period=" + i); - AmMetrics.alertmonitor_sync_interval_seconds.labels(providerConfig.getName()).set(i); + LogFactory.getLogger().info("Start periodic sync task with period=" + interval); syncTimer = new Timer("SyncTimer"); prometheusSyncTask = new PrometheusSyncTask(this); - syncTimer.schedule(prometheusSyncTask, 5 * 1000, i * 1000); + int randomDelay = (int) (Math.random() * 30 + 5); + syncTimer.schedule(prometheusSyncTask, randomDelay * 1000, interval * 1000); } } diff --git a/src/main/java/si/matjazcerkvenik/alertmonitor/providers/PrometheusSyncTask.java b/src/main/java/si/matjazcerkvenik/alertmonitor/providers/PrometheusSyncTask.java index 2465721..0e27ed2 100644 --- a/src/main/java/si/matjazcerkvenik/alertmonitor/providers/PrometheusSyncTask.java +++ b/src/main/java/si/matjazcerkvenik/alertmonitor/providers/PrometheusSyncTask.java @@ -44,7 +44,7 @@ public PrometheusSyncTask(AbstractDataProvider dataProvider) { @Override public void run() { - logger.info("SYNC: === starting periodic synchronization ==="); + logger.info("SYNCTASK[" + dataProvider.getProviderConfig().getName() + "]: === starting periodic synchronization ==="); dataProvider.setLastSyncTimestamp(System.currentTimeMillis()); PrometheusApiClient api = dataProvider.getPrometheusApiClientPool().getClient(); @@ -54,8 +54,8 @@ public void run() { List activeAlerts = api.alerts(); if (activeAlerts == null) { - logger.error("SYNC: null response returned"); - logger.info("SYNC: === Periodic synchronization complete ==="); + logger.error("SYNCTASK[" + dataProvider.getProviderConfig().getName() + "]: null response returned"); + logger.info("SYNCTASK[" + dataProvider.getProviderConfig().getName() + "]: === Periodic synchronization complete ==="); dataProvider.syncFailedCount++; dataProvider.addWarning("sync", "Synchronization is failing"); return; @@ -129,7 +129,7 @@ public void run() { // set correlation ID e.generateCID(); - logger.debug("SYNC: " + e.toString()); + logger.debug("SYNCTASK[" + dataProvider.getProviderConfig().getName() + "]: " + e.toString()); syncAlerts.add(e); } // for each alert @@ -141,7 +141,7 @@ public void run() { dataProvider.removeWarning("sync"); } catch (Exception e) { - logger.error("SYNC: failed to synchronize alarms; root cause: " + e.getMessage()); + logger.error("SYNCTASK[" + dataProvider.getProviderConfig().getName() + "]: failed to synchronize alarms; root cause: " + e.getMessage()); dataProvider.syncFailedCount++; AmMetrics.alertmonitor_sync_success.labels(dataProvider.providerConfig.getName()).set(0); dataProvider.addWarning("sync", "Synchronization is failing"); @@ -149,7 +149,7 @@ public void run() { dataProvider.getPrometheusApiClientPool().returnClient(api); } - logger.info("SYNC: === Periodic synchronization complete ==="); + logger.info("SYNCTASK[" + dataProvider.getProviderConfig().getName() + "]: === Periodic synchronization complete ==="); } diff --git a/src/main/java/si/matjazcerkvenik/alertmonitor/util/HttpClientFactory.java b/src/main/java/si/matjazcerkvenik/alertmonitor/util/HttpClientFactory.java index d32c670..db941d9 100644 --- a/src/main/java/si/matjazcerkvenik/alertmonitor/util/HttpClientFactory.java +++ b/src/main/java/si/matjazcerkvenik/alertmonitor/util/HttpClientFactory.java @@ -15,10 +15,11 @@ */ package si.matjazcerkvenik.alertmonitor.util; -import okhttp3.OkHttpClient; +import okhttp3.*; import si.matjazcerkvenik.alertmonitor.data.DAO; import javax.net.ssl.*; +import java.io.IOException; import java.security.cert.CertificateException; import java.util.concurrent.TimeUnit; @@ -31,13 +32,11 @@ public class HttpClientFactory { * @param readTimeout read timeout in seconds * @return OkHttpClient */ - public static OkHttpClient instantiateHttpClient(boolean secure, int connectTimeout, int readTimeout) { - - // TODO basic authentication (see cdrpr) + public static OkHttpClient instantiateHttpClient(boolean secure, int connectTimeout, int readTimeout, String username, String password) { if (!secure) { - LogFactory.getLogger().info("HttpClientFactory: instantiating HTTP client"); + LogFactory.getLogger().debug("HttpClientFactory: instantiating HTTP client"); OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(connectTimeout, TimeUnit.SECONDS) @@ -48,7 +47,7 @@ public static OkHttpClient instantiateHttpClient(boolean secure, int connectTime // continue if https - LogFactory.getLogger().info("HttpClientFactory: instantiating HTTPS client"); + LogFactory.getLogger().debug("HttpClientFactory: instantiating HTTPS client"); OkHttpClient.Builder builder = new OkHttpClient.Builder(); @@ -87,12 +86,27 @@ public boolean verify(String hostname, SSLSession session) { return true; } }); + + if (username != null && password != null) { + builder.authenticator(new Authenticator() { + @Override + public Request authenticate(Route route, Response response) throws IOException { + if (response.request().header("Authorization") != null) + return null; //if you've tried to authorize and failed, give up + + String credential = Credentials.basic(username, password); + return response.request().newBuilder().header("Authorization", credential).build(); + } + }); + } + builder.connectTimeout(connectTimeout, TimeUnit.SECONDS); builder.readTimeout(readTimeout, TimeUnit.SECONDS); return builder.build(); } catch (Exception e) { + LogFactory.getLogger().error("Exception instantiating http client: ", e); return null; } diff --git a/src/main/java/si/matjazcerkvenik/alertmonitor/util/TaskManager.java b/src/main/java/si/matjazcerkvenik/alertmonitor/util/TaskManager.java index e551432..8887d01 100644 --- a/src/main/java/si/matjazcerkvenik/alertmonitor/util/TaskManager.java +++ b/src/main/java/si/matjazcerkvenik/alertmonitor/util/TaskManager.java @@ -62,7 +62,7 @@ public void stopDbMaintenanceTask() { public String getVersionFromGithub() { try { - OkHttpClient httpClient = HttpClientFactory.instantiateHttpClient(true, 10, 30); + OkHttpClient httpClient = HttpClientFactory.instantiateHttpClient(true, 10, 30, null, null); Request request = new Request.Builder() .url("https://raw.githubusercontent.com/matjaz99/alertmonitor/master/src/main/webapp/WEB-INF/version.txt") diff --git a/src/main/webapp/WEB-INF/version.txt b/src/main/webapp/WEB-INF/version.txt index 017674f..e853788 100644 --- a/src/main/webapp/WEB-INF/version.txt +++ b/src/main/webapp/WEB-INF/version.txt @@ -1 +1 @@ -2.4.3-SNAPSHOT \ No newline at end of file +2.4.4-SNAPSHOT \ No newline at end of file diff --git a/src/main/webapp/providers/providers.xhtml b/src/main/webapp/providers/providers.xhtml index c376bb9..9ec86f8 100644 --- a/src/main/webapp/providers/providers.xhtml +++ b/src/main/webapp/providers/providers.xhtml @@ -60,8 +60,8 @@

Counters

- - + +