[KEYCLOAK-11707] Add documentation for the Elytron Credential Store provider

This commit is contained in:
Stefan Guilhen 2019-12-04 15:55:36 -03:00 committed by Hynek Mlnařík
parent 16ac876820
commit 2297644ee4
3 changed files with 185 additions and 11 deletions

View file

@ -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].
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.

View file

@ -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]
----
<spi name="vault">
<default-provider>elytron-cs-keystore</default-provider>
<provider name="elytron-cs-keystore" enabled="true">
<properties>
<property name="location" value="${jboss.home.dir}/standalone/configuration/vault/credential-store.jceks" />
<property name="secret" value="secretpw1!"/>
</properties>
</provider>
</spi>
----
If the underlying keystore has a format other than `JCEKS`, this format has to be informed using the `keyStoreType`:
[source, xml]
----
<spi name="vault">
<default-provider>elytron-cs-keystore</default-provider>
<provider name="elytron-cs-keystore" enabled="true">
<properties>
<property name="location" value="${jboss.home.dir}/standalone/configuration/vault/credential-store.p12" />
<property name="secret" value="secretpw1!"/>
<property name="keyStoreType" value="PKCS12"/>
</properties>
</provider>
</spi>
----
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]
----
<spi name="vault">
...
<property name="secret" value="MASK-3u2HNQaMogJJ8VP7J6gRIl;12345678;321"/>
...
</spi>
----
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]
----
<spi name="vault">
<default-provider>files-plaintext</default-provider>
<provider name="files-plaintext" enabled="true">
<properties>
<property name="dir" value="${jboss.home.dir}/standalone/configuration/vault/" />
<property name="keyResolvers" value="REALM_UNDERSCORE_KEY, KEY_ONLY"/>
</properties>
</provider>
</spi>
----
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]
----
<spi name="vault">
<default-provider>custom-elytron-cs-keystore</default-provider>
<provider name="custom-elytron-cs-keystore" enabled="true">
<properties>
<property name="location" value="${jboss.home.dir}/standalone/configuration/vault/credential-store.p12" />
<property name="secret" value="MASK-3u2HNQaMogJJ8VP7J6gRIl;12345678;321"/>
<property name="keyStoreType" value="PKCS12"/>
<property name="keyResolvers" value="FACTORY_PROVIDED"/>
</properties>
</provider>
</spi>
----
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::[]

View file

@ -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