Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Datasource authentication updates #1289

Merged
merged 10 commits into from
Sep 17, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,22 @@ data:
"secretAccessKey": "",
"logLevel": "INFO"
}
}
},
"datasource": [
{
"name": "prometheus-1",
"provider": "prometheus",
"serviceName": "prometheus-k8s",
"namespace": "openshift-monitoring",
"url": "",
"authentication": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that this change, including the check for the authentication object, is applied only when local=true. The Kruize pods should not fail if the authentication object is absent, as sometimes datasource authentication is not required.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The datasource object is applicable only for local monitoring. Therefore, any code changes related to it have been made specifically within the local monitoring context and do not affect the ROS case.

"type": "bearer",
"credentials": {
"tokenFilePath": "/var/run/secrets/kubernetes.io/serviceaccount/token"
}
}
}
]
}
---
apiVersion: apps/v1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,13 @@ data:
"provider": "prometheus",
"serviceName": "prometheus-k8s",
"namespace": "openshift-monitoring",
"url": ""
"url": "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the other openshift yaml in BYODB

"authentication": {
"type": "bearer",
"credentials": {
"tokenFilePath": "/var/run/secrets/kubernetes.io/serviceaccount/token"
}
}
}
]
}
Expand Down Expand Up @@ -463,3 +469,16 @@ spec:
- name: nginx-config-volume
configMap:
name: nginx-config
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kruize-monitoring-view
subjects:
- kind: ServiceAccount
name: kruize-sa
namespace: openshift-tuning
roleRef:
kind: ClusterRole
name: cluster-monitoring-view
apiGroup: rbac.authorization.k8s.io
2 changes: 1 addition & 1 deletion migrations/kruize_local_ddl.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
create table IF NOT EXISTS kruize_datasources (version varchar(255), name varchar(255), provider varchar(255), serviceName varchar(255), namespace varchar(255), url varchar(255), primary key (name));
create table IF NOT EXISTS kruize_datasources (version varchar(255), name varchar(255), provider varchar(255), serviceName varchar(255), namespace varchar(255), url varchar(255), authentication jsonb, primary key (name));
create table IF NOT EXISTS kruize_dsmetadata (id serial, version varchar(255), datasource_name varchar(255), cluster_name varchar(255), namespace varchar(255), workload_type varchar(255), workload_name varchar(255), container_name varchar(255), container_image_name varchar(255), primary key (id));
alter table kruize_experiments add column metadata_id bigint references kruize_dsmetadata(id), alter column datasource type varchar(255);
create table IF NOT EXISTS kruize_metric_profiles (api_version varchar(255), kind varchar(255), metadata jsonb, name varchar(255) not null, k8s_type varchar(255), profile_version float(53) not null, slo jsonb, primary key (name));
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@
import com.autotune.analyzer.utils.AnalyzerConstants;
import com.autotune.analyzer.utils.AnalyzerErrorConstants;
import com.autotune.common.data.ValidationOutputData;
import com.autotune.common.data.dataSourceQueries.PromQLDataSourceQueries;
import com.autotune.common.data.metrics.AggregationFunctions;
import com.autotune.common.data.metrics.Metric;
import com.autotune.common.data.metrics.MetricAggregationInfoResults;
import com.autotune.common.data.metrics.MetricResults;
import com.autotune.common.data.result.ContainerData;
import com.autotune.common.data.result.IntervalResults;
import com.autotune.common.datasource.DataSourceInfo;
import com.autotune.common.auth.AuthenticationConfig;
import com.autotune.common.auth.AuthenticationStrategy;
import com.autotune.common.auth.AuthenticationStrategyFactory;
import com.autotune.common.exceptions.DataSourceNotExist;
import com.autotune.common.k8sObjects.K8sObject;
import com.autotune.common.utils.CommonUtils;
Expand Down Expand Up @@ -1619,6 +1621,10 @@ public void fetchMetricsBasedOnProfileAndDatasource(KruizeObject kruizeObject, T
long interval_end_time_epoc = 0;
long interval_start_time_epoc = 0;
SimpleDateFormat sdf = new SimpleDateFormat(KruizeConstants.DateFormats.STANDARD_JSON_DATE_FORMAT, Locale.ROOT);
AuthenticationStrategy authenticationStrategy = AuthenticationStrategyFactory.createAuthenticationStrategy(
dataSourceInfo.getAuthenticationConfig());
// Create the client
GenericRestApiClient client = new GenericRestApiClient(authenticationStrategy);

String metricProfileName = kruizeObject.getPerformanceProfile();
PerformanceProfile metricProfile = MetricProfileCollection.getInstance().getMetricProfileCollection().get(metricProfileName);
Expand Down Expand Up @@ -1671,7 +1677,8 @@ public void fetchMetricsBasedOnProfileAndDatasource(KruizeObject kruizeObject, T
URLEncoder.encode(queryToEncode, CHARACTER_ENCODING)
);
LOGGER.info(dateMetricsUrl);
JSONObject genericJsonObject = new GenericRestApiClient(dateMetricsUrl).fetchMetricsJson(KruizeConstants.APIMessages.GET, "");
client.setBaseURL(dateMetricsUrl);
JSONObject genericJsonObject = client.fetchMetricsJson(KruizeConstants.APIMessages.GET, "");
JsonObject jsonObject = new Gson().fromJson(genericJsonObject.toString(), JsonObject.class);
JsonArray resultArray = jsonObject.getAsJsonObject(KruizeConstants.JSONKeys.DATA).getAsJsonArray(KruizeConstants.DataSourceConstants.DataSourceQueryJSONKeys.RESULT);
// Process fetched metrics
Expand Down Expand Up @@ -1744,7 +1751,8 @@ public void fetchMetricsBasedOnProfileAndDatasource(KruizeObject kruizeObject, T
interval_end_time_epoc,
measurementDurationMinutesInDouble.intValue() * KruizeConstants.TimeConv.NO_OF_SECONDS_PER_MINUTE);
LOGGER.info(podMetricsUrl);
JSONObject genericJsonObject = new GenericRestApiClient(podMetricsUrl).fetchMetricsJson(KruizeConstants.APIMessages.GET, "");
client.setBaseURL(podMetricsUrl);
JSONObject genericJsonObject = client.fetchMetricsJson(KruizeConstants.APIMessages.GET, "");
JsonObject jsonObject = new Gson().fromJson(genericJsonObject.toString(), JsonObject.class);
JsonArray resultArray = jsonObject.getAsJsonObject(KruizeConstants.JSONKeys.DATA).getAsJsonArray(KruizeConstants.DataSourceConstants.DataSourceQueryJSONKeys.RESULT);
// Process fetched metrics
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.autotune.common.auth;

public class APIKeyAuthenticationStrategy implements AuthenticationStrategy {
private final String apiKey;

public APIKeyAuthenticationStrategy(String apiKey) {
this.apiKey = apiKey;
}

@Override
public String applyAuthentication() {
return "Api-Key " + apiKey;
}
}
76 changes: 76 additions & 0 deletions src/main/java/com/autotune/common/auth/AuthenticationConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.autotune.common.auth;

import com.autotune.analyzer.utils.AnalyzerConstants;
import com.autotune.utils.KruizeConstants;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AuthenticationConfig {
private String type; // "basic", "bearer", "apiKey", "oauth2"
private Credentials credentials;
private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationConfig.class);

public AuthenticationConfig(String type, Credentials credentials) {
this.type = type;
this.credentials = credentials;
}

public AuthenticationConfig() {
}

public String getType() {
return type;
}

public Credentials getCredentials() {
return credentials;
}

public static AuthenticationConfig createAuthenticationConfigObject(JSONObject authenticationObj) {
// Parse and map authentication methods if they exist
if (authenticationObj != null) {
String type = authenticationObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_TYPE);
JSONObject credentialsObj = authenticationObj.getJSONObject(KruizeConstants.AuthenticationConstants.AUTHENTICATION_CREDENTIALS);

Credentials credentials = new Credentials();
switch (type.toLowerCase()) {
case KruizeConstants.AuthenticationConstants.BASIC:
credentials.setUsername(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_USERNAME));
credentials.setPassword(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_PASSWORD));
break;
case KruizeConstants.AuthenticationConstants.BEARER:
credentials.setTokenFilePath(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_TOKEN_FILE));
break;
case KruizeConstants.AuthenticationConstants.API_KEY:
credentials.setApiKey(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_API_KEY));
credentials.setHeaderName(credentialsObj.optString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_HEADER_NAME, "X-API-Key"));
break;
case KruizeConstants.AuthenticationConstants.OAUTH2:
credentials.setTokenEndpoint(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_TOKEN_ENDPOINT));
credentials.setClientId(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_CLIENT_ID));
credentials.setClientSecret(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_CLIENT_SECRET));
credentials.setGrantType(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_GRANT_TYPE));
break;
default:
LOGGER.error(KruizeConstants.AuthenticationConstants.UNKNOWN_AUTHENTICATION + "{}", type);
}

return new AuthenticationConfig(type, credentials);
}
return noAuth();
}

// Static method to return a no-auth config
public static AuthenticationConfig noAuth() {
return new AuthenticationConfig(AnalyzerConstants.NONE, null);
}

@Override
public String toString() {
return "AuthenticationConfig{" +
"type='" + type + '\'' +
", credentials=" + credentials +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.autotune.common.auth;

public interface AuthenticationStrategy {
String applyAuthentication();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.autotune.common.auth;

import com.autotune.utils.KruizeConstants;

public class AuthenticationStrategyFactory {

public static AuthenticationStrategy createAuthenticationStrategy(AuthenticationConfig authConfig) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also support for no auth stratgey

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String type = authConfig.getType();
switch (type) {
case KruizeConstants.AuthenticationConstants.BASIC:
String username = authConfig.getCredentials().getUsername();
String password = authConfig.getCredentials().getPassword();
return new BasicAuthenticationStrategy(username, password);
case KruizeConstants.AuthenticationConstants.BEARER:
String tokenFilePath = authConfig.getCredentials().getTokenFilePath();
return new BearerAuthenticationStrategy(tokenFilePath);
case KruizeConstants.AuthenticationConstants.API_KEY:
String apiKey = authConfig.getCredentials().getApiKey();
return new APIKeyAuthenticationStrategy(apiKey);
case KruizeConstants.AuthenticationConstants.OAUTH2:
String tokenEndpoint = authConfig.getCredentials().getTokenEndpoint();
String clientId = authConfig.getCredentials().getClientId();
String clientSecret = authConfig.getCredentials().getClientSecret();
return new OAuth2AuthenticationStrategy(tokenEndpoint, clientId, clientSecret);
default:
throw new IllegalArgumentException(KruizeConstants.AuthenticationConstants.UNKNOWN_AUTHENTICATION+ type);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.autotune.common.auth;

import java.util.Base64;

public class BasicAuthenticationStrategy implements AuthenticationStrategy {
private final String username;
private final String password;

public BasicAuthenticationStrategy(String username, String password) {
this.username = username;
this.password = password;
}

@Override
public String applyAuthentication() {
String auth = username + ":" + password;
return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.autotune.common.auth;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BearerAuthenticationStrategy implements AuthenticationStrategy {
private final String tokenFilePath;

public BearerAuthenticationStrategy(String tokenFilePath) {
this.tokenFilePath = tokenFilePath;
}

@Override
public String applyAuthentication() {
// Read token from file
try {
BufferedReader reader = new BufferedReader(new FileReader(tokenFilePath));
String token = reader.readLine();
reader.close();
return "Bearer " + token;
} catch (IOException e) {
throw new RuntimeException("Failed to read Bearer token: " + e.getMessage());
}
}
}
108 changes: 108 additions & 0 deletions src/main/java/com/autotune/common/auth/Credentials.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.autotune.common.auth;

public class Credentials {
private String grantType; // OAuth2
private String clientId; // OAuth2
private String clientSecret; // OAuth2
private String username; // Basic auth
private String password; // Basic auth
private String tokenEndpoint; // OAuth2
private String tokenFilePath; // Bearer token
private String apiKey; // API key
private String headerName; // API key header name

public Credentials(String username, String password) {
this.username = username;
this.password = password;
}

public Credentials() {
}

public String getUsername() {
return username;
}

public String getGrantType() {
return grantType;
}

public String getClientSecret() {
return clientSecret;
}

public String getClientId() {
return clientId;
}

public String getTokenEndpoint() {
return tokenEndpoint;
}

public String getHeaderName() {
return headerName;
}

public String getApiKey() {
return apiKey;
}

public String getTokenFilePath() {
return tokenFilePath;
}

public String getPassword() {
return password;
}

public void setGrantType(String grantType) {
this.grantType = grantType;
}

public void setClientId(String clientId) {
this.clientId = clientId;
}

public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

public void setTokenEndpoint(String tokenEndpoint) {
this.tokenEndpoint = tokenEndpoint;
}

public void setTokenFilePath(String tokenFilePath) {
this.tokenFilePath = tokenFilePath;
}

public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}

public void setHeaderName(String headerName) {
this.headerName = headerName;
}

@Override
public String toString() {
return "Credentials{" +
"grantType='" + grantType + '\'' +
", clientId='" + clientId + '\'' +
", clientSecret='" + clientSecret + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", tokenEndpoint='" + tokenEndpoint + '\'' +
", tokenFilePath='" + tokenFilePath + '\'' +
", apiKey='" + apiKey + '\'' +
", headerName='" + headerName + '\'' +
'}';
}
}
Loading
Loading