Skip to content

Commit fd6ba5d

Browse files
committed
remove oci sdk deps
1 parent c8cd0af commit fd6ba5d

2 files changed

Lines changed: 112 additions & 39 deletions

File tree

spring-boot-starter-waffle-oci-vault/build.gradle.kts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,5 @@ group = "com.wafflestudio.spring"
33
dependencies {
44
implementation("org.springframework.boot:spring-boot")
55

6-
implementation("com.oracle.oci.sdk:oci-java-sdk-secrets:3.80.1")
7-
implementation("com.oracle.oci.sdk:oci-java-sdk-common-httpclient-jersey3:3.80.1") {
8-
exclude(group = "org.glassfish.jersey.inject", module = "jersey-hk2")
9-
}
10-
implementation("com.oracle.oci.sdk:oci-java-sdk-addons-oke-workload-identity:3.80.1")
11-
126
testImplementation(kotlin("test"))
137
}

spring-boot-starter-waffle-oci-vault/src/main/kotlin/com/wafflestudio/spring/ocivault/config/OciVaultEnvironmentPostProcessor.kt

Lines changed: 112 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,27 @@ package com.wafflestudio.spring.ocivault.config
22

33
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
44
import com.fasterxml.jackson.module.kotlin.readValue
5-
import com.oracle.bmc.Region
6-
import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider
7-
import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider
8-
import com.oracle.bmc.secrets.SecretsClient
9-
import com.oracle.bmc.secrets.model.Base64SecretBundleContentDetails
10-
import com.oracle.bmc.secrets.requests.GetSecretBundleRequest
115
import org.springframework.boot.SpringApplication
126
import org.springframework.boot.env.EnvironmentPostProcessor
137
import org.springframework.core.env.ConfigurableEnvironment
148
import org.springframework.core.env.MapPropertySource
9+
import java.net.URI
10+
import java.net.http.HttpClient
11+
import java.net.http.HttpRequest
12+
import java.net.http.HttpResponse
13+
import java.nio.file.Files
14+
import java.nio.file.Path
15+
import java.security.KeyFactory
16+
import java.security.Signature
17+
import java.security.spec.PKCS8EncodedKeySpec
18+
import java.time.ZoneOffset
19+
import java.time.ZonedDateTime
20+
import java.time.format.DateTimeFormatter
1521
import java.util.Base64
1622

1723
class OciVaultEnvironmentPostProcessor : EnvironmentPostProcessor {
1824
private val objectMapper = jacksonObjectMapper()
25+
private val httpClient = HttpClient.newHttpClient()
1926

2027
override fun postProcessEnvironment(
2128
environment: ConfigurableEnvironment,
@@ -27,27 +34,19 @@ class OciVaultEnvironmentPostProcessor : EnvironmentPostProcessor {
2734
}
2835
val secretIdsProperty = environment.getProperty("oci-vault-secret-ids") ?: return
2936
val secretIds = secretIdsProperty.split(",").map { it.trim() }
30-
val region =
31-
Region.fromRegionId(
32-
environment.getProperty("oci.vault.region", "ap-chuncheon-1"),
33-
)
37+
val region = environment.getProperty("oci.vault.region", "ap-chuncheon-1")
3438

35-
val authProvider = createAuthProvider(environment)
36-
val client = SecretsClient.builder().region(region).build(authProvider)
39+
val ociConfig = loadOciConfig()
3740
val secrets = mutableMapOf<String, Any>()
3841

39-
try {
40-
secretIds.forEach { secretId ->
41-
val secretString = getSecretString(client, secretId)
42-
val parsedSecrets = objectMapper.readValue<Map<String, Any>>(secretString)
43-
secrets.putAll(
44-
parsedSecrets.filterKeys {
45-
environment.getProperty(it).isNullOrEmpty()
46-
},
47-
)
48-
}
49-
} finally {
50-
client.close()
42+
secretIds.forEach { secretId ->
43+
val secretString = getSecretString(ociConfig, region, secretId)
44+
val parsedSecrets = objectMapper.readValue<Map<String, Any>>(secretString)
45+
secrets.putAll(
46+
parsedSecrets.filterKeys {
47+
environment.getProperty(it).isNullOrEmpty()
48+
},
49+
)
5150
}
5251

5352
if (secrets.isNotEmpty()) {
@@ -57,20 +56,100 @@ class OciVaultEnvironmentPostProcessor : EnvironmentPostProcessor {
5756
}
5857
}
5958

60-
private fun createAuthProvider(environment: ConfigurableEnvironment): BasicAuthenticationDetailsProvider {
61-
return ConfigFileAuthenticationDetailsProvider("DEFAULT")
62-
}
63-
6459
private fun getSecretString(
65-
client: SecretsClient,
60+
config: OciConfig,
61+
region: String,
6662
secretId: String,
6763
): String {
64+
val host = "secrets.vaults.$region.oci.oraclecloud.com"
65+
val path = "/20190301/secretbundles/$secretId"
66+
val date = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))
67+
68+
val signingString = "(request-target): get $path\ndate: $date\nhost: $host"
69+
val signature = sign(config.privateKey, signingString)
70+
val keyId = "${config.tenancy}/${config.user}/${config.fingerprint}"
71+
val authHeader =
72+
"""Signature version="1",keyId="$keyId",algorithm="rsa-sha256",headers="(request-target) date host",signature="$signature""""
73+
6874
val request =
69-
GetSecretBundleRequest.builder()
70-
.secretId(secretId)
75+
HttpRequest.newBuilder()
76+
.uri(URI.create("https://$host$path"))
77+
.header("date", date)
78+
.header("authorization", authHeader)
79+
.GET()
7180
.build()
72-
val response = client.getSecretBundle(request)
73-
val content = (response.secretBundle.secretBundleContent as Base64SecretBundleContentDetails).content
81+
82+
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
83+
if (response.statusCode() != 200) {
84+
throw RuntimeException("OCI Vault API error (${response.statusCode()}): ${response.body()}")
85+
}
86+
87+
val body = objectMapper.readValue<Map<String, Any>>(response.body())
88+
89+
@Suppress("UNCHECKED_CAST")
90+
val secretBundleContent =
91+
body["secretBundleContent"] as Map<String, Any>
92+
val content = secretBundleContent["content"] as String
7493
return String(Base64.getDecoder().decode(content))
7594
}
95+
96+
private fun sign(
97+
privateKeyPem: String,
98+
signingString: String,
99+
): String {
100+
val pemContent =
101+
privateKeyPem
102+
.substringAfter("-----\n")
103+
.substringBefore("\n-----")
104+
.replace("\\s".toRegex(), "")
105+
val keyBytes = Base64.getDecoder().decode(pemContent)
106+
val keySpec = PKCS8EncodedKeySpec(keyBytes)
107+
val keyFactory = KeyFactory.getInstance("RSA")
108+
val key = keyFactory.generatePrivate(keySpec)
109+
val signature = Signature.getInstance("SHA256withRSA")
110+
signature.initSign(key)
111+
signature.update(signingString.toByteArray())
112+
return Base64.getEncoder().encodeToString(signature.sign())
113+
}
114+
115+
private fun loadOciConfig(): OciConfig {
116+
val configPath = Path.of(System.getProperty("user.home"), ".oci", "config")
117+
val lines = Files.readAllLines(configPath)
118+
val props = mutableMapOf<String, String>()
119+
var inDefaultProfile = false
120+
121+
for (line in lines) {
122+
val trimmed = line.trim()
123+
if (trimmed == "[DEFAULT]") {
124+
inDefaultProfile = true
125+
continue
126+
}
127+
if (trimmed.startsWith("[")) {
128+
inDefaultProfile = false
129+
continue
130+
}
131+
if (inDefaultProfile && trimmed.contains("=")) {
132+
val (key, value) = trimmed.split("=", limit = 2)
133+
props[key.trim()] = value.trim()
134+
}
135+
}
136+
137+
val keyFilePath =
138+
props["key_file"]?.replace("~", System.getProperty("user.home"))
139+
?: throw RuntimeException("key_file not found in OCI config")
140+
141+
return OciConfig(
142+
tenancy = props["tenancy"] ?: throw RuntimeException("tenancy not found in OCI config"),
143+
user = props["user"] ?: throw RuntimeException("user not found in OCI config"),
144+
fingerprint = props["fingerprint"] ?: throw RuntimeException("fingerprint not found in OCI config"),
145+
privateKey = Files.readString(Path.of(keyFilePath)),
146+
)
147+
}
148+
149+
private data class OciConfig(
150+
val tenancy: String,
151+
val user: String,
152+
val fingerprint: String,
153+
val privateKey: String,
154+
)
76155
}

0 commit comments

Comments
 (0)