在运行时更新MongoClient凭据



我使用spring-boot-starter-data-mongodb-reactivespring-cloud-vault-config-databases进行动态秘密旋转。每当秘密租约到期时,我都需要手动更新MongoClient凭据。不幸的是,我找不到这样做的方法,因为MongoClientImpl.settings中的credentials字段无法重新分配。

有没有任何有效的方法可以实现这一点,而不需要交换孔MongoDB客户端配置?

目前这是不可能的。看见https://jira.mongodb.org/browse/JAVA-3896

我已经成功地使用Java Reflection通过干净的破解使更新/旋转的凭据工作。我们需要遵守凭据轮换策略,而不必在每次凭据更改时手动重新启动我们的服务。

我们使用AWS DocumentDB 5.0.0(MongoDB兼容性(和存储在AWS Secrets Manager中的凭据。每次更改时,我们都会联系Secrets Manager服务,当场检索并更新。SpringBoot API应用程序中的MongoDB依赖项如下所示;

org.springframework.data:spring-data-mongodb:3.3.3
org.mongodb:mongodb-driver-core:4.4.2
org.mongodb:mongodb-driver-sync:4.4.2

在配置中,MongoCredential作为单例springbean注入到MongoClientSettings中(请参阅下面代码中的定义(。如果您调试到驱动程序的Authenticator实现,您将看到在那里也维护了相同的实例。

...

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public MongoCredential mongoCredential(
final DocumentDBConnectionDetails documentDBConnectionDetails,
final ApplicationProperties applicationProperties
) {
return MongoCredential.createCredential(
documentDBConnectionDetails.getUsername(),
applicationProperties.getDatabase().getName(),
documentDBConnectionDetails.getPassword().toCharArray()
);
}

...

然后,您可以拥有另一个springbean,它将负责更新用户名和密码对。下面我介绍了目前正在为我们工作的实现。然后,您所需要做的就是将MongoCredentialUpdaterbean注入到您的新更新凭据可用的部分,并调用其update(String, char[])方法。


import com.mongodb.MongoCredential;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@Slf4j
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class MongoCredentialUpdater {
private final MongoCredential mongoCredential;
private final Field usernameField;
private final Field passwordField;
public MongoCredentialUpdater(
final MongoCredential mongoCredential
) throws NoSuchFieldException, IllegalAccessException {
this.mongoCredential = mongoCredential;
this.usernameField = getAccessibleField("userName");
this.passwordField = getAccessibleField("password");
}
public boolean update(final String username, final char[] password) {
try {
usernameField.set(mongoCredential, username);
passwordField.set(mongoCredential, password);
return true;
} catch (final IllegalAccessException e) {
log.error("Failed to update the MongoCredential", e);
return false;
}
}
private Field getAccessibleField(final String fieldname) throws NoSuchFieldException, IllegalAccessException {
final Field field = MongoCredential.class.getDeclaredField(fieldname);
final Field modifiers = Field.class.getDeclaredField("modifiers");
field.setAccessible(true);
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
return field;
}
}

限制

在MongoDB DRIVERS-1463票证中,您将看到:

。。。然而,我们必须允许客户在以下两种情况中进行选择:a(尽快耗尽现有连接,并使用新凭据创建一组新连接;b( 根据需要保留现有的连接,可能直到下次重新启动MongoDB Server实例,或者直到应用程序代码决定使用它们重新进行身份验证。

上述方法仅涵盖选项b(。在我们的案例中,这很好,因为安全部门有适当的机制来跟踪API生态系统之外的请求,并当场丢弃带有泄露凭据的恶意连接。但是,如果您的要求仍然是重新初始化驱动程序使用的整个ConnectionPool,则需要以某种方式访问其实例,定位所有活动连接并使其无效。然后,任何新的请求都需要新的连接,这些连接将从头开始建立,因为池中没有活动的连接。这些连接将使用更新的凭据创建。您还需要研究连接的无效是否会对正在进行的操作/请求造成影响,因为您不希望每次凭据更改时请求都开始失败。

在MongoDB的驱动程序中实现该功能之前,希望以上内容能有所帮助。

最新更新