Skip to content

Commit

Permalink
basic auth for providers
Browse files Browse the repository at this point in the history
  • Loading branch information
matjaz99 committed Dec 20, 2022
1 parent 7a23ab5 commit 6403ae8
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 37 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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*
Expand Down
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down Expand Up @@ -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:

Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/si/matjazcerkvenik/alertmonitor/data/DAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,27 @@ 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;

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));
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,10 @@ public boolean synchronizeAlerts(List<DEvent> 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);
Expand All @@ -139,14 +139,14 @@ public boolean synchronizeAlerts(List<DEvent> 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;
}
Expand All @@ -162,17 +162,17 @@ public boolean synchronizeAlerts(List<DEvent> 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());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -54,8 +54,8 @@ public void run() {
List<PAlert> 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;
Expand Down Expand Up @@ -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
Expand All @@ -141,15 +141,15 @@ 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");
} finally {
dataProvider.getPrometheusApiClientPool().returnClient(api);
}

logger.info("SYNC: === Periodic synchronization complete ===");
logger.info("SYNCTASK[" + dataProvider.getProviderConfig().getName() + "]: === Periodic synchronization complete ===");

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)
Expand All @@ -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();

Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion src/main/webapp/WEB-INF/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.4.3-SNAPSHOT
2.4.4-SNAPSHOT
4 changes: 2 additions & 2 deletions src/main/webapp/providers/providers.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@

<p:panelGrid columns="1" cellspacing="5" width="100%">
<h3>Counters</h3>
<p:outputLabel value="URI msg count: #{p.webhookMessagesReceivedCount}" />
<p:outputLabel value="Journal count: #{p.journalCount}" />
<p:outputLabel value="URI requests: #{p.webhookMessagesReceivedCount}" />
<p:outputLabel value="Journal events: #{p.journalCount}" />
<p:outputLabel value="Journal size: #{p.journalSize}" />
<p:outputLabel value="Firing count: #{p.raisingEventCount}" />
<p:outputLabel value="Resolved count: #{p.clearingEventCount}" />
Expand Down

0 comments on commit 6403ae8

Please sign in to comment.