
# 双用户凭据轮换策略
#### 简介
双用户轮换策略是指在一个凭据中更新两个用户所保存的信息。
例如：您的应用程序需要保持高可用性，如果您使用单用户轮换，那么您在修改用户密码和更新凭据内容的过程中会出现访问应用失败的情况，这样您就需要使用双用户凭据轮换策略。
您需要有一个账号例如Admin有权限创建并修改user1、user2用户的账号密码，首先您创建一个凭据创建并保存user1的账号与密码，记为user1/password1。通过新增凭据版本v2创建user2，并且保存user2的账号密码，记为user2/password2。修改user1的密码后，您会需要新增凭据版本v3，记为user1/password3。比如在您轮换创建新的凭据版本v3时，应用程序会继续使用现有的凭据版本v2获取信息。一旦凭据版本v3准备就绪，轮换会切换暂存标签，以便应用程序使用凭据版本v3获取信息。
#### 约束条件
您首先需要确保您的账号拥有KMS Administrator或者KMS CMKFullAccess权限，详情见[DEW权限管理](https://support.huaweicloud.com/productdesc-dew/dew_01_0018.html)。
#### 轮换凭据的API
您可以调用以下API，在本地进行凭据轮换。
| API名称                                                                        | 说明                     |
|:---|:---|
| [创建凭据版本](https://support.huaweicloud.com/api-dew/CreateSecretVersion.html)   | 创建新的凭据版本。              |
| [查询凭据版本与凭据值](https://support.huaweicloud.com/api-dew/ShowSecretVersion.html) | 查询指定凭据版本的信息和版本中的明文凭据值。 |
| [更新凭据版本状态](https://support.huaweicloud.com/api-dew/UpdateSecretStage.html)   | 更新凭据的版本状态。             |
| [查询凭据版本状态](https://support.huaweicloud.com/api-dew/ShowSecretStage.html)     | 查询指定的凭据版本状态标记的版本信息。    |
   
#### 双账号凭据轮换代码示例
1. 通过华为云控制台，使用管理员账号创建一个凭据，详情见[创建凭据](https://support.huaweicloud.com/usermanual-dew/dew_01_9993.html)。
2. 请准备基础认证信息。
   - ACCESS_KEY: 华为账号Access Key
   
   - SECRET_ACCESS_KEY: 华为账号Secret Access Key
   
   - PROJECT_ID: 华为云局点项目ID，请参见[华为云局点项目](https://support.huaweicloud.com/api-iam/iam_17_0002.html)。
   
   - CSMS_ENDPOINT: 华为云CSMS服务访问终端地址。
   
   - 认证用的ak和sk直接写到代码中有很大的安全风险，建议在配置文件或者环境变量中密文存放，使用时解密，确保安全。
   
   - 本示例以ak和sk保存在环境变量中来实现身份验证为例，运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。
    
3. 进行双用户凭据轮换。 示例代码中：
   - 凭据名称：华为云控制台创建的凭据名称。
   
   - 凭据内容：华为云控制台创建的凭据中所保存的值。
   
   - 凭据版本Id：华为云控制台创建凭据后自行生成凭据ID。
   
   - 凭据版本：LATEST_VERSION
     ```
     import com.huaweicloud.sdk.core.auth.BasicCredentials;
     import com.huaweicloud.sdk.csms.v1.CsmsClient;
     import com.huaweicloud.sdk.csms.v1.model.CreateSecretVersionRequest;
     import com.huaweicloud.sdk.csms.v1.model.CreateSecretVersionRequestBody;
     import com.huaweicloud.sdk.csms.v1.model.CreateSecretVersionResponse;
     import com.huaweicloud.sdk.csms.v1.model.ListSecretStageRequest;
     import com.huaweicloud.sdk.csms.v1.model.ListSecretStageResponse;
     import com.huaweicloud.sdk.csms.v1.model.ShowSecretVersionRequest;
     import com.huaweicloud.sdk.csms.v1.model.ShowSecretVersionResponse;
     import com.huaweicloud.sdk.csms.v1.model.UpdateSecretStageRequest;
     import com.huaweicloud.sdk.csms.v1.model.UpdateSecretStageRequestBody;
     import java.util.Collections;
     import java.util.List;
     public class CsmsDualAccountExample {
         /**
          * 基础认证信息：
          * - ACCESS_KEY: 华为账号Access Key
          * - SECRET_ACCESS_KEY: 华为账号Secret Access Key
          * - PROJECT_ID: 华为云局点项目ID 详情见https://support.huaweicloud.com/productdesc-iam/iam_01_0023.html
          * - CSMS_ENDPOINT: 华为云CSMS服务访问终端地址 详情见https://support.huaweicloud.com/api-dew/dew_02_0052.html
          * - 认证用的ak和sk直接写到代码中有很大的安全风险，建议在配置文件或者环境变量中密文存放，使用时解密，确保安全；
          * - 本示例以ak和sk保存在环境变量中来实现身份验证为例，运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。
          */
         private static final String ACCESS_KEY = System.getenv("HUAWEICLOUD_SDK_AK");
         private static final String SECRET_ACCESS_KEY = System.getenv("HUAWEICLOUD_SDK_SK");
         private static final String PROJECT_ID = "<ProjectID>";
         private static final String CSMS_ENDPOINT = "<CsmsEndpoint>";
         // 用来查询凭据最新版本详情的版本Id
         private static final String LATEST_VERSION = "latest";
         private static final String HW_CURRENT_STAGE = "SYSCURRENT";
         private static final String HW_PENDING_STAGE = "HW_PENDING";
         public static void main(String[] args) {
             String secretName = args[0];
             String secretString = args[1];
             dualAccountRotation(secretName, secretString);
         }
         /**
          * 凭据轮换-双账号模式代码示例
          *
          * @param secretName
          * @param secretString
          */
         private static void dualAccountRotation(String secretName, String secretString) {
             // 创建带自定义版本状态的新版本凭据，假如凭据内容为服务A的账号密码。
             CreateSecretVersionResponse newSecretVersion = createNewSecretVersionWithStage(secretName,
                     secretString, Collections.singletonList(HW_PENDING_STAGE));
             String versionId = newSecretVersion.getVersionMetadata().getId();
             //  轮换前检查pending是否被修改
             ListSecretStageResponse listSecretStageResponse = showSecretStage(secretName, HW_PENDING_STAGE);
             assert listSecretStageResponse.getStage().getVersionId().equals(versionId);
             // 在更新服务A的账号密码后，同步将新版本凭据的版本状态更新为SYSCURRENT，之前旧的凭据仍存储在服务中，已无法通过latest访问。
             updateSecretStage(secretName, versionId, HW_CURRENT_STAGE);
             // 此时通过"latest"版本状态查询最新版本凭据，完成双账号凭据轮转。
             ShowSecretVersionResponse secretResponse = showVersionDetail(secretName, LATEST_VERSION);
             assert secretResponse.getVersion().getSecretString().equals(secretString);
         }
         /**
          * 创建凭据示例
          * 使用自定义版本状态的方式添加凭据版本，程序不会将SYSCURRENT版本状态指向新版本凭据。
          *
          * @param secretName
          * @param newSecretVersionText
          * @param stageList
          * @return
          */
         private static CreateSecretVersionResponse createNewSecretVersionWithStage(String secretName,
                                                                                    String newSecretVersionText,
                                                                                    List<String> stageList) {
             CsmsClient csmsClient = getCsmsClient();
             // 将新版凭据托管到凭据管理服务
             CreateSecretVersionRequest createSecretVersionRequest = new CreateSecretVersionRequest()
                     .withSecretName(secretName)
                     .withBody(new CreateSecretVersionRequestBody()
                             .withSecretString(newSecretVersionText)
                             .withVersionStages(stageList));
             CreateSecretVersionResponse secretVersion = csmsClient.createSecretVersion(createSecretVersionRequest);
             System.out.printf("Created new version success, version id: %s", secretVersion.getVersionMetadata().getId());
             return secretVersion;
         }
         /**
          * 将传入的版本状态指向指定凭据版本
          * @param secretName
          * @param versionId
          * @param newStageName
          */
         private static void updateSecretStage(String secretName, String versionId, String newStageName) {
             UpdateSecretStageRequest updateSecretStageRequest = new UpdateSecretStageRequest().withSecretName(secretName)
                     .withStageName(newStageName).withBody(new UpdateSecretStageRequestBody().withVersionId(versionId));
             CsmsClient csmsClient = getCsmsClient();
             csmsClient.updateSecretStage(updateSecretStageRequest);
             System.out.printf("Version stage update success. version id:%s, new stage name:%s", versionId, newStageName);
         }
         /**
          * 查询指定版本凭据
          * @param secretName
          * @param versionId
          * @return
          */
         private static ShowSecretVersionResponse showVersionDetail(String secretName, String versionId) {
             ShowSecretVersionRequest showSecretVersionRequest = new ShowSecretVersionRequest().withSecretName(secretName)
                     .withVersionId(versionId);
             CsmsClient csmsClient = getCsmsClient();
             ShowSecretVersionResponse secretDetail = csmsClient.showSecretVersion(showSecretVersionRequest);
             System.out.printf("Query latest version success. version id:%s",
      secretDetail.getVersion().getVersionMetadata().getId());
             return secretDetail;
         }
         /**
          * 查询指定版本状态详情
          * @param secretName
          * @param stageName
          * @return
          */
         private static ListSecretStageResponse showSecretStage(String secretName, String stageName) {
             ShowSecretStageRequest showSecretStageRequest = new ShowSecretStageRequest()
                     .withSecretName(secretName).withStageName(stageName);
             CsmsClient csmsClient = getCsmsClient();
             return csmsClient.showSecretStage(showSecretStageRequest);
         }
         /**
          * 获取CSMS服务客户端
          *
          * @return
          */
         private static CsmsClient getCsmsClient() {
             BasicCredentials auth = new BasicCredentials()
                     .withAk(ACCESS_KEY)
                     .withSk(SECRET_ACCESS_KEY)
                     .withProjectId(PROJECT_ID);
             return CsmsClient.newBuilder().withCredential(auth).withEndpoint(CSMS_ENDPOINT).build();
         }
     }
     ```
     
    
 
