使用 AWS Secrets Manager 管理凭证 - AWS Prescriptive Guidance

使用 AWS Secrets Manager 管理凭证

Durga Prasad Cheepuri,Amazon Web Services

摘要

此模式将引导您使用 AWS Secrets Manager 动态获取 Java Spring 应用程序的数据库凭证。

过去,当您创建自定义应用程序以从数据库中检索信息时,通常必须在应用程序中直接嵌入访问数据库所需凭证(密钥)。当需要轮换凭证时,您必须投入时间更新应用程序以使用新的凭证,然后分配更新后的应用程序。如果您有多个应用程序共享凭证,而您错过更新其中一个,则该应用程序将会失效。为应对此类风险,许多用户选择不定期轮换凭证,然而,这种行为实际上会带来新的风险。

Secrets Manager 允许您将代码中的硬编码凭证(包括密码)替换为 API 调用,以编程方式检索密钥。这有助于确保检查者不会泄露密钥,因为代码中根本不包含密钥。此外,您还可以配置 Secrets Manager 根据您指定的计划自动轮换密钥。这样,您就可以将长期密钥替换为短期密钥,从而显著降低泄露风险。有关更多信息,请参阅 AWS Secrets Manager 文档

先决条件和限制

先决条件

  • 可访问 Secrets Manager 的 Amazon Web Services 账户

  • Java Spring 应用程序

架构

源技术堆栈

  • Java Spring 应用程序,其中包含访问数据库的代码,其数据库凭证由 application.properties 文件管理。

目标技术堆栈

  • Java Spring 应用程序,其中包含访问数据库的代码,其数据库凭证在 Secrets Manager 中管理。application.properties 文件负责保存 Secrets Manager 的密钥。

Secrets Manager 与应用程序集成

Diagram showing AWS Secrets Manager interaction with an admin, custom app, and personnel database.

工具

  • Secrets Manager - AWS Secrets Manager 是一项 Amazon Web Service,可让您更轻松地管理密钥。密钥可以是数据库凭证、密码、第三方 API 密钥,甚至是任意文本。您可以通过使用 Secrets Manager 控制台 Secrets Manager 命令行界面(CLI)或 Secrets Manager API 和开发工具包来集中存储这些密钥并控制对其的访问。

操作说明

任务描述所需技能
将数据库凭证作为密钥存储在 Secrets Manager 中。

按照 Secrets Manager 文档中创建密钥中的步骤,将 Amazon Relational Database Service (Amazon RDS) 或其他数据库凭证作为密钥存储在 Secrets Manager 中。

系统管理员
为 Spring 应用程序设置访问 Secrets Manager 的权限。

根据 Java Spring 应用程序使用 Secrets Manager 的方式设置相应权限。要控制对密钥的访问权限,请根据 Secrets Manager 文档中对 Secrets Manager 使用基于身份的策略 (IAM Policy) 和 ABAC 以及对 Secrets Manager 使用基于资源的策略部分提供的信息创建策略。按照 Secrets Manager 文档中检索密钥值部分所述的步骤进行操作。

系统管理员
任务描述所需技能
添加 JAR 依赖项以使用 Secrets Manager。

有关详细信息,请参阅其他信息部分。

Java 开发人员
将密钥的详细信息添加到 Spring 应用程序。

使用密钥名称、端点和 AWS 区域更新 application.properties 文件。有关示例,请参阅其他信息部分。

Java 开发人员
在 Java 中更新数据库凭证检索代码。

在应用程序中,更新获取数据库凭证的 Java 代码,以从 Secrets Manager 获取这些详细信息。有关示例代码,请参阅其他信息部分。

Java 开发人员

相关资源

其他信息

添加使用 Secrets Manager 所需 JAR 依赖项

Maven:

<groupId>com.amazonaws</groupId>     <artifactId>aws-java-sdk-secretsmanager</artifactId>     <version>1.11. 355 </version>

Gradle:

compile group: 'com.amazonaws', name: 'aws-java-sdk-secretsmanager', version: '1.11.355'

使用密钥的详细信息更新 application.properties 文件

spring.aws.secretsmanager.secretName=postgres-local spring.aws.secretsmanager.endpoint=secretsmanager.us-east-1.amazonaws.com spring.aws.secretsmanager.region=us-east-1

在 Java 中更新数据库凭证检索代码

String  secretName  =  env.getProperty("spring.aws.secretsmanager.secretName"); String  endpoints  =  env.getProperty("spring.aws.secretsmanager.endpoint"); String  AWS Region  =  env.getProperty("spring.aws.secretsmanager.region"); AwsClientBuilder.EndpointConfiguration  config  =  new  AwsClientBuilder.EndpointConfiguration(endpoints, AWS Region); AWSSecretsManagerClientBuilder  clientBuilder  =  AWSSecretsManagerClientBuilder.standard(); clientBuilder.setEndpointConfiguration(config); AWSSecretsManager  client  =  clientBuilder.build();        ObjectMapper  objectMapper  =  new  ObjectMapper();   JsonNode  secretsJson  =  null;   ByteBuffer  binarySecretData;   GetSecretValueRequest  getSecretValueRequest  =  new  GetSecretValueRequest().withSecretId(secretName);    GetSecretValueResult  getSecretValueResponse  =  null;   try  {      getSecretValueResponse  =  client.getSecretValue(getSecretValueRequest);     }   catch  (ResourceNotFoundException  e)  {      log.error("The requested secret "  +  secretName  +  " was not found");     }      catch  (InvalidRequestException  e)  {          log.error("The request was invalid due to: "  +  e.getMessage());      }      catch  (InvalidParameterException  e)  {          log.error("The request had invalid params: "  +  e.getMessage());      } if  (getSecretValueResponse  ==  null)  {          return  null;      }  // Decrypted secret using the associated KMS key // Depending on whether the secret was a string or binary, one of these fields will be populated               String secret = getSecretValueResponse.getSecretString();     if (secret != null) {        try {                         secretsJson  =  objectMapper.readTree(secret);                }           catch  (IOException  e)  {                         log.error("Exception while retrieving secret values: "  +  e.getMessage());                } }      else  {          log.error("The Secret String returned is null");          return null;              }      String  host  =  secretsJson.get("host").textValue();      String  port  =  secretsJson.get("port").textValue();      String  dbname  =  secretsJson.get("dbname").textValue();      String  username  =  secretsJson.get("username").textValue();      String  password  =  secretsJson.get("password").textValue(); }