[[filesystem-security-realm]]
= Filesystem Security Realm

ifdef::env-github[]
:tip-caption: :bulb:
:note-caption: :information_source:
:important-caption: :heavy_exclamation_mark:
:caution-caption: :fire:
:warning-caption: :warning:
endif::[]

The filesystem security realm is a security realm developed to support storing of identities in a filesystem with the option of associating multiple credentials and multiple attributes with each identity. Both credentials and attributes can contain multiple values.

== Create and Populate Filesystem Security Realm

Every identity is stored in an XML file where the name of the file is the name of the identity. It is sufficient to just specify the path to the directory where identity files will be stored. There are other optional attributes.

* `path` The actual filesystem path. Treated as an absolute path, unless the 'relative-to' attribute is specified, in which case the value is treated as relative to that path.
* `relative-to` If 'relative-to' is provided, the value of the 'path' attribute is treated as relative to the path specified by this attribute.
* `levels` The number of levels of directory hashing to apply. When this attribute is set to positive value, filesystem realm will store identities in directory structure where the name of subdirectories will be derived from first characters of identity name. For example, location of identity named "alex" could be `a/l/alex.xml`. This is useful not only because some filesystems can limit the amount of files that can be stored in a single directory, but also for performance reasons, because that might be influenced by the number of entries in a single directory as well. Default value is 2.
* `encoded` When encoding is set to true, the identity names will be BASE32 encoded before they are used as filenames. This is beneficial, because some filesystems are case-insensitive or might restrict set of characters allowed in a filename. Default value is true.
* `hash-encoding` specifies the string format for the password if it is not stored in plain text. Set to BASE64 bt default, but HEX is also supported.
* `hash-charset` specifies the character set to use when converting the password string to a byte array. Set to UTF-8 by default.
* `credential-store` specifies the credential store where the secret key is stored if the filesystem realm is to be encrypted. This attribute is optional.
* `secret-key` specifies the alias of the secret key that is stored in the previously specified credential store. This attribute is optional.
* `key-store` specifies the key store where the key pair is stored if the integrity is to be enabled on the filesystem realm. This attribute is optional.
* `key-store-alias` specifies the alias of the key pair that is stored in the previously specified key store. This attribute is optional.

Filesystem security realm can be added with WildFly management CLI or with Elytron API.

=== WildFly management CLI

The following command creates realm with name `fsRealm` that resides in directory `fs-realm-users` inside the base configuration directory defined by `jboss.server.config.dir` property. The levels number is 2 and encoded value is true.
The charset is UTF-8 and the string encoding is BASE64.

The `credential-store` being referenced is called `mycredstore` and the alias of the `secret-key` in that `credential-store` is `key`. The secret key will be used to encrypt and decrypt the filesystem realm.

The key-store being referenced is called `keystore` and the alias of the key pair in that key-store is `localhost`. The `localhost` key pair will be used to sign identity files and verify the pre-existing signatures to ensure that the filesystem realm has not been tampered with.

[source,options="nowrap"]
----
/subsystem=elytron/filesystem-realm=fsRealm:add(path=fs-realm-users, relative-to=jboss.server.config.dir, credential-store=mycredstore, secret-key=key, key-store=keystore, key-store-alias=localhost)
----

NOTE: Although identities stored within the same realm can be hashed using different algorithms, they are all
hashed using the same character set and stored using the same string encoding across the whole realm.

To add identity with the name "alex" to this filesystem realm:

[source,options="nowrap"]
----
/subsystem=elytron/filesystem-realm=fsRealm:add-identity(identity=alex)
----

To delete existing identity from filesystem realm:

[source,options="nowrap"]
----
/subsystem=elytron/filesystem-realm=fsRealm:remove-identity(identity=alex)
----

It's possible to update the ``key-store`` or ``key-store-alias`` to use for integrity checking using the ``update-key-pair`` command as shown below. These commands will also update the signature in any existing identity files.
[source, options="nowrap"]
----
batch
/subsystem=elytron/filesystem-realm=fsRealm:write-attribute(name=key-store, value=newKeystore)
/subsystem=elytron/filesystem-realm=fsRealm:write-attribute(name=key-store-alias, value=newKeystoreAlias)
run-batch
reload
/subsystem=elytron/filesystem-realm=fsRealm:update-key-pair()
----

IMPORTANT: An update of attributes requires a reload or restart
to take effect. Otherwise, the ``update-key-pair`` operation can result in an inconsistent state.

=== Programmatic approach

When creating filesystem realm with Elytron API, you can specify `name rewriter` in the constructor. The name rewriter will be applied to identity names to transform them from one form to another. It can be used to normalize the case, trim extra whitespaces, map one naming scheme to another, remove realm component from identity name (e.g. "user@realm" to "user") or to perform validation step on the syntax of the name.

To enable encryption for a filesystem realm programmatically, you can specify a `SecretKey` when instantiating the realm.

To enable integrity for the filesystem realm programmatically, you can specify a PublicKey and PrivateKey when instantiating the realm.

A `org.wildfly.security.auth.realm.FileSystemSecurityRealm` can be instantiated using a `builder()` method as shown in the example below.

[source,options="nowrap"]
----
FileSystemSecurityRealm fsRealm = FileSystemSecurityRealm.builder()
    .setRoot(fsRealmPath)
    .setNameRewriter(IDENTITY_REWRITER)
    .setLevels(2)
    .setEncoded(true)
    .setProviders(ELYTRON_PASSWORD_PROVIDERS)
    .setSecretKey(secretKey)
    .setPublicKey(publicKey)
    .setPrivateKey(privateKey)
    .build();
----

`org.wildfly.security.auth.server.NameRewriter.IDENTITY_REWRITER` is a simple identity name rewriter that does no rewriting. You can implement the `NameRewriter` interface and use your own rewriter.

Class `org.wildfly.security.auth.principal.NamePrincipal` represents principal comprised of a simple name. Handle for the identity can be obtained by passing the `NamePrincipal` instance to the `getRealmIdentity` or `getRealmIdentityForUpdate` method. Received identity for the given principal might or might not exist in the filesystem realm. Method `exists` can be called to check whether the received identity exists. If it does not, you cannot modify its credentials or attributes.

To add non existent identity to the filesystem realm, `create` method must be called. After this call, credentials and roles of this identity are empty. The exception will be thrown if method `create` is called on an existing identity.



[source,options="nowrap"]
----
ModifiableRealmIdentity modifiableIdentity = fsRealm.getRealmIdentityForUpdate(new NamePrincipal("alex"));
if (!modifiableIdentity.exists()) {
    modifiableIdentity.create();
}
----

To obtain read only instance of identity:
[source,java]
----
RealmIdentity identity = fsRealm.getRealmIdentity(identityPrincipal);
----

To obtain identity that can be modified:
[source,java]
----
ModifiableRealmIdentity modifiableIdentity = fsRealm.getRealmIdentityForUpdate(identityPrincipal);
----

ModifiableRealmIdentity handle must be cleaned up by a call to `dispose` when the modifying is done:

[source,options="nowrap"]
----
modifiableIdentity.dispose();
----

To delete the identity from the realm:

[source,options="nowrap"]
----
identity.delete();
----

== Credentials

Supported password types for identity in filesystem realm are `Bcrypt`, `Clear`, `Simple Digest`, `Salted Simple Digest`, `Scram Digest`, `Digest` and `OTP`.

=== WildFly management CLI

To set clear password to the identity:

[source,options="nowrap"]
----
/subsystem=elytron/filesystem-realm=fsRealm:set-password(clear={password="alexPassword"}, identity=alex)
----

The above command will store password in clear text in identity's file. It is not recommended to use clear passwords in a production set up.

To set digest password to the identity:

[source,options="nowrap"]
----
/subsystem=elytron/filesystem-realm=fsRealm:set-password(digest={algorithm=digest-md5,password="demoPassword",realm=demoRealm},identity=alex)
----

*Note:* Operation set-password` replaces any existing credential(s) with the new value.

=== Programmatic approach

When using Elytron API, working with passwords require interaction with the `org.wildfly.security.password.PasswordFactory` API. Here is the simplest example that will store password in clear text in identity's file. Examples on how to work with other types of passwords can be found in <<Passwords, Passwords>> section of this documentation.

[source,options="nowrap"]
----
PasswordFactory passwordFactory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR);
PasswordCredential clearPassword = new PasswordCredential(passwordFactory.generatePassword(new ClearPasswordSpec("alexPassword".toCharArray())));
identity.setCredentials(Collections.singleton(clearPassword));
----

*Note:* Operation `setCredentials` replaces any existing credential(s) with the new value.

== Attributes

Attributes associated with identities can be read and updated through CLI as well as programmatically.

=== WildFly management CLI

To add attribute to the identity, you have to specify the name of the attribute and its values (values are of type LIST):

[source,options="nowrap"]
----
/subsystem=elytron/filesystem-realm=fsRealm:add-identity-attribute(
    identity="alex",
    name=Email,
    value=["alex@email.com", "alex_email@email.com"])
{"outcome" => "success"}
----

To read the identity with its attributes:

[source,options="nowrap"]
----
/subsystem=elytron/filesystem-realm=fsRealm:read-identity(identity="alex")
{
    "outcome" => "success",
    "result" => {
        "name" => "alex",
        "attributes" => {"Email" => [
            "alex@email.com",
            "alex_email@email.com"
        ]}
    }
}
----

To remove individual values of the attribute:

[source,options="nowrap"]
----
/subsystem=elytron/filesystem-realm=fsRealm:remove-identity-attribute(
    identity=alex,
    name=Email,
    value=[alex@email.com])
{"outcome" => "success"}
/subsystem=elytron/filesystem-realm=fsRealm:read-identity(identity="alex")
{
    "outcome" => "success",
    "result" => {
        "name" => "alex",
        "attributes" => {"Email" => ["alex_email@email.com"]}
    }
}
----

To remove the whole attribute:

[source,options="nowrap"]
----
/subsystem=elytron/filesystem-realm=fsRealm:remove-identity-attribute(identity=alex, name=Email)
{"outcome" => "success"}
/subsystem=elytron/filesystem-realm=fsRealm:read-identity(identity=alex)
{
    "outcome" => "success",
    "result" => {
        "name" => "alex",
        "attributes" => undefined
    }
}
----

=== Programmatic approach

Interface `org.wildfly.security.authz.Attributes` represents collection of string attributes. To get attributes associated with specific identity:

[source,java]
----
Attributes identityAttributes = identity.getAttributes();
----

To update attributes you can use `ModifiableRealmIdentity` instance. Class `org.wildfly.security.authz.MapAttributes` represents collection of attributes backed by `java.util.Map`:

[source, java]
----
ModifiableRealmIdentity modifiableIdentity = fsRealm.getRealmIdentityForUpdate(identityPrincipal);
MapAttributes attributes = new MapAttributes();
attributes.addLast("email","alex@email.com");
modifiableIdentity.setAttributes(attributes);
modifiableIdentity.dispose();
----

== Converting legacy properties file into Filesystem realm

WildFly can use authentication with a properties file based identity store. One properties file maps users to passwords and another maps users to roles. You can use `Elytron Tool` to convert these properties files into a filesystem realm. Below is an example of how to run this tool from the command line:

[source,options="nowrap"]
----
$JBOSS_HOME/bin/elytron-tool.sh filesystem-realm -u conf/users.properties -r conf/roles.properties --output-location realms/example --summary -f example-fs-realm
----

This command creates new filesystem realm with users taken from users.properties file and roles taken from roles.properties file. Script `example-fs-realm.sh` that contains the commands for WildFly CLI is generated as well. The script adds this filesystem realm to the Elytron subsystem and also adds new security domain that uses this filesystem realm as a default realm.

== Converting an unencrypted filesystem realm into an encrypted filesystem realm

It is possible to convert an unencrypted filesystem realm into an encrypted one using the Elytron Tool. In particular, the `filesystem-realm-encrypt` command can be used as shown below:

[source,options="nowrap"]
----
$JBOSS_HOME/bin/elytron-tool.sh filesystem-realm-encrypt -i ./standalone/configuration/fs-realm-plain -o ./standalone/configuration/fs-realm-enc -c ./mycredstore.cs
----

This command creates a new filesystem realm by taking the existing filesystem realm from the specified input location, `./standalone/configuration/fs-realm-plain`, and encrypting its contents using the secret key with alias `key` from the specified credential store, `./mycredstore.cs`. The new filesystem realm is stored in the specified output location, `./standalone/configuration/fs-realm-enc`.

A `.cli` script will also be generated at the root of the filesystem realm. The script contains WildFly CLI commands that can be used to configure a `secret-key-credential-store` resource and a `filesystem-realm` resource in the Elytron subsystem that makes use of the newly encrypted realm content.
