fix: This fixes the DDOS that appsmith production database faced when the sessions set in redis were in bad state. (#19807)
1. Pre fetch and set the anonymous user cache instead of fetching it when its required. This is now done on startup. In case any request for anonymous user permission group ids comes through and the cache is not ready, we throw an error to request the user to try again in some time. This stops the DDOS on the mongo database 2. In case the session is in bad state and the user object is malformed, before we fetch the user permission group ids, we check the presence of email, tenant and user id before making a bad database query which was another reason for DDOS.
This commit is contained in:
parent
c93ed4f54c
commit
e97370ec85
|
|
@ -5,6 +5,7 @@ import com.appsmith.server.domains.Config;
|
|||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.repositories.CacheableRepositoryHelper;
|
||||
import com.appsmith.server.services.ConfigService;
|
||||
import com.appsmith.util.WebClientUtils;
|
||||
import io.sentry.Sentry;
|
||||
|
|
@ -38,8 +39,11 @@ public class InstanceConfig implements ApplicationListener<ApplicationReadyEvent
|
|||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final CacheableRepositoryHelper cacheableRepositoryHelper;
|
||||
|
||||
private boolean isRtsAccessible = false;
|
||||
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
|
||||
|
||||
|
|
@ -55,6 +59,8 @@ public class InstanceConfig implements ApplicationListener<ApplicationReadyEvent
|
|||
|
||||
checkInstanceSchemaVersion()
|
||||
.flatMap(signal -> registrationAndRtsCheckMono)
|
||||
// Prefill the server cache with anonymous user permission group ids.
|
||||
.then(cacheableRepositoryHelper.preFillAnonymousUserPermissionGroupIdsCache())
|
||||
.subscribe(null, e -> {
|
||||
log.debug("Application start up encountered an error: {}", e.getMessage());
|
||||
Sentry.captureException(e);
|
||||
|
|
|
|||
|
|
@ -152,7 +152,9 @@ public enum AppsmithError {
|
|||
PUBLIC_APP_NO_PERMISSION_GROUP(500, 5020, "Invalid state. Public application does not have the required roles set for public access. Please reach out to Appsmith customer support to resolve this.", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null),
|
||||
RTS_SERVER_ERROR(500, 5021, "RTS server error while processing request: {0}", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null),
|
||||
SCHEMA_MISMATCH_ERROR(500, 5022, "Looks like you skipped some required update(s), please go back to the mandatory upgrade path {0}, or refer to ''https://docs.appsmith.com/'' for more info", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null),
|
||||
SCHEMA_VERSION_NOT_FOUND_ERROR(500, 5023, "Could not find mandatory instance schema version config. Please reach out to Appsmith customer support to resolve this.", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null)
|
||||
SCHEMA_VERSION_NOT_FOUND_ERROR(500, 5023, "Could not find mandatory instance schema version config. Please reach out to Appsmith customer support to resolve this.", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null),
|
||||
SERVER_NOT_READY(500, 5024, "Appsmith server is not ready. Please try again in some time.", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null),
|
||||
SESSION_BAD_STATE(500, 5025, "User session is invalid. Please log out and log in again.", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null)
|
||||
;
|
||||
|
||||
private final Integer httpErrorCode;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ public interface CacheableRepositoryHelperCE {
|
|||
|
||||
Mono<Set<String>> getPermissionGroupsOfUser(User user);
|
||||
|
||||
Mono<Set<String>> preFillAnonymousUserPermissionGroupIdsCache();
|
||||
|
||||
Mono<Set<String>> getPermissionGroupsOfAnonymousUser();
|
||||
|
||||
Mono<Void> evictPermissionGroupsUser(String email, String tenantId);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import com.appsmith.server.domains.QTenant;
|
|||
import com.appsmith.server.domains.QUser;
|
||||
import com.appsmith.server.domains.Tenant;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
|
@ -23,6 +25,7 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.server.constants.FieldName.PERMISSION_GROUP_ID;
|
||||
import static com.appsmith.server.constants.ce.FieldNameCE.ANONYMOUS_USER;
|
||||
import static com.appsmith.server.repositories.BaseAppsmithRepositoryImpl.fieldName;
|
||||
import static com.appsmith.server.repositories.ce.BaseAppsmithRepositoryCEImpl.notDeleted;
|
||||
|
||||
|
|
@ -45,6 +48,18 @@ public class CacheableRepositoryHelperCEImpl implements CacheableRepositoryHelpe
|
|||
@Cache(cacheName = "permissionGroupsForUser", key = "{#user.email + #user.tenantId}")
|
||||
@Override
|
||||
public Mono<Set<String>> getPermissionGroupsOfUser(User user) {
|
||||
|
||||
// If the user is anonymous, then we don't need to fetch the permission groups from the database. We can just
|
||||
// return the cached permission group ids.
|
||||
if (ANONYMOUS_USER.equals(user.getUsername())) {
|
||||
return getPermissionGroupsOfAnonymousUser();
|
||||
}
|
||||
|
||||
if (user.getEmail() == null || user.getEmail().isEmpty() || user.getId() == null || user.getId().isEmpty()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.SESSION_BAD_STATE));
|
||||
}
|
||||
|
||||
|
||||
Criteria assignedToUserIdsCriteria = Criteria.where(fieldName(QPermissionGroup.permissionGroup.assignedToUserIds)).is(user.getId());
|
||||
Criteria notDeletedCriteria = notDeleted();
|
||||
|
||||
|
|
@ -60,9 +75,9 @@ public class CacheableRepositoryHelperCEImpl implements CacheableRepositoryHelpe
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<Set<String>> getPermissionGroupsOfAnonymousUser() {
|
||||
public Mono<Set<String>> preFillAnonymousUserPermissionGroupIdsCache() {
|
||||
|
||||
if (anonymousUserPermissionGroupIds != null) {
|
||||
if (anonymousUserPermissionGroupIds != null && !anonymousUserPermissionGroupIds.isEmpty()) {
|
||||
return Mono.just(anonymousUserPermissionGroupIds);
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +89,19 @@ public class CacheableRepositoryHelperCEImpl implements CacheableRepositoryHelpe
|
|||
.doOnSuccess(permissionGroupIds -> anonymousUserPermissionGroupIds = permissionGroupIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Set<String>> getPermissionGroupsOfAnonymousUser() {
|
||||
|
||||
if (anonymousUserPermissionGroupIds != null) {
|
||||
return Mono.just(anonymousUserPermissionGroupIds);
|
||||
}
|
||||
|
||||
// If we have reached this state, then the cache is not populated. We need to wait for this to get populated
|
||||
// Anonymous user cache is getting populated at #InstanceConfig.onApplicationEvent
|
||||
// Return an error to the user so that the user can re-try in some time
|
||||
return Mono.error(new AppsmithException(AppsmithError.SERVER_NOT_READY));
|
||||
}
|
||||
|
||||
@CacheEvict(cacheName = "permissionGroupsForUser", key = "{#email + #tenantId}")
|
||||
@Override
|
||||
public Mono<Void> evictPermissionGroupsUser(String email, String tenantId) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user