Skip to content
Merged
Prev Previous commit
Next Next commit
add configuration for ApiClient
  • Loading branch information
marceljk committed Aug 1, 2025
commit 3b2eb24bdc564e2d0f810951403c53ce2aa8f1ef
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package cloud.stackit.sdk.core;

import cloud.stackit.sdk.core.model.ServiceAccountCredentials;
import cloud.stackit.sdk.core.model.ServiceAccountKey;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
Expand All @@ -9,12 +8,10 @@
import okhttp3.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -64,7 +61,6 @@ public KeyFlowAuthenticator(ServiceAccountKey saKey) {
createAccessToken();
}


public synchronized String getAccessToken() throws IOException {
if (token == null || token.isExpired()) {
createAccessTokenWithRefreshToken();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package cloud.stackit.sdk.core.keyflow;
package cloud.stackit.sdk.core;

import cloud.stackit.sdk.core.KeyFlowAuthenticator;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;

Expand All @@ -14,6 +14,7 @@ public KeyFlowInterceptor(KeyFlowAuthenticator authenticator) {
this.authenticator = authenticator;
}

@NotNull
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Expand Down
151 changes: 151 additions & 0 deletions core/src/main/java/cloud/stackit/sdk/core/auth/SetupAuth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package cloud.stackit.sdk.core.auth;

import cloud.stackit.sdk.core.KeyFlowAuthenticator;
import cloud.stackit.sdk.core.config.Configuration;
import cloud.stackit.sdk.core.config.EnvironmentVariables;
import cloud.stackit.sdk.core.KeyFlowInterceptor;
import cloud.stackit.sdk.core.model.ServiceAccountKey;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import okhttp3.Interceptor;

import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;

public class SetupAuth {
private Interceptor authHandler;
private final String defaultCredentialsFilePath = "~/.stackit/credentials.json";

public SetupAuth() {
this(new Configuration.Builder().build());
}

public SetupAuth(Configuration cfg) {
if (cfg == null) {
cfg = new Configuration.Builder().build();
}

try {
ServiceAccountKey saKey = setupKeyFlow(cfg);
authHandler = new KeyFlowInterceptor(new KeyFlowAuthenticator(saKey));
} catch (Exception e) {
e.printStackTrace();
}
}

public Interceptor getAuthHandler() {
return authHandler;
}

private ServiceAccountKey setupKeyFlow(Configuration cfg) throws Exception {
ServiceAccountKey saKey = null;
// Explicit config in code
if (cfg.getServiceAccountKey() != null && !cfg.getServiceAccountKey().trim().isEmpty()) {
saKey = ServiceAccountKey.loadCredentials(cfg.getServiceAccountKey());
loadPrivateKey(cfg, saKey);
return saKey;
}

if (cfg.getServiceAccountKeyPath() != null && !cfg.getServiceAccountKeyPath().trim().isEmpty()) {
String fileContent = new String(Files.readAllBytes(Paths.get(cfg.getServiceAccountKeyPath())), StandardCharsets.UTF_8);
saKey = new Gson().fromJson(fileContent, ServiceAccountKey.class);
loadPrivateKey(cfg, saKey);
return saKey;
}

// Env config
if (!EnvironmentVariables.STACKIT_SERVICE_ACCOUNT_KEY.trim().isEmpty()) {
saKey = ServiceAccountKey.loadCredentials(EnvironmentVariables.STACKIT_SERVICE_ACCOUNT_KEY.trim());
loadPrivateKey(cfg, saKey);
return saKey;
}

if (!EnvironmentVariables.STACKIT_SERVICE_ACCOUNT_KEY_PATH.trim().isEmpty()) {
String fileContent = new String(Files.readAllBytes(Paths.get(cfg.getServiceAccountKeyPath())), StandardCharsets.UTF_8);
saKey = new Gson().fromJson(fileContent, ServiceAccountKey.class);
loadPrivateKey(cfg, saKey);
return saKey;
}

if (!EnvironmentVariables.STACKIT_CREDENTIALS_PATH.trim().isEmpty()) {
String saKeyJson = readValueFromCredentialsFile(EnvironmentVariables.STACKIT_CREDENTIALS_PATH, EnvironmentVariables.ENV_STACKIT_SERVICE_ACCOUNT_KEY, EnvironmentVariables.ENV_STACKIT_SERVICE_ACCOUNT_KEY_PATH);
saKey = new Gson().fromJson(saKeyJson, ServiceAccountKey.class);
loadPrivateKey(cfg, saKey);
return saKey;
} else {
try {
String saKeyJson = readValueFromCredentialsFile(defaultCredentialsFilePath, EnvironmentVariables.ENV_STACKIT_SERVICE_ACCOUNT_KEY, EnvironmentVariables.ENV_STACKIT_SERVICE_ACCOUNT_KEY_PATH);
saKey = new Gson().fromJson(saKeyJson, ServiceAccountKey.class);
loadPrivateKey(cfg, saKey);
return saKey;
} catch (Exception e) {
throw new Exception("could not find service account key");
}
}
}

private void loadPrivateKey(Configuration cfg, ServiceAccountKey saKey) throws Exception {
if (!saKey.getCredentials().isPrivateKeySet()) {
try {
String privateKey = getPrivateKey(cfg);
saKey.getCredentials().setPrivateKey(privateKey);
} catch (Exception e) {
throw new Exception("could not find private key", e);
}
}
}

private String getPrivateKey(Configuration cfg) throws Exception {
// Explicit code config
// Set private key
if (cfg.getPrivateKey() != null && !cfg.getPrivateKey().trim().isEmpty()) {
return cfg.getPrivateKey();
}
// Set private key path
if (cfg.getPrivateKeyPath() != null && !cfg.getPrivateKeyPath().trim().isEmpty()) {
String privateKeyPath = cfg.getPrivateKeyPath();
return new String(Files.readAllBytes(Paths.get(privateKeyPath)), StandardCharsets.UTF_8);
}
// Set credentials file
if (cfg.getCredentialsFilePath() != null && !cfg.getCredentialsFilePath().trim().isEmpty()) {
return readValueFromCredentialsFile(cfg.getCredentialsFilePath(), EnvironmentVariables.ENV_STACKIT_PRIVATE_KEY, EnvironmentVariables.ENV_STACKIT_PRIVATE_KEY_PATH);
}

// ENVs config
if (EnvironmentVariables.STACKIT_PRIVATE_KEY != null && !EnvironmentVariables.STACKIT_PRIVATE_KEY.trim().isEmpty()) {
return EnvironmentVariables.STACKIT_PRIVATE_KEY.trim();
}
if (EnvironmentVariables.STACKIT_PRIVATE_KEY_PATH != null && !EnvironmentVariables.STACKIT_PRIVATE_KEY_PATH.trim().isEmpty()) {
return new String(Files.readAllBytes(Paths.get(EnvironmentVariables.STACKIT_PRIVATE_KEY_PATH)), StandardCharsets.UTF_8);
}
if (EnvironmentVariables.STACKIT_CREDENTIALS_PATH != null && !EnvironmentVariables.STACKIT_CREDENTIALS_PATH.trim().isEmpty()) {
return readValueFromCredentialsFile(EnvironmentVariables.STACKIT_CREDENTIALS_PATH, EnvironmentVariables.ENV_STACKIT_PRIVATE_KEY, EnvironmentVariables.ENV_STACKIT_PRIVATE_KEY_PATH);
}

// Read from credentials file in defaultCredentialsFilePath
return readValueFromCredentialsFile(defaultCredentialsFilePath, EnvironmentVariables.ENV_STACKIT_PRIVATE_KEY, EnvironmentVariables.ENV_STACKIT_PRIVATE_KEY_PATH);
}

private String readValueFromCredentialsFile(String path, String valueKey, String pathKey) throws Exception {
// Read credentials file
String fileContent = new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
Type credentialsFileType = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> map = new Gson().fromJson(fileContent, credentialsFileType);

// Read STACKIT_PRIVATE_KEY from credentials file
String privateKey = map.get(valueKey);
if (privateKey != null && !privateKey.trim().isEmpty()) {
return privateKey;
}

// Read STACKIT_PRIVATE_KEY_PATH from credentials file
String privateKeyPath = map.get(pathKey);
if (privateKeyPath != null && !privateKeyPath.trim().isEmpty()) {
return new String(Files.readAllBytes(Paths.get(privateKeyPath)));
}
throw new Exception("could not find private key");
}
}
124 changes: 124 additions & 0 deletions core/src/main/java/cloud/stackit/sdk/core/config/Configuration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package cloud.stackit.sdk.core.config;

import java.util.Map;

public class Configuration {
private final Map<String, String> defaultHeader;
private final String serviceAccountKey;
private final String serviceAccountKeyPath;
private final String privateKeyPath;
private final String privateKey;
private final String customEndpoint;
private final String credentialsFilePath;
private final String tokenCustomUrl;
private final String tokenExpirationLeeway;

Configuration(Builder builder) {
this.defaultHeader = builder.defaultHeader;
this.serviceAccountKey = builder.serviceAccountKey;
this.serviceAccountKeyPath = builder.serviceAccountKeyPath;
this.privateKeyPath = builder.privateKeyPath;
this.privateKey = builder.privateKey;
this.customEndpoint = builder.customEndpoint;
this.credentialsFilePath = builder.credentialsFilePath;
this.tokenCustomUrl = builder.tokenCustomUrl;
this.tokenExpirationLeeway = builder.tokenExpirationLeeway;
}

public Map<String, String> getDefaultHeader() {
return defaultHeader;
}

public String getServiceAccountKey() {
return serviceAccountKey;
}

public String getServiceAccountKeyPath() {
return serviceAccountKeyPath;
}

public String getPrivateKeyPath() {
return privateKeyPath;
}

public String getPrivateKey() {
return privateKey;
}

public String getCustomEndpoint() {
return customEndpoint;
}

public String getCredentialsFilePath() {
return credentialsFilePath;
}

public String getTokenCustomUrl() {
return tokenCustomUrl;
}

public String getTokenExpirationLeeway() {
return tokenExpirationLeeway;
}

public static class Builder {
private Map<String, String> defaultHeader;
private String serviceAccountKey;
private String serviceAccountKeyPath;
private String privateKeyPath;
private String privateKey;
private String customEndpoint;
private String credentialsFilePath;
private String tokenCustomUrl;
private String tokenExpirationLeeway;

public Builder defaultHeader(Map<String, String> defaultHeader) {
this.defaultHeader = defaultHeader;
return this;
}

public Builder serviceAccountKey(String serviceAccountKey) {
this.serviceAccountKey = serviceAccountKey;
return this;
}

public Builder serviceAccountKeyPath(String serviceAccountKeyPath) {
this.serviceAccountKeyPath = serviceAccountKeyPath;
return this;
}

public Builder privateKeyPath(String privateKeyPath) {
this.privateKeyPath = privateKeyPath;
return this;
}

public Builder privateKey(String privateKey) {
this.privateKey = privateKey;
return this;
}

public Builder customEndpoint(String customEndpoint) {
this.customEndpoint = customEndpoint;
return this;
}

public Builder credentialsFilePath(String credentialsFilePath) {
this.credentialsFilePath = credentialsFilePath;
return this;
}

public Builder tokenCustomUrl(String tokenCustomUrl) {
this.tokenCustomUrl = tokenCustomUrl;
return this;
}

public Builder tokenExpirationLeeway(String tokenExpirationLeeway) {
this.tokenExpirationLeeway = tokenExpirationLeeway;
return this;
}

public Configuration build() {
return new Configuration(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cloud.stackit.sdk.core.config;

public class EnvironmentVariables {
public final static String ENV_STACKIT_SERVICE_ACCOUNT_KEY_PATH = "STACKIT_SERVICE_ACCOUNT_KEY_PATH";
public final static String ENV_STACKIT_SERVICE_ACCOUNT_KEY = "STACKIT_SERVICE_ACCOUNT_KEY";
public final static String ENV_STACKIT_PRIVATE_KEY_PATH = "STACKIT_PRIVATE_KEY_PATH";
public final static String ENV_STACKIT_PRIVATE_KEY = "STACKIT_PRIVATE_KEY";
public final static String ENV_STACKIT_TOKEN_BASEURL = "STACKIT_TOKEN_BASEURL";
public final static String ENV_STACKIT_CREDENTIALS_PATH = "STACKIT_CREDENTIALS_PATH";

public final static String STACKIT_SERVICE_ACCOUNT_KEY_PATH = System.getenv(ENV_STACKIT_SERVICE_ACCOUNT_KEY_PATH);
public final static String STACKIT_SERVICE_ACCOUNT_KEY = System.getenv(ENV_STACKIT_SERVICE_ACCOUNT_KEY);
public final static String STACKIT_PRIVATE_KEY_PATH = System.getenv(ENV_STACKIT_PRIVATE_KEY_PATH);
public final static String STACKIT_PRIVATE_KEY = System.getenv(ENV_STACKIT_PRIVATE_KEY);
public final static String STACKIT_TOKEN_BASEURL = System.getenv(ENV_STACKIT_TOKEN_BASEURL);
public final static String STACKIT_CREDENTIALS_PATH = System.getenv(ENV_STACKIT_CREDENTIALS_PATH);


@Override
public String toString() {
return "EnvironmentVariables{" +
"STACKIT_SERVICE_ACCOUNT_KEY_PATH='" + STACKIT_SERVICE_ACCOUNT_KEY_PATH + '\'' +
", STACKIT_SERVICE_ACCOUNT_KEY='" + STACKIT_SERVICE_ACCOUNT_KEY + '\'' +
", STACKIT_PRIVATE_KEY_PATH='" + STACKIT_PRIVATE_KEY_PATH + '\'' +
", STACKIT_PRIVATE_KEY='" + STACKIT_PRIVATE_KEY + '\'' +
", STACKIT_TOKEN_BASEURL='" + STACKIT_TOKEN_BASEURL + '\'' +
", STACKIT_CREDENTIALS_PATH='" + STACKIT_CREDENTIALS_PATH + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
Expand All @@ -13,7 +12,7 @@ public class ServiceAccountCredentials {
private final String aud;
private final String iss;
private final String kid;
private final String privateKey;
private String privateKey;
private final UUID sub;

public ServiceAccountCredentials(String aud, String iss, String kid, String privateKey, UUID sub) {
Expand All @@ -40,6 +39,14 @@ public String getPrivateKey() {
return privateKey;
}

public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}

public boolean isPrivateKeySet() {
return !privateKey.trim().isEmpty();
}

public UUID getSub() {
return sub;
}
Expand Down
Loading