Updated on 2025-09-24 GMT+08:00

Accessing OBS Using CSMS Without Hardcoding AK/SK

Scenario

If the AK/SK required for authentication is hardcoded or stored in plaintext, high security risks may occur. If Java OBS SDK client is initialized, you can use CSMS secrets to securely create and configure OBS client solutions without hardcoded AK/SK.

Principles

This section describes how to use the IObsCredentialsProvider API provided by OBS SDK to create an ObsClient instance when the AK/SK has been hosted in CSMS.

The CsmsObsCredentialsProvider class automatically obtains temporary access secrets from the ECS server, accesses CSMS to obtain the AK/SK, and uses them as the access secrets of the OBS client.

Prerequisites

  1. An ECS agency has been created in IAM and a temporary secret has been obtained. For details, see Using CSMS to Prevent AK/SK Leakage.
  2. The AK/SK has been stored in CSMS in either of the following ways:
    1. The AK/SK has been manually stored in CSMS as a secret value. For details, see Saving and Viewing Secret Values.
    2. The AK/SK has been automatically stored in CSMS as an IAM secret rotated using FunctionGraph. For details, see Rotating IAM Secrets Using FunctionGraph.

Code Example

  1. Obtain the dependency statements of CSMS SDK and OBS SDK.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
    <dependency>
        <groupId>com.huaweicloud</groupId>
        <artifactId>esdk-obs-java-bundle</artifactId>
        <!-- obs java SDK version. 3.24.9 is used as an example. Replace it with the actual version number.-->
        <version>3.24.9</version>
    </dependency>
    <dependency>
        <groupId>com.huaweicloud.sdk</groupId>
        <artifactId>huaweicloud-sdk-csms</artifactId>
        <!-- csms java SDK version. 3.1.94 is used as an example. Replace it with the actual version number.-->
        <version>3.1.94</version>
    </dependency>
    <dependency>
        <groupId>org.json</groupId>
        <artifactId>json</artifactId>
        <!-- json version. 20231013 is used as an example. Replace it with the actual version number.-->
        <version>20231013</version>
    </dependency>
</dependencies>

  1. Create and configure the OBS client.
1
2
3
4
5
6
7
8
9
// CN North-Beijing4 is used as an example. Replace it with the actual endpoint.
String endPoint = "https://obs.cn-north-4.myhuaweicloud.com"; 
// Secret name is the name of the managed AK/SK.
String secretName = "<Your CSMS_SECRET_NAME>"; 
// In the ECS scenario, obtain authentication information from CSMS and initialize the OBS client.
ObsClient obsClient = new ObsClient(new CsmsObsCredentialsProvider(secretName), endPoint); 
// Access OBS.
// Close obsClient.
obsClient.close();

  1. Implement IObsCredentialsProvider in OBS SDK.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import static com.obs.services.internal.security.LimitedTimeSecurityKey.getUtcTime; 
import com.huaweicloud.sdk.core.auth.BasicCredentials; 
import com.huaweicloud.sdk.core.auth.ICredential; 
import com.huaweicloud.sdk.core.region.Region; 
import com.huaweicloud.sdk.csms.v1.CsmsClient; 
import com.huaweicloud.sdk.csms.v1.model.ShowSecretVersionRequest; 
import com.huaweicloud.sdk.csms.v1.model.ShowSecretVersionResponse; 
import com.obs.log.ILogger; 
import com.obs.log.LoggerBuilder; 
import com.obs.services.IObsCredentialsProvider; 
import com.obs.services.internal.security.EcsSecurityUtils; 
import com.obs.services.internal.security.LimitedTimeSecurityKey; 
import com.obs.services.internal.security.SecurityKey; 
import com.obs.services.internal.security.SecurityKeyBean; 
import com.obs.services.internal.utils.JSONChange; 
import com.obs.services.model.ISecurityKey; 
import kotlin.Pair; 
import org.json.JSONObject; 
import java.io.IOException; 
import java.util.Calendar; 
import java.util.Date; 
import java.util.concurrent.atomic.AtomicBoolean; 
 
public class CsmsObsCredentialsProvider implements IObsCredentialsProvider { 
    private volatile LimitedTimeSecurityKey securityKey; 
    private AtomicBoolean getNewKeyFlag = new AtomicBoolean(false); 
    private static final ILogger ILOG = LoggerBuilder.getLogger(CsmsObsCredentialsProvider.class); 
    // default is -1, not retry 
    private int maxRetryTimes = -1; 
    // csms secret name 
    private final String secretName; 
   
    
    private final String regionCode = "CN-Hong Kong";
     
    
    private final String csmsEndpoint = "https://kms.CN-Hong Kong.myhuaweicloud.com";
    
    
    private final String iamEndpoint = "https://iam.CN-Hong Kong.myhuaweicloud.com"; 
    // Rotation time The value must be less than the secret rotation time. 15 minutes is used as an example.
    private final String rotationTime = "15m"; 
    public CsmsObsCredentialsProvider(String secretName) { 
        this.maxRetryTimes = 3; 
        this.secretName = secretName; 
    } 
    public CsmsObsCredentialsProvider(int maxRetryTimes, String secretName) { 
        this.maxRetryTimes = maxRetryTimes; 
        this.secretName = secretName; 
    } 
    @Override 
    public void setSecurityKey(ISecurityKey securityKey) { 
        throw new UnsupportedOperationException("CsmsObsCredentialsProvider class does not support this method"); 
    } 
    @Override 
    public ISecurityKey getSecurityKey() { 
        if (getNewKeyFlag.compareAndSet(false, true)) { 
            try { 
                if (securityKey == null || securityKey.willSoonExpire()) { 
                    refresh(false); 
                } else if (securityKey.aboutToExpire()) { 
                    refresh(true); 
                } 
            } finally { 
                getNewKeyFlag.set(false); 
            } 
        } else { 
            if (ILOG.isDebugEnabled()) { 
                ILOG.debug("some other thread is refreshing."); 
            } 
        } 
        return securityKey; 
    } 
    /** 
     * refresh 
     * 
     * @param ignoreException ignore exception 
     */ 
    private void refresh(boolean ignoreException) { 
        int times = 0; 
        do { 
            try { 
                securityKey = getNewSecurityKey(); 
                break; 
            } catch (IOException | RuntimeException e) { 
                ILOG.warn("refresh new security key failed. times : " + times + "; maxRetryTimes is : " + maxRetryTimes 
                        + "; ignoreException : " + ignoreException, e); 
                if (times >= this.maxRetryTimes) { 
                    ILOG.error("refresh new security key failed.", e); 
                    if (!ignoreException) { 
                        throw new IllegalArgumentException(e); 
                    } 
                } 
            } 
        } while (times++ < maxRetryTimes); 
    } 
    private LimitedTimeSecurityKey getNewSecurityKey() throws IOException, IllegalArgumentException { 
        String content = EcsSecurityUtils.getSecurityKeyInfoWithDetail(); 
        SecurityKey securityInfo = (SecurityKey) JSONChange.jsonToObj(new SecurityKey(), content); 
        if (securityInfo == null) { 
            throw new IllegalArgumentException("Invalid securityKey : " + content); 
        } 
        SecurityKeyBean securityKeyBean = securityInfo.getBean(); 
        ICredential auth = new BasicCredentials().withIamEndpoint(iamEndpoint) 
                .withAk(securityKeyBean.getAccessKey()).withSk(securityKeyBean.getSecretKey()) 
                .withSecurityToken(securityKeyBean.getSecurityToken()); 
        Pair<String, String> akSkFromCSMS = getAKSKFromCSMS(auth, secretName); 
        // Current time plus the rotation time, which is the secret expiration time.
        Date expiryDate = getUtcTimeAfterMinuteAdd(rotationTime); 
        StringBuilder strAccess = new StringBuilder(); 
        String accessKey = akSkFromCSMS.getFirst(); 
        int length = accessKey.length(); 
        strAccess.append(accessKey.substring(0, length / 3)); 
        strAccess.append("******"); 
        strAccess.append(accessKey.substring(2 * length / 3, length - 1)); 
        ILOG.warn("the AccessKey : " + strAccess.toString() + "will expiry at UTC time : " + expiryDate); 
        return new LimitedTimeSecurityKey(akSkFromCSMS.getFirst(), akSkFromCSMS.getSecond(), null, expiryDate); 
    } 
    /** 
     * Pair 
     * first is access key id 
     * second is access key secret 
     */ 
    private Pair<String, String> getAKSKFromCSMS(ICredential auth, String secretName) { 
        ILOG.info("Get ak sk from csms secret name " + secretName + " begin."); 
        CsmsClient client = CsmsClient.newBuilder().withCredential(auth) 
                .withRegion(new Region(regionCode, csmsEndpoint)).build(); 
        try { 
            ShowSecretVersionRequest showSecretVersionRequest = new ShowSecretVersionRequest(); 
            showSecretVersionRequest.withSecretName(secretName); 
            showSecretVersionRequest.withVersionId("latest"); 
            ShowSecretVersionResponse showSecretVersionResponse = client.showSecretVersion(showSecretVersionRequest); 
            String secretString = showSecretVersionResponse.getVersion().getSecretString(); 
            JSONObject secretJsonObject = new JSONObject(secretString); 
            return new Pair<>((String) secretJsonObject.get("access_key_id"), 
                    (String) secretJsonObject.get("access_key_secret")); 
        } catch (Exception e) { 
            // Exception printing. Replace it with a service-related exception. The following uses log printing and RuntimeException throwing as an example.
            ILOG.info("error message:" + e.getMessage()); 
            throw new RuntimeException(e.getMessage()); 
        } 
    } 
    private static Date getUtcTimeAfterMinuteAdd(String afterMinute) { 
        Calendar calendar = Calendar.getInstance(); 
        int offset = calendar.get(Calendar.ZONE_OFFSET); 
        int dstOffset = calendar.get(Calendar.DST_OFFSET); 
        // Use the add method of the Calendar class to add afterMinute to the current time.
        calendar.add(Calendar.MINUTE, convertToMinute(afterMinute)); 
        // Obtain the UTC time by subtracting the time zone offset and DST offset.
        calendar.add(Calendar.MILLISECOND, -(offset + dstOffset)); 
        return calendar.getTime(); 
    } 
    private static int convertToMinute(String period) { 
        String unit = period.substring(period.length() - 1); 
        int time = Integer.parseInt(period.substring(0, period.length() - 1)); 
        switch (unit) { 
            case "d": 
                time = time * 24 * 60; 
                break; 
            case "h": 
                time = time * 60; 
                break; 
            case "m": 
                break; 
            default: 
                time = 0; 
                break; 
        } 
        return time; 
    } 
}