Read a password from a file using SpEL - Mon, Nov 29, 2021
Conveniently read a file containing a password with an SpEL expression
Read a password from a file using Spring’s SpEL
Recently we added Checkov
as a static code analysis tool for our k8s resources to our CI/CD pipeline.
Checkov scans the resources against a list of rules. One rule our containers failed to comply with was CKV_K8S_35:Prefer using secrets as files over secrets as environment variables
.
The reason for that was fairly simply: The configuration of Spring
allows an easy reference of environmental variables in the configuration or in annotations:
@Value("${MY_PASSWORD}")
private String myPassword;
So the question arose how these annotations need to be changed in order to support secrets in files.
Why are secrets ae environment variables bad ?
The reason for this rule is that applications may log their environment including environment variables. These environment variables may then end up in the applications monitoring stack like the ELK stack
. Not only may these passwords then be viewed by people not authorized but in addition may end up in some persistent nosql database.
So it makes sense to comply with this rule.
Developing the SpEL expression
The expression in the string of the @Value
annotation is actually an expression written in the Spring Expression Language (SpEL)
and means that the value of the environment variable MY_PASSWORD
should be assigned
to the variable myPassword
. SpEL allows for much mor sophisticated expressions calling java functions included. For example the expression #{(T(java.lang.Math).random())}"
would call the method random of the class java.lang.Math
.
So to solve the problem we need an expression that reads the content to a file directly to a String.
When assuming that content is UTF-8
encoded, the java code as a one liner for that would like like this:
String myPassowrd = Files.readString(Path.of("/etc/secrets/password.txt"));
Converting the right part of the assignment to a SpEL expression can be a bit hard because of all the parenthesis. Especially if you can only test the expression when
you run the actual application so that the @Value
annotation gets actually evaluated. Luckily creating a small program for testing expressions is quiet easy as the following code snippet shows:
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
...
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("T(java.lang.System).getenv().get('MY_PASSWORD')");
String myPassword = (String) exp.getValue();
The code above is the SpEL equivalent to the value of the original @Value
annotation.
The actual conversion of the pure Java code to an SpEL expression contains the following steps:
- Put all classes in parenthesis
- Add the prefix
T
- Use fully qualified names for the classes
In this example the resulting expression looks like this:
T(java.nio.file.Files).readString(T(java.nio.file.Path).of('/etc/secrets/password.txt'))
If the location fo the password file is not know in advance we could even use an environment variable for it:
T(java.nio.file.Files).readString(T(java.nio.file.Path).of('${PASSWORD_FILE}'))
Though this might already be a little bit over engineered
. One last thing that needs to be noted ist that the expression needs to be prefixed by #{
and suffixed by }
when used as an annotation value or in a config file:
@Value("#{T(java.nio.file.Files).readString(T(java.nio.file.Path).of('${PASSWORD_FILE}'))}")
Since this expression is rather long and cumbersome to read, a custom function with an easier signature that acts like a facade could be introduced. For example:
#{T(com.jeeatwork.SecretHelper).getPassword('database')
That function could then hide details such as:
- if the password is actually contained in a file or an environment variable or
- where the password file is located
thus introducing more flexibility.
Conclusion
Not using environment variables for passwords is a best practice in a cloud environment since these might be logged and then potentially end up in a database.
In Spring files containing passwords can be read by using SpEL expressions as part of @Value
annotations or configuration files. Thought the syntax is more complex compared to environment variables.