keycloak-scim/server_admin/topics/vault.adoc

372 lines
17 KiB
Text

[[_vault-administration]]
== Using a vault to obtain secrets
To obtain a secret from a vault rather than entering it directly, enter the following specially crafted string into the appropriate field:
[source]
----
**${vault.**_key_**}**
----
where the `_key_` is the name of the secret recognized by the vault.
To prevent secrets from leaking across realms, {project_name} combines the realm name with the `_key_` obtained from the vault expression. This method means that the `_key_` does not directly map to an entry in the vault but creates the final entry name according to the algorithm used to combine the `_key_` with the realm name.
You can obtain the secret from the vault in the following fields:
SMTP password::
In the realm <<_email,SMTP settings>>
LDAP bind credential::
In the <<_ldap,LDAP settings>> of LDAP-based user federation.
OIDC identity provider secret::
In the _Client Secret_ inside identity provider <<_identity_broker_oidc,OpenID Connect Config>>
To use a vault, register a vault provider in {project_name}. You can use the providers described <<_providers, here>> or implement your provider. See the link:{developerguide_link}[{developerguide_name}] for more information.
[NOTE]
====
{project_name} permits a maximum of one active vault provider per {project_name} instance at a time. Configure the vault provider in each instance within the cluster consistently.
====
[[_providers]]
=== Kubernetes / OpenShift files plain-text vault provider
{project_name} supports vault implementation for https://kubernetes.io/docs/concepts/configuration/secret/[Kubernetes secrets]. You can mount Kubernetes secrets as data volumes, and they appear as a directory with a flat-file structure. {project_name} represents each secret as a file with the file's name as the secret name and the file's contents as the secret value.
You must name the files within this directory as the secret name prefixed by the realm name and an underscore. Double all underscores within the secret name or the realm name in the file name. For example, for a field within a realm named `sso_realm`, a reference to a secret with the name `secret-name` would be written as `${vault.secret-name}`, and the file name looked up would be `sso+++__+++realm+++_+++secret-name`. Note the underscore doubled in realm name.
To use this type of secret store, you must declare the `files-plaintext` vault provider in the standalone.xml file and set its parameter for the directory containing the mounted volume. This example shows the `files-plaintext` provider with the directory where vault files are searched set to `standalone/configuration/vault` relative to the {project_name} base directory:
[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/" />
</properties>
</provider>
</spi>
----
Here is the equivalent configuration using CLI commands:
[source,bash]
----
/subsystem=keycloak-server/spi=vault/:add
/subsystem=keycloak-server/spi=vault/provider=files-plaintext/:add(enabled=true,properties={dir => "${jboss.home.dir}/standalone/configuration/vault"})
----
=== 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 can retrieve secrets from the credential store's keystore based implementation, which is also the default implementation Elytron provides.
A keystore backs this credential store. `JCEKS` is the default format, but you can use other formats such as `PKCS12`. Users can create and manage the store contents using the `elytron` subsystem in WildFly/JBoss EAP, or the `elytron-tool.sh` script.
To use this provider, you must declare the `elytron-cs-keystore` in the `keycloak-server` subsystem and set the location and master secret of the keystore 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 different from `JCEKS`, you must specify this format by 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 clear-text values and masked values by using the `elytron-tool.sh` script:
[source, xml]
----
<spi name="vault">
...
<property name="secret" value="MASK-3u2HNQaMogJJ8VP7J6gRIl;12345678;321"/>
...
</spi>
----
For more information about creating and managing elytron credential stores and masking keystore secrets, see the Elytron documentation.
[NOTE]
====
{project_name} implements the `elytron-cs-keystore` vault provider as a WildFly extension and is available if the {project_name} server runs on WildFly/JBoss EAP only.
====
=== Key resolvers
All built-in providers support the configuration of key resolvers. A key resolver implements the algorithm or strategy for combining the realm name with the key, obtained from the `${vault.key}` expression, into the final entry name used to retrieve the secret from the vault. {project_name} uses the `keyResolvers` property to configure the resolvers that the provider uses. The value is a comma-separated list of resolver names. An example of the 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 run in the same order you declare them in the configuration. For each resolver, {project_name} uses the last entry name the resolver produces, which combines the realm with the vault key to search for the vault's secret. If {project_name} finds a secret, it returns the secret. If not, {project_name} uses the next resolver. This search continues until {project_name} finds a non-empty secret or runs out of resolvers. If {project_name} finds no secret, {project_name} returns an empty secret.
In the previous example, {project_name} uses the `REALM_UNDERSCORE_KEY` resolver first. If {project_name} finds an entry in the vault that using that resolver, {project_name} returns that entry. If not, {project_name} searches again using the `KEY_ONLY` resolver. If {project_name} finds an entry by using the `KEY_ONLY` resolver, {project_name} returns that entry. If {project_name} uses all resolvers, {project_name} returns an empty secret.
A list of the currently available resolvers follows:
|===
|Name |Description
| KEY_ONLY
| {project_name} ignores the realm name and uses the key from the vault expression.
| REALM_UNDERSCORE_KEY
| {project_name} combines the realm and key by using an underscore character. {project_name} escapes occurrences of underscores in the realm or key with another underscore character. For example, if the realm is called `master_realm` and the key is `smtp_key`, the combined key is `master+++__+++realm_smtp+++__+++key`.
| REALM_FILESEPARATOR_KEY
| {project_name} combines the realm and key by using the platform file separator character.
ifeval::[{project_community}==true]
| FACTORY_PROVIDED
| {project_name} combines the realm and key by using the vault provider factory's `VaultKeyResolver`, allowing the creation of a custom key resolver by extending an existing factory and implementing the `getFactoryResolver` method.
endif::[]
|===
If you have not configured a resolver for the built-in providers, {project_name} selects the `REALM_UNDERSCORE_KEY`.
ifeval::[{project_community}==true]
The `FACTORY_PROVIDED` resolver provides a hook that you can use 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 the built-in resolvers do not 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. For example, an entry would be `master_realm###smtp_key`. Install this factory like any custom provider.
[NOTE]
====
The custom factory must override both the `getFactoryResolver` and `getId` methods. The second method is necessary so that you can properly configure the custom factory in {project_name}.
====
To install and use the previous custom provider, the configuration would look similar to 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>
----
This configuration makes {project_name} set up the custom Elytron provider and use the key resolver that the custom factory creates.
endif::[]
=== Sample Configuration
The following is an example of configuring a vault and credential store. The procedure involves two parts:
* Creating the credential store and a vault, where the credential store and vault passwords are in plain text.
* Updating the credential store and vault to have the password use a mask provided by `elytron-tool.sh`.
In this example, the test target used is an LDAP instance with `BIND DN credential: secret12`. The target is mapped using user federation in the realm `ldaptest`.
==== Configuring the credential store and vault without a mask
You create the credential store and a vault where the credential store and vault passwords are in plain text.
.Prerequisites
* A running LDAP instance has `BIND DN credential: secret12`.
* The alias uses the format <realm-name>_< key-value> when using the default key resolver. In this case, the instance is running in the realm `ldaptest` and `ldaptest_ldap_secret` is the alias that corresponds to the value `ldap_secret` in that realm.
NOTE: The resolver replaces underscore characters with double underscore characters in the realm and key names. For example, for the key `ldaptest_ldap_secret`, the final key will be `ldaptest_ldap__secret`.
.Procedure
. Create the Elytron credential store.
+
[source,bash,subs=+attributes]
----
[standalone@localhost:9990 /] /subsystem=elytron/credential-store=test-store:add(create=true, location=/home/test/test-store.p12, credential-reference={clear-text=testpwd1!},implementation-properties={keyStoreType=PKCS12})
----
. Add an alias to the credential store.
+
[source,bash,subs=+attributes]
----
/subsystem=elytron/credential-store=test-store:add-alias(alias=ldaptest_ldap__secret,secret-value=secret12)
----
+
Notice how the resolver causes the key `ldaptest_ldap__secret` to use double underscores.
. List the aliases from the credential store to inspect the contents of the keystore that is produced by Elytron.
+
[source,bash,subs=+attributes]
----
keytool -list -keystore /home/test/test-store.p12 -storetype PKCS12 -storepass testpwd1!
Keystore type: PKCS12
Keystore provider: SUN
Your keystore contains 1 entries
ldaptest_ldap__secret/passwordcredential/clear/, Oct 12, 2020, SecretKeyEntry,
----
. Configure the vault SPI in {project_name}.
+
[source,bash,subs=+attributes]
----
/subsystem=keycloak-server/spi=vault:add(default-provider=elytron-cs-keystore)
/subsystem=keycloak-server/spi=vault/provider=elytron-cs-keystore:add(enabled=true, properties={location=>/home/test/test-store.p12, secret=>testpwd1!, keyStoreType=>PKCS12})
----
+
At this point, the vault and credentials store passwords are not masked.
+
[source,bash,subs=+attributes]
----
<spi name="vault">
<default-provider>elytron-cs-keystore</default-provider>
<provider name="elytron-cs-keystore" enabled="true">
<properties>
<property name="location" value="/home/test/test-store.p12"/>
<property name="secret" value="testpwd1!"/>
<property name="keyStoreType" value="PKCS12"/>
</properties>
</provider>
</spi>
<credential-stores>
<credential-store name="test-store" location="/home/test/test-store.p12" create="true">
<implementation-properties>
<property name="keyStoreType" value="PKCS12"/>
</implementation-properties>
<credential-reference clear-text="testpwd1!"/>
</credential-store>
</credential-stores>
----
. In the LDAP provider, replace `binDN credential` with `${vault.ldap_secret}`.
. Test your LDAP connection.
+
.LDAP Vault
image:images/ldap-vault.png[LDAP Vault]
==== Masking the password in the credential store and vault
You can now update the credential store and vault to have passwords that use a mask provided by `elytron-tool.sh`.
. Create a masked password using values for the `salt` and the `iteration` parameters:
+
[source,bash,subs=+attributes]
----
$ EAP_HOME/bin/elytron-tool.sh mask --salt SALT --iteration ITERATION_COUNT --secret PASSWORD
----
+
For example:
+
[source,bash,subs=+attributes]
----
elytron-tool.sh mask --salt 12345678 --iteration 123 --secret testpwd1!
MASK-3BUbFEyWu0lRAu8.fCqyUk;12345678;123
----
. Update the Elytron credential store configuration to use the masked password.
+
[source,bash,subs=+attributes]
----
/subsystem=elytron/credential-store=cs-store:write-attribute(name=credential-reference.clear-text,value="MASK-3BUbFEyWu0lRAu8.fCqyUk;12345678;123")
----
. Update the {project_name} vault configuration to use the masked password.
+
[source,bash,subs=+attributes]
----
/subsystem=keycloak-server/spi=vault/provider=elytron-cs-keystore:remove()
/subsystem=keycloak-server/spi=vault/provider=elytron-cs-keystore:add(enabled=true, properties={location=>/home/test/test-store.p12, secret=>”MASK-3BUbFEyWu0lRAu8.fCqyUk;12345678;123”, keyStoreType=>PKCS12})
----
+
The vault and credential store are now masked:
+
[source,bash,subs=+attributes]
----
<spi name="vault">
<default-provider>elytron-cs-keystore</default-provider>
<provider name="elytron-cs-keystore" enabled="true">
<properties>
<property name="location" value="/home/test/test-store.p12"/>
<property name="secret" value="MASK-3BUbFEyWu0lRAu8.fCqyUk;12345678;123"/>
<property name="keyStoreType" value="PKCS12"/>
</properties>
</provider>
</spi>
....
.....
<credential-stores>
<credential-store name="test-store" location="/home/test/test-store.p12" create="true">
<implementation-properties>
<property name="keyStoreType" value="PKCS12"/>
</implementation-properties>
<credential-reference clear-text="MASK-3BUbFEyWu0lRAu8.fCqyUk;12345678;123"/>
</credential-store>
</credential-stores>
----
. You can now test the connection to the LDAP using `${vault.ldap_secret}`.
[role="_additional-resources"]
.Additional resources
For more information about the Elytron tool, see link:https://access.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform/7.3/html/how_to_configure_server_security/securely_storing_credentials#cred_store_elytron_client[Using Credential Stores with Elytron Client].