Skip to content

Commit 2614388

Browse files
committed
feat: various security hardening
1 parent eec299d commit 2614388

7 files changed

Lines changed: 48 additions & 24 deletions

File tree

src/dist/conf/application.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ security:
4343
# RAppArmor profile to apply to user role, see setup instructions at <https://github.com/jeroen/RAppArmor>
4444
apparmor:
4545
enabled: false
46-
profile: testprofile
46+
profile: testprofile
47+
# Security headers
48+
headers:
49+
content-security-policy: "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'; frame-ancestors 'none';"
50+
x-content-type-options: "nosniff"
51+
x-frame-options: "DENY"

src/main/java/org/obiba/rock/domain/ExceptionErrorMessage.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@ public class ExceptionErrorMessage extends ErrorMessage {
1919
public ExceptionErrorMessage(String status, Exception exception, String... args) {
2020
setStatus(status);
2121
setKey(exception.getClass().getSimpleName());
22-
setMessage(exception.getMessage());
22+
// Use generic message to prevent information disclosure
23+
setMessage("An error occurred");
2324
setArgs(Lists.newArrayList(args));
2425
}
2526

2627
public ExceptionErrorMessage(HttpStatus status, Exception exception, String... args) {
2728
setStatus(status.value() + "");
2829
setKey(exception.getClass().getSimpleName());
29-
setMessage(exception.getMessage());
30+
// Use generic message to prevent information disclosure
31+
setMessage("An error occurred");
3032
setArgs(Lists.newArrayList(args));
3133
}
3234
}

src/main/java/org/obiba/rock/r/FileReadROperation.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public class FileReadROperation extends AbstractROperation {
2121
private final File destination;
2222

2323
public FileReadROperation(String fileName, File destination) {
24+
// Validate file path to prevent directory traversal
25+
if (fileName == null || fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
26+
throw new IllegalArgumentException("Invalid file path: contains illegal characters");
27+
}
2428
this.fileName = fileName;
2529
this.destination = destination;
2630
}

src/main/java/org/obiba/rock/r/FileWriteROperation.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public class FileWriteROperation extends AbstractROperation {
2121
private final File source;
2222

2323
public FileWriteROperation(String fileName, File source) {
24+
// Validate file path to prevent directory traversal
25+
if (fileName == null || fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
26+
throw new IllegalArgumentException("Invalid file path: contains illegal characters");
27+
}
2428
this.fileName = fileName;
2529
this.source = source;
2630
}

src/main/java/org/obiba/rock/rest/RSessionsController.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public class RSessionsController {
4040
*/
4141
@GetMapping
4242
List<RSession> getRSessions(@AuthenticationPrincipal User user, @RequestParam(name = "subject", required = false) String subject) {
43+
// Validate subject parameter
44+
if (subject != null && (subject.contains("..") || subject.contains("/"))) {
45+
throw new IllegalArgumentException("Invalid subject parameter: contains illegal characters");
46+
}
47+
4348
if (Roles.isAdmin(user) || Roles.isManager(user)) {
4449
// get all/filtered sessions
4550
return Strings.isNullOrEmpty(subject) ? rSessionService.getRSessions()

src/main/java/org/obiba/rock/security/CustomBasicAuthenticationEntryPoint.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A
2626
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
2727
response.setContentType("application/json");
2828
PrintWriter writer = response.getWriter();
29-
writer.println("{\"status\": \"401\", \"key\": \"UnAuthorized\", \"message\": \"" + authEx.getMessage() + "\"}");
29+
writer.println("{\"status\": \"401\", \"key\": \"UnAuthorized\", \"message\": \"Authentication failed\"}");
3030
}
3131

3232
@Override
3333
public void afterPropertiesSet() {
34-
setRealmName("RockRealm");
34+
setRealmName("Rock");
3535
super.afterPropertiesSet();
3636
}
3737
}

src/main/java/org/obiba/rock/security/SecurityConfiguration.java

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,24 +59,24 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
5959
private void configure(HttpSecurity http) throws Exception {
6060
if (!securityProperties.isEnabled())
6161
http
62-
.csrf(AbstractHttpConfigurer::disable)
63-
.authorizeHttpRequests((configurer) -> configurer.requestMatchers("/**").permitAll());
62+
.csrf(AbstractHttpConfigurer::disable)
63+
.authorizeHttpRequests((configurer) -> configurer.requestMatchers("/**").permitAll());
6464
else
6565
http
66-
.csrf(AbstractHttpConfigurer::disable)
67-
.formLogin(AbstractHttpConfigurer::disable)
68-
.authorizeHttpRequests((configurer) -> configurer
69-
.requestMatchers("/rserver/**").hasAnyRole(Roles.ROCK_ADMIN, Roles.ROCK_MANAGER)
70-
.requestMatchers("/r/sessions/**").hasAnyRole(Roles.ROCK_ADMIN, Roles.ROCK_MANAGER, Roles.ROCK_USER)
71-
.requestMatchers("/r/session/**").hasAnyRole(Roles.ROCK_ADMIN, Roles.ROCK_USER)
72-
.requestMatchers("/").permitAll()
73-
.requestMatchers("/_check").permitAll()
74-
.requestMatchers("/_info").permitAll()
75-
.anyRequest().denyAll())
76-
.httpBasic((configurer) -> configurer
77-
.realmName("RockRealm")
78-
.authenticationEntryPoint(getBasicAuthenticationEntryPoint()))
79-
.sessionManagement((configurer) -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
66+
.csrf(AbstractHttpConfigurer::disable)
67+
.formLogin(AbstractHttpConfigurer::disable)
68+
.authorizeHttpRequests((configurer) -> configurer
69+
.requestMatchers("/rserver/**").hasAnyRole(Roles.ROCK_ADMIN, Roles.ROCK_MANAGER)
70+
.requestMatchers("/r/sessions/**").hasAnyRole(Roles.ROCK_ADMIN, Roles.ROCK_MANAGER, Roles.ROCK_USER)
71+
.requestMatchers("/r/session/**").hasAnyRole(Roles.ROCK_ADMIN, Roles.ROCK_USER)
72+
.requestMatchers("/").permitAll()
73+
.requestMatchers("/_check").permitAll()
74+
.requestMatchers("/_info").permitAll()
75+
.anyRequest().denyAll())
76+
.httpBasic((configurer) -> configurer
77+
.realmName("Rock")
78+
.authenticationEntryPoint(getBasicAuthenticationEntryPoint()))
79+
.sessionManagement((configurer) -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
8080
}
8181

8282
private BasicAuthenticationEntryPoint getBasicAuthenticationEntryPoint() {
@@ -85,11 +85,10 @@ private BasicAuthenticationEntryPoint getBasicAuthenticationEntryPoint() {
8585

8686
private PasswordEncoder newPasswordEncoder() {
8787
Map<String, PasswordEncoder> encoders = Maps.newHashMap();
88-
encoders.put("noop", new NoOpPasswordEncoder());
8988
encoders.put("bcrypt", new BCryptPasswordEncoder(-1, new SecureRandom()));
9089
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
9190
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
92-
return new DelegatingPasswordEncoder("noop", encoders);
91+
return new DelegatingPasswordEncoder("bcrypt", encoders);
9392
}
9493

9594
@Bean
@@ -109,8 +108,13 @@ public UserDetailsService userDetailsService() {
109108
log.debug(u.getId() + ":" + u.getSecret() + ":" + Joiner.on(";").join(u.getRoles()));
110109
String[] roles = new String[u.getRoles().size()];
111110
roles = u.getRoles().stream().map(String::toUpperCase).toList().toArray(roles);
111+
String encodedPassword = u.getSecret().startsWith("{") ? u.getSecret() : passwordEncoder.encode(u.getSecret());
112+
// Validate password is not empty and follows expected format
113+
if (encodedPassword == null || encodedPassword.isEmpty() || encodedPassword.length() < 8) {
114+
throw new IllegalArgumentException("Password must be at least 8 characters long");
115+
}
112116
manager.createUser(User.withUsername(u.getId())
113-
.password(u.getSecret().startsWith("{") ? u.getSecret() : passwordEncoder.encode(u.getSecret()))
117+
.password(encodedPassword)
114118
.roles(roles)
115119
.build());
116120
});

0 commit comments

Comments
 (0)