Skip to content

Commit 2100cb1

Browse files
Make JWKS connection timeouts configurable (#1146)
--------- Signed-off-by: Oriol Muñoz <oriol.munoz@digitalasset.com>
1 parent 34fb459 commit 2100cb1

File tree

14 files changed

+152
-5
lines changed

14 files changed

+152
-5
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,4 @@ repos:
8787
- id: rstcheck
8888
name: Check sphinx format
8989
additional_dependencies: ['rstcheck[sphinx]']
90+
args: [--report-level, WARNING]

apps/app/src/pack/examples/sv-helm/sv-values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ auth:
1414
audience: "OIDC_AUTHORITY_SV_AUDIENCE"
1515
# replace OIDC_AUTHORITY_URL with your provider's OIDC URL
1616
jwksUrl: "https://OIDC_AUTHORITY_URL/.well-known/jwks.json"
17+
# optionally, reconfigure the timeouts used when querying the JWKS endpoint:
18+
# jwks:
19+
# connectionTimeout: "10 seconds"
20+
# readTimeout: "10 seconds"
1721
cometBFT:
1822
enabled: true
1923
connectionUri: "http://global-domain-MIGRATION_ID-cometbft-cometbft-rpc:26657"

apps/app/src/pack/examples/sv-helm/validator-values.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ auth:
2626
# replace OIDC_AUTHORITY_URL with your provider's OIDC URL
2727
jwksUrl: "https://OIDC_AUTHORITY_URL/.well-known/jwks.json"
2828

29+
# optionally, reconfigure the timeouts used when querying the JWKS endpoint:
30+
# jwks:
31+
# connectionTimeout: "10 seconds"
32+
# readTimeout: "10 seconds"
33+
2934
# ENABLEWALLET_START
3035
# This will disable the wallet HTTP server and wallet automations when set to false
3136
enableWallet: true

apps/common/src/main/scala/org/lfdecentralizedtrust/splice/auth/AuthConfig.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
package org.lfdecentralizedtrust.splice.auth
55

6+
import com.digitalasset.canton.config.NonNegativeDuration
7+
68
import java.net.URL
79

810
sealed trait AuthConfig {
@@ -18,6 +20,8 @@ object AuthConfig {
1820
case class Rs256(
1921
audience: String,
2022
jwksUrl: URL,
23+
connectionTimeout: NonNegativeDuration = NonNegativeDuration.ofSeconds(10),
24+
readTimeout: NonNegativeDuration = NonNegativeDuration.ofSeconds(10),
2125
) extends AuthConfig
2226

2327
def hideConfidential(config: AuthConfig): AuthConfig = {
@@ -26,7 +30,8 @@ object AuthConfig {
2630
case Hs256Unsafe(audience, _) => Hs256Unsafe(audience, hidden)
2731
// being explicit here to avoid accidental leaks if we extend
2832
// `AuthConfig` at some point
29-
case Rs256(audience, jwksUrl) => Rs256(audience, jwksUrl)
33+
case Rs256(audience, jwksUrl, connectionTimeout, readTimeout) =>
34+
Rs256(audience, jwksUrl, connectionTimeout, readTimeout)
3035
}
3136
}
3237
}

apps/common/src/main/scala/org/lfdecentralizedtrust/splice/auth/SignatureVerifier.scala

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.auth0.jwk.{JwkProvider, JwkProviderBuilder}
77
import com.auth0.jwt.JWT
88
import com.auth0.jwt.algorithms.Algorithm
99
import com.auth0.jwt.interfaces.DecodedJWT
10+
import com.digitalasset.canton.config.NonNegativeDuration
1011

1112
import java.net.URL
1213
import java.util.concurrent.TimeUnit
@@ -49,12 +50,19 @@ trait SignatureVerifier {
4950
}
5051
}
5152

52-
class RSAVerifier(audience: String, jwksUrl: URL) extends SignatureVerifier {
53+
class RSAVerifier(audience: String, jwksUrl: URL, timeoutsConfig: RSAVerifier.TimeoutsConfig)
54+
extends SignatureVerifier {
5355
override val expectedAudience: String = audience;
5456

5557
private val provider: JwkProvider = new JwkProviderBuilder(jwksUrl)
5658
.cached(10, 24, TimeUnit.HOURS)
5759
.rateLimited(10, 1, TimeUnit.MINUTES)
60+
.timeouts(
61+
// You'd need 2^31 milliseconds for this to overflow, which is about 25 days.
62+
// Surely nobody needs timeouts that long.
63+
timeoutsConfig.connectTimeout.duration.toMillis.toInt,
64+
timeoutsConfig.readTimeout.duration.toMillis.toInt,
65+
)
5866
.build()
5967

6068
private def algorithm = Algorithm.RSA256(new JwksRSAKeyProvider(provider))
@@ -63,9 +71,12 @@ class RSAVerifier(audience: String, jwksUrl: URL) extends SignatureVerifier {
6371
case _ => Left("Invalid token algorithm for rs-256 auth mode")
6472
}
6573
}
74+
object RSAVerifier {
75+
case class TimeoutsConfig(connectTimeout: NonNegativeDuration, readTimeout: NonNegativeDuration)
76+
}
6677

6778
class HMACVerifier(audience: String, secret: String) extends SignatureVerifier {
68-
override val expectedAudience: String = audience;
79+
override val expectedAudience: String = audience
6980

7081
private def algorithm = Algorithm.HMAC256(secret)
7182
override def validateAlgorithm(algorithm: String) = algorithm match {

apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,12 @@ class SvApp(
496496

497497
verifier = config.auth match {
498498
case AuthConfig.Hs256Unsafe(audience, secret) => new HMACVerifier(audience, secret)
499-
case AuthConfig.Rs256(audience, jwksUrl) => new RSAVerifier(audience, jwksUrl)
499+
case AuthConfig.Rs256(audience, jwksUrl, connectionTimeout, readTimeout) =>
500+
new RSAVerifier(
501+
audience,
502+
jwksUrl,
503+
RSAVerifier.TimeoutsConfig(connectionTimeout, readTimeout),
504+
)
500505
}
501506

502507
// Start the servers for the SvApp's APIs

apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/ValidatorApp.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,12 @@ class ValidatorApp(
870870

871871
verifier = config.auth match {
872872
case AuthConfig.Hs256Unsafe(audience, secret) => new HMACVerifier(audience, secret)
873-
case AuthConfig.Rs256(audience, jwksUrl) => new RSAVerifier(audience, jwksUrl)
873+
case AuthConfig.Rs256(audience, jwksUrl, connectionTimeout, readTimeout) =>
874+
new RSAVerifier(
875+
audience,
876+
jwksUrl,
877+
RSAVerifier.TimeoutsConfig(connectionTimeout, readTimeout),
878+
)
874879
}
875880

876881
handler =

cluster/helm/splice-sv-node/templates/sv.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ spec:
108108
value: {{ .Values.auth.audience | quote }}
109109
- name: SPLICE_APP_SV_AUTH_JWKS_URL
110110
value: {{ .Values.auth.jwksUrl | quote }}
111+
{{ if (.Values.auth.jwks).connectionTimeout }}
112+
- name: SPLICE_APP_VALIDATOR_AUTH_JWKS_CONNECTION_TIMEOUT
113+
value: {{ .Values.auth.jwks.connectionTimeout | quote }}
114+
{{ end }}
115+
{{ if (.Values.auth.jwks).readTimeout }}
116+
- name: SPLICE_APP_VALIDATOR_AUTH_JWKS_READ_TIMEOUT
117+
value: {{ .Values.auth.jwks.readTimeout | quote }}
118+
{{ end }}
111119
{{ if .Values.disableIngestUpdateHistoryFromParticipantBegin }}
112120
- name: ADDITIONAL_CONFIG_UPDATE_HISTORY_INGESTION
113121
value: |
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
suite: "Validator values"
5+
templates:
6+
- sv.yaml
7+
release:
8+
# Set for testing labels
9+
name: mock-sv
10+
chart:
11+
# Override for testing labels
12+
version: 0.1.1
13+
appVersion: 0.1.0
14+
tests:
15+
- it: "sets JWKS timeouts"
16+
set:
17+
# Things we need just to pass the schema
18+
domain:
19+
sequencerPublicUrl: "https://sequencer.mock.com"
20+
sequencerAddress: "sequencer-address"
21+
mediatorAddress: "mediator-address"
22+
sequencerPruningConfig:
23+
enabled: false
24+
scan:
25+
publicUrl: "https://scan.mock.com"
26+
internalUrl: "https://scan-internal.mock.com"
27+
nodeIdentifier: "helm-mock-1-validator"
28+
onboardingName: "some-sv"
29+
participantAddress: "mock-address"
30+
spliceInstanceNames:
31+
networkName: MockNet
32+
networkFaviconUrl: https://mock.net/favicon.ico
33+
amuletName: Mocklet
34+
amuletNameAcronym: MCK
35+
nameServiceName: Mock Name Service
36+
nameServiceNameAcronym: MNS
37+
auth:
38+
jwksUrl: "https://mock.com/.well-known/jwks.json"
39+
audience: "mock_audience"
40+
# actual test
41+
jwks:
42+
connectionTimeout: "33s"
43+
readTimeout: "44s"
44+
documentSelector:
45+
path: kind
46+
value: Deployment
47+
asserts:
48+
- equal:
49+
path: spec.template.spec.containers[?(@.name=='sv-app')].env[?(@.name=='SPLICE_APP_VALIDATOR_AUTH_JWKS_CONNECTION_TIMEOUT')].value
50+
value: "33s"
51+
- equal:
52+
path: spec.template.spec.containers[?(@.name=='sv-app')].env[?(@.name=='SPLICE_APP_VALIDATOR_AUTH_JWKS_READ_TIMEOUT')].value
53+
value: "44s"

cluster/helm/splice-validator/templates/validator.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ spec:
9393
value: {{ .Values.auth.audience | quote }}
9494
- name: SPLICE_APP_VALIDATOR_AUTH_JWKS_URL
9595
value: {{ .Values.auth.jwksUrl | quote }}
96+
{{ if (.Values.auth.jwks).connectionTimeout }}
97+
- name: SPLICE_APP_VALIDATOR_AUTH_JWKS_CONNECTION_TIMEOUT
98+
value: {{ .Values.auth.jwks.connectionTimeout | quote }}
99+
{{ end }}
100+
{{ if (.Values.auth.jwks).readTimeout }}
101+
- name: SPLICE_APP_VALIDATOR_AUTH_JWKS_READ_TIMEOUT
102+
value: {{ .Values.auth.jwks.readTimeout | quote }}
103+
{{ end }}
96104
{{ if .Values.svValidator }}
97105
- name: SPLICE_APP_VALIDATOR_SV_VALIDATOR
98106
value: "true"

0 commit comments

Comments
 (0)