Skip to content

Commit fb1fb27

Browse files
authored
Merge pull request #4160 from mercedes-benz/feature-4159-handle-token-cache-in-cluster
Feature 4159 handle token cache in cluster
2 parents 8dbd279 + 6d609af commit fb1fb27

File tree

56 files changed

+2907
-386
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2907
-386
lines changed

sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/TestAdministrationSecurityConfiguration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import com.mercedesbenz.sechub.commons.core.shutdown.ApplicationShutdownHandler;
1212
import com.mercedesbenz.sechub.sharedkernel.security.SecHubSecurityConfiguration;
13+
import com.mercedesbenz.sechub.spring.security.OAuth2OpaqueTokenIntrospectionResponseCryptoAccessProvider;
1314

1415
@Configuration
1516
@Import(SecHubSecurityConfiguration.class)
@@ -24,4 +25,9 @@ RestTemplate restTemplate() {
2425
ApplicationShutdownHandler applicationShutdownHandler() {
2526
return mock();
2627
}
28+
29+
@Bean
30+
OAuth2OpaqueTokenIntrospectionResponseCryptoAccessProvider cryptoAccessProvider() {
31+
return mock();
32+
}
2733
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// SPDX-License-Identifier: MIT
2+
package com.mercedesbenz.sechub.commons.core.cache;
3+
4+
import static java.util.Objects.*;
5+
6+
import java.io.Serializable;
7+
import java.time.Duration;
8+
import java.time.Instant;
9+
10+
import javax.crypto.SealedObject;
11+
12+
import com.mercedesbenz.sechub.commons.core.security.CryptoAccess;
13+
import com.mercedesbenz.sechub.commons.core.security.CryptoAccessProvider;
14+
15+
/**
16+
* Represents the data stored in the cache under a specific key.
17+
*
18+
* <p>
19+
* The cached data can be any serializable object. If cryptoAccess provider is
20+
* not null, the value is securely sealed using a {@link CryptoAccess} instance
21+
* of type otherwise the value be stored directly. <code>T</code>.
22+
* </p>
23+
*/
24+
public class CacheData<T extends Serializable> {
25+
26+
private final CryptoAccessProvider<T> cryptoAccessProvider;
27+
private final SealedObject sealedValue;
28+
private final Duration duration;
29+
private final T value;
30+
private final Instant createdAt;
31+
32+
/**
33+
* Creates a cache data object. If crypto access provider is not null, a sealed
34+
* value will be used, otherwise the plain value will be stored directly in
35+
* memory.
36+
*
37+
* @param value value
38+
* @param duration duration
39+
* @param cryptoAccessProvider crypto access provider
40+
* @param createdAt creation time of cache entry, normally
41+
* Instant.now() except when the cache element is
42+
* restored from somewhere else.
43+
*/
44+
public CacheData(T value, Duration duration, CryptoAccessProvider<T> cryptoAccessProvider, Instant createdAt) {
45+
46+
requireNonNull(value, "Property 'value' must not be null");
47+
48+
this.createdAt = createdAt != null ? createdAt : Instant.now();
49+
this.cryptoAccessProvider = cryptoAccessProvider;
50+
51+
if (cryptoAccessProvider == null) {
52+
this.value = value;
53+
this.sealedValue = null;
54+
} else {
55+
this.value = null;
56+
this.sealedValue = cryptoAccessProvider.getCryptoAccess().seal(value);
57+
}
58+
this.duration = requireNonNull(duration, "Property 'duration' must not be null");
59+
}
60+
61+
public boolean isSealed() {
62+
return sealedValue != null;
63+
}
64+
65+
public T getValue() {
66+
if (cryptoAccessProvider == null) {
67+
return value;
68+
}
69+
return cryptoAccessProvider.getCryptoAccess().unseal(sealedValue);
70+
}
71+
72+
public Duration getDuration() {
73+
return duration;
74+
}
75+
76+
public Instant getCreatedAt() {
77+
return createdAt;
78+
}
79+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: MIT
2+
package com.mercedesbenz.sechub.commons.core.cache;
3+
4+
import java.io.Serializable;
5+
import java.time.Instant;
6+
7+
public interface CachePersistence<T extends Serializable> {
8+
9+
/**
10+
* Updates or creates a cache entry
11+
*
12+
* @param key the key for the cache entry
13+
* @param cacheData cache data containing
14+
*/
15+
void put(String key, CacheData<T> cacheData);
16+
17+
/**
18+
* Fetch cache entry for given key
19+
*
20+
* @param key key to identify value
21+
* @return cache data or <code>null</code> if not available
22+
*/
23+
CacheData<T> get(String key);
24+
25+
/**
26+
* Remove entry from cache
27+
*
28+
* @param key key to identify value
29+
*/
30+
void remove(String key);
31+
32+
/**
33+
* Removes all outdated entries from cache
34+
*
35+
* @param now an instant representing now
36+
*/
37+
void removeOutdated(Instant now);
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-License-Identifier: MIT
2+
package com.mercedesbenz.sechub.commons.core.cache;
3+
4+
import java.io.Serializable;
5+
import java.time.Duration;
6+
import java.time.Instant;
7+
import java.util.Map;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
10+
/**
11+
* <b>Note:</b> This cache persistence is local to a single application instance
12+
* and is not shared across multiple instances. Avoid using it in scenarios
13+
* where a distributed caching mechanism is required.
14+
*
15+
* @param <T>
16+
*/
17+
public class InMemoryCachePersistence<T extends Serializable> implements CachePersistence<T> {
18+
19+
private final Map<String, CacheData<T>> cacheMap = new ConcurrentHashMap<>();
20+
21+
@Override
22+
public void remove(String key) {
23+
cacheMap.remove(key);
24+
}
25+
26+
@Override
27+
public void put(String key, CacheData<T> cacheData) {
28+
cacheMap.put(key, cacheData);
29+
}
30+
31+
@Override
32+
public CacheData<T> get(String key) {
33+
return cacheMap.get(key);
34+
}
35+
36+
@Override
37+
public void removeOutdated(Instant now) {
38+
cacheMap.forEach((key, value) -> {
39+
Instant cacheDataCreatedAt = value.getCreatedAt();
40+
Duration cacheDataDuration = value.getDuration();
41+
42+
if (cacheDataCreatedAt.plus(cacheDataDuration).isBefore(now)) {
43+
cacheMap.remove(key);
44+
}
45+
});
46+
47+
}
48+
49+
}
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
// SPDX-License-Identifier: MIT
22
package com.mercedesbenz.sechub.commons.core.cache;
33

4-
import static java.util.Objects.requireNonNull;
4+
import static java.util.Objects.*;
55

66
import java.io.Serializable;
77
import java.time.Duration;
88
import java.time.Instant;
99
import java.util.Optional;
10-
import java.util.concurrent.ConcurrentHashMap;
1110
import java.util.concurrent.ScheduledExecutorService;
1211
import java.util.concurrent.ScheduledFuture;
1312
import java.util.concurrent.TimeUnit;
1413

15-
import javax.crypto.SealedObject;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
1616

17-
import com.mercedesbenz.sechub.commons.core.security.CryptoAccess;
17+
import com.mercedesbenz.sechub.commons.core.security.CryptoAccessProvider;
1818
import com.mercedesbenz.sechub.commons.core.shutdown.ApplicationShutdownHandler;
1919
import com.mercedesbenz.sechub.commons.core.shutdown.ShutdownListener;
2020

@@ -23,47 +23,44 @@
2323
* caching mechanism for generic data.
2424
*
2525
* <p>
26-
* It uses a {@link ConcurrentHashMap} to store data and a scheduled task to
26+
* It uses a {@link CachePersistence} to store data and a scheduled task to
2727
* clear expired entries periodically. By default, the cache cleanup runs every
2828
* minute with an initial delay of 1 minute. These values can be customized via
2929
* the constructor.
3030
* </p>
3131
*
32-
* <p>
33-
* <b>Note:</b> This cache is local to a single application instance and is not
34-
* shared across multiple instances. Avoid using it in scenarios where a
35-
* distributed caching mechanism is required.
36-
* </p>
37-
*
3832
* @param <T> the type of data stored in the cache (must be of type
3933
* {@link Serializable})
4034
*
41-
* @author hamidonos
35+
* @author hamidonos, de-jcup
4236
*/
43-
public class InMemoryCache<T extends Serializable> implements ShutdownListener {
37+
public class SelfCleaningCache<T extends Serializable> implements ShutdownListener {
4438

45-
private static final Duration DEFAULT_CACHE_CLEAR_JOB_PERIOD = Duration.ofMinutes(1);
39+
private static final Logger logger = LoggerFactory.getLogger(SelfCleaningCache.class);
4640

47-
private final ConcurrentHashMap<String, CacheData> cacheMap = new ConcurrentHashMap<>();
41+
private final String cacheName;
4842
private final ScheduledExecutorService scheduledExecutorService;
49-
private final CryptoAccess<T> cryptoAccess = new CryptoAccess<>();
5043
private final ScheduledFuture<?> cacheClearJob;
5144
private final Duration cacheClearJobPeriod;
52-
53-
public InMemoryCache(ScheduledExecutorService scheduledExecutorService, ApplicationShutdownHandler applicationShutdownHandler) {
54-
this(DEFAULT_CACHE_CLEAR_JOB_PERIOD, scheduledExecutorService, applicationShutdownHandler);
55-
}
45+
private final CachePersistence<T> cachePersistence;
46+
private final CryptoAccessProvider<T> cryptoAccessProvider;
5647

5748
/* @formatter:off */
58-
public InMemoryCache(Duration cacheClearJobPeriod,
49+
public SelfCleaningCache(String cacheName, CachePersistence<T> cachePersistence, Duration cacheClearJobPeriod,
5950
ScheduledExecutorService scheduledExecutorService,
60-
ApplicationShutdownHandler applicationShutdownHandler) {
51+
ApplicationShutdownHandler applicationShutdownHandler, CryptoAccessProvider<T> cryptoAccessProvider) {
6152
/* @formatter:on */
53+
this.cacheName = requireNonNull(cacheName, "Parameter 'cacheName' must not be null");
54+
this.cachePersistence = requireNonNull(cachePersistence, "Parameter 'cachePersistence' must not be null");
6255
this.scheduledExecutorService = requireNonNull(scheduledExecutorService, "Property 'scheduledExecutorService' must not be null");
6356
this.cacheClearJobPeriod = requireNonNull(cacheClearJobPeriod, "Property 'cacheClearJobPeriod' must not be null");
57+
this.cryptoAccessProvider = cryptoAccessProvider;
58+
6459
cacheClearJob = scheduleClearCacheJob();
60+
6561
requireNonNull(applicationShutdownHandler, "Property 'applicationShutdownHandler' must not be null");
6662
applicationShutdownHandler.register(this);
63+
6764
}
6865

6966
/**
@@ -81,7 +78,11 @@ public InMemoryCache(Duration cacheClearJobPeriod,
8178
*/
8279
public void put(String key, T value, Duration duration) {
8380
requireNonNull(key, "Argument 'key' must not be null");
84-
cacheMap.put(key, new CacheData(value, duration));
81+
if (logger.isTraceEnabled()) {
82+
logger.trace("Cache:{} - Put to persistence ({}): key={}, duration={}, value= {}", cacheName, cachePersistence.getClass().getSimpleName(), key,
83+
duration, value);
84+
}
85+
cachePersistence.put(key, new CacheData<T>(value, duration, cryptoAccessProvider, Instant.now()));
8586
}
8687

8788
/**
@@ -98,12 +99,18 @@ public void put(String key, T value, Duration duration) {
9899
public Optional<T> get(String key) {
99100
requireNonNull(key, "Argument 'key' must not be null");
100101

101-
CacheData cacheData = cacheMap.get(key);
102+
CacheData<T> cacheData = cachePersistence.get(key);
102103

103104
if (cacheData == null) {
105+
if (logger.isTraceEnabled()) {
106+
logger.trace("Cache:{} - Get: key={} - not found", cacheName, key);
107+
}
104108
return Optional.empty();
105109
}
106110

111+
if (logger.isTraceEnabled()) {
112+
logger.trace("Cache:{} - Get: key={} - found", cacheName, key);
113+
}
107114
return Optional.of(cacheData.getValue());
108115
}
109116

@@ -115,7 +122,11 @@ public Optional<T> get(String key) {
115122
* @throws NullPointerException if the specified key is null
116123
*/
117124
public void remove(String key) {
118-
cacheMap.remove(key);
125+
requireNonNull(key, "key must not be null!");
126+
if (logger.isTraceEnabled()) {
127+
logger.trace("Cache:{} - Remove: key={}", cacheName, key);
128+
}
129+
cachePersistence.remove(key);
119130
}
120131

121132
public Duration getCacheClearJobPeriod() {
@@ -139,49 +150,10 @@ private ScheduledFuture<?> scheduleClearCacheJob() {
139150
}
140151

141152
private void clearCache() {
142-
Instant now = Instant.now();
143-
144-
cacheMap.forEach((key, value) -> {
145-
Instant cacheDataCreatedAt = value.getCreatedAt();
146-
Duration cacheDataDuration = value.getDuration();
147-
148-
if (cacheDataCreatedAt.plus(cacheDataDuration).isBefore(now)) {
149-
cacheMap.remove(key);
150-
}
151-
});
152-
}
153-
154-
/**
155-
* Represents the data stored in the cache under a specific key.
156-
*
157-
* <p>
158-
* The cached data can be any serializable object. It is securely sealed using a
159-
* {@link CryptoAccess} instance of type <code>T</code>.
160-
* </p>
161-
*/
162-
private class CacheData {
163-
164-
private final SealedObject sealedValue;
165-
private final Duration duration;
166-
private final Instant createdAt = Instant.now();
167-
168-
public CacheData(T value, Duration duration) {
169-
requireNonNull(value, "Property 'value' must not be null");
170-
this.sealedValue = InMemoryCache.this.cryptoAccess.seal(value);
171-
this.duration = requireNonNull(duration, "Property 'duration' must not be null");
172-
}
173-
174-
public T getValue() {
175-
return cryptoAccess.unseal(sealedValue);
176-
}
177-
178-
public Duration getDuration() {
179-
return duration;
180-
}
181-
182-
public Instant getCreatedAt() {
183-
return createdAt;
153+
if (logger.isTraceEnabled()) {
154+
logger.trace("Cache:{} - clear cache", cacheName);
184155
}
156+
cachePersistence.removeOutdated(Instant.now());
185157
}
186158

187159
}

sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/CryptoAccess.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010
import javax.crypto.SecretKey;
1111

1212
/**
13-
* Represents a common possibility to encrypt data. The secret key of an
14-
* instance can unseal and seal objects for example. Be aware: Every crypto
15-
* access object has its own secret key inside! So you need to use the same
16-
* crypto access object for you operations...
13+
* Represents a common possibility to encrypt data inside ONE JVM. The secret
14+
* key of an instance can unseal and seal objects for example. Be aware: Every
15+
* crypto access object has its own secret key inside! So you need to use the
16+
* same crypto access object for you operations...
17+
*
18+
* NOTE: If you want to persist or share an object in a encrypted way you have
19+
* to use other ways! This class is only meant to ensure that memory dumps do
20+
* not reveal object data.
1721
*
1822
* @author Albert Tregnaghi
1923
*

0 commit comments

Comments
 (0)