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:
Trisha Anand 2023-01-17 16:22:10 +05:30 committed by GitHub
parent c93ed4f54c
commit e97370ec85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 3 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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) {