fix: security hardening — remove dead routes, add JWT audience validation
- Remove broken observe/dashboard Traefik routes (server accessed via /server only) - Remove unused acme volume - Add JWT audience claim validation (https://api.cameleer.local) in SecurityConfig - Secure bootstrap output file with chmod 600 - Add dev-only comments on TLS_SKIP_VERIFY and credential logging Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,8 +15,13 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtValidators;
|
||||
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
|
||||
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
@@ -71,7 +76,8 @@ public class SecurityConfig {
|
||||
@ConditionalOnMissingBean
|
||||
public JwtDecoder jwtDecoder(
|
||||
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") String jwkSetUri,
|
||||
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri:}") String issuerUri) throws Exception {
|
||||
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri:}") String issuerUri,
|
||||
@Value("${cameleer.identity.audience:}") String audience) throws Exception {
|
||||
var jwkSource = JWKSourceBuilder.create(new URL(jwkSetUri)).build();
|
||||
var keySelector = new JWSVerificationKeySelector<SecurityContext>(
|
||||
JWSAlgorithm.ES384, jwkSource);
|
||||
@@ -81,9 +87,15 @@ public class SecurityConfig {
|
||||
processor.setJWSTypeVerifier((type, context) -> { /* accept JWT and at+jwt */ });
|
||||
|
||||
var decoder = new NimbusJwtDecoder(processor);
|
||||
var validators = new ArrayList<OAuth2TokenValidator<Jwt>>();
|
||||
validators.add(new JwtTimestampValidator());
|
||||
if (issuerUri != null && !issuerUri.isEmpty()) {
|
||||
decoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuerUri));
|
||||
validators.add(new JwtIssuerValidator(issuerUri));
|
||||
}
|
||||
if (audience != null && !audience.isEmpty()) {
|
||||
validators.add(new JwtClaimValidator<List<String>>("aud", aud -> aud != null && aud.contains(audience)));
|
||||
}
|
||||
decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators));
|
||||
return decoder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ cameleer:
|
||||
m2m-client-id: ${LOGTO_M2M_CLIENT_ID:}
|
||||
m2m-client-secret: ${LOGTO_M2M_CLIENT_SECRET:}
|
||||
spa-client-id: ${LOGTO_SPA_CLIENT_ID:}
|
||||
audience: ${CAMELEER_OIDC_AUDIENCE:https://api.cameleer.local}
|
||||
runtime:
|
||||
max-jar-size: 209715200
|
||||
jar-storage-path: ${CAMELEER_JAR_STORAGE_PATH:/data/jars}
|
||||
|
||||
Reference in New Issue
Block a user