我正在使用 CloudFormation 从 Docker 镜像在 ECS Fargate 中部署 Spring Boot 应用程序。它使用 Spring Boot Data MongoDB 连接到 AWS DocumentDB。由于 CloudFormation 模板中的所有内容都是独立的(即,所有内容都是通过模板部署的,无需复制和粘贴或手动配置),因此我将数据库集群以及来自 Secrets Manager 的用户名+密码注入(inject)到任务容器定义中:
Environment:
- Name: SPRING_DATA_MONGODB_HOST
Value: !GetAtt DbCluster.Endpoint
- Name: SPRING_DATA_MONGODB_PORT
Value: !GetAtt DbCluster.Port
Secrets:
- Name: SPRING_DATA_MONGODB_USERNAME
ValueFrom: !Sub "${DbCredentials}:username::"
- Name: SPRING_DATA_MONGODB_PASSWORD
ValueFrom: !Sub "${DbCredentials}:password::"
我刚刚发现 DocumentDB doesn't support retryable writes 。因此,当我的 ECS 任务启动时,它会显示:
com.mongodb.MongoCommandException: Command failed with error 301: 'Retryable writes are not supported' on server my-db-cluster.cluster-xxxxxxxxxxxx.us-east-1.docdb.amazonaws.com:27017. The full response is {"ok": 0.0, "code": 301, "errmsg": "Retryable writes are not supported", "operationTime": {"$timestamp": {"t": xxxxxxxxxx, "i": 1}}}
AWS 文档和其他各种答案都说设置 retryWrites=False
切换到完整SPRING_DATA_MONGODB_URI
与 connection URI format像这样:
mongodb://<username>:<password>@my-db-cluster.cluster-xxxxxxxxxxxx.us-east-1.docdb.amazonaws.com:27017/database?retryWrites=False
在 CloudFormation 中我的任务容器定义中会是这样的:
- Name: SPRING_DATA_MONGODB_URI
Value: !Sub "mongodb://${DbCluster.Endpoint}:${DbCluster.Port}/?retryWrites=False"
但定义连接 URI 似乎会覆盖用户名和密码环境变量。那么现在如何将 Secrets Manager 中的用户名+密码注入(inject)到 CloudFormation 中的连接 URI 中?
或者是否有其他方法可以禁用 Spring Boot Data MongoDB 中的可重试写入,但仍允许我从 Secrets Manager 注入(inject)凭据?
最佳答案
每当您指定单独的 Spring Boot Data MongoDB 环境变量(例如 SPRING_DATA_MONGODB_PASSWORD
)(可通过配置属性(例如 spring.data.mongodb.password
)在内部访问) ,Spring会动态构造连接URL。但是,如果您指定连接 URI 环境变量 SPRING_DATA_MONGODB_URI
,Spring 将忽略各个组件,包括 ECS 从 Secrets Manager 注入(inject)的用户名和密码,如问题中所述。
但是有一种解决方法可以强制 Spring 提取您通过连接 URI 注入(inject)的 secret 凭据。 Spring Boot 提供了一种使用 property placeholders 动态插入配置属性的工具,类似于 CloudFormation 引用,只不过这些引用在运行时被替换。因此可以继续注入(inject)用户名和密码,然后在实际连接 URI 中使用占位符,如下所示:
Environment:
- Name: SPRING_DATA_MONGODB_HOST
Value: !GetAtt DbCluster.Endpoint
- Name: SPRING_DATA_MONGODB_PORT
Value: !GetAtt DbCluster.Port
- Name: SPRING_DATA_MONGODB_URI
Value: mongodb://${spring.data.mongodb.username}:${spring.data.mongodb.passsword}@${spring.data.mongodb.host}:${spring.data.mongodb.port}/?retryWrites=False
Secrets:
- Name: SPRING_DATA_MONGODB_USERNAME
ValueFrom: !Sub "${DbCredentials}:username::"
- Name: SPRING_DATA_MONGODB_PASSWORD
ValueFrom: !Sub "${DbCredentials}:password::"
根据 AWS DocumentDB 控制台提供的建议,您甚至可能希望使用此扩展连接 URI:
mongodb://${spring.data.mongodb.username}:${spring.data.mongodb.password}@${spring.data.mongodb.host}:${spring.data.mongodb .port}/?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false
现在,用户名和密码会在部署时注入(inject)到环境变量中,Spring Boot 本身会在运行时将这些值插入到连接 URI 中。
有一个小警告需要提及,但否则无关紧要。如果密码包含纯 @
或 :
字符,则会使 URI 的 username:password@...
部分不明确,因此MongoDB 要求对它们进行 URI 编码。此外,如果要包含 %
,则该字符也需要进行编码,因为它是 URI 编码序列中的第一个字符。由于 Spring Boot 无法对配置属性引用进行动态 URI 编码,因此可以很容易地告诉 AWS Secrets Manager 在生成密码时排除这三个字符。 AWS::DocDB::DBCluster
已经禁止使用 /
、"
和 @
字符,因此实际上您只需从可用字符中删除两个额外的字符。
因此您可能会生成 AWS::SecretsManager::Secret
在 CloudFormation 中就像这样:
DbCredentials:
Type: AWS::SecretsManager::Secret
Properties:
Name: db-creds
GenerateSecretString:
SecretStringTemplate: '{"username": "somebody"}'
GenerateStringKey: "password"
PasswordLength: 99
ExcludeCharacters: '/"@%:'
(提示:虽然 DocumentDB 声称支持 100 个字符的密码,但实际上如果密码长度超过 99 个字符,底层 RDS 仍然会报错。)
Spring Boot Data MongoDB documentation说:
Username and password credentials used in XML-based configuration must be URL-encoded when these contain reserved characters, such as
:
,%
,@
, or,
. The following example shows encoded credentials:m0ng0@dmin:mo_res:bw6},Qsdxx@admin@database
→m0ng0%40dmin:mo_res%3Abw6%7D%2CQsdxx%40admin@database
See section 2.2 of RFC 3986 for further details.
但是RFC 3986 § 3.2.1. User Information并没有如此限制,实际上可以包含:第 2.2 节中的 sub-delims
。
userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
因此,如果我们从第 2.2 节中排除剩余的分隔符,即 gen-delims
,就会得到以下结果,这可能是最正确和最安全的:
# exclude `/"@` as per DocumentDB restrictions
# exclude `%`, the URI encoding delimiter, so the password can be interpolated into a connection URI
# exclude `:/?#[]@` `gen-delims` from [RFC 3986 § 2.2](https://www.rfc-editor.org/rfc/rfc3986.html#section-2.2)
ExcludeCharacters: "/\"@%:?#[]"
关于amazon-web-services - 使用 CloudFormation 禁用 AWS ECS Fargate 中的 Spring Boot Data MongoDB 可重试写入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75997367/