This article is part of a series âSecure code developmentâ in which I would like to show some tips and good practices for developers to help You build more secure software. Targeted audiences are IT students and junior developers but hopefully, anyone inside the IT industry would read something interesting. Provided examples are created using java and the spring-boot framework.
TL;DR;
Password management within the code base is critical. You should never allow the password to be stored in plain text within both database and code. For storing authentication data use secure hashing functions for example BCrypt. For Reusable secrets use CI Variables, config server or HashiCorp Vault.
Password Management
If a non-technical person asks an IT security professional about a tip on how to be more secure in the cyber world â probably the most important answer would be:
âKeep your passwords secure. Use different passwords for different services. Use strong and complicated passwordsâ
Some security guy
And it was easy for us to say. It was hard to encourage people to care about this topic. Just read about the CCleaner breach. It all started with a developer who used the same password for multiple services. But for a long time already the things have changed. There are plenty of Password Managers available on a market (some of them shared as open-source software) for free for personal use. They are really easily integrated with browsers and applications. There is a possibility to synchronize the manager with the smartphone and use configured passwords on both personal computers and a smartphone. And the answer to the most frequent allegation âhow to remember all these passwordsâ is âYou donât have to, there is a software that can do it for Youâ.
There is a reason for mentioning this. The reply to the question above asked by the software developer would be the same. The context would be different.Â
In the further text, I would use a âsecretâ keyword for password, token and keys.
In software development there are two types for secrets considering the use:
- Used for user/service authentication
- Used by software to authenticate in remote service
I will not describe a process to integrate with 3rd party authentication using Facebook, Google, GitHub or so on.
Ideal Solution – Use x509 Client Certificates (for both user auth and auth in remote service)
Use SSL/TLS
It Is really easy to build a service that will use Client Certificate authentication using Spring Security.
application.properties
ssl.server.client-auth=want
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class X509SecurityAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService())
.and()
.exceptionHandling()
.accessDeniedPage("/forbidden");
}
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_API"));
}
};
}
}
You must remember here to avoid using self-signed certificates. They are easy to generate and use on development environment but in order to make use of them You have to disable multiple options like SSL Verification which never should be disabled.
User/service authentication
Context to implement:
- User us creating an account (providing a password)
- Software is storing the provided password
- User use credentials in order to log in
- Software is verifying if user provided correct password
It must be stated here that software must never store password in database in format which user provided (plaintext).
This topic is pretty straight forward and almost each modern framework provides functionalities to make it secure. Password should be stored in database in hash form using secure algorithm (no MD5!). Hashes are being used because there are one direction function. Once You create a hash for a particular string You should never been able to retrieve a actual password from it.
// Declaration for BCrytpPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
public AuthService(PasswordEncoder passwordEncoder, UserRepository userRepository) {
this.passwordEncoder = passwordEncoder;
this.userRepository = userRepository;
}
public User register(UserDto accountDto) {
User user = new User(accountDto);
user.setPassword(passwordEncoder.encode(accountDto.getPassword()));
return userRepository.save(user);
}
Using above pseudocode user would have password set like follows
Bcrypt function is automatically generating random hash and concatenate it with a password. Also, there is a possibility to implement the number of iterations hashing algorithms will be executed (above example 11 iterations).
The operation which verifies if a user is providing the correct password is counting hash of an entered password and then it compares newly created hash with hash from the database.
if (bCryptPasswordEncoder.matches(passwordAuthModel.getPassword(), user.get().getPassword())) return âauthenticatedâ;
As easy as it is, it is enough to make sure that even if the database would leak somewhere, bad guys wonât have an easy job to retrieve passwords from such a table.
Authenticate in remote service
Things are different when You need to reuse saved password so You will be properly authenticated within another resource. For example, the application needs the password to login to the database or apiKey for REST API used by mobile applications. Most of today’s applications need a connection with remote systems. And each such connection has to be authenticated. You must remember to never put such secrets within codebase (even if there is a history in git with the previous commit which contains hardcoded passwords – change it now).
There are number of solutions to audit Your codebase for hardcoded secrets
- https://github.com/kootenpv/gittyleaks which is python package looking in commit history for hardcoded passwords
- https://github.com/auth0/repo-supervisor – deployed as docker which is detecting for each kind of secrets stored within the codebase
What to do instead? There are multiple solutions to avoid hardcoding passwords:
- Use CI Variables for storing secrets which are used inside of application build
Both GitLab and GitHub offer a place to store securely secrets:
2. Use HashiCorp Vault
Require more configuration as it is another software that can take charge of secure storage of secrets.
It is really easy to configure and run Vault from docker which is available via DockerHub – https://hub.docker.com/_/vault
running vault:
docker run --cap-add=IPC_LOCK -e 'VAULT_LOCAL_CONFIG={"backend": {"file": {"path": "/vault/file"}}, "default_lease_ttl": "168h", "max_lease_ttl": "720h"}' vault server
from now on it is available on http://localhost:8200
Spring Integration with Vault:
private final VaultOperations vaultOperations;
@Autowired
public VaultHelper (VaultOperations vaultOperations){
this.vaultOperations = vaultOperations;
}
/**
* Saves password at path secret/ for fiven user within Vault.
*
* @param password password to save within vault
* @param token token which will be stored as accessible parameter
*/
public void savePassword(String password, String token){
Map<String, String> upassMap = new HashMap<>();
upassMap.put("password", password);
vaultOperations.write("secret/" + token, upassMap);
}
/**
* Getting password from location secret/token on form of a String
*
* @param passwordLoc String needed to accessing password value from Vault
* @return String password
*/
public String getPassword(String passwordLoc){
VaultResponseSupport<Map<String, Object>> password = vaultOperations.read("secret/" + passwordLoc);
assert password != null;
return Objects.requireNonNull(password.getData()).get(Constants.PASSWORD).toString();
}
3. Use Spring Cloud config
This solution can be used with both CI Variables and HashiCorp Vault. Config servers are able to store and serve configuration of the same application for multiple environments and profiles. You can store configuration for dev, int and prod environment within a single config server and in the end, build an app using proper one inside Your CICD pipeline. It requires some effort in order to be installed and configured but the properly set infrastructure can be easily accessed by developers. No more problems migrating from one environment to
Conclusions
You can use both CI variables and Vault for almost every solution (PHP, Python, .net and more). You can see it is really easy to avoid using hardcoded passwords. Althoguh with even the strongest password management procedures developed within the process of software delivery, no authentication method would be stronger then TLS/SSL Certificate Client Authentication.
References: https://www.hashicorp.com https://github.com https://hub.docker.com/_/vault https://cloud.spring.io/spring-cloud-config/reference/html/ https://cloud.spring.io/spring-cloud-vault/reference/html/ https://www.baeldung.com/spring-security-registration-password-encoding-bcrypt
Comments are closed