Skip to content

Commit 321ba83

Browse files
feat(kms): onboard kms service (#1215)
STACKITSDK-610
1 parent f2141e8 commit 321ba83

50 files changed

Lines changed: 18143 additions & 6 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@
33
- `automation`:
44
- [v0.1.1](services/automation/CHANGELOG.md#v011)
55
- **Dependencies:** Bump `org.openapitools:jackson-databind-nullable` from `0.2.8` to `0.2.12`
6-
- `serverbackup`:
7-
- [v0.2.1](services/serverbackup/CHANGELOG.md#v021)
8-
- **Dependencies:** Bump `org.openapitools:jackson-databind-nullable` from `0.2.8` to `0.2.12`
9-
- `serverupdate`:
10-
- [v0.2.2](services/serverupdate/CHANGELOG.md#v022)
11-
- **Dependencies:** Bump `org.openapitools:jackson-databind-nullable` from `0.2.8` to `0.2.12`
6+
- `kms`:
7+
- [v0.1.0](services/kms/CHANGELOG.md#v010)
8+
- Initial onboarding of STACKIT Java SDK for STACKIT Key Management Service (KMS)
129
- `objectstorage`:
1310
- [v0.5.0](services/objectstorage/CHANGELOG.md#v050)
1411
- **Dependencies:** Bump `org.openapitools:jackson-databind-nullable` from `0.2.8` to `0.2.12`
@@ -23,6 +20,12 @@
2320
- [v0.7.0](services/resourcemanager/CHANGELOG.md#v070)
2421
- **Dependencies:** Bump `org.openapitools:jackson-databind-nullable` from `0.2.8` to `0.2.12`
2522
- **Feature:** The `members` attribute in the `CreateProjectPayload` model class is now optional (previously required) and therefore changed from `@javax.annotation.Nonnull` to `@javax.annotation.Nullable`
23+
- `serverbackup`:
24+
- [v0.2.1](services/serverbackup/CHANGELOG.md#v021)
25+
- **Dependencies:** Bump `org.openapitools:jackson-databind-nullable` from `0.2.8` to `0.2.12`
26+
- `serverupdate`:
27+
- [v0.2.2](services/serverupdate/CHANGELOG.md#v022)
28+
- **Dependencies:** Bump `org.openapitools:jackson-databind-nullable` from `0.2.8` to `0.2.12`
2629
- `sfs`:
2730
- [v0.6.0](services/sfs/CHANGELOG.md#v060)
2831
- **Dependencies:** Bump `org.openapitools:jackson-databind-nullable` from `0.2.8` to `0.2.12`

‎examples/kms/build.gradle‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dependencies {
2+
implementation project (':services:kms')
3+
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
4+
}
5+
6+
ext.mainClassName = 'cloud.stackit.sdk.kms.examples.KmsExample'
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package cloud.stackit.sdk.kms.examples;
2+
3+
import cloud.stackit.sdk.core.KeyFlowAuthenticator;
4+
import cloud.stackit.sdk.core.config.CoreConfiguration;
5+
import cloud.stackit.sdk.core.exception.ApiException;
6+
import cloud.stackit.sdk.kms.v1api.api.KmsApi;
7+
import cloud.stackit.sdk.kms.v1api.model.Algorithm;
8+
import cloud.stackit.sdk.kms.v1api.model.CreateKeyPayload;
9+
import cloud.stackit.sdk.kms.v1api.model.CreateKeyRingPayload;
10+
import cloud.stackit.sdk.kms.v1api.model.Key;
11+
import cloud.stackit.sdk.kms.v1api.model.KeyList;
12+
import cloud.stackit.sdk.kms.v1api.model.KeyRing;
13+
import cloud.stackit.sdk.kms.v1api.model.KeyRingList;
14+
import cloud.stackit.sdk.kms.v1api.model.Purpose;
15+
import cloud.stackit.sdk.kms.v1api.model.Version;
16+
import cloud.stackit.sdk.kms.v1api.model.VersionList;
17+
import java.io.IOException;
18+
import java.util.Objects;
19+
import java.util.UUID;
20+
import java.util.concurrent.TimeUnit;
21+
import okhttp3.OkHttpClient;
22+
23+
/**
24+
* Creates a key ring and symmetric key, lists metadata, then cleans up only the created resources.
25+
* Key deletion is scheduled by KMS; you'll have to manually delete the key and the keyring
26+
* afterwards.
27+
*/
28+
@SuppressWarnings("PMD.SystemPrintln")
29+
public final class KmsExample {
30+
private static final String REGION = "eu01";
31+
private static final long WAIT_TIMEOUT_NANOS = TimeUnit.MINUTES.toNanos(10);
32+
private static final long POLL_INTERVAL_SECONDS = 5;
33+
34+
private KmsExample() {}
35+
36+
public static void main(String[] args) throws IOException, ApiException, InterruptedException {
37+
// The id of your STACKIT project, read from an environment variable for this example.
38+
String projectIdValue = System.getenv("STACKIT_PROJECT_ID");
39+
if (projectIdValue == null || projectIdValue.isEmpty()) {
40+
System.err.println("Environment variable 'STACKIT_PROJECT_ID' not found.");
41+
return;
42+
}
43+
UUID projectId = UUID.fromString(projectIdValue);
44+
45+
// Credentials are read from ~/.stackit/credentials.json or the environment variables
46+
// STACKIT_SERVICE_ACCOUNT_KEY_PATH / STACKIT_SERVICE_ACCOUNT_KEY.
47+
CoreConfiguration configuration = new CoreConfiguration();
48+
OkHttpClient httpClient = new OkHttpClient();
49+
KeyFlowAuthenticator authenticator = new KeyFlowAuthenticator(httpClient, configuration);
50+
httpClient =
51+
httpClient
52+
.newBuilder()
53+
.authenticator(authenticator)
54+
.callTimeout(60, TimeUnit.SECONDS)
55+
.build();
56+
KmsApi kmsApi = new KmsApi(httpClient);
57+
58+
runExample(kmsApi, projectId);
59+
}
60+
61+
private static void runExample(KmsApi kmsApi, UUID projectId)
62+
throws ApiException, InterruptedException {
63+
UUID keyRingID = null;
64+
UUID keyID = null;
65+
System.out.println("creating key ring");
66+
try {
67+
KeyRing keyRing =
68+
kmsApi.createKeyRing(
69+
projectId,
70+
REGION,
71+
new CreateKeyRingPayload()
72+
.displayName("java-sdk-example-keyring")
73+
.description("A Java SDK example key ring"));
74+
keyRingID = Objects.requireNonNull(keyRing.getId());
75+
waitForKeyRing(kmsApi, projectId, keyRingID);
76+
System.out.println("key ring created: " + keyRingID);
77+
System.out.println("creating key");
78+
Key key =
79+
kmsApi.createKey(
80+
projectId,
81+
REGION,
82+
keyRingID,
83+
new CreateKeyPayload()
84+
.algorithm(Algorithm.AES_256_GCM)
85+
.protection("software")
86+
.purpose(Purpose.SYMMETRIC_ENCRYPT_DECRYPT)
87+
.displayName("java-sdk-example-key")
88+
.description("A Java SDK example key"));
89+
keyID = Objects.requireNonNull(key.getId());
90+
waitForKey(kmsApi, projectId, keyRingID, keyID);
91+
System.out.println("key created: " + keyID);
92+
listKeyRings(kmsApi, projectId);
93+
} finally {
94+
if (keyID != null) {
95+
// schedule key for deletion
96+
System.out.println("scheduling key deletion");
97+
kmsApi.deleteKey(projectId, REGION, keyRingID, keyID);
98+
System.out.println(
99+
"Key scheduled for deletion. ⚠️ The key ring can't be deleted until the key deletion is done. This may take some days. Delete the key ring manually after this period.");
100+
}
101+
// key ring can only be deleted if no key exists.
102+
if (keyRingID != null && keyID == null) {
103+
System.out.println("deleting key ring");
104+
kmsApi.deleteKeyRing(projectId, REGION, keyRingID);
105+
System.out.println("key ring deleted");
106+
}
107+
}
108+
}
109+
110+
private static void waitForKeyRing(KmsApi kmsApi, UUID projectId, UUID keyRingId)
111+
throws ApiException, InterruptedException {
112+
waitUntil(
113+
"key ring " + keyRingId + " to become active",
114+
() -> {
115+
KeyRing.StateEnum state =
116+
kmsApi.getKeyRing(projectId, REGION, keyRingId).getState();
117+
if (state == KeyRing.StateEnum.ACTIVE) {
118+
return true;
119+
}
120+
if (state != KeyRing.StateEnum.CREATING) {
121+
throw new IllegalStateException("Unexpected key ring state: " + state);
122+
}
123+
return false;
124+
});
125+
}
126+
127+
private static void waitForKey(KmsApi kmsApi, UUID projectId, UUID keyRingId, UUID keyId)
128+
throws ApiException, InterruptedException {
129+
waitUntil(
130+
"key " + keyId + " to become active",
131+
() -> {
132+
Key.StateEnum state =
133+
kmsApi.getKey(projectId, REGION, keyRingId, keyId).getState();
134+
if (state == Key.StateEnum.ACTIVE) {
135+
return true;
136+
}
137+
if (state != Key.StateEnum.CREATING) {
138+
throw new IllegalStateException("Unexpected key state: " + state);
139+
}
140+
return false;
141+
});
142+
}
143+
144+
private static void waitUntil(String description, ApiCondition condition)
145+
throws ApiException, InterruptedException {
146+
long start = System.nanoTime();
147+
while (System.nanoTime() - start < WAIT_TIMEOUT_NANOS) {
148+
if (condition.isSatisfied()) {
149+
return;
150+
}
151+
TimeUnit.SECONDS.sleep(POLL_INTERVAL_SECONDS);
152+
}
153+
throw new IllegalStateException("Timed out waiting for " + description);
154+
}
155+
156+
@FunctionalInterface
157+
private interface ApiCondition {
158+
boolean isSatisfied() throws ApiException;
159+
}
160+
161+
private static void listKeyRings(KmsApi kmsApi, UUID projectId) throws ApiException {
162+
System.out.println("List key rings:");
163+
KeyRingList keyRings = kmsApi.listKeyRings(projectId, REGION);
164+
for (KeyRing keyRing : Objects.requireNonNull(keyRings.getKeyRings())) {
165+
UUID keyRingId = Objects.requireNonNull(keyRing.getId());
166+
System.out.println("* Key Ring ID: " + keyRingId);
167+
System.out.println("* Key Ring Name: " + keyRing.getDisplayName());
168+
System.out.println("* Key Ring State: " + keyRing.getState());
169+
listKeys(kmsApi, projectId, keyRingId);
170+
}
171+
}
172+
173+
private static void listKeys(KmsApi kmsApi, UUID projectId, UUID keyRingId)
174+
throws ApiException {
175+
KeyList keys = kmsApi.listKeys(projectId, REGION, keyRingId);
176+
for (Key key : Objects.requireNonNull(keys.getKeys())) {
177+
UUID keyId = Objects.requireNonNull(key.getId());
178+
System.out.println(" * Key ID: " + keyId);
179+
System.out.println(" * Key Name: " + key.getDisplayName());
180+
System.out.println(" * Algorithm: " + key.getAlgorithm());
181+
System.out.println(" * Purpose: " + key.getPurpose());
182+
System.out.println(" * Key State: " + key.getState());
183+
if (key.getState() == Key.StateEnum.DELETED) {
184+
continue;
185+
}
186+
187+
VersionList versions = kmsApi.listVersions(projectId, REGION, keyRingId, keyId);
188+
for (Version version : Objects.requireNonNull(versions.getVersions())) {
189+
System.out.println(" * Version Number: " + version.getNumber());
190+
System.out.println(" * Version State: " + version.getState());
191+
System.out.println(" * Disabled: " + version.getDisabled());
192+
}
193+
}
194+
}
195+
}

‎services/kms/CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
## v0.1.0
2+
- Initial onboarding of STACKIT Java SDK for STACKIT Key Management Service (KMS)

‎services/kms/README.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# STACKIT Java SDK for STACKIT Key Management Service API
2+
3+
This package is part of the STACKIT Java SDK. For additional information, please visit the [GitHub repository](https://github.com/stackitcloud/stackit-sdk-java) of the SDK.
4+
5+
## Installation from Maven Central (recommended)
6+
7+
The release artifacts for this SDK submodule are available on [Maven Central](https://central.sonatype.com/artifact/cloud.stackit.sdk/kms).
8+
9+
### Maven users
10+
11+
Add this dependency to your project's POM:
12+
13+
```xml
14+
<dependency>
15+
<groupId>cloud.stackit.sdk</groupId>
16+
<artifactId>kms</artifactId>
17+
<version><SDK_VERSION></version>
18+
<scope>compile</scope>
19+
</dependency>
20+
```
21+
22+
### Gradle users
23+
24+
Add this dependency to your project's build file:
25+
26+
```groovy
27+
repositories {
28+
mavenCentral()
29+
}
30+
31+
dependencies {
32+
implementation "cloud.stackit.sdk:kms:<SDK_VERSION>"
33+
}
34+
```
35+
36+
## Installation from local build
37+
38+
Building the API client library requires:
39+
1. Java SDK (version 11 to 21 should be supported) installed on your system
40+
41+
To install the API client library to your local Maven repository, simply execute:
42+
43+
```shell
44+
./gradlew services:kms:publishToMavenLocal
45+
```
46+
47+
### Maven users
48+
49+
Add this dependency to your project's POM:
50+
51+
```xml
52+
<dependency>
53+
<groupId>cloud.stackit.sdk</groupId>
54+
<artifactId>kms</artifactId>
55+
<version><SDK_VERSION></version>
56+
<scope>compile</scope>
57+
</dependency>
58+
```
59+
60+
### Gradle users
61+
62+
Add this dependency to your project's build file:
63+
64+
```groovy
65+
repositories {
66+
mavenLocal()
67+
}
68+
69+
dependencies {
70+
implementation "cloud.stackit.sdk:kms:<SDK_VERSION>"
71+
}
72+
```
73+
74+
## Getting Started
75+
76+
See the [kms examples](https://github.com/stackitcloud/stackit-sdk-java/tree/main/examples/kms/src/main/java/cloud/stackit/sdk/kms/examples).
77+
78+
## Recommendation
79+
80+
It's recommended to create an instance of `ApiClient` per thread in a multithreaded environment to avoid any potential issues.

‎services/kms/VERSION‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.1.0

‎services/kms/build.gradle‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
ext {
3+
jakarta_annotation_version = "1.3.5"
4+
}
5+
6+
dependencies {
7+
implementation "com.google.code.findbugs:jsr305:3.0.2"
8+
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
9+
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
10+
implementation 'com.google.code.gson:gson:2.9.1'
11+
implementation 'io.gsonfire:gson-fire:1.9.0'
12+
implementation 'jakarta.ws.rs:jakarta.ws.rs-api:2.1.6'
13+
implementation 'org.openapitools:jackson-databind-nullable:0.2.12'
14+
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.18.0'
15+
implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version"
16+
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3'
17+
testImplementation 'org.mockito:mockito-core:3.12.4'
18+
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.3'
19+
}

‎services/kms/oas_commit‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
08b95d7f2e3b9c288a1b5d1f1f6d13b0632b7ad0

0 commit comments

Comments
 (0)