Skip to content

Commit 29a4b7e

Browse files
chore:add rds connect
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 13806ae commit 29a4b7e

File tree

2 files changed

+206
-13
lines changed

2 files changed

+206
-13
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.volcengine.feature.rds.auth;
2+
3+
import com.volcengine.sign.Credentials;
4+
import com.volcengine.sign.VolcstackSign;
5+
6+
import org.apache.commons.lang.StringUtils;
7+
8+
9+
import java.util.*;
10+
11+
/**
12+
* RDS MySQL database connection utility class
13+
*/
14+
public class ConnectUtils {
15+
16+
private static final String SERVICE_NAME = "rds_mysql";
17+
private static final String ACTION = "ConnectDatabase";
18+
private static final String VERSION = "2022-01-01";
19+
private static final int DEFAULT_EXPIRES_SECONDS = 900; // 15 minutes
20+
21+
/**
22+
* Generate an authorization token for database connection (used as password)
23+
*
24+
* @param credentials Credentials containing AccessKey and SecretKey for signing
25+
* @param region Region, e.g., "cn-beijing"
26+
* @param dbUser Database user account
27+
* @param instanceId Database instance ID
28+
* @param expires Expiration time in seconds, defaults to 900 seconds (15 minutes) if <= 0
29+
* @return Presigned URL string that can be used as the authorization token for database connection
30+
* @throws Exception if parameters are invalid or signing fails
31+
*/
32+
public static String buildAuthToken(
33+
Credentials credentials,
34+
String region,
35+
String dbUser,
36+
String instanceId,
37+
int expires
38+
) throws Exception {
39+
// Parameter validation
40+
if (credentials == null ||
41+
StringUtils.isEmpty(credentials.getAccessKey()) ||
42+
StringUtils.isEmpty(credentials.getSecretKey())) {
43+
throw new IllegalArgumentException("credentials must not be null and must contain valid access key and secret key");
44+
}
45+
46+
if (StringUtils.isEmpty(region)) {
47+
throw new IllegalArgumentException("region must not be empty");
48+
}
49+
50+
if (StringUtils.isEmpty(dbUser) || StringUtils.isEmpty(instanceId)) {
51+
throw new IllegalArgumentException("dbUser or instanceId must not be empty");
52+
}
53+
54+
// Build query parameters
55+
Map<String, String> queryParams = new HashMap<>();
56+
queryParams.put("Action", ACTION);
57+
queryParams.put("Version", VERSION);
58+
queryParams.put("X-Expires", expires > 0 ? String.valueOf(expires) : String.valueOf(DEFAULT_EXPIRES_SECONDS));
59+
queryParams.put("DBUser", dbUser);
60+
queryParams.put("InstanceId", instanceId);
61+
62+
// Create signature object
63+
VolcstackSign sign = new VolcstackSign(credentials);
64+
sign.setRegion(region);
65+
sign.setService(SERVICE_NAME);
66+
sign.setMethod("GET");
67+
68+
// Generate presigned URL
69+
Map<String, String> presignedParams = sign.presign(queryParams);
70+
71+
// Build complete URL
72+
return buildUrl(presignedParams);
73+
}
74+
75+
/**
76+
* Build complete URL
77+
*
78+
* @param presignedParams Presigned query parameters
79+
* @return Complete URL string
80+
*/
81+
private static String buildUrl(Map<String, String> presignedParams) {
82+
StringBuilder url = new StringBuilder();
83+
// Sort parameter keys
84+
List<String> keys = new ArrayList<>(presignedParams.keySet());
85+
Collections.sort(keys);
86+
87+
for (int i = 0; i < keys.size(); i++) {
88+
String key = keys.get(i);
89+
String value = presignedParams.get(key);
90+
url.append(key).append("=").append(value);
91+
if (i < keys.size() - 1) {
92+
url.append("&");
93+
}
94+
}
95+
96+
return url.toString();
97+
}
98+
}

volcengine-java-sdk-core/src/main/java/com/volcengine/sign/VolcstackSign.java

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public void applyToParams(List<Pair> queryParams, Map<String, String> headerPara
6161
SDK_CORE_LOGGER.debugSign("VolcstackSign start...");
6262

6363
try {
64-
sign(params, headerParams,payload);
64+
sign(params, headerParams, payload);
6565
} catch (Exception e) {
6666
SDK_CORE_LOGGER.error(()->"VolcstackSign exception: ", e);
6767
}
@@ -123,7 +123,7 @@ private List<String> sortHeaderKeys(Map<String, String> headerParams) {
123123
return listHeaderKeys;
124124
}
125125

126-
private void buildCredentialScope(Map<String, String> headerParams,SignRequest signRequest){
126+
private void buildCredentialScope(Map<String, String> headerParams, SignRequest signRequest) {
127127
StringBuilder credentialScope = new StringBuilder("");
128128
credentialScope.append(headerParams.get("X-Date").substring(0, 8));
129129
credentialScope.append("/");
@@ -135,7 +135,7 @@ private void buildCredentialScope(Map<String, String> headerParams,SignRequest s
135135
SDK_CORE_LOGGER.debugSign("credentialScope: " + signRequest.credentialScope);
136136
}
137137

138-
private void buildStringToSign(Map<String, String> headerParams,SignRequest signRequest)throws Exception{
138+
private void buildStringToSign(Map<String, String> headerParams, SignRequest signRequest) throws Exception {
139139
StringBuilder stringToSign = new StringBuilder("");
140140
stringToSign.append("HMAC-SHA256");
141141
stringToSign.append("\n");
@@ -148,7 +148,7 @@ private void buildStringToSign(Map<String, String> headerParams,SignRequest sign
148148
SDK_CORE_LOGGER.debugSign("stringToSign: " + signRequest.stringToSign);
149149
}
150150

151-
private void buildAuthorization(Map<String, String> headerParams,SignRequest signRequest)throws Exception{
151+
private void buildAuthorization(Map<String, String> headerParams, SignRequest signRequest) throws Exception {
152152
byte[] signingkeyByte = getHmacSHA256("request", getHmacSHA256(
153153
service, getHmacSHA256(
154154
region, getHmacSHA256(
@@ -186,7 +186,7 @@ private void buildSignedHeaders(Map<String, String> headerParams, SignRequest si
186186
StringBuilder canonicalHeaders = new StringBuilder("");
187187
StringBuilder signedHeaders = new StringBuilder("");
188188
for (String key : listHeaderKeys) {
189-
if (!key.equalsIgnoreCase("x-date")){
189+
if (!key.equalsIgnoreCase("x-date")) {
190190
continue;
191191
}
192192
canonicalHeaders.append(key.toLowerCase());
@@ -203,9 +203,9 @@ private void buildSignedHeaders(Map<String, String> headerParams, SignRequest si
203203
SDK_CORE_LOGGER.debugSign("signedHeaders: " + signRequest.signedHeaders);
204204
}
205205

206-
private void initHeaders(Map<String,String> headerParams){
207-
if (!StringUtils.isEmpty(credentials.getSessionToken())){
208-
headerParams.put("X-Security-Token",credentials.getSessionToken());
206+
private void initHeaders(Map<String, String> headerParams) {
207+
if (!StringUtils.isEmpty(credentials.getSessionToken())) {
208+
headerParams.put("X-Security-Token", credentials.getSessionToken());
209209
}
210210
if (!headerParams.containsKey("X-Date")) {
211211
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
@@ -247,17 +247,17 @@ private void sign(Map<String, String> queryParams, Map<String, String> headerPar
247247
// step 1
248248
SDK_CORE_LOGGER.debugSign("step 1: buildCanonicalRequest start");
249249
buildCanonicalRequest(queryParams, signRequest);
250-
buildSignedHeaders(headerParams,signRequest);
251-
buildPayload(signRequest,payload);
250+
buildSignedHeaders(headerParams, signRequest);
251+
buildPayload(signRequest, payload);
252252

253253
// step 2
254254
SDK_CORE_LOGGER.debugSign("step 2: buildCredentialScope and buildStringToSign start");
255-
buildCredentialScope(headerParams,signRequest);
256-
buildStringToSign(headerParams,signRequest);
255+
buildCredentialScope(headerParams, signRequest);
256+
buildStringToSign(headerParams, signRequest);
257257

258258
// step 3
259259
SDK_CORE_LOGGER.debugSign("step 3: buildAuthorization start");
260-
buildAuthorization(headerParams,signRequest);
260+
buildAuthorization(headerParams, signRequest);
261261

262262
}
263263

@@ -324,5 +324,100 @@ public VolcstackSign copy() {
324324
return copySign;
325325
}
326326

327+
328+
329+
/**
330+
* Generate presigned URL query parameters
331+
*
332+
* @param queryParams Original query parameters
333+
* @return Complete query parameters Map containing all signature information
334+
*/
335+
public Map<String, String> presign(Map<String, String> queryParams) throws Exception {
336+
Map<String, String> presignedParams = new HashMap<>(queryParams);
337+
338+
// Generate timestamp
339+
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
340+
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
341+
String xDate = sdf.format(new Date());
342+
String dateStamp = xDate.substring(0, 8);
343+
344+
// Build credential scope
345+
String credentialScope = dateStamp + "/" + region + "/" + service + "/request";
346+
347+
// Add required query parameters for presigning
348+
presignedParams.put("X-Date", xDate);
349+
presignedParams.put("X-NotSignBody", "");
350+
presignedParams.put("X-Credential", credentials.getAccessKey() + "/" + credentialScope);
351+
presignedParams.put("X-Algorithm", "HMAC-SHA256");
352+
presignedParams.put("X-SignedHeaders", "");
353+
presignedParams.put("X-SignedQueries", ""); // Set to empty first, will be updated later
354+
355+
// Collect all query parameter keys (excluding X-Security-Token), sort and update X-SignedQueries
356+
List<String> queryKeys = new ArrayList<>(presignedParams.keySet());
357+
Collections.sort(queryKeys);
358+
presignedParams.put("X-SignedQueries", String.join(";", queryKeys));
359+
360+
// Important: X-Security-Token must be added after X-SignedQueries calculation is complete
361+
if (!StringUtils.isEmpty(credentials.getSessionToken())) {
362+
presignedParams.put("X-Security-Token", credentials.getSessionToken());
363+
}
364+
365+
// Re-collect all parameter keys (including X-Security-Token) to build canonical request
366+
List<String> allKeys = new ArrayList<>(presignedParams.keySet());
367+
Collections.sort(allKeys);
368+
369+
// Build canonical request
370+
StringBuilder canonicalRequest = new StringBuilder();
371+
canonicalRequest.append(this.method);
372+
canonicalRequest.append("\n");
373+
canonicalRequest.append("/");
374+
canonicalRequest.append("\n");
375+
376+
// Canonical Query String - concatenate all parameters after sorting
377+
StringBuilder canonicalQueryString = new StringBuilder();
378+
for (String key : allKeys) {
379+
canonicalQueryString.append(signStringEncoder(key));
380+
canonicalQueryString.append("=");
381+
canonicalQueryString.append(signStringEncoder(presignedParams.get(key)));
382+
canonicalQueryString.append("&");
383+
}
384+
canonicalRequest.append(canonicalQueryString.substring(0, canonicalQueryString.length() - 1));
385+
canonicalRequest.append("\n");
386+
387+
// Canonical Headers - empty (because X-SignedHeaders is empty)
388+
canonicalRequest.append("\n");
389+
390+
// Signed Headers - empty
391+
canonicalRequest.append("");
392+
canonicalRequest.append("\n");
393+
394+
// Extra newline before Payload Hash (required by volcstack presigning specification)
395+
canonicalRequest.append("\n");
396+
397+
// Payload Hash - use hash value of X-NotSignBody (empty string)
398+
canonicalRequest.append(getSHA256(""));
399+
400+
// Build string to sign
401+
StringBuilder stringToSign = new StringBuilder();
402+
stringToSign.append("HMAC-SHA256");
403+
stringToSign.append("\n");
404+
stringToSign.append(xDate);
405+
stringToSign.append("\n");
406+
stringToSign.append(credentialScope);
407+
stringToSign.append("\n");
408+
stringToSign.append(getSHA256(canonicalRequest.toString()));
409+
410+
// Calculate signature
411+
byte[] signingKey = getHmacSHA256("request",
412+
getHmacSHA256(service,
413+
getHmacSHA256(region,
414+
getHmacSHA256(dateStamp, credentials.getSecretKey().getBytes(StandardCharsets.UTF_8)))));
415+
String signature = getHmacSHA256Hex(stringToSign.toString(), signingKey);
416+
417+
// Add signature to query parameters
418+
presignedParams.put("X-Signature", signature);
419+
420+
return presignedParams;
421+
}
327422
}
328423

0 commit comments

Comments
 (0)