1. Apollo中敏感数据的安全挑战
很多用户在使用 Apollo 的配置管理功能时,逐渐开始在 Apollo 中存储敏感数据(如数据源、Token、用户名和密码等)。首先,用户自建的 Apollo 实例并未接入过多的安全防护,很容易受到安全攻击;其次,采用明文格式存储的敏感信息,很容易被窃取,给公司的信息安全带来了极大的隐患。国家三级等保(三级信息安全等级保护)是针对重要信息系统的安全保护要求。其中对于应用与数据安全,提出了如下要求:
- 软件开发过程应遵循安全编码原则,减少潜在漏洞。
- 数据存储和处理过程中应进行必要的加密,同时也要有数据完整性检查机制。
- 敏感数据如AK被攻击后,明文易被获取,并且可以直接被利用。
2. 产品架构
KMS产品为 Apollos 提供了敏感配置加解密集成方案:KMS 为配置加解密,Apollo 存储加密后的敏感配置。整体的产品逻辑架构图如下:
3. 使用Apollo和KMS3.0,为敏感配置加密的最佳实践
下面介绍如何使用Apollp提供的配置加密能力,具体的文档说明。
整个最佳实践可以归纳为如下几个步骤:
- 自建、开通Apollo
- 开通密钥管理服务KMS
- 登陆密钥管理服务KMS控制台,创建KMS专属实例,创建密钥资源
- Apollo配置KMS的SDK进行加密配置;
3.1 开通微服务引擎MSE
安装好Docker , docker-compose
下载 docker-compose.yml ,apolloconfigdb.sql,apolloportaldb.sql
3.2 启动Apollo 配置中心
3.3 访问Apollo 配置中心
默认账号信息【apollo/admin】
3.4 使用Apollo 管理端api创建更新删除获取Apollo 配置项
注册第三方应用,具体如下图所示
4. 开通密钥管理服务KMS
您可登录密钥管理服务KMS,查看并开通KMS。
4.1 登陆密钥管理服务KMS控制台,创建KMS专属实例,创建密钥资源
4.2 登录密钥管理服务KMS产品控制台,创建KMS专属实例
这里依次选择“实例管理”,“确定地域”(如北京),“创建实例”。
注意:
- KMS实例一定要和使用配置加解密的应用程序的机器在同一个VPC内,否则会导致配置加解密无法使用。
- 不用保证MSE Nacos与KMS实例的网络互通性。
4.3 确定KMS实例规格
其中,在创建KMS专属实例时,需要确认实例资源规格。这里需要强调的是,使用MSE Nacos的配置加解密能力,仅需1个密钥资源即可。其他具体实例规格信息可参考购买和启用KMS实例。
4.4 创建密钥资源
KMS实例创建完成后,需要继续创建密钥资源。按照上图依次在密钥管理服务KMS控制台选择“密钥管理”、“确定地域(如北京)”、“创建密钥”。
4.5 确定密钥规格
对于用于MSE Nacos配置加解密的密钥,需要确定2个参数信息:
- 密钥类型:对称密钥
- 密钥规格:Aliyun_AES_256
其他密钥参数,可根据客户自己需求设定,具体密钥管理功能可参考密钥管理。
5. 在Apollo,开启配置加密功能,绑定KMS密钥到Apollo实例,创建并使用加密配置
5.1 KMS应用接入Apollo 配置中心
<dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-openapi</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>alibabacloud-dkms-gcs-sdk</artifactId> <version>0.5.2</version> </dependency>
5.2 创建KmsUtils类,通过Apollo api 查询配置项及加密发布配置项
package com.aliyun.demo; import com.aliyun.dkms.gcs.openapi.models.Config; import com.aliyun.dkms.gcs.sdk.Client; import com.aliyun.dkms.gcs.sdk.models.*; import java.util.Base64; import java.nio.charset.StandardCharsets; public class KmsUtils { public Client getkmsClient() { Client client = null; try{ String caContent="-----BEGIN CERTIFICATE-----\n" + "MIIDuzCCAqOgAwIBAgIJALTKwWAjvbMiMA0GCSqGSIb3DQEBCwUAMHQxCzAJBgNV\n" + "BAYTAkNOMREwDwYDVQQIDAhaaGVKaWFuZzERMA8GA1UEBwwISGFuZ1pob3UxEDAO\n" + "BgNVBAoMB0FsaWJhYmExDzANBgNVBAsMBkFsaXl1bjEcMBoGA1UEAwwTUHJpdmF0\n" + "ZSBLTVMgUm9vdCBDQTAeFw0yMTA3MDgwNjU1MjlaFw00MTA3MDMwNjU1MjlaMHQx\n" + "CzAJBgNVBAYTAkNOMREwDwYDVQQIDAhaaGVKaWFuZzERMA8GA1UEBwwISGFuZ1po\n" + "b3UxEDAOBgNVBAoMB0FsaWJhYmExDzANBgNVBAsMBkFsaXl1bjEcMBoGA1UEAwwT\n" + "UHJpdmF0ZSBLTVMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" + "ggEBAM99IVpxedcGYZVXXX4XZ+bYWw1gVD5Uli9kBrlq3nBT8c0b+4/1W4aQzr+S\n" + "zBEWMrRZaMH3c5rV63qILyy8w4Gm2J0++nIA7uXVhpbliq6lf6p0i3cKpp+JGCbP\n" + "kLvOpONrZ4an/htNE+vpfbsW3WcwcVbmZpQyuGIXIST8iyfTwckZSMkxAPW4rhMa\n" + "QtmQcQiWaJsR0WJoqP7jXcHZobYehnUlzi/ZzdtmnkhTjz0+GvX9/1GBHCyfVEOO\n" + "a0RiT5nEz55xWahZKbj+1nhmInbc7BUqfhz/mbQjtk5lAsJpA8JrbukRhTiAMbj9\n" + "TqUqLe/meEVdjtD6wWsaZoSeoucCAwEAAaNQME4wHQYDVR0OBBYEFAVKzUR5/d6j\n" + "nYM/bHlxURkGhe2EMB8GA1UdIwQYMBaAFAVKzUR5/d6jnYM/bHlxURkGhe2EMAwG\n" + "A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAMCxpkV/KPuKVOBsT4yYkeX1\n" + "Q7IQQoICOAkZOkg7KEej4BJpW2Ml121aFScKxdnRcV2omr48K+GQt/mPNAXgf3k2\n" + "eKQce7xmBwntRplDJFrzdZPBdjel4i62JoWlaTejht2L5ano+x3a3keqF0GoOnn0\n" + "StwpG2xa0S6RmyNFiMugwDBCtSCJAJKr8yAbO+hoe1lQR0M78dy8ENteC/BXuAks\n" + "cktoG0/ryX9EqE9xQ2Do3INDq2PxIuA9yPvZ1eV3xa3bd1u+02feGIrtc9cJ5chf\n" + "vUk5tbgg58NVXrg29yE5eq3j2BErUlAs2LB/Bt/Jhkekvp7qR42btJj+/zQnDSw=\n" + "-----END CERTIFICATE-----\n" + "-----BEGIN CERTIFICATE-----\n" + "MIID4DCCAsigAwIBAgIJAKOtsZIcfRgaMA0GCSqGSIb3DQEBCwUAMHQxCzAJBgNV\n" + "BAYTAkNOMREwDwYDVQQIDAhaaGVKaWFuZzERMA8GA1UEBwwISGFuZ1pob3UxEDAO\n" + "BgNVBAoMB0FsaWJhYmExDzANBgNVBAsMBkFsaXl1bjEcMBoGA1UEAwwTUHJpdmF0\n" + "ZSBLTVMgUm9vdCBDQTAeFw0yMTEyMDgwNzE0MDlaFw00MTEyMDMwNzE0MDlaMIGC\n" + "MQswCQYDVQQGEwJDTjERMA8GA1UECAwIWmhlSmlhbmcxETAPBgNVBAcMCEhhbmda\n" + "aG91MRAwDgYDVQQKDAdBbGliYWJhMQ8wDQYDVQQLDAZBbGl5dW4xKjAoBgNVBAMM\n" + "IVByaXZhdGUgS01TIGNuLXNoYW5naGFpIFNlY29uZCBDQTCCASIwDQYJKoZIhvcN\n" + "AQEBBQADggEPADCCAQoCggEBAL9dlbTTaHtLFNB0Rnda9uYjnuewU99uoK1bwW9n\n" + "NScUdqIlcBTu6xDHFFQSUjC/IkkloHpn1wBg9lUNp9u2YET1/RDQDFYgbinbLv2r\n" + "NhUiu8qcX31K9EXkWPILCsjLHTQIgLmvTgzHTOB2PxWUQmlvgjqaII6cFCTNd0uQ\n" + "fMOBaxg/o5Vjs6dgVQSwC9u6RcXS2VQuWXUo2pk7H3M6zUSXg6RpzZ8HPds+fyIX\n" + "OM5JhLeHaLl0LtwojpSr2yvXifxr1o7uumK+Sp/bAAV2CiN7eC4KX39MWBu6QpaV\n" + "tpKSHuXsH8DizlAidPqQsGVZ8AS1tD2mJXpLCXMeT5TkGy0CAwEAAaNmMGQwHQYD\n" + "VR0OBBYEFJvw48QbrOMQTsrhzsX9exQ1Nb52MB8GA1UdIwQYMBaAFAVKzUR5/d6j\n" + "nYM/bHlxURkGhe2EMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGG\n" + "MA0GCSqGSIb3DQEBCwUAA4IBAQCKz1Y29h/bWNdr20pFg/QJ2c4kF93Rp90CJnqx\n" + "F6TFaabuFbw/42mjB3IB5RtR/+fz5je5WykII/ST4xQdKCU3reU6zZ3jU9erhHVP\n" + "8G1om0hfiiDnYJ3E/03JAzXWWlrztR9fE5aVEAxXDoJQs2gJZJLIQOKjesDh6+gr\n" + "FwLb4ltDYHMNlN1HCj31Z8NxWUtnIH7Xv1c93FTCFoeOc9fssNDgsy5FFXy0XIkm\n" + "1xrT6gQcxRKoDCC4LwEmLwV3S1OfrNXhgJzx1R65pahAzjJR0vgWU8NbkmY8ZZS8\n" + "i6LVVFDar9z0K/8UN1n+nl6saJSUuVFdWzHBRX4wYuUxQI+P\n" + "-----END CERTIFICATE-----\n"; // KMS client key 认证 String protocol = "https"; String endpoint = "kst-sXXXXp.cryptoservice.kms.aliyuncs.com"; //KMS的连接的endpoint String clientKeyContent="{\n" + " \"KeyId\": \"KAAP.4b922417-XXXXX-b73e-cc841101bda0\",\n" + " \"PrivateKeyData\": \"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\"\n" + "}"; String clientKeyPass = "3a32bced4XXXXXXe47d050a76"; client = new Client(new Config().setProtocol(protocol).setEndpoint(endpoint).setCa(caContent).setClientKeyContent(clientKeyContent).setPassword(clientKeyPass)); //System.out.println("print kms client--------------"); if(client !=null){ System.out.println(client.toString()); //System.out.println("--------------kms client init success-----"); //System.out.println("\n"); } }catch(Exception e){ e.printStackTrace(); } return client; } public String Enc(Client client,String data,String keyId){ String cipherblobtext=null; try { String cipherKeyId = keyId; //Data to be encrypted. [The data here is processed according to the actual business situation, and the following examples are for reference only] byte[] plaintext = data.getBytes(StandardCharsets.UTF_8); AdvanceEncryptRequest advanceEncryptRequest = new AdvanceEncryptRequest(); advanceEncryptRequest.setKeyId(cipherKeyId); advanceEncryptRequest.setPlaintext(plaintext); //encrypt your plaintext data AdvanceEncryptResponse encryptResponse = client.advanceEncrypt(advanceEncryptRequest); cipherblobtext= Base64.getEncoder().encodeToString(encryptResponse.getCiphertextBlob()); // System.out.println("cipherblobtext : "+cipherblobtext); }catch (Exception e){e.printStackTrace();} return cipherblobtext; } public String Dec(Client client,String cipherblobtext){ String plaintext=null; try { AdvanceDecryptRequest decryptRequest = new AdvanceDecryptRequest(); byte[] cipherblob = Base64.getDecoder().decode(cipherblobtext); decryptRequest.setCiphertextBlob(cipherblob); AdvanceDecryptResponse decryptResponse = client.advanceDecrypt(decryptRequest); plaintext=new String(decryptResponse.getPlaintext()); }catch (Exception e){e.printStackTrace();} return plaintext; } }
5.3 通过Apollo openapi 新增Apollo配置项并获取打印
package com.aliyun.demo; import com.aliyun.dkms.gcs.sdk.Client; import com.ctrip.framework.apollo.openapi.client.ApolloOpenApiClient; import com.ctrip.framework.apollo.openapi.dto.NamespaceReleaseDTO; import com.ctrip.framework.apollo.openapi.dto.NamespaceReleaseDTO; import java.util.Date; public class ApolloTest { public static void main(String[] args) { KmsUtils kmsUtils=new KmsUtils(); Client kmsClient=kmsUtils.getkmsClient(); String keyId="key-bjj65669e7ddfXXXX2l"; String portalUrl = "http://XXXXX/"; // portal url apollo的地址 String token = "2cb13b3581eb7XXXXa02eb2c3"; // 申请的token ApolloOpenApiClient client = ApolloOpenApiClient.newBuilder().withPortalUrl(portalUrl).withToken(token).build(); System.out.println("token : "+client.getToken()); System.out.println("url : "+client.getPortalUrl()); System.out.println("apps : "+client.getAllApps()); OpenItemDTO opt=client.getItem("test","dev","default","application","user"); System.out.println("item old : "+opt+"\n"); //Create Apollo Item opt.setKey("test-user"); String value=kmsUtils.Enc(kmsClient,"this is your apollo item value",keyId); //System.out.println("value : \n"+value); opt.setValue(value); opt.setComment("xxx"); opt.setDataChangeCreatedTime(new Date()); client.createOrUpdateItem("test","dev","default","application",opt); //Get Apollo Item OpenItemDTO openItemDTO=client.getItem("test","dev","default","application","test-user"); System.out.println("print apollo item key : "+openItemDTO.getKey()); System.out.println("print decrypted value : "+kmsUtils.Dec(kmsClient,openItemDTO.getValue())); // release Apollo Namespace NamespaceReleaseDTO nsrdto=new NamespaceReleaseDTO(); nsrdto.setReleaseTitle("application title"); nsrdto.setReleaseComment("release comment xxxx"); nsrdto.setReleasedBy("apollo"); client.publishNamespace("test","dev","default","application",nsrdto); } }
6. 在Apollo运行结果
6.1 在Apollo运行结果Apollo 管理端界面查看