diff --git a/release_notes/topics/9_0_0.adoc b/release_notes/topics/9_0_0.adoc
index e86a9ebc69..3592a59b20 100644
--- a/release_notes/topics/9_0_0.adoc
+++ b/release_notes/topics/9_0_0.adoc
@@ -6,4 +6,10 @@ The Drools Policy was finally removed after the deprecation period. If you need
== Pagination support for clients
-Pagination support was added to clients in the Admin Console and REST API. Thanks to https://github.com/saibot94[saibot94].
\ No newline at end of file
+Pagination support was added to clients in the Admin Console and REST API. Thanks to https://github.com/saibot94[saibot94].
+
+== New Elytron Credential Store Vault Provider
+
+A new built-in vault provider that reads secrets from a keystore-backed Elytron credential store has been added as a WildFly
+extension. The creation and management of the credential store is handled by Elytron using either the `elytron` subsystem or the
+`elytron-tool.sh` script.
\ No newline at end of file
diff --git a/server_admin/topics/vault.adoc b/server_admin/topics/vault.adoc
index 5848ea3bd8..a838bde1df 100644
--- a/server_admin/topics/vault.adoc
+++ b/server_admin/topics/vault.adoc
@@ -6,9 +6,13 @@ Several fields in the administration support obtaining the value of a secret fro
To obtain a secret from a vault instead of entering it directly, enter
the following specially crafted string into the appropriate field:
-`**${vault.**_entry-name_**}**` where you replace the `_entry-name_`
+`**${vault.**_key_**}**` where you replace the `_key_`
with the name of the secret as recognized by the vault.
+In order to prevent secrets from leaking across realms, implementations may combine the realm name with the `_key_`
+obtained from the vault expression. This means that the `_key_` won't directly map to an entry in the vault, but rather
+be used to create the final entry name according to the algorithm used to combine it with the realm name.
+
Currently, the secret can be obtained from the vault in the following fields:
SMTP password::
@@ -24,7 +28,11 @@ To use a vault, a vault provider must be registered within {project_name}.
It is possible to either use a built-in provider described below or
implement your own provider. See the link:{developerguide_link}[{developerguide_name}] for more information.
-=== Kubernetes / OpenShift files plaintext vault provider
+NOTE: There is at most one vault provider active per {project_name} instance
+at any given time, and the vault provider in each instance within the cluster
+has to be configured consistently.
+
+=== Kubernetes / OpenShift Files Plaintext Vault Provider
{project_name} supports vault implementation for https://kubernetes.io/docs/concepts/configuration/secret/[Kubernetes secrets]. These secrets
can be mounted as data volumes, and they appear as a directory with a flat file structure, where each secret is represented by a file whose name is the secret name, and contents of that file is the secret value.
@@ -54,6 +62,157 @@ Here is the equivalent configuration using CLI commands:
/subsystem=keycloak-server/spi=vault/provider=files-plaintext/:add(enabled=true,properties={dir => "${jboss.home.dir}/standalone/configuration/vault"})
----
-NOTE: There is at most one vault provider active per {project_name} instance
-at any given time, and the vault provider in each instance within the cluster
-have to be configured consistently.
+=== Elytron Credential Store Vault Provider
+
+{project_name} also provides support for reading secrets stored in an Elytron credential store. The `elytron-cs-keystore`
+vault provider is capable of retrieving secrets from the keystore-based implementation of the credential store, which
+is also the default implementation provided by Elytron.
+
+This credential store is backed by a keystore (`JCEKS` is the default format, but it is possible to use other formats such as `PKCS12`)
+and users can create and manage the store contents using either the `elytron` subsystem in WildFly/JBoss EAP, or using the
+`elytron-tool.sh` script.
+
+To use this provider, you have to declare the `elytron-cs-keystore` in the `keycloak-server` subsystem and set the location
+and master secret of the keystore that was created by Elytron. An example of the minimal configuration for the provider follows:
+
+[source, xml]
+----
+
+ elytron-cs-keystore
+
+
+
+
+
+
+
+----
+
+If the underlying keystore has a format other than `JCEKS`, this format has to be informed using the `keyStoreType`:
+
+[source, xml]
+----
+
+ elytron-cs-keystore
+
+
+
+
+
+
+
+
+----
+
+For the secret, the `elytron-cs-keystore` provider supports both clear-text values (as shown above) and also values that
+were masked using the `elytron-tool.sh` script:
+
+[source, xml]
+----
+
+ ...
+
+ ...
+
+----
+
+For more detailed information on how to create/manage elytron credential stores, as well as how to mask keystore secrets,
+please refer to the Elytron documentation.
+
+NOTE: The `elytron-cs-keystore` vault provider has been implemented as a WildFly extension and as such is only available
+if the {project_name} server runs on WildFly/JBoss EAP.
+
+=== Key Resolvers
+
+All built-in providers support the configuration of one or more key resolvers. A key resolver essentially implements
+the algorithm or strategy for combining the realm name with the key (as obtained from the `${vault.key}` expression} into
+the final entry name that will be used to retrieve the secret from the vault. The `keyResolvers` property is used to configure
+the resolvers that are to be used by the provider. The value is a comma-separated list of resolver names. An example of
+configuration for the `files-plaintext` provider follows:
+
+[source, xml]
+----
+
+ files-plaintext
+
+
+
+
+
+
+
+----
+
+The resolvers are executed in the same order that they are declared in the configuration. For each resolver, the final entry
+name produced by the resolver that combines the realm with the vault key is used to search for the secret in the vault.
+If a secret is found, it is immediately returned. If not, the next resolver is used and this continues until a non-empty
+secret is found or all resolvers have been tried, in which case an empty secret is returned. In the example above, first
+the `REALM_UNDERSCORE_KEY` resolver is used. If an entry is found in the vault with the name it produces, it is returned.
+If not, then the `KEY_ONLY` resolver is used. Again, if an entry is found in the vault with the name it produces, it is
+returned. If not, an empty secret is returned since there are no more resolvers to be used.
+
+A list of the currently available resolvers follows:
+
+* `KEY_ONLY`: the realm name is ignored and the key from the vault expression is used as is.
+* `REALM_UNDERSCORE_KEY`: the realm and key are combined using an underscore `_` character. Occurrences of underscore in either the
+realm or key are escaped by another underscore character. So if the realm is called `master_realm` and the key is `smtp_key`, the
+combined key will be `master+++__+++realm_smtp+++__+++key`.
+* `REALM_FILESEPARATOR_KEY`: the realm and key are combined using the platform file separator character. This is useful in situations
+where the keys are grouped by realm using a directory structure.
+ifeval::[{project_community}==true]
+* `FACTORY_PROVIDED`: the realm and key are combined using the `VaultKeyResolver` that is provided by the vault provider factory,
+allowing the creation of a custom key resolver by extending an existing factory and implementing the `getFactoryResolver` method.
+endif::[]
+
+If no resolver is configured for the built-in providers, the `REALM_UNDERSCORE_KEY` is selected by default.
+
+ifeval::[{project_community}==true]
+The `FACTORY_PROVIDED` resolver provides a hook that can be used to implement a custom resolver by extending the provider
+factory of choice and overriding the `getFactoryResolver` method so it returns the custom resolver. For example, if you want
+to use the `elytron-cs-keystore` provider but none of the built-in resolvers match the format used in your keystore, you
+can extend the `ElytronCSKeystoreProvider and implement the getFactoryResolver method:
+
+[source,java]
+----
+ public class CustomElytronProviderFactory extends ElytronCSKeyStoreProviderFactory {
+ ...
+ @Override
+ protected VaultKeyResolver getFactoryResolver() {
+ return (realm, key) -> realm + "###" + key;
+ }
+
+ @Override
+ public String getId() {
+ return "custom-elytron-cs-keystore;
+ }
+
+ ...
+ }
+----
+
+The custom factory returns a key resolver that combines the realm and key with a triple `#` character. So an entry would look
+like `master_realm###smtp_key`, for example. This factory must then be installed just like any custom provider.
+
+Note that the custom factory must override both the `getFactoryResolver` and `getId` methods. The second method is needed so that
+we can properly configure the custom factory in {project_name}.
+
+To install and use the above custom provider the configuration would look something like this:
+
+[source, xml]
+----
+
+ custom-elytron-cs-keystore
+
+
+
+
+
+
+
+
+
+----
+
+The configuration above tells {project_name} to setup the custom Elytron provider and use the key resolver that is created by
+the custom factory.
+endif::[]
\ No newline at end of file
diff --git a/server_development/topics/vault.adoc b/server_development/topics/vault.adoc
index 5c544dbb61..c16fdc29da 100644
--- a/server_development/topics/vault.adoc
+++ b/server_development/topics/vault.adoc
@@ -7,15 +7,24 @@ You can use a vault SPI from `org.keycloak.vault` package to write custom extens
The built-in `files-plaintext` provider is an example of the implementation of this SPI. In general the following rules apply:
-* To prevent a secret from leaking across realms, you mey want to isolate or limit what secrets can be retrieved by a realm.
+* To prevent a secret from leaking across realms, you may want to isolate or limit the secrets that can be retrieved by a realm.
In that case, your provider should take into account the realm name when looking up secrets, for example by prefixing
- entries with the realm name. For example, an expression `${vault.secret-id}` would then evaluate generally
- to different values of a secret `secret-id`, depending on whether it was used in a realm _A_ or realm _B_.
- To differentiate between realms, the realm needs to be passed to the created `VaultProvider` instance from
- `VaultProviderFactory.create()` method where it is available from its the `KeycloakSession` parameter.
+ entries with the realm name. For example, an expression `${vault.key}` would then evaluate generally to different entry
+ names, depending on whether it was used in a realm _A_ or realm _B_. To differentiate between realms, the realm needs to
+ be passed to the created `VaultProvider` instance from `VaultProviderFactory.create()` method where it is available from the
+ `KeycloakSession` parameter.
* The vault provider needs to implement a single method `obtainSecret` that returns a `VaultRawSecret` for the given secret name. That class holds the representation of the secret either in `byte[]` or `ByteBuffer` and is expected to convert between the two upon demand. Note that this buffer would be discarded after usage as explained below.
+ifeval::[{project_community}==true]
+Regarding realm separation, all built-in vault provider factories allow the configuration of one or more key resolvers. Represented
+by the `VaultKeyResolver` interface, a key resolver essentially implements the algorithm or strategy for combining the realm name
+with the key (as obtained from the `${vault.key}` expression} into the final entry name that will be used to retrieve the
+secret from the vault. The code that handles this configuration has been extracted into abstract vault provider and vault
+provider factory classes, so custom implementations that want to offer support for key resolvers may extend these abstract classes
+instead of the implementing SPI interfaces to inherit the ability to configure the key resolvers that should be tried when retrieving a secret.
+endif::[]
+
For details on how to package and deploy a custom provider refer to the <<_providers,Service Provider Interfaces>> chapter.
=== Consuming values from vault