[KEYCLOAK-11707] Add documentation for the Elytron Credential Store provider
This commit is contained in:
parent
16ac876820
commit
2297644ee4
3 changed files with 185 additions and 11 deletions
|
@ -6,4 +6,10 @@ The Drools Policy was finally removed after the deprecation period. If you need
|
||||||
|
|
||||||
== Pagination support for clients
|
== 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.
|
|
@ -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
|
To obtain a secret from a vault instead of entering it directly, enter
|
||||||
the following specially crafted string into the appropriate field:
|
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.
|
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:
|
Currently, the secret can be obtained from the vault in the following fields:
|
||||||
|
|
||||||
SMTP password::
|
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
|
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.
|
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
|
{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.
|
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"})
|
/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
|
=== Elytron Credential Store Vault Provider
|
||||||
at any given time, and the vault provider in each instance within the cluster
|
|
||||||
have to be configured consistently.
|
{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::[]
|
|
@ -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:
|
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
|
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
|
entries with the realm name. For example, an expression `${vault.key}` would then evaluate generally to different entry
|
||||||
to different values of a secret `secret-id`, depending on whether it was used in a realm _A_ or realm _B_.
|
names, depending on whether it was used in a realm _A_ or realm _B_. To differentiate between realms, the realm needs to
|
||||||
To differentiate between realms, the realm needs to be passed to the created `VaultProvider` instance from
|
be passed to the created `VaultProvider` instance from `VaultProviderFactory.create()` method where it is available from the
|
||||||
`VaultProviderFactory.create()` method where it is available from its the `KeycloakSession` parameter.
|
`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.
|
* 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.
|
For details on how to package and deploy a custom provider refer to the <<_providers,Service Provider Interfaces>> chapter.
|
||||||
|
|
||||||
=== Consuming values from vault
|
=== Consuming values from vault
|
||||||
|
|
Loading…
Reference in a new issue