Skip to content

Commit 59bf12d

Browse files
committed
Add circuit breaker for OCSP service requests
1 parent 5c6fc14 commit 59bf12d

18 files changed

+941
-191
lines changed

pom.xml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<bouncycastle.version>1.81</bouncycastle.version>
1717
<jackson.version>2.19.1</jackson.version>
1818
<slf4j.version>2.0.17</slf4j.version>
19+
<resilience4j.version>1.7.0</resilience4j.version>
1920
<junit-jupiter.version>5.13.3</junit-jupiter.version>
2021
<assertj.version>3.27.3</assertj.version>
2122
<mockito.version>5.18.0</mockito.version>
@@ -65,6 +66,33 @@
6566
<artifactId>bcpkix-jdk18on</artifactId>
6667
<version>${bouncycastle.version}</version>
6768
</dependency>
69+
<dependency>
70+
<groupId>io.github.resilience4j</groupId>
71+
<artifactId>resilience4j-all</artifactId>
72+
<version>${resilience4j.version}</version>
73+
<exclusions>
74+
<exclusion>
75+
<groupId>io.github.resilience4j</groupId>
76+
<artifactId>resilience4j-retry</artifactId>
77+
</exclusion>
78+
<exclusion>
79+
<groupId>io.github.resilience4j</groupId>
80+
<artifactId>resilience4j-bulkhead</artifactId>
81+
</exclusion>
82+
<exclusion>
83+
<groupId>io.github.resilience4j</groupId>
84+
<artifactId>resilience4j-cache</artifactId>
85+
</exclusion>
86+
<exclusion>
87+
<groupId>io.github.resilience4j</groupId>
88+
<artifactId>resilience4j-ratelimiter</artifactId>
89+
</exclusion>
90+
<exclusion>
91+
<groupId>io.github.resilience4j</groupId>
92+
<artifactId>resilience4j-timelimiter</artifactId>
93+
</exclusion>
94+
</exclusions>
95+
</dependency>
6896

6997
<dependency>
7098
<groupId>org.junit.jupiter</groupId>
@@ -90,6 +118,12 @@
90118
<version>${slf4j.version}</version>
91119
<scope>test</scope>
92120
</dependency>
121+
<dependency>
122+
<groupId>org.awaitility</groupId>
123+
<artifactId>awaitility</artifactId>
124+
<version>4.3.0</version>
125+
<scope>test</scope>
126+
</dependency>
93127
</dependencies>
94128

95129
<build>

src/main/java/eu/webeid/security/validator/AuthTokenValidationConfiguration.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import eu.webeid.security.certificate.SubjectCertificatePolicies;
2626
import eu.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
27+
import eu.webeid.security.validator.ocsp.service.FallbackOcspServiceConfiguration;
28+
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
2729
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
2830

2931
import java.net.MalformedURLException;
@@ -51,6 +53,8 @@ public final class AuthTokenValidationConfiguration {
5153
private Duration allowedOcspResponseTimeSkew = Duration.ofMinutes(15);
5254
private Duration maxOcspResponseThisUpdateAge = Duration.ofMinutes(2);
5355
private DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration;
56+
private Collection<FallbackOcspServiceConfiguration> fallbackOcspServiceConfigurations = new HashSet<>();
57+
private CircuitBreakerConfig circuitBreakerConfig;
5458
// Don't allow Estonian Mobile-ID policy by default.
5559
private Collection<ASN1ObjectIdentifier> disallowedSubjectCertificatePolicies = newHashSet(
5660
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V1,
@@ -71,6 +75,8 @@ private AuthTokenValidationConfiguration(AuthTokenValidationConfiguration other)
7175
this.allowedOcspResponseTimeSkew = other.allowedOcspResponseTimeSkew;
7276
this.maxOcspResponseThisUpdateAge = other.maxOcspResponseThisUpdateAge;
7377
this.designatedOcspServiceConfiguration = other.designatedOcspServiceConfiguration;
78+
this.fallbackOcspServiceConfigurations = Set.copyOf(other.fallbackOcspServiceConfigurations);
79+
this.circuitBreakerConfig = other.circuitBreakerConfig;
7480
this.disallowedSubjectCertificatePolicies = Set.copyOf(other.disallowedSubjectCertificatePolicies);
7581
this.nonceDisabledOcspUrls = Set.copyOf(other.nonceDisabledOcspUrls);
7682
}
@@ -135,6 +141,18 @@ public Collection<URI> getNonceDisabledOcspUrls() {
135141
return nonceDisabledOcspUrls;
136142
}
137143

144+
public Collection<FallbackOcspServiceConfiguration> getFallbackOcspServiceConfigurations() {
145+
return fallbackOcspServiceConfigurations;
146+
}
147+
148+
public CircuitBreakerConfig getCircuitBreakerConfig() {
149+
return circuitBreakerConfig;
150+
}
151+
152+
public void setCircuitBreakerConfig(CircuitBreakerConfig circuitBreakerConfig) {
153+
this.circuitBreakerConfig = circuitBreakerConfig;
154+
}
155+
138156
/**
139157
* Checks that the configuration parameters are valid.
140158
*
@@ -150,6 +168,7 @@ void validate() {
150168
requirePositiveDuration(ocspRequestTimeout, "OCSP request timeout");
151169
requirePositiveDuration(allowedOcspResponseTimeSkew, "Allowed OCSP response time-skew");
152170
requirePositiveDuration(maxOcspResponseThisUpdateAge, "Max OCSP response thisUpdate age");
171+
// TODO: Add OCSP fallback/response validation
153172
}
154173

155174
AuthTokenValidationConfiguration copy() {

src/main/java/eu/webeid/security/validator/AuthTokenValidatorBuilder.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import eu.webeid.security.validator.ocsp.OcspClient;
2727
import eu.webeid.security.validator.ocsp.OcspClientImpl;
2828
import eu.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
29+
import eu.webeid.security.validator.ocsp.service.FallbackOcspServiceConfiguration;
30+
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
2931
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
3032
import org.slf4j.Logger;
3133
import org.slf4j.LoggerFactory;
@@ -187,6 +189,43 @@ public AuthTokenValidatorBuilder withDesignatedOcspServiceConfiguration(Designat
187189
return this;
188190
}
189191

192+
/**
193+
* // TODO: Describe the configuration option
194+
*
195+
* @param serviceConfiguration configurations of the fallback OCSP services
196+
* @return the builder instance for method chaining
197+
*/
198+
public AuthTokenValidatorBuilder withFallbackOcspServiceConfiguration(FallbackOcspServiceConfiguration... serviceConfiguration) {
199+
// TODO: Validate that no two configurations have the same OCSP service access location
200+
Collections.addAll(configuration.getFallbackOcspServiceConfigurations(), serviceConfiguration);
201+
LOG.debug("Fallback OCSP services set to {}", configuration.getFallbackOcspServiceConfigurations());
202+
return this;
203+
}
204+
205+
206+
/**
207+
* // TODO: Describe the configuration option
208+
*
209+
* @param slidingWindowSize
210+
* @param minimumNumberOfCalls
211+
* @param failureRateThreshold
212+
* @param permittedNumberOfCallsInHalfOpenState
213+
* @param waitDurationInOpenState
214+
*
215+
* @return the builder instance for method chaining
216+
*/
217+
public AuthTokenValidatorBuilder withCircuitBreakerConfig(int slidingWindowSize, int minimumNumberOfCalls, int failureRateThreshold, int permittedNumberOfCallsInHalfOpenState, Duration waitDurationInOpenState) { // TODO: What do we allow to configure? Use configuration builder.
218+
configuration.setCircuitBreakerConfig(CircuitBreakerConfig.custom()
219+
.slidingWindowSize(slidingWindowSize)
220+
.minimumNumberOfCalls(minimumNumberOfCalls)
221+
.failureRateThreshold(failureRateThreshold)
222+
.permittedNumberOfCallsInHalfOpenState(permittedNumberOfCallsInHalfOpenState)
223+
.waitDurationInOpenState(waitDurationInOpenState)
224+
.build());
225+
LOG.debug("Using the OCSP circuit breaker configuration");
226+
return this;
227+
}
228+
190229
/**
191230
* Uses the provided OCSP client instance during user certificate revocation check with OCSP.
192231
* The provided client instance must be thread-safe.

src/main/java/eu/webeid/security/validator/AuthTokenValidatorImpl.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import eu.webeid.security.validator.certvalidators.SubjectCertificateValidatorBatch;
3838
import eu.webeid.security.validator.ocsp.OcspClient;
3939
import eu.webeid.security.validator.ocsp.OcspServiceProvider;
40+
import eu.webeid.security.validator.ocsp.ResilientOcspService;
4041
import eu.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
4142
import org.slf4j.Logger;
4243
import org.slf4j.LoggerFactory;
@@ -65,9 +66,8 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
6566
private final CertStore trustedCACertificateCertStore;
6667
// OcspClient uses built-in HttpClient internally by default.
6768
// A single HttpClient instance is reused for all HTTP calls to utilize connection and thread pools.
68-
private OcspClient ocspClient;
69-
private OcspServiceProvider ocspServiceProvider;
7069
private final AuthTokenSignatureValidator authTokenSignatureValidator;
70+
private ResilientOcspService resilientOcspService;
7171

7272
/**
7373
* @param configuration configuration parameters for the token validator
@@ -88,12 +88,15 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
8888

8989
if (configuration.isUserCertificateRevocationCheckWithOcspEnabled()) {
9090
// The OCSP client may be provided by the API consumer.
91-
this.ocspClient = Objects.requireNonNull(ocspClient, "OCSP client must not be null when OCSP check is enabled");
92-
ocspServiceProvider = new OcspServiceProvider(
91+
Objects.requireNonNull(ocspClient, "OCSP client must not be null when OCSP check is enabled");
92+
OcspServiceProvider ocspServiceProvider = new OcspServiceProvider(
9393
configuration.getDesignatedOcspServiceConfiguration(),
9494
new AiaOcspServiceConfiguration(configuration.getNonceDisabledOcspUrls(),
9595
trustedCACertificateAnchors,
96-
trustedCACertificateCertStore));
96+
trustedCACertificateCertStore),
97+
configuration.getFallbackOcspServiceConfigurations());
98+
resilientOcspService = new ResilientOcspService(ocspClient, ocspServiceProvider, configuration.getCircuitBreakerConfig(), configuration.getAllowedOcspResponseTimeSkew(),
99+
configuration.getMaxOcspResponseThisUpdateAge());
97100
}
98101

99102
authTokenSignatureValidator = new AuthTokenSignatureValidator(configuration.getSiteOrigin());
@@ -182,11 +185,7 @@ private SubjectCertificateValidatorBatch getCertTrustValidators() {
182185
return SubjectCertificateValidatorBatch.createFrom(
183186
certTrustedValidator::validateCertificateTrusted
184187
).addOptional(configuration.isUserCertificateRevocationCheckWithOcspEnabled(),
185-
new SubjectCertificateNotRevokedValidator(certTrustedValidator,
186-
ocspClient, ocspServiceProvider,
187-
configuration.getAllowedOcspResponseTimeSkew(),
188-
configuration.getMaxOcspResponseThisUpdateAge()
189-
)::validateCertificateNotRevoked
188+
new SubjectCertificateNotRevokedValidator(resilientOcspService, certTrustedValidator)::validateCertificateNotRevoked
190189
);
191190
}
192191

0 commit comments

Comments
 (0)