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
- 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.
- The AK/SK has been stored in CSMS in either of the following ways:
- The AK/SK has been manually stored in CSMS as a secret value. For details, see Saving and Viewing Secret Values.
- 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
- 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> |
- 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(); |
- 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; } } |
Feedback
Was this page helpful?
Provide feedbackThank you very much for your feedback. We will continue working to improve the documentation.See the reply and handling status in My Cloud VOC.
For any further questions, feel free to contact us through the chatbot.
Chatbot