文档首页/ 数据加密服务 DEW/ 最佳实践/ 凭据管理/ 云服务使用凭据管理服务/ 通过凭据管理服务免AKSK硬编码访问OBS服务
更新时间:2024-12-13 GMT+08:00
分享

通过凭据管理服务免AKSK硬编码访问OBS服务

应用场景

在代码中将认证所需的 AK 和 SK 硬编码或以明文方式存储,会带来较大的安全风险。针对初始化Java OBS SDK客户端的场景,介绍一种通过动态获取托管于凭据管理服务(CSMS)的凭证,避免硬编码 AK/SK,从而安全地创建和配置 OBS 客户端的解决方案。

实现原理

本示例基于AK和SK已托管于凭据管理服务(CSMS)的场景,演示如何通过实现OBS SDK提供的 IObsCredentialsProvider接口来创建ObsClient实例。

CsmsObsCredentialsProvider 类通过从ECS服务器自动获取临时访问凭证,进一步访问CSMS获取托管的AK和SK,并将其用作OBS客户端的访问凭证。

前提条件

  1. IAM创建ECS云服务委托并获取临时凭证。具体操作请参见IAM创建ECS委托
  2. 已将目标AK/SK存入凭据管理服务,可以通过以下两种方式:
    1. AK/SK按照存入凭据值的方式手动托管在凭据管理服务中,具体操作可参见存入凭据值
    2. 通过函数工作流轮转IAM凭据的方式自动化托管AK/SK,具体操作可参见通过函数工作流轮转IAM凭证

代码示例

  1. 获取CSMS SDK和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 版本号以3.24.9为例其他版本替换成相应的版本号-->
        <version>3.24.9</version>
    </dependency>
    <dependency>
        <groupId>com.huaweicloud.sdk</groupId>
        <artifactId>huaweicloud-sdk-csms</artifactId>
        <!-- csms java SDK 版本号以3.1.94为例其他版本替换成相应的版本号-->
        <version>3.1.94</version>
    </dependency>
    <dependency>
        <groupId>org.json</groupId>
        <artifactId>json</artifactId>
        <!-- json 版本号以20231013为例其他版本替换成相应的版本号-->
        <version>20231013</version>
    </dependency>
</dependencies>

  1. 创建并配置OBS客户端示例代码如下:
1
2
3
4
5
6
7
8
9
// Endpoint以北京四为例,其他地区请按实际情况填写。 
String endPoint = "https://obs.cn-north-4.myhuaweicloud.com"; 
// Secret name为托管AKSK凭据的凭据名称 
String secretName = "<Your CSMS_SECRET_NAME>"; 
// ECS场景从凭据管理服务获取认证信息,并初始化OBS客户端 
ObsClient obsClient = new ObsClient(new CsmsObsCredentialsProvider(secretName), endPoint); 
// 使用访问OBS 
// 关闭obsClient 
obsClient.close();

  1. 实现OBS SDK中的IObsCredentialsProvider的示例代码如下:
  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
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; 
    // region code 以北京四为例,其他地区请按实际情况填写。
    private final String regionCode = "cn-north-4";
    // Csms endpoint以北京四为例,其他地区请按实际情况填写。 
    private final String csmsEndpoint = "https://kms.cn-north-4.myhuaweicloud.com";
    // Iam endpoint以北京四为例,其他地区请按实际情况填写。 
    private final String iamEndpoint = "https://iam.cn-north-4.myhuaweicloud.com"; 
    // Rotation time轮转刷新时间。注意:该值要小于凭据轮转时间,此处以15分钟为例。 
    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); 
        // 当前时间加上轮转时间为此访问凭据的过期时间。 
        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) { 
            // 异常情况打印,此处可以替换成业务相关的异常,以打印日志和抛出RuntimeException为例。 
            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); 
        // 使用Calendar类的add方法,将当前时间增加afterMinute分钟 
        calendar.add(Calendar.MINUTE, convertToMinute(afterMinute)); 
        // 减去时区的偏移量和夏令时的偏移量,获取UTC时间 
        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; 
    } 
}

相关文档