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