Skip to content

Commit 46f2838

Browse files
committed
Merge 'feature/support_rds_auth' into 'master'
chore:add rds connect See merge request: !883
2 parents 2674cfa + d296fe9 commit 46f2838

File tree

4 files changed

+319
-26
lines changed

4 files changed

+319
-26
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.volcengine.feature.rds.auth;
2+
3+
import com.volcengine.ApiClient;
4+
import com.volcengine.ApiException;
5+
import com.volcengine.Pair;
6+
import com.volcengine.endpoint.ResolveEndpointOption;
7+
import com.volcengine.endpoint.ResolvedEndpoint;
8+
import com.volcengine.endpoint.StandardEndpointProvider;
9+
import com.volcengine.interceptor.*;
10+
import com.volcengine.sign.ServiceInfo;
11+
12+
import org.apache.commons.lang.StringUtils;
13+
14+
import java.util.*;
15+
16+
/**
17+
* RDS MySQL database connection utility class.
18+
* Generates a presigned URL that can be used as the authentication token (password) for database connections.
19+
*/
20+
public class ConnectUtils {
21+
22+
private static final String SERVICE_NAME = "rds_mysql";
23+
private static final String ACTION = "ConnectDatabase";
24+
private static final String VERSION = "2022-01-01";
25+
private static final int DEFAULT_EXPIRES_SECONDS = 900; // 15 minutes
26+
27+
/**
28+
* Generate an authorization token for database connection (used as password).
29+
* Aligned with Go SDK's BuildAuthToken implementation.
30+
*
31+
* @param apiClient ApiClient containing Credentials, Region, DisableSSL, and UseDualStack settings
32+
* @param dbUser Database user account
33+
* @param instanceId Database instance ID
34+
* @param expires Expiration time in seconds, defaults to 900 seconds (15 minutes) if <= 0
35+
* @return Presigned URL string that can be used as the authorization token for database connection
36+
* @throws ApiException if parameters are invalid or signing fails
37+
*/
38+
public static String buildAuthToken(ApiClient apiClient, String dbUser, String instanceId, int expires) throws ApiException {
39+
// Parameter validation
40+
if (apiClient == null) {
41+
throw new IllegalArgumentException("apiClient must not be null");
42+
}
43+
44+
if (StringUtils.isEmpty(apiClient.getRegion())) {
45+
throw new IllegalArgumentException("region must not be empty");
46+
}
47+
48+
if (StringUtils.isEmpty(dbUser)) {
49+
throw new IllegalArgumentException("dbUser must not be empty");
50+
}
51+
52+
if (StringUtils.isEmpty(instanceId)) {
53+
throw new IllegalArgumentException("instanceId must not be empty");
54+
}
55+
56+
// Use default expires if <= 0
57+
if (expires <= 0) {
58+
expires = DEFAULT_EXPIRES_SECONDS;
59+
}
60+
61+
// Resolve endpoint: prefer user-configured endpoint, fallback to StandardEndpointProvider
62+
String endpoint = getEndpoint(apiClient);
63+
64+
// SSL handling, ResolveEndpointInterceptor dose not support this
65+
String schema = apiClient.getDisableSSL() ? "http" : "https";
66+
67+
// Build InterceptorContext with presigned flag
68+
InterceptorContext context = new InterceptorContext(apiClient.getHttpClient(), null);
69+
context.setApiClient(apiClient);
70+
71+
RequestInterceptorContext reqCtx = context.getRequestContext();
72+
reqCtx.setPresigned(true);
73+
reqCtx.setSchema(schema);
74+
reqCtx.setHost(endpoint);
75+
reqCtx.setMethod("GET");
76+
reqCtx.setServiceInfo(new ServiceInfo(SERVICE_NAME, "GET"));
77+
reqCtx.setHeaderParams(new HashMap<>());
78+
79+
List<Pair> queryParams = new ArrayList<>();
80+
queryParams.add(new Pair("Action", ACTION));
81+
queryParams.add(new Pair("Version", VERSION));
82+
queryParams.add(new Pair("X-Expires", String.valueOf(expires)));
83+
queryParams.add(new Pair("DBUser", dbUser));
84+
queryParams.add(new Pair("InstanceId", instanceId));
85+
reqCtx.setQueryParams(queryParams);
86+
87+
// Execute sign interceptor for presigning
88+
new SignRequestInterceptor().intercept(context);
89+
90+
return reqCtx.getPresignedUrl();
91+
}
92+
93+
/**
94+
* Resolve endpoint host from ApiClient.
95+
* If the user has configured a custom endpoint (via apiClient.setEndpoint), it takes priority.
96+
* Strips schema prefix if present to return pure host.
97+
* Falls back to StandardEndpointProvider for region-based resolution when no custom endpoint is set.
98+
*
99+
* @return endpoint host without schema
100+
*/
101+
private static String getEndpoint(ApiClient apiClient) {
102+
if (StringUtils.isNotEmpty(apiClient.getEndpoint())) {
103+
String ep = apiClient.getEndpoint();
104+
if (ep.startsWith("https://")) {
105+
return ep.substring("https://".length());
106+
}
107+
if (ep.startsWith("http://")) {
108+
return ep.substring("http://".length());
109+
}
110+
return ep;
111+
}
112+
StandardEndpointProvider endpointProvider = new StandardEndpointProvider();
113+
ResolveEndpointOption option = new ResolveEndpointOption();
114+
option.setService(SERVICE_NAME);
115+
option.setRegion(apiClient.getRegion());
116+
option.setUseDualStack(apiClient.getUseDualStack());
117+
ResolvedEndpoint resolved = endpointProvider.endpointFor(option);
118+
return resolved.getEndpoint();
119+
}
120+
}

volcengine-java-sdk-core/src/main/java/com/volcengine/interceptor/RequestInterceptorContext.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public class RequestInterceptorContext {
2222
private ProgressRequestBody.ProgressRequestListener progressRequestListener;
2323
private boolean isCommon;
2424

25+
private boolean presigned = false;
26+
private String presignedUrl;
27+
2528
private ServiceInfo serviceInfo;
2629
private Request request;
2730

@@ -120,4 +123,20 @@ public Request getRequest() {
120123
public void setRequest(Request request) {
121124
this.request = request;
122125
}
126+
127+
public boolean isPresigned() {
128+
return presigned;
129+
}
130+
131+
public void setPresigned(boolean presigned) {
132+
this.presigned = presigned;
133+
}
134+
135+
public String getPresignedUrl() {
136+
return presignedUrl;
137+
}
138+
139+
public void setPresignedUrl(String presignedUrl) {
140+
this.presignedUrl = presignedUrl;
141+
}
123142
}

volcengine-java-sdk-core/src/main/java/com/volcengine/interceptor/SignRequestInterceptor.java

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
import org.apache.commons.lang.StringUtils;
1515

1616
import java.io.IOException;
17-
import java.util.List;
18-
import java.util.Map;
17+
import java.util.*;
1918

2019
public class SignRequestInterceptor implements RequestInterceptor {
2120

@@ -35,17 +34,8 @@ public InterceptorContext intercept(InterceptorContext context) throws ApiExcept
3534
ServiceInfo serviceInfo = context.getRequestContext().getServiceInfo();
3635
String[] authNames = context.getRequestContext().getAuthNames();
3736
RequestBody reqBody = context.getRequestContext().getRequestBody();
38-
ProgressRequestBody.ProgressRequestListener progressRequestListener = context.getInitInterceptorContext().getProgressRequestListener();
3937

4038
//sign
41-
final Buffer buffer = new Buffer();
42-
try {
43-
if (reqBody != null) {
44-
reqBody.writeTo(buffer);
45-
}
46-
} catch (IOException e) {
47-
throw new ApiException(e);
48-
}
4939
VolcstackSign volcengineSign = new VolcstackSign();
5040
if (context.getApiClient().getCredentials() != null) {
5141
volcengineSign.setCredentials(context.getApiClient().getCredentials());
@@ -74,8 +64,39 @@ public InterceptorContext intercept(InterceptorContext context) throws ApiExcept
7464
if (StringUtils.isEmpty(volcengineSign.getRegion())) {
7565
throw new RuntimeException("Region must set when ApiClient init");
7666
}
67+
68+
// Presigned branch
69+
if (context.getRequestContext().isPresigned()) {
70+
Map<String, String> queryParamsMap = new HashMap<>();
71+
for (Pair p : queryParams) {
72+
queryParamsMap.put(p.getName(), p.getValue());
73+
}
74+
75+
String host = context.getRequestContext().getHost();
76+
try {
77+
Map<String, String> presignedParams = volcengineSign.presign(queryParamsMap, host);
78+
String presignedUrl = buildPresignedUrl(
79+
context.getRequestContext().getSchema(), host, presignedParams);
80+
context.getRequestContext().setPresignedUrl(presignedUrl);
81+
} catch (Exception e) {
82+
throw new ApiException(e);
83+
}
84+
return context;
85+
}
86+
87+
// Normal sign branch
88+
final Buffer buffer = new Buffer();
89+
try {
90+
if (reqBody != null) {
91+
reqBody.writeTo(buffer);
92+
}
93+
} catch (IOException e) {
94+
throw new ApiException(e);
95+
}
7796
volcengineSign.applyToParams(queryParams, headerParams, buffer.readUtf8());
7897

98+
ProgressRequestBody.ProgressRequestListener progressRequestListener = context.getInitInterceptorContext().getProgressRequestListener();
99+
79100
//build final call
80101
StringBuilder url = new StringBuilder();
81102
url.append(context.getRequestContext().getSchema());
@@ -101,4 +122,23 @@ public InterceptorContext intercept(InterceptorContext context) throws ApiExcept
101122
context.getRequestContext().setRequest(request);
102123
return context;
103124
}
125+
126+
private static String buildPresignedUrl(String scheme, String host, Map<String, String> presignedParams) {
127+
StringBuilder url = new StringBuilder();
128+
url.append(scheme).append("://").append(host).append("?");
129+
130+
List<String> keys = new ArrayList<>(presignedParams.keySet());
131+
Collections.sort(keys);
132+
133+
for (int i = 0; i < keys.size(); i++) {
134+
String key = keys.get(i);
135+
String value = presignedParams.get(key);
136+
url.append(key).append("=").append(value);
137+
if (i < keys.size() - 1) {
138+
url.append("&");
139+
}
140+
}
141+
142+
return url.toString();
143+
}
104144
}

0 commit comments

Comments
 (0)