This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
The following commit(s) were added to refs/heads/master by this push:
new b5df7d215 changed the token signing implementation to store/fetch key
pairs from vault
b5df7d215 is described below
commit b5df7d215a1240cc4388dd3b40679290cbe9f506
Author: lahiruj <[email protected]>
AuthorDate: Mon Dec 2 19:23:15 2024 -0500
changed the token signing implementation to store/fetch key pairs from vault
---
.../api/identity/IdentityManagementController.java | 12 +-
application/src/main/resources/application.yml | 2 -
application/src/main/resources/static/favicon.ico | Bin 0 -> 3084 bytes
.../org/apache/custos/service/auth/KeyLoader.java | 96 ----------------
.../org/apache/custos/service/auth/KeyService.java | 121 +++++++++++++++++++++
.../apache/custos/service/auth/TokenService.java | 10 +-
.../credential/store/CredentialStoreService.java | 24 ++++
7 files changed, 156 insertions(+), 109 deletions(-)
diff --git
a/api/src/main/java/org/apache/custos/api/identity/IdentityManagementController.java
b/api/src/main/java/org/apache/custos/api/identity/IdentityManagementController.java
index 487401d39..12f1b9d34 100644
---
a/api/src/main/java/org/apache/custos/api/identity/IdentityManagementController.java
+++
b/api/src/main/java/org/apache/custos/api/identity/IdentityManagementController.java
@@ -39,7 +39,7 @@ import
org.apache.custos.core.identity.management.api.AuthorizationResponse;
import org.apache.custos.core.identity.management.api.EndSessionRequest;
import org.apache.custos.core.identity.management.api.GetCredentialsRequest;
import org.apache.custos.service.auth.AuthClaim;
-import org.apache.custos.service.auth.KeyLoader;
+import org.apache.custos.service.auth.KeyService;
import org.apache.custos.service.auth.TokenAuthorizer;
import org.apache.custos.service.credential.store.Credential;
import org.apache.custos.service.credential.store.CredentialManager;
@@ -84,12 +84,12 @@ public class IdentityManagementController {
private final IdentityManagementService identityManagementService;
private final TokenAuthorizer tokenAuthorizer;
- private final KeyLoader keyLoader;
+ private final KeyService keyService;
- public IdentityManagementController(IdentityManagementService
identityManagementService, TokenAuthorizer tokenAuthorizer, KeyLoader
keyLoader) {
+ public IdentityManagementController(IdentityManagementService
identityManagementService, TokenAuthorizer tokenAuthorizer, KeyService
keyService) {
this.identityManagementService = identityManagementService;
this.tokenAuthorizer = tokenAuthorizer;
- this.keyLoader = keyLoader;
+ this.keyService = keyService;
}
@PostMapping("/authenticate")
@@ -529,11 +529,11 @@ public class IdentityManagementController {
@GetMapping("/.well-known/jwks.json")
public ResponseEntity<?> keys() {
- KeyPair keyPair = keyLoader.getKeyPair();
+ KeyPair keyPair = keyService.getKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
JWK jwk = new RSAKey.Builder(rsaPublicKey)
- .keyID(keyLoader.getKeyID())
+ .keyID(keyService.getKeyID())
.keyUse(KeyUse.SIGNATURE)
.build();
diff --git a/application/src/main/resources/application.yml
b/application/src/main/resources/application.yml
index 506630738..f3e563e29 100644
--- a/application/src/main/resources/application.yml
+++ b/application/src/main/resources/application.yml
@@ -29,8 +29,6 @@ custos:
tenant:
base:
uri: http://localhost/api/v1/tenant-management/oauth2/tenant
- keys:
- location:<CHANGE_PATH>/application/src/main/resources/keys
api:
domain: "http://localhost:8081"
diff --git a/application/src/main/resources/static/favicon.ico
b/application/src/main/resources/static/favicon.ico
new file mode 100644
index 000000000..c56418262
Binary files /dev/null and b/application/src/main/resources/static/favicon.ico
differ
diff --git
a/services/src/main/java/org/apache/custos/service/auth/KeyLoader.java
b/services/src/main/java/org/apache/custos/service/auth/KeyLoader.java
deleted file mode 100644
index c734373ab..000000000
--- a/services/src/main/java/org/apache/custos/service/auth/KeyLoader.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.custos.service.auth;
-
-import jakarta.annotation.PostConstruct;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.Base64;
-
-@Component
-public class KeyLoader {
-
- private KeyPair keyPair;
- private String keyID;
-
- @Value("${custos.keys.location}")
- private String keysLocation;
-
- @PostConstruct
- public void init() {
- try {
- this.keyPair = loadKeyPair();
- this.keyID = computeKeyID(keyPair.getPublic());
- } catch (Exception e) {
- throw new RuntimeException("Failed to load key pair", e);
- }
- }
-
- public KeyPair getKeyPair() {
- return this.keyPair;
- }
-
- public String getKeyID() {
- return this.keyID;
- }
-
- private KeyPair loadKeyPair() throws Exception {
- System.out.println("Key location - " + keysLocation);
- String privateKeyContent = new
String(Files.readAllBytes(Paths.get(keysLocation + "/private_key.pem")));
- String publicKeyContent = new
String(Files.readAllBytes(Paths.get(keysLocation + "/public_key.pem")));
-
- privateKeyContent = privateKeyContent.replaceAll("-----BEGIN PRIVATE
KEY-----", "")
- .replaceAll("-----END PRIVATE KEY-----", "")
- .replaceAll("\\s+", "");
-
- publicKeyContent = publicKeyContent.replaceAll("-----BEGIN PUBLIC
KEY-----", "")
- .replaceAll("-----END PUBLIC KEY-----", "")
- .replaceAll("\\s+", "");
-
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-
- byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyContent);
- byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyContent);
-
- PKCS8EncodedKeySpec privateKeySpec = new
PKCS8EncodedKeySpec(privateKeyBytes);
- X509EncodedKeySpec publicKeySpec = new
X509EncodedKeySpec(publicKeyBytes);
-
- PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
- PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
-
- return new KeyPair(publicKey, privateKey);
- }
-
- private String computeKeyID(PublicKey publicKey) throws Exception {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- byte[] hash = digest.digest(publicKey.getEncoded());
- return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
- }
-}
diff --git
a/services/src/main/java/org/apache/custos/service/auth/KeyService.java
b/services/src/main/java/org/apache/custos/service/auth/KeyService.java
new file mode 100644
index 000000000..a64fef285
--- /dev/null
+++ b/services/src/main/java/org/apache/custos/service/auth/KeyService.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.custos.service.auth;
+
+import org.apache.custos.service.credential.store.CredentialStoreService;
+import org.springframework.stereotype.Service;
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+import java.util.Map;
+
+@Service
+public class KeyService {
+
+ private final CredentialStoreService credentialStoreService;
+
+ private volatile KeyPair keyPair;
+ private volatile String keyID;
+
+ public KeyService(CredentialStoreService credentialStoreService) {
+ this.credentialStoreService = credentialStoreService;
+ }
+
+ public KeyPair getKeyPair() {
+ if (this.keyPair == null) {
+ synchronized (this) {
+ if (this.keyPair == null) {
+ loadOrGenerateKeyPair();
+ }
+ }
+ }
+ return this.keyPair;
+ }
+
+ public String getKeyID() {
+ if (this.keyID == null) {
+ synchronized (this) {
+ if (this.keyID == null) {
+ this.keyID = computeKeyID(getKeyPair().getPublic());
+ }
+ }
+ }
+ return this.keyID;
+ }
+
+ private void loadOrGenerateKeyPair() {
+ try {
+ Map<String, String> keyData =
credentialStoreService.retrieveKeyPair();
+ if (keyData == null || keyData.isEmpty()) {
+ generateAndStoreKeyPair();
+ } else {
+ this.keyPair = createKeyPairFromData(keyData);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load or generate key pair",
e);
+ }
+ }
+
+ private void generateAndStoreKeyPair() {
+ try {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ KeyPair generatedKeyPair = keyGen.generateKeyPair();
+
+ String privateKey =
Base64.getEncoder().encodeToString(generatedKeyPair.getPrivate().getEncoded());
+ String publicKey =
Base64.getEncoder().encodeToString(generatedKeyPair.getPublic().getEncoded());
+
+ credentialStoreService.storeKeyPair(privateKey, publicKey);
+
+ this.keyPair = generatedKeyPair;
+ this.keyID = computeKeyID(generatedKeyPair.getPublic());
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to generate and store key
pair", e);
+ }
+ }
+
+ private KeyPair createKeyPairFromData(Map<String, String> keyData) throws
Exception {
+ byte[] privateKeyBytes =
Base64.getDecoder().decode(keyData.get("privateKey"));
+ byte[] publicKeyBytes =
Base64.getDecoder().decode(keyData.get("publicKey"));
+
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PrivateKey privateKey = keyFactory.generatePrivate(new
PKCS8EncodedKeySpec(privateKeyBytes));
+ PublicKey publicKey = keyFactory.generatePublic(new
X509EncodedKeySpec(publicKeyBytes));
+
+ return new KeyPair(publicKey, privateKey);
+ }
+
+ private String computeKeyID(PublicKey publicKey) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] hash = digest.digest(publicKey.getEncoded());
+ return
Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to compute key ID", e);
+ }
+ }
+}
diff --git
a/services/src/main/java/org/apache/custos/service/auth/TokenService.java
b/services/src/main/java/org/apache/custos/service/auth/TokenService.java
index d7644aa41..7124b898b 100644
--- a/services/src/main/java/org/apache/custos/service/auth/TokenService.java
+++ b/services/src/main/java/org/apache/custos/service/auth/TokenService.java
@@ -51,21 +51,21 @@ public class TokenService {
private static final Logger LOGGER =
LoggerFactory.getLogger(TokenService.class);
- private final KeyLoader keyLoader;
+ private final KeyService keyService;
private final UserProfileService userProfileService;
private final CacheManager cacheManager;
@Autowired
- public TokenService(KeyLoader keyLoader, UserProfileService
userProfileService, CacheManager cacheManager) {
- this.keyLoader = keyLoader;
+ public TokenService(KeyService keyService, UserProfileService
userProfileService, CacheManager cacheManager) {
+ this.keyService = keyService;
this.userProfileService = userProfileService;
this.cacheManager = cacheManager;
}
public String generateWithCustomClaims(String token, long tenantId) throws
Exception {
- KeyPair keyPair = keyLoader.getKeyPair();
- String keyID = keyLoader.getKeyID();
+ KeyPair keyPair = keyService.getKeyPair();
+ String keyID = keyService.getKeyID();
SignedJWT signedJWT = SignedJWT.parse(token);
diff --git
a/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
b/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
index e080d9686..61b8f5ff9 100644
---
a/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
+++
b/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
@@ -44,10 +44,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.vault.core.VaultTemplate;
+import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultResponseSupport;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
/**
@@ -60,6 +63,7 @@ public class CredentialStoreService {
private static final Logger LOGGER =
LoggerFactory.getLogger(CredentialStoreService.class);
private static final String BASE_PATH = "/secret/";
+ private static final String KEY_PATH = BASE_PATH + "keys";
private final VaultTemplate vaultTemplate;
@@ -597,6 +601,26 @@ public class CredentialStoreService {
}
}
+ public void storeKeyPair(String privateKey, String publicKey) {
+ Map<String, String> keys = new HashMap<>();
+ keys.put("privateKey", privateKey);
+ keys.put("publicKey", publicKey);
+
+ Map<String, Object> data = new HashMap<>();
+ data.put("data", keys);
+
+ vaultTemplate.write(KEY_PATH, data);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Map<String, String> retrieveKeyPair() {
+ VaultResponse response = vaultTemplate.read(KEY_PATH);
+ if (response != null && response.getData() != null) {
+ return (Map<String, String>) response.getData().get("data");
+ }
+ return null;
+ }
+
private OperationMetadata convertFromEntity(StatusEntity entity) {
return OperationMetadata.newBuilder()
.setEvent(entity.getEvent())