diff --git a/.github/workflows/.trivyignore b/.github/workflows/.trivyignore index 919ad390c..b7ea1265d 100644 --- a/.github/workflows/.trivyignore +++ b/.github/workflows/.trivyignore @@ -1,5 +1,4 @@ -# org.springframework:spring-web:5.3.31.RELEASE, ineffective vulnerability - hawkBit doesn't use beans of type HttpInvokerServiceExporter in applications -CVE-2016-1000027 - -# org.yaml:snakeyaml:1.33, ineffective vulnerability - Not applicable. Applications does not consume user-provided YAML data -CVE-2022-1471 \ No newline at end of file +# org.eclipse.hawkbit:* +# this trivy finding is false positive since the current master (and other, new temporary, branches) doesn't contain http dependencies anymore +# trivy detects 0-SNAPSHOT version and could not 'understand' that it is newer then 0.3.0M2 (since which the http dependencies were removed) +CVE-2019-10240 \ No newline at end of file diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManager.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManager.java index 94af1b862..21182cff3 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManager.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManager.java @@ -18,6 +18,7 @@ import java.util.concurrent.ConcurrentHashMap; import com.github.benmanes.caffeine.cache.Caffeine; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Value; @@ -27,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.support.AbstractValueAdaptingCache; import org.springframework.context.event.EventListener; import org.springframework.core.env.Environment; import org.springframework.lang.Nullable; @@ -122,6 +124,26 @@ public class TenantAwareCacheManager implements CacheManager { } } + // Nop if, null, blank, "nop", "none", "maximumSize=0" or "expireAfterWrite=0" + static boolean isNop(@Nullable String spec) { + if (spec == null || spec.isBlank()) { + return true; + } + final String trimmed = spec.replaceAll("\\s", ""); + return "nop".equalsIgnoreCase(trimmed) || + "none".equalsIgnoreCase(trimmed) || + (trimmed.contains("maximumSize=0") && + ("maximumSize=0".equals(trimmed) || + trimmed.startsWith("maximumSize=0,") || + trimmed.contains(",maximumSize=0,") || + trimmed.endsWith(",maximumSize=0"))) || + (trimmed.contains("expireAfterWrite=0") && + ("expireAfterWrite=0".equals(trimmed) || + trimmed.startsWith("expireAfterWrite=0,") || + trimmed.contains(",expireAfterWrite=0,") || + trimmed.endsWith(",expireAfterWrite=0"))); + } + public interface CacheEvictEvent { String getTenant(); @@ -161,6 +183,11 @@ public class TenantAwareCacheManager implements CacheManager { spec = defaultSpec; } } + // it seems that setting maximumSize=0 doesn't filly disables the Caffeine cache, so we explicitly check for Nop cache + if (isNop(spec)) { + log.info("Using NOP cache for tenant '{}' and cache '{}'", tenant, name); + return new Nop(name); + } try { return new CaffeineCache(n, Caffeine.from(spec).build(), false) { @@ -188,35 +215,35 @@ public class TenantAwareCacheManager implements CacheManager { } @Value - private static class Nop implements Cache { + @EqualsAndHashCode(callSuper = true) + private static class Nop extends AbstractValueAdaptingCache { String name; + private Nop(final String name) { + super(false); + this.name = name; + } + @NonNull @Override public String getName() { return name; } - @NonNull @Override - public Object getNativeCache() { + public @NonNull Object getNativeCache() { return this; } @Override - public ValueWrapper get(@NonNull final Object key) { - return null; - } - - @Override - public T get(@NonNull final Object key, final Class type) { - return null; - } - - @Override + @SuppressWarnings("unchecked") public T get(@NonNull final Object key, @NonNull final Callable valueLoader) { - return null; + try { + return (T) fromStoreValue(valueLoader.call()); + } catch (Exception e) { + throw new ValueRetrievalException(key, valueLoader, e); + } } @Override @@ -233,5 +260,10 @@ public class TenantAwareCacheManager implements CacheManager { public void clear() { // nop } + + @Override + protected Object lookup(@NonNull final Object key) { + return null; // nop cache doesn't cache anything, especially used to DO NOT cache null values + } } } \ No newline at end of file diff --git a/hawkbit-core/src/test/java/org/eclipse/hawkbit/im/authentication/SpPermissionTest.java b/hawkbit-core/src/test/java/org/eclipse/hawkbit/auth/SpPermissionTest.java similarity index 92% rename from hawkbit-core/src/test/java/org/eclipse/hawkbit/im/authentication/SpPermissionTest.java rename to hawkbit-core/src/test/java/org/eclipse/hawkbit/auth/SpPermissionTest.java index 147667d3d..82ef8940b 100644 --- a/hawkbit-core/src/test/java/org/eclipse/hawkbit/im/authentication/SpPermissionTest.java +++ b/hawkbit-core/src/test/java/org/eclipse/hawkbit/auth/SpPermissionTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.im.authentication; +package org.eclipse.hawkbit.auth; import static org.assertj.core.api.Assertions.assertThat; @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; * Feature: Unit Tests - Security
* Story: Permission Test */ -final class SpPermissionTest { +class SpPermissionTest { /** * Double-checks that all permissions doesn't contain any hierarchies. diff --git a/hawkbit-core/src/test/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManagerTest.java b/hawkbit-core/src/test/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManagerTest.java new file mode 100644 index 000000000..10e0784ba --- /dev/null +++ b/hawkbit-core/src/test/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManagerTest.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.tenancy; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collection; + +import org.junit.jupiter.api.Test; + +class TenantAwareCacheManagerTest { + + /** + * Double-checks that all permissions doesn't contain any hierarchies. + */ + @Test + void testNopCaches() { + assertThat(TenantAwareCacheManager.isNop(null)).isTrue(); + assertThat(TenantAwareCacheManager.isNop("")).isTrue(); + assertThat(TenantAwareCacheManager.isNop(" ")).isTrue(); + assertThat(TenantAwareCacheManager.isNop("\t")).isTrue(); + assertThat(TenantAwareCacheManager.isNop("nop")).isTrue(); + assertThat(TenantAwareCacheManager.isNop("none")).isTrue(); + assertThat(TenantAwareCacheManager.isNop("maximumSize=0")).isTrue(); + assertThat(TenantAwareCacheManager.isNop("maximumSize=0,something")).isTrue(); + assertThat(TenantAwareCacheManager.isNop("something, maximumSize=0")).isTrue(); + assertThat(TenantAwareCacheManager.isNop("something, maximumSize=0 \t, something")).isTrue(); + assertThat(TenantAwareCacheManager.isNop(" expireAfterWrite=0")).isTrue(); + assertThat(TenantAwareCacheManager.isNop("expireAfterWrite=0,something")).isTrue(); + assertThat(TenantAwareCacheManager.isNop("something, expireAfterWrite =0")).isTrue(); + assertThat(TenantAwareCacheManager.isNop("something, expireAfterWrite= 0 \t, something")).isTrue(); + + assertThat(TenantAwareCacheManager.isNop("maximumSize=01")).isFalse(); + assertThat(TenantAwareCacheManager.isNop("maximumSize=100")).isFalse(); + assertThat(TenantAwareCacheManager.isNop("expireAfterWrite=01")).isFalse(); + assertThat(TenantAwareCacheManager.isNop("expireAfterWrite=100")).isFalse(); + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/resources/hawkbit-repository-defaults.properties b/hawkbit-repository/hawkbit-repository-core/src/main/resources/hawkbit-repository-defaults.properties index 639064687..64ccb3672 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/resources/hawkbit-repository-defaults.properties +++ b/hawkbit-repository/hawkbit-repository-core/src/main/resources/hawkbit-repository-defaults.properties @@ -23,9 +23,9 @@ hawkbit.controller.maintenanceWindowPollCount=3 # Cache config hawkbit.cache.ttl=10s -hawkbit.cache.spec=expireAfterAccess=${hawkbit.cache.ttl} -hawkbit.cache.RolloutStatus.spec=maximumSize=50000,expireAfterAccess=${hawkbit.cache.ttl} -hawkbit.cache.RolloutGroupStatus.spec=maximumSize=50000,expireAfterAccess=${hawkbit.cache.ttl} +hawkbit.cache.spec=expireAfterWrite=${hawkbit.cache.ttl} +hawkbit.cache.RolloutStatus.spec=maximumSize=50000,expireAfterWrite=${hawkbit.cache.ttl} +hawkbit.cache.RolloutGroupStatus.spec=maximumSize=50000,expireAfterWrite=${hawkbit.cache.ttl} # Cache config - END # Attention: if you want to use a maximumPollingTime greater 23:59:59 you have to update the DurationField in the configuration window diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TenantConfigurationManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TenantConfigurationManagementTest.java index bc4c689dd..dd96b6a5f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TenantConfigurationManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TenantConfigurationManagementTest.java @@ -89,9 +89,6 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple tenantConfigurationManagement.addOrUpdateConfiguration( TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, newConfigurationValue2); - // sometimes it reads old value, maybe if read too early. wait to settle up? - waitMillis(100); - // verify that new configuration value is used final TenantConfigurationValue updatedConfigurationValue2 = tenantConfigurationManagement .getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class);