Credentials
The previous subsections covered how to protect a server by restricting it to an internal network and by requiring authentication. But how should a client connect to a protected server? The client should know the credentials of the server to connect to. These credentials could be stored in properties, but then they could be read by anyone who has access to the Frank configuration. This section explains the credentials mechanism of the Frank!Framework, the way to keep credentials secret.
Keeping secrets
We continue the example started in subsection HTTP Interfaces and that was also treated in the previous subsection. We turn to the client container of docker-compose.yml
:
services:
frank-authorization-server:
image: frankframework/frankframework:latest
volumes:
- ./server/configurations:/opt/frank/configurations
- ./server/resources:/opt/frank/resources
environment:
instance.name: frank-authorization-server
dtap.stage: DEV
configurations.directory.autoLoad: true
application.security.http.transportGuarantee: none
frank-authorization-client:
image: frankframework/frankframework:latest
ports:
- 8080:8080
volumes:
- ./client/configurations:/opt/frank/configurations
- ./client/resources:/opt/frank/resources
- ./client/secrets:/opt/frank/secrets
environment:
instance.name: frank-authorization-client
dtap.stage: LOC
configurations.directory.autoLoad: true
credentialFactory.class: org.frankframework.credentialprovider.PropertyFileCredentialFactory
credentialFactory.map.properties: /opt/frank/secrets/credentials.properties
Property credentialFactory.class
defines what Java class should be used by the Frank!Framework to read credentials. Class org.frankframework.credentialprovider.PropertyFileCredentialFactory
is the Java class that reads credentials from a properties file. The Frank!Framework also provides other ways to store credentials, for example in the application server.
Property credentialFactory.map.properties
is only relevant when credentialFactory.class=org.frankframework.credentialprovider.PropertyFileCredentialFactory
and specifies the file to read the properties from. Properties are read from /opt/frank/secrets/credentials.properties
in this case. The shown docker-compose.yml
is only used for development. It defines a volume so that some credentails.properties
can be read during development. In production, these properties should be configured by the system administrator of the customer’s site. See Credentials.
Here is the credentials.properties
used in this example during development:
myAlias/username=ADMIN
myAlias/password=PASSWORD1234
The user and the password configured for the server are repeated here so that the client should be able to authorize to the server. The words username
and password
are both prepended by myAlias/
. With PropertyFileCredentialFactory
you can define multiple sets of credentials, aliases, that could give access to different servers requiring authentication. In this case there is one alias that is named myAlias
.
Note
It is possible to store usernames and passwords in separate files; each username and each password in a dedicated file. This can be done by setting credentialFactory.class=org.frankframework.credentialprovider.FileSystemCredentialFactory
. Frank applications usually need multiple aliases, so this approach is more complicated than managing all aliases in a single filee using the org.frankframework.credentialprovider.PropertyFileCredentialFactory
.
Using secrets to authenticate
The client is to authenticate itself to the following server Configuration.xml
:
<Configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../FrankConfig.xsd"
>
<Adapter name="Server">
<Receiver>
<ApiListener name="server" uriPattern="/server" allowAllParams="false"/>
</Receiver>
<Pipeline>
<EchoPipe name="showMessage" getInputFromFixedValue="Hello World" />
</Pipeline>
</Adapter>
</Configuration>
The client’s request has to reach the <ApiListener>
that listens to uriPattern="/server"
. This relative URL is automatically prepended by /api
because it is serviced by an <ApiListener>
. And the server’s domain is http://frank-authorization-server:8080
because of the service’s name given to it in the Docker Compose file. The port number is 8080
, the port where the Frank!Framework is serviced on the internal network. Here is the client configuration that accesses the server:
<Configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../FrankConfig.xsd"
>
<Adapter name="Client">
<Receiver>
<JavaListener name="client" />
</Receiver>
<Pipeline>
<SenderPipe name="callServer">
<HttpSender name="callServer"
url="http://frank-authorization-server:8080/api/server"
authAlias="myAlias">
</HttpSender>
</SenderPipe>
</Pipeline>
</Adapter>
</Configuration>
The trick is the attribute added to the <HttpSender>
: authAlias="myAlias"
. This tells the Frank!Framework to authenticate itself using basic authentication, using the secrets from alias myAlias
.
This finishes the example started in subsection HTTP Interfaces. It is available for download
.
How to authenticate when the server URL is protected by another mechanism than basic authentication? If the secrets are part of the URL or when they are needed as query parameters or within headers, the secrets can be kept by wrapping them in parameters. Here is an example:
...
<HttpSender name="callServer"
urlParam="urlParam">
<Param name="urlParam" authAlias="myAlias"
pattern="http://frank-authorization-server:8080/api/server/{username}/{password}"
hidden="true"/>
</HttpSender>
...
The <HttpSender>
does not have attribute url
, but attribute urlParam
. This way, the tricks available in the Frank!Framework’s <Param>
element to form patterns become available. The <Param>
’s authAlias
is needed to get access to the username and the password, which are accessed as {username}
and {password}
. The attribute hidden="true"
is of key importance. Without it, the Frank!Framework would write the URL to logfiles and to Ladybug reports. With hidden="true"
, only stars are shown where the expanded URL would appear otherwise. See the Frank!Doc page of the <Param>
element for details.
See https://github.com/wearefrank/frankframework-demo/blob/main/src/main/configurations/UpdateTemperature/Configuration.xml for an example where the password is given as a query parameter.
Danger
It might be tempting to simply reference credentials as if they were properties, like ${credential:username:myAlias}
. The FrankFramework allows this if the value of property authAliases.expansion.allowed
, a comma-separated list of aliases, contains the alias to expand: in this case myAlias
. This way, secrets are not kept secret! They can appear in logfiles, in Ladybug reports, and as results of Test a Pipeline. Use this only during development for testing purposes.