@@ -4,20 +4,25 @@ use std::{
44 path:: PathBuf ,
55} ;
66
7- use eyre:: { bail, OptionExt , Result } ;
7+ use docker_image:: DockerImage ;
8+ use eyre:: { bail, ensure, OptionExt , Result } ;
89use serde:: { Deserialize , Serialize } ;
910use tonic:: transport:: { Certificate , Identity } ;
1011use url:: Url ;
1112
1213use super :: {
1314 load_jwt_secrets, load_optional_env_var, utils:: load_env_var, CommitBoostConfig ,
14- SIGNER_ENDPOINT_ENV , SIGNER_IMAGE_DEFAULT ,
15+ SIGNER_ENDPOINT_ENV , SIGNER_IMAGE_DEFAULT , SIGNER_JWT_AUTH_FAIL_LIMIT_ENV ,
16+ SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_ENV ,
1517} ;
1618use crate :: {
1719 config:: { DIRK_CA_CERT_ENV , DIRK_CERT_ENV , DIRK_DIR_SECRETS_ENV , DIRK_KEY_ENV } ,
18- signer:: { ProxyStore , SignerLoader , DEFAULT_SIGNER_PORT } ,
20+ signer:: {
21+ ProxyStore , SignerLoader , DEFAULT_JWT_AUTH_FAIL_LIMIT ,
22+ DEFAULT_JWT_AUTH_FAIL_TIMEOUT_SECONDS , DEFAULT_SIGNER_PORT ,
23+ } ,
1924 types:: { Chain , ModuleId } ,
20- utils:: { default_host, default_u16} ,
25+ utils:: { default_host, default_u16, default_u32 } ,
2126} ;
2227
2328#[ derive( Debug , Serialize , Deserialize , Clone ) ]
@@ -32,11 +37,39 @@ pub struct SignerConfig {
3237 /// Docker image of the module
3338 #[ serde( default = "default_signer" ) ]
3439 pub docker_image : String ,
40+
41+ /// Number of JWT auth failures before rate limiting an endpoint
42+ /// If set to 0, no rate limiting will be applied
43+ #[ serde( default = "default_u32::<DEFAULT_JWT_AUTH_FAIL_LIMIT>" ) ]
44+ pub jwt_auth_fail_limit : u32 ,
45+
46+ /// Duration in seconds to rate limit an endpoint after the JWT auth failure
47+ /// limit has been reached
48+ #[ serde( default = "default_u32::<DEFAULT_JWT_AUTH_FAIL_TIMEOUT_SECONDS>" ) ]
49+ pub jwt_auth_fail_timeout_seconds : u32 ,
50+
3551 /// Inner type-specific configuration
3652 #[ serde( flatten) ]
3753 pub inner : SignerType ,
3854}
3955
56+ impl SignerConfig {
57+ /// Validate the signer config
58+ pub async fn validate ( & self ) -> Result < ( ) > {
59+ // Port must be positive
60+ ensure ! ( self . port > 0 , "Port must be positive" ) ;
61+
62+ // The Docker tag must parse
63+ ensure ! ( !self . docker_image. is_empty( ) , "Docker image is empty" ) ;
64+ ensure ! (
65+ DockerImage :: parse( & self . docker_image) . is_ok( ) ,
66+ format!( "Invalid Docker image: {}" , self . docker_image)
67+ ) ;
68+
69+ Ok ( ( ) )
70+ }
71+ }
72+
4073fn default_signer ( ) -> String {
4174 SIGNER_IMAGE_DEFAULT . to_string ( )
4275}
@@ -100,6 +133,8 @@ pub struct StartSignerConfig {
100133 pub store : Option < ProxyStore > ,
101134 pub endpoint : SocketAddr ,
102135 pub jwts : HashMap < ModuleId , String > ,
136+ pub jwt_auth_fail_limit : u32 ,
137+ pub jwt_auth_fail_timeout_seconds : u32 ,
103138 pub dirk : Option < DirkConfig > ,
104139}
105140
@@ -119,12 +154,31 @@ impl StartSignerConfig {
119154 SocketAddr :: from ( ( signer_config. host , signer_config. port ) )
120155 } ;
121156
157+ // Load the JWT auth fail limit the same way
158+ let jwt_auth_fail_limit =
159+ if let Some ( limit) = load_optional_env_var ( SIGNER_JWT_AUTH_FAIL_LIMIT_ENV ) {
160+ limit. parse ( ) ?
161+ } else {
162+ signer_config. jwt_auth_fail_limit
163+ } ;
164+
165+ // Load the JWT auth fail timeout the same way
166+ let jwt_auth_fail_timeout_seconds = if let Some ( timeout) =
167+ load_optional_env_var ( SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_ENV )
168+ {
169+ timeout. parse ( ) ?
170+ } else {
171+ signer_config. jwt_auth_fail_timeout_seconds
172+ } ;
173+
122174 match signer_config. inner {
123175 SignerType :: Local { loader, store, .. } => Ok ( StartSignerConfig {
124176 chain : config. chain ,
125177 loader : Some ( loader) ,
126178 endpoint,
127179 jwts,
180+ jwt_auth_fail_limit,
181+ jwt_auth_fail_timeout_seconds,
128182 store,
129183 dirk : None ,
130184 } ) ,
@@ -153,6 +207,8 @@ impl StartSignerConfig {
153207 chain : config. chain ,
154208 endpoint,
155209 jwts,
210+ jwt_auth_fail_limit,
211+ jwt_auth_fail_timeout_seconds,
156212 loader : None ,
157213 store,
158214 dirk : Some ( DirkConfig {
0 commit comments