Skip to content

Commit

Permalink
✨ feat(VSecM): 448 Java SDK (#732)
Browse files Browse the repository at this point in the history
* ✨ feat(VSecM): 448 Java SDK

Create Java SDK poc

Signed-off-by: sahinakyol <akyolsahinn@gmail.com>

* ✨ feat(VSecM): 448 Remove unnecessary classes

Signed-off-by: sahinakyol <akyolsahinn@gmail.com>

* 📚 docs(VSecM): 448 Add simple doc

Signed-off-by: sahinakyol <akyolsahinn@gmail.com>

* 🐛 fix(VSecM): 448 Revert go changes

Signed-off-by: sahinakyol <akyolsahinn@gmail.com>

---------

Signed-off-by: sahinakyol <akyolsahinn@gmail.com>
  • Loading branch information
sahinakyol authored May 7, 2024
1 parent 4b6521b commit 564db67
Show file tree
Hide file tree
Showing 14 changed files with 527 additions and 0 deletions.
42 changes: 42 additions & 0 deletions sdk-java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store

dependency-reduced-pom.xml

.idea
30 changes: 30 additions & 0 deletions sdk-java/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# How to implement to your project

```
<dependency>
<groupId>org.wsecm</groupId>
<artifactId>sdk-java</artifactId>
<version>1.0.0</version>
</dependency>
```

```
public void start() {
LOGGER.info("START....");
String fetch = fetch("https://vsecm-safe.vsecm-system.svc.cluster.local:8443/sentinel/v1/secrets?reveal=true");
LOGGER.info("RESULT .... " + fetch);
TaskFactory taskFactory = new FetchSecretsTaskFactory();
Future<String> watchedData = watch(taskFactory, "https://vsecm-safe.vsecm-system.svc.cluster.local:8443/sentinel/v1/secrets?reveal=true");
if (watchedData.isDone()) {
try {
saveData(watchedData.get(), "/opt/vsecm/secrets.json");
} catch (InterruptedException | ExecutionException e) {
LOGGER.info("ERROR .... " + e.getMessage());
}
}
}
```

# TEST INSTRUCTIONS
- If you want to test properly you should revert changes to this **[commit](https://github.com/vmware-tanzu/secrets-manager/pull/732/commits/caa16943b6e2aeac714486b5e73b2aa13f1515c1)**
- Check README
39 changes: 39 additions & 0 deletions sdk-java/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.vsecm</groupId>
<artifactId>sdk-java</artifactId>
<version>1.0.0</version>
<name>sdk-java</name>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>io.spiffe</groupId>
<artifactId>java-spiffe-provider</artifactId>
<version>0.8.6</version>
</dependency>
<dependency>
<groupId>io.spiffe</groupId>
<artifactId>grpc-netty-linux</artifactId>
<version>0.8.6</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
76 changes: 76 additions & 0 deletions sdk-java/src/main/java/org/vsecm/client/VSecMHttpClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Provides an HTTP client configured with SPIFFE based SSL context for secure communication.
* This client is specifically designed for environments that utilize SPIFFE for workload identification
* and secure communication.
*
* <p>This implementation leverages the SPIFFE Workload API to fetch X.509 SVIDs for establishing TLS connections,
* ensuring that the communication is both secure and aligned with the SPIFFE standards.</p>
*/
package org.vsecm.client;

import io.spiffe.exception.SocketEndpointAddressException;
import io.spiffe.exception.X509SourceException;
import io.spiffe.provider.SpiffeSslContextFactory;
import io.spiffe.workloadapi.DefaultX509Source;

import javax.net.ssl.SSLContext;
import java.net.http.HttpClient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Represents a HTTP client that is secured with SPIFFE-based mutual TLS,
* utilizing the SPIFFE Workload API for fetching SVIDs.
*/
public class VSecMHttpClient {
private static final Logger LOGGER = Logger.getLogger(VSecMHttpClient.class.getName());
private static final String SPIFFE_SOCKET_PATH = "unix:///spire-agent-socket/agent.sock";

/**
* Creates an instance of {@link HttpClient} with SPIFFE-based SSL context.
* This client can be used for secure HTTP communication.
*
* @return A configured {@link HttpClient} instance ready for secure communication.
* @throws RuntimeException if there's an issue configuring the SSL context,
* encapsulating any underlying exceptions.
*/
public HttpClient client() {
try {
SSLContext sslContext = configureSSLContext();
return HttpClient.newBuilder().sslContext(sslContext).build();
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Failed to fetch secrets", e);
throw new RuntimeException(e);
}
}

/**
* Configures and returns an {@link SSLContext} suitable for SPIFFE based secure communication.
*
* @return An {@link SSLContext} configured with SPIFFE X.509 SVIDs for mutual TLS.
* @throws SocketEndpointAddressException If the SPIFFE Workload API socket endpoint address is incorrect.
* @throws X509SourceException If there's an issue fetching or processing the X.509 SVIDs.
* @throws NoSuchAlgorithmException If the SSL context cannot be instantiated due to a missing algorithm.
* @throws KeyManagementException If there's an issue initializing the {@link SSLContext} with SPIFFE SVIDs.
*/
private SSLContext configureSSLContext() throws SocketEndpointAddressException, X509SourceException, NoSuchAlgorithmException, KeyManagementException {
DefaultX509Source.X509SourceOptions sourceOptions = DefaultX509Source.X509SourceOptions
.builder()
.spiffeSocketPath(SPIFFE_SOCKET_PATH)
.build();

DefaultX509Source x509Source = DefaultX509Source.newSource(sourceOptions);
LOGGER.info("SPIFFE_ID: " + x509Source.getX509Svid().getSpiffeId());

SpiffeSslContextFactory.SslContextOptions sslContextOptions = SpiffeSslContextFactory.SslContextOptions
.builder()
.acceptAnySpiffeId()
.x509Source(x509Source)
.build();

return SpiffeSslContextFactory.getSslContext(sslContextOptions);
}

}
47 changes: 47 additions & 0 deletions sdk-java/src/main/java/org/vsecm/service/FetchService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* A service class for fetching data from specified URIs using a SPIFFE-enabled HTTP client.
* This service leverages {@link org.vsecm.client.VSecMHttpClient} for secure HTTP requests,
* ensuring communication adheres to the SPIFFE standard for secure and authenticated connections.
*/
package org.vsecm.service;

import org.vsecm.client.VSecMHttpClient;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Provides static utility methods for fetching data from a specified URI.
* This class cannot be instantiated and serves purely as a utility class.
*/
public final class FetchService {
private static final Logger LOGGER = Logger.getLogger(FetchService.class.getName());

private FetchService(){}

/**
* Fetches data as a {@link String} from the specified URI using an HTTP GET request.
* This method utilizes a SPIFFE-enabled HTTP client for secure communication.
*
* @param secretUri The URI from which to fetch data. Must be a valid URI string.
* @return The body of the HTTP response as a {@link String}. Returns an empty string if the request fails.
*/
public static String fetch(String secretUri) {
try {
HttpRequest request = HttpRequest.newBuilder(new URI(secretUri)).GET().build();
VSecMHttpClient vSecMHttpClient = new VSecMHttpClient();
HttpResponse<String> response = vSecMHttpClient.client().send(request, HttpResponse.BodyHandlers.ofString());
LOGGER.info("Response status code: " + response.statusCode());
LOGGER.info("Response body: " + response.body());
return response.body();
} catch (URISyntaxException | IOException | InterruptedException e) {
LOGGER.log(Level.SEVERE, "Failed processes when request to URI : " + secretUri, e);
return "";
}
}
}
48 changes: 48 additions & 0 deletions sdk-java/src/main/java/org/vsecm/service/SaveToFileService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Provides functionality to save data to a file, creating the necessary directories if they do not exist.
* This service is designed to facilitate the saving of data to the file system, ensuring that the file
* path's parent directories are created if necessary.
*/
package org.vsecm.service;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Utility class for saving data to a file. It ensures that the parent directories of the specified
* file path are created before writing the data. This class cannot be instantiated.
*/
public class SaveToFileService {
private static final Logger LOGGER = Logger.getLogger(SaveToFileService.class.getName());

/**
* Saves the provided data to a file at the specified path. If the file's parent directories do not exist,
* they are created. If writing to the file fails, an error is logged.
*
* @param data The string data to be saved to the file.
* @param pathStr The file system path where the data should be saved. Must be a valid path string.
*/
public static void saveData(String data, String pathStr) {
Path path = Paths.get(pathStr);

// Attempt to create the directory(ies) if they don't exist
try {
Files.createDirectories(path.getParent());
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to create directory for secrets", e);
return;
}

// Attempt to write the data to the file
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
writer.write(data);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error saving data to " + pathStr, e);
}
}
}
38 changes: 38 additions & 0 deletions sdk-java/src/main/java/org/vsecm/service/WatchService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Provides a scheduling service for executing tasks that monitor or interact with specified resources, such as URIs.
* Utilizes a {@link TaskFactory} to create tasks that can be scheduled for execution.
* This service is designed to be flexible, allowing for different types of tasks to be executed based on the implementation
* provided by the {@link TaskFactory}.
*/
package org.vsecm.service;

import org.vsecm.task.Task;
import org.vsecm.task.TaskFactory;

import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;

/**
* A utility class for scheduling and executing tasks. This class cannot be instantiated and is used
* to schedule tasks provided by a {@link TaskFactory}. The tasks can perform any operations defined
* by their implementation, such as monitoring changes to a resource or processing data.
*/
public final class WatchService {
private WatchService() {}

/**
* Schedules and executes a task created by the provided {@link TaskFactory}. The task is executed
* using a single-threaded {@link ScheduledExecutorService}.
*
* @param taskFactory The factory to create a task for execution.
* @param secretUri The URI or resource identifier that the task will operate on or monitor.
* @return A {@link Future} representing pending completion of the task. The future's get method will
* return the task's result upon completion.
*/
public static Future<String> watch(TaskFactory taskFactory, String secretUri) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Task task = taskFactory.createTask(scheduler);
return task.execute(secretUri);
}
}
65 changes: 65 additions & 0 deletions sdk-java/src/main/java/org/vsecm/task/FetchSecretsTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.vsecm.task;

import org.vsecm.task.backoff.BackoffStrategy;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.vsecm.service.FetchService.fetch;

/**
* A task for fetching secrets from a specified URI, with retry logic based on a backoff strategy.
* This task is designed to periodically fetch data, adjusting its retry interval based on the success
* and failure outcomes of each attempt.
*/
public class FetchSecretsTask implements Task {

private static final Logger LOGGER = Logger.getLogger(FetchSecretsTask.class.getName());
private final ScheduledExecutorService scheduler;
private final BackoffStrategy backoffStrategy;
private long interval;
private long successCount = 0;
private long errorCount = 0;

/**
* Constructs a new {@code FetchSecretsTask} with the specified scheduler, backoff strategy, and initial interval.
*
* @param scheduler The {@link ScheduledExecutorService} to schedule task execution.
* @param backoffStrategy The {@link BackoffStrategy} to determine the interval between retries.
* @param initialInterval The initial interval (in milliseconds) before the first execution or retry.
*/
public FetchSecretsTask(ScheduledExecutorService scheduler, BackoffStrategy backoffStrategy, long initialInterval) {
this.scheduler = scheduler;
this.backoffStrategy = backoffStrategy;
this.interval = initialInterval;
}

/**
* Schedules and executes the task of fetching secrets from the provided URI. If the fetch fails,
* the task will retry based on the backoff strategy provided during construction.
*
* @param secretUri The URI from which to fetch secrets.
* @return A {@link Future} representing pending completion of the task, containing the fetched data as a {@link String}
* upon success, or {@code null} if an error occurs.
*/
@Override
public Future<String> execute(String secretUri) {
Callable<String> task = () -> {
try {
String result = fetch(secretUri);
successCount++;
return result;
} catch (Exception e) {
errorCount++;
interval = backoffStrategy.calculateBackoff(interval, successCount, errorCount);
LOGGER.log(Level.WARNING, "Could not fetch secrets. Will retry in " + interval + "ms.", e);
return null;
}
};
return scheduler.schedule(task, interval, TimeUnit.MILLISECONDS);
}
}
Loading

0 comments on commit 564db67

Please sign in to comment.