From 72b238fb489aa9da257a5896772d6efde4a0f7d9 Mon Sep 17 00:00:00 2001 From: Peter Zaoral Date: Wed, 24 May 2023 18:20:30 +0200 Subject: [PATCH] Keystore vault (#19644) * KeystoreVault SPI * added KeystoreVault - a Vault SPI implementation (#19281) Closes #17252 Signed-off-by: Peter Zaoral --- .../server_admin/topics/vault.adoc | 4 +- docs/guides/server/vault.adoc | 45 +++++++++-- .../org/keycloak/config/VaultOptions.java | 37 +++++++-- .../quarkus/deployment/KeycloakProcessor.java | 2 + .../mappers/VaultPropertyMappers.java | 12 +++ .../FilesKeystoreVaultProviderFactory.java | 43 +++++++++++ .../org.keycloak.vault.VaultProviderFactory | 1 + .../configuration/test/ConfigurationTest.java | 16 +++- ...ndDistTest.testBuildHelp.unix.approved.txt | 2 +- ...istTest.testStartDevHelp.unix.approved.txt | 5 +- ...Test.testStartDevHelpAll.unix.approved.txt | 5 +- ...ndDistTest.testStartHelp.unix.approved.txt | 5 +- ...istTest.testStartHelpAll.unix.approved.txt | 5 +- ...t.testStartOptimizedHelp.unix.approved.txt | 3 + ...estStartOptimizedHelpAll.unix.approved.txt | 3 + .../vault/FilesKeystoreVaultProvider.java | 71 ++++++++++++++++++ .../FilesKeystoreVaultProviderFactory.java | 70 +++++++++++++++++ .../org.keycloak.vault.VaultProviderFactory | 3 +- .../vault/KeystoreVaultProviderTest.java | 69 +++++++++++++++++ .../test/resources/org/keycloak/vault/myks | Bin 0 -> 396 bytes .../resources/org/keycloak/vault/myks.jceks | Bin 0 -> 641 bytes .../servers/auth-server/common/vault/myks | Bin 0 -> 706 bytes .../src/main/content/conf/keycloak.conf | 4 - .../arquillian/VaultTestExecutionDecider.java | 10 --- .../arquillian/annotation/EnableVault.java | 41 +--------- .../keycloak/testsuite/util/VaultUtils.java | 18 +++++ ...st.java => AbstractKeycloakVaultTest.java} | 56 ++++++-------- ...st.java => KeycloakKeystoreVaultTest.java} | 17 +++-- .../vault/KeycloakPlaintextVaultTest.java | 42 +++++++++++ .../resources/META-INF/keycloak-server.json | 6 ++ .../tests/base/src/test/resources/vault/myks | Bin 0 -> 706 bytes 31 files changed, 483 insertions(+), 112 deletions(-) create mode 100644 quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/vault/FilesKeystoreVaultProviderFactory.java create mode 100644 services/src/main/java/org/keycloak/vault/FilesKeystoreVaultProvider.java create mode 100644 services/src/main/java/org/keycloak/vault/FilesKeystoreVaultProviderFactory.java create mode 100644 services/src/test/java/org/keycloak/vault/KeystoreVaultProviderTest.java create mode 100644 services/src/test/resources/org/keycloak/vault/myks create mode 100644 services/src/test/resources/org/keycloak/vault/myks.jceks create mode 100644 testsuite/integration-arquillian/servers/auth-server/common/vault/myks rename testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/{KeycloakVaultTest.java => AbstractKeycloakVaultTest.java} (55%) rename testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/{KeycloakElytronCSVaultTest.java => KeycloakKeystoreVaultTest.java} (57%) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakPlaintextVaultTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/vault/myks diff --git a/docs/documentation/server_admin/topics/vault.adoc b/docs/documentation/server_admin/topics/vault.adoc index 29b6b28886..2a0b0fccc4 100644 --- a/docs/documentation/server_admin/topics/vault.adoc +++ b/docs/documentation/server_admin/topics/vault.adoc @@ -3,6 +3,8 @@ == Using a vault to obtain secrets +Keycloak currently provides two out-of-the-box implementations of the Vault SPI: a plain-text file-based vault and Java KeyStore-based vault. + To obtain a secret from a vault rather than entering it directly, enter the following specially crafted string into the appropriate field: [source] @@ -11,7 +13,7 @@ ${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. +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. In case of the file-based vault, such combination reflects to a specific filename, for the Java KeyStore-based vault it's a specific alias name. You can obtain the secret from the vault in the following fields: diff --git a/docs/guides/server/vault.adoc b/docs/guides/server/vault.adoc index 5d6e3b7329..04ac325db2 100644 --- a/docs/guides/server/vault.adoc +++ b/docs/guides/server/vault.adoc @@ -2,31 +2,41 @@ <#import "/templates/kc.adoc" as kc> <@tmpl.guide -title="Using Kubernetes secrets" -summary="Learn how to use Kubernetes/OpenShift secrets in Keycloak" +title="Using a vault" +summary="Learn how to use and configure a vault in Keycloak" priority=30 includedOptions="vault vault-*"> -Keycloak supports a file-based vault implementation for Kubernetes/OpenShift secrets. Mount Kubernetes secrets into the Keycloak Container, and the data fields will be available in the mounted folder with a flat-file structure. +Keycloak provides two out-of-the-box implementations of the Vault SPI: a plain-text file-based vault and Java KeyStore-based vault. + +The file-based vault implementation is especially useful for Kubernetes/OpenShift secrets. You can mount Kubernetes secrets into the Keycloak Container, and the data fields will be available in the mounted folder with a flat-file structure. + +The Java KeyStore-based vault implementation is useful for storing secrets in bare metal installations. You can use the KeyStore vault, which is encrypted using a password. == Available integrations -You can use Kubernetes/OpenShift secrets for the following purposes: +Secrets stored in the vaults can be used at the following places of the Administration Console: * Obtain the SMTP Mail server Password * Obtain the LDAP Bind Credential when using LDAP-based User Federation * Obtain the OIDC identity providers Client Secret when integrating external identity providers -== Enabling the vault -Enable the file based vault by building Keycloak using the following build option: +== Enabling a vault +For enabling the file-based vault you need to build Keycloak first using the following build option: <@kc.build parameters="--vault=file"/> -== Setting the base directory to lookup secrets +Analogically, for the Java KeyStore-based you need to specify the following build option: + +<@kc.build parameters="--vault=keystore"/> + +== Configuring the file-based vault + +=== Setting the base directory to lookup secrets Kubernetes/OpenShift secrets are basically mounted files. To configure a directory where these files should be mounted, enter this command: <@kc.start parameters="--vault-dir=/my/path"/> -== Realm-specific secret files +=== Realm-specific secret files Kubernetes/OpenShift Secrets are used on a per-realm basis in Keycloak, which requires a naming convention for the file in place: [source, bash] ---- @@ -46,6 +56,25 @@ sso__realm_ldap__credential ---- Note the doubled underscores between __sso__ and __realm__ and also between __ldap__ and __credential__. +== Configuring the Java KeyStore-based vault + +In order to use the Java KeyStore-based vault, you need to create a KeyStore file first. You can use the following command for doing so: +[source, bash] +---- +keytool -importpass -alias _ -keystore keystore.p12 -storepass keystorepassword +---- +and then enter a value you want to store in the vault. Note that the format of the `-alias` parameter depends on the key resolver used. The default key resolver is `REALM_UNDERSCORE_KEY`. + +This by default results to storing the value in a form of generic PBEKey (password based encryption) within SecretKeyEntry. + +You can then start Keycloak using the following runtime options: + +<@kc.start parameters=" --vault-file=/path/to/keystore.p12 --vault-pass= --vault-type="/> + +Note that the `--vault-type` parameter is optional and defaults to `PKCS12`. + +Secrets stored in the vault can then be accessed in a realm via the following placeholder (assuming using the `REALM_UNDERSCORE_KEY` key resolver): `${r"${vault.realm-name_alias}"}`. + == Example: Use an LDAP bind credential secret in the Admin Console .Example setup diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/VaultOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/VaultOptions.java index ca1b33dd87..6e0044aa36 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/VaultOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/VaultOptions.java @@ -1,22 +1,49 @@ package org.keycloak.config; -import java.io.File; +import java.nio.file.Path; public class VaultOptions { - public enum Provider { - file; + public enum VaultType { + + file("file"), + keystore("keystore"); + + private final String provider; + + VaultType(String provider) { + this.provider = provider; + } + + public String getProvider() { + return provider; + } } - public static final Option VAULT = new OptionBuilder<>("vault", Provider.class) + public static final Option VAULT = new OptionBuilder<>("vault", VaultType.class) .category(OptionCategory.VAULT) .description("Enables a vault provider.") .buildTime(true) .build(); - public static final Option VAULT_DIR = new OptionBuilder<>("vault-dir", File.class) + public static final Option VAULT_DIR = new OptionBuilder<>("vault-dir", Path.class) .category(OptionCategory.VAULT) .description("If set, secrets can be obtained by reading the content of files within the given directory.") .build(); + public static final Option VAULT_PASS = new OptionBuilder<>("vault-pass", String.class) + .category(OptionCategory.VAULT) + .description("Password for the vault keystore.") + .build(); + + public static final Option VAULT_FILE = new OptionBuilder<>("vault-file", Path.class) + .category(OptionCategory.VAULT) + .description("Path to the keystore file.") + .build(); + + public static final Option VAULT_TYPE = new OptionBuilder<>("vault-type", String.class) + .category(OptionCategory.VAULT) + .description("Specifies the type of the keystore file.") + .defaultValue("PKCS12") + .build(); } diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index bc065b6ff6..dc6ec56db7 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -110,6 +110,7 @@ import org.keycloak.url.DefaultHostnameProviderFactory; import org.keycloak.url.FixedHostnameProviderFactory; import org.keycloak.url.RequestHostnameProviderFactory; import org.keycloak.util.JsonSerialization; +import org.keycloak.vault.FilesKeystoreVaultProviderFactory; import org.keycloak.vault.FilesPlainTextVaultProviderFactory; import jakarta.persistence.Entity; @@ -176,6 +177,7 @@ class KeycloakProcessor { DefaultHostnameProviderFactory.class, FixedHostnameProviderFactory.class, RequestHostnameProviderFactory.class, + FilesKeystoreVaultProviderFactory.class, FilesPlainTextVaultProviderFactory.class, BlacklistPasswordPolicyProviderFactory.class, ClasspathThemeResourceProviderFactory.class, diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/VaultPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/VaultPropertyMappers.java index a5bb140675..78e60d2558 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/VaultPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/VaultPropertyMappers.java @@ -17,6 +17,18 @@ final class VaultPropertyMappers { fromOption(VaultOptions.VAULT_DIR) .to("kc.spi-vault-file-dir") .paramLabel("dir") + .build(), + fromOption(VaultOptions.VAULT_FILE) + .to("kc.spi-vault-keystore-file") + .paramLabel("file") + .build(), + fromOption(VaultOptions.VAULT_PASS) + .to("kc.spi-vault-keystore-pass") + .paramLabel("pass") + .build(), + fromOption(VaultOptions.VAULT_TYPE) + .to("kc.spi-vault-keystore-type") + .paramLabel("type") .build() }; } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/vault/FilesKeystoreVaultProviderFactory.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/vault/FilesKeystoreVaultProviderFactory.java new file mode 100644 index 0000000000..666a4dd637 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/vault/FilesKeystoreVaultProviderFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.quarkus.runtime.vault; + +import org.keycloak.Config; +import org.keycloak.provider.EnvironmentDependentProviderFactory; +import org.keycloak.quarkus.runtime.configuration.Configuration; + +public class FilesKeystoreVaultProviderFactory extends org.keycloak.vault.FilesKeystoreVaultProviderFactory + implements EnvironmentDependentProviderFactory { + + public static final String ID = "keystore"; + + @Override + public String getId() { + return ID; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public boolean isSupported(Config.Scope config) { + return getId().equals(Configuration.getRawValue("kc.vault")); + } +} diff --git a/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.vault.VaultProviderFactory b/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.vault.VaultProviderFactory index c1f56e0ae5..03067cbf3a 100644 --- a/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.vault.VaultProviderFactory +++ b/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.vault.VaultProviderFactory @@ -1 +1,2 @@ org.keycloak.quarkus.runtime.vault.FilesPlainTextVaultProviderFactory +org.keycloak.quarkus.runtime.vault.FilesKeystoreVaultProviderFactory diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java index 48de36cd8a..7105966544 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java @@ -28,10 +28,11 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigProviderResolver; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.PostgreSQLDialect; import io.quarkus.runtime.LaunchMode; -import io.smallrye.config.SmallRyeConfig; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.hibernate.dialect.MariaDBDialect; @@ -44,8 +45,8 @@ import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider; import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import io.quarkus.runtime.configuration.ConfigUtils; -import io.smallrye.config.SmallRyeConfigProviderResolver; import org.keycloak.quarkus.runtime.Environment; +import org.keycloak.quarkus.runtime.vault.FilesKeystoreVaultProviderFactory; import org.keycloak.quarkus.runtime.vault.FilesPlainTextVaultProviderFactory; import org.mariadb.jdbc.MariaDbDataSource; import org.postgresql.xa.PGXADataSource; @@ -142,6 +143,12 @@ public class ConfigurationTest { assertEquals("/foo/bar", config.get("dir")); assertTrue(config.getPropertyNames() .contains("kc.spi-vault-".concat(FilesPlainTextVaultProviderFactory.ID).concat("-dir"))); + + putEnvVar("KC_VAULT_TYPE", "JKS"); + config = initConfig("vault", FilesKeystoreVaultProviderFactory.ID); + assertEquals("JKS", config.get("type")); + assertTrue(config.getPropertyNames() + .contains("kc.spi-vault-".concat(FilesKeystoreVaultProviderFactory.ID).concat("-type"))); } @Test @@ -215,6 +222,11 @@ public class ConfigurationTest { assertEquals(1, config.getPropertyNames().size()); assertEquals("secrets", config.get("dir")); + System.setProperty(CLI_ARGS, "--vault-type=JKS"); + config = initConfig("vault", FilesKeystoreVaultProviderFactory.ID); + assertEquals(1, config.getPropertyNames().size()); + assertEquals("JKS", config.get("type")); + System.getProperties().remove(CLI_ARGS); System.setProperty("kc.spi-client-registration-openid-connect-static-jwk-url", "http://c.jwk.url"); config = initConfig("client-registration", "openid-connect"); diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt index 1de5463096..4b3ce5d13b 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt @@ -80,7 +80,7 @@ Metrics: Vault: ---vault Enables a vault provider. Possible values are: file. +--vault Enables a vault provider. Possible values are: file, keystore. Security: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt index 08ede9ce0d..1143453ae6 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt @@ -173,9 +173,12 @@ Proxy: Vault: ---vault Enables a vault provider. Possible values are: file. +--vault Enables a vault provider. Possible values are: file, keystore. --vault-dir If set, secrets can be obtained by reading the content of files within the given directory. +--vault-file Path to the keystore file. +--vault-pass Password for the vault keystore. +--vault-type Specifies the type of the keystore file. Default: PKCS12. Logging: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt index b0b7dc70f4..de64d0274a 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt @@ -236,9 +236,12 @@ Proxy: Vault: ---vault Enables a vault provider. Possible values are: file. +--vault Enables a vault provider. Possible values are: file, keystore. --vault-dir If set, secrets can be obtained by reading the content of files within the given directory. +--vault-file Path to the keystore file. +--vault-pass Password for the vault keystore. +--vault-type Specifies the type of the keystore file. Default: PKCS12. Logging: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt index ebb0c4e487..c5fd1643d1 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt @@ -179,9 +179,12 @@ Proxy: Vault: ---vault Enables a vault provider. Possible values are: file. +--vault Enables a vault provider. Possible values are: file, keystore. --vault-dir If set, secrets can be obtained by reading the content of files within the given directory. +--vault-file Path to the keystore file. +--vault-pass Password for the vault keystore. +--vault-type Specifies the type of the keystore file. Default: PKCS12. Logging: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt index 8398cb47fd..9e66573959 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt @@ -242,9 +242,12 @@ Proxy: Vault: ---vault Enables a vault provider. Possible values are: file. +--vault Enables a vault provider. Possible values are: file, keystore. --vault-dir If set, secrets can be obtained by reading the content of files within the given directory. +--vault-file Path to the keystore file. +--vault-pass Password for the vault keystore. +--vault-type Specifies the type of the keystore file. Default: PKCS12. Logging: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt index 641048ec19..c3f3918748 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt @@ -125,6 +125,9 @@ Vault: --vault-dir If set, secrets can be obtained by reading the content of files within the given directory. +--vault-file Path to the keystore file. +--vault-pass Password for the vault keystore. +--vault-type Specifies the type of the keystore file. Default: PKCS12. Logging: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt index b270d92928..fbb9a44945 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt @@ -144,6 +144,9 @@ Vault: --vault-dir If set, secrets can be obtained by reading the content of files within the given directory. +--vault-file Path to the keystore file. +--vault-pass Password for the vault keystore. +--vault-type Specifies the type of the keystore file. Default: PKCS12. Logging: diff --git a/services/src/main/java/org/keycloak/vault/FilesKeystoreVaultProvider.java b/services/src/main/java/org/keycloak/vault/FilesKeystoreVaultProvider.java new file mode 100644 index 0000000000..3c9fc9fa81 --- /dev/null +++ b/services/src/main/java/org/keycloak/vault/FilesKeystoreVaultProvider.java @@ -0,0 +1,71 @@ +package org.keycloak.vault; + +import java.nio.ByteBuffer; + +import org.jboss.logging.Logger; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableEntryException; +import java.security.cert.CertificateException; +import java.util.List; +import java.util.Optional; + +public class FilesKeystoreVaultProvider extends AbstractVaultProvider { + + private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass()); + + private final Path keystorePath; + private final String keystorePass; + private final String keystoreType; + + /** + * Creates a new {@link FilesKeystoreVaultProvider}. + * + * @param keystorePath A path to a vault. Can not be null. + * @param keystorePass A password to a vault. Can not be null. + * @param keystoreType Specifies a type of keystore. Can not be null. Default value is PKCS12. + * @param realmName A realm name. Can not be null. + */ + public FilesKeystoreVaultProvider(@Nonnull Path keystorePath, @Nonnull String keystorePass, @Nonnull String keystoreType, + @Nonnull String realmName, @Nonnull List resolvers) { + super(realmName, resolvers); + this.keystorePath = keystorePath; + this.keystorePass = keystorePass; + this.keystoreType = keystoreType; + logger.debugf("KeystoreVaultProvider will operate in %s directory", keystorePath.toAbsolutePath()); + } + + @Override + protected VaultRawSecret obtainSecretInternal(String alias) { + KeyStore ks; + Key key; + try { + if (!Files.exists(keystorePath.toRealPath())) { + throw new VaultNotFoundException("The keystore file for Keycloak Vault was not found"); + } + ks = KeyStore.getInstance(keystoreType); + ks.load(Files.newInputStream(keystorePath.toRealPath()), keystorePass.toCharArray()); + key = ks.getKey(alias, keystorePass.toCharArray()); + if (key == null) { + logger.warnf("Cannot find secret %s in %s", alias, keystorePath); + return DefaultVaultRawSecret.forBuffer(Optional.empty()); + } + } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableEntryException e) { + throw new RuntimeException(e); + } + return DefaultVaultRawSecret.forBuffer(Optional.of(ByteBuffer.wrap(new String(key.getEncoded()).getBytes()))); + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/vault/FilesKeystoreVaultProviderFactory.java b/services/src/main/java/org/keycloak/vault/FilesKeystoreVaultProviderFactory.java new file mode 100644 index 0000000000..f1b9d9f310 --- /dev/null +++ b/services/src/main/java/org/keycloak/vault/FilesKeystoreVaultProviderFactory.java @@ -0,0 +1,70 @@ +package org.keycloak.vault; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FilesKeystoreVaultProviderFactory extends AbstractVaultProviderFactory { + + private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass()); + + public static final String PROVIDER_ID = "files-keystore"; + + private Path keystoreFile; + private String keystorePass; + private String keystoreType; + + @Override + public VaultProvider create(KeycloakSession session) { + if (keystoreFile == null) { + logger.debug("Can not create a vault since it's not initialized correctly"); + return null; + } + return new FilesKeystoreVaultProvider(keystoreFile, keystorePass, keystoreType, getRealmName(session), super.keyResolvers); + } + + @Override + public void init(Config.Scope config) { + super.init(config); + + String pathConfigProperty = config.get("file"); + if (pathConfigProperty == null) { + logger.debug("Path to the vault keystore is not configured"); + return; + } + keystoreFile = Paths.get(pathConfigProperty); + if (!Files.exists(keystoreFile)) { + throw new VaultNotFoundException("The vault does not exist on the path " + keystoreFile.toAbsolutePath()); + } + + keystorePass = config.get("pass"); + if (keystorePass == null) { + logger.debug("Password for the vault keystore is not configured"); + return; + } + + keystoreType = config.get("type", "PKCS12"); + logger.debugf("A type of the provided keystore is %s", keystoreType); + + logger.debugf("Configured KeystoreVaultProviderFactory with the keystore file located in %s", keystoreFile.toString()); + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/services/src/main/resources/META-INF/services/org.keycloak.vault.VaultProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.vault.VaultProviderFactory index 4977fb5de4..3ab716921b 100644 --- a/services/src/main/resources/META-INF/services/org.keycloak.vault.VaultProviderFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.vault.VaultProviderFactory @@ -1 +1,2 @@ -org.keycloak.vault.FilesPlainTextVaultProviderFactory \ No newline at end of file +org.keycloak.vault.FilesPlainTextVaultProviderFactory +org.keycloak.vault.FilesKeystoreVaultProviderFactory \ No newline at end of file diff --git a/services/src/test/java/org/keycloak/vault/KeystoreVaultProviderTest.java b/services/src/test/java/org/keycloak/vault/KeystoreVaultProviderTest.java new file mode 100644 index 0000000000..fee70accb1 --- /dev/null +++ b/services/src/test/java/org/keycloak/vault/KeystoreVaultProviderTest.java @@ -0,0 +1,69 @@ +package org.keycloak.vault; + +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.common.util.Environment; + +import java.nio.file.Paths; +import java.util.Arrays; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertThrows; +import static org.keycloak.vault.SecretContains.secretContains; + +/** + * Tests for {@link FilesKeystoreVaultProvider}. + * + * @author Peter Zaoral + */ +public class KeystoreVaultProviderTest { + + @Before + public void before() { + // TODO: improve when the supported keystore types for FIPS will be unified across the codebase + Assume.assumeFalse("Java is in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode()); + } + + @Test + public void shouldObtainSecret() { + //given + // keytool -importpass -storetype pkcs12 -alias test_alias -keystore myks -storepass keystorepassword + VaultProvider provider = new FilesKeystoreVaultProvider(Paths.get(Scenario.EXISTING.getAbsolutePathAsString() + "/myks"), "keystorepassword", "PKCS12","test", + Arrays.asList(AbstractVaultProviderFactory.AvailableResolvers.REALM_UNDERSCORE_KEY.getVaultKeyResolver())); + + //when + VaultRawSecret secret1 = provider.obtainSecret("alias"); + + //then + assertNotNull(secret1); + assertNotNull(secret1.get().get()); + assertThat(secret1, secretContains("topsecret")); + } + + @Test + public void shouldObtainSecretFromDifferentKeystoreType() { + //given + VaultProvider provider = new FilesKeystoreVaultProvider(Paths.get(Scenario.EXISTING.getAbsolutePathAsString() + "/myks.jceks"), "keystorepassword", "JCEKS", "test", + Arrays.asList(AbstractVaultProviderFactory.AvailableResolvers.REALM_UNDERSCORE_KEY.getVaultKeyResolver())); + + //when + VaultRawSecret secret1 = provider.obtainSecret("alias"); + + //then + assertNotNull(secret1); + assertNotNull(secret1.get().get()); + assertThat(secret1, secretContains("topsecret")); + } + + @Test + public void shouldFailBecauseOfTypeMismatch() { + //given + VaultProvider provider = new FilesKeystoreVaultProvider(Paths.get(Scenario.EXISTING.getAbsolutePathAsString() + "/myks"), "keystorepassword", "JCEKS", "test", + Arrays.asList(AbstractVaultProviderFactory.AvailableResolvers.REALM_UNDERSCORE_KEY.getVaultKeyResolver())); + + //when + assertThrows("java.io.IOException: Invalid keystore format", RuntimeException.class, () -> provider.obtainSecret("alias")); + } +} \ No newline at end of file diff --git a/services/src/test/resources/org/keycloak/vault/myks b/services/src/test/resources/org/keycloak/vault/myks new file mode 100644 index 0000000000000000000000000000000000000000..1e933dad548a8c94121899c73b730fe2c2f299fb GIT binary patch literal 396 zcmXqLV(egIWHxAGG-Bh_YV&CO&dbQoxS)wqnWc$Q9w;mg#Nwz@96%`+pfH0$<6kyz zs3smRM%D$5iwznVAPX@qXq>>(*lW<(Wsn9pgq7DI(!c`2`j3TqD zX8MeZ^5R$7@{5_6)CHIr6%2UTI3Tt#ak4TP$g*)Jw0SV5GP5vhu?RF<%+Wk_N7wkA zBBSt5#-BG?6twCXZ+^-@B2Q!ZQ;4 zF0n0KeCmwvseg$V6!)-(-GAfnv29-8CN@{CXNET?EH~TVu39gZ9DS-i==hZNVkNVa zjYB7z9G9?s^L?#gSY-U#9gICYHAiHvEhd<4Kvc_UZvI z)~VO}J}o&|n`3+{rC*M1^|viw`oEUNY^vNeb3zG&Sb&pjcxFk4uZyW;UP?$&W ei)%1WiEj0sVuw0aKAzZ}?EYm=pzQ{xwln}YHvxbE literal 0 HcmV?d00001 diff --git a/testsuite/integration-arquillian/servers/auth-server/common/vault/myks b/testsuite/integration-arquillian/servers/auth-server/common/vault/myks new file mode 100644 index 0000000000000000000000000000000000000000..a2eaf70613cf551f18c87e303b6102af178843c8 GIT binary patch literal 706 zcmXqLV%o>V$ZXKWl)=WS)#lOmotKfFaX}MPBuf)hC{Q>Mh<#C|?0`~MKw&e3CPqm% zZm2FEE=JY`jhhV`Hy{f!EofZ8(m2b zPkj4u{@u&a4-d@`GclSLWiXIs<4kDtU`%CZVbo#~=zEjwc5&IQ zp2$`HVJ=Zi@3R;zk_`JAJgF^1WPAPMdL^Uvr)7>Ta@)drcxr?m7styfR`J>k+BdSC zzG<+nb@%quY+DUO4D{iS;S@1cl2c&FWk_TwW+-7uWhi2Z2eNX3>;fPwo*^43R%xJ! z&>(6k!XgxsnVYI$Xl7w(VQ6V)ZfWs)BD)^o{jEGHs4OH zc}+~<*sN@dP&VImG3v3}2LY`;TUiWN75v>gvC5=LnYnLMR!*~Z?EZO+D!7~D3L`&# z+}b?9;zD9{ePZ66oa)TpXfC^?2*UsaZG>M%4CUlxz`+6v98v?u+{n_z(!#*kz|epf z9*0bftPCs)ZuZM=1?qIYnY8KA>yvUj9``tzbZ$-Gs&_Zaa&}#{#MRrF>I9 UEbmdhXgxpmj@0?T^FToW0FD{mD*ylh literal 0 HcmV?d00001 diff --git a/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/keycloak.conf b/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/keycloak.conf index 9f391977e3..193faf05a0 100644 --- a/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/keycloak.conf +++ b/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/keycloak.conf @@ -44,7 +44,3 @@ spi-events-store-jpa-max-detail-length=2000 # set known protocol ports for basicsamltest spi-login-protocol-saml-known-protocols=http=8180,https=8543 - -# File-Based Vault -vault=file -vault-dir=${kc.home.dir}/secrets diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/VaultTestExecutionDecider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/VaultTestExecutionDecider.java index 26a26ce222..95b8e00cc1 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/VaultTestExecutionDecider.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/VaultTestExecutionDecider.java @@ -42,16 +42,6 @@ public class VaultTestExecutionDecider implements TestExecutionDecider { // if test was annotated with EnableVault, check if it has selected the elytron credential store provider. if (testContext.getTestClass().isAnnotationPresent(EnableVault.class)) { EnableVault.PROVIDER_ID providerId = testContext.getTestClass().getAnnotation(EnableVault.class).providerId(); - if (providerId == EnableVault.PROVIDER_ID.ELYTRON_CS_KEYSTORE) { - // if the auth server is undertow, skip the test. - SuiteContext suiteContext = testContext.getSuiteContext(); - if (suiteContext != null && suiteContext.getAuthServerInfo() != null && suiteContext.getAuthServerInfo().isUndertow()) { - return ExecutionDecision.dontExecute("@EnableVault with Elytron credential store provider not supported on Undertow, skipping"); - } - if (suiteContext != null && suiteContext.getAuthServerInfo() != null && suiteContext.getAuthServerInfo().isQuarkus()) { - return ExecutionDecision.dontExecute("@EnableVault with Elytron credential store provider not supported on Quarkus, skipping"); - } - } } return ExecutionDecision.execute(); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/EnableVault.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/EnableVault.java index fa4badc7f2..1554fe61f8 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/EnableVault.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/EnableVault.java @@ -33,53 +33,18 @@ public @interface EnableVault { enum PROVIDER_ID { - PLAINTEXT("files-plaintext", - new String[] { - "/subsystem=keycloak-server/spi=vault/provider=files-plaintext/:add(enabled=true, " + - "properties={dir => \"${jboss.home.dir}/standalone/configuration/vault\"})"}, - new String[] {}), - - ELYTRON_CS_KEYSTORE("elytron-cs-keystore", - new String[] { - // create and populate an elytron credential store on the fly. - "/subsystem=elytron/credential-store=test-cred-store:add(location=standalone/configuration/vault/cred-store.jceks, create=true," + - "relative-to=jboss.home.dir, credential-reference={clear-text => \"secretpwd1!\"})", - "/subsystem=elytron/credential-store=test-cred-store:add-alias(alias=master_smtp__key, secret-value=secure_master_smtp_secret)", - "/subsystem=elytron/credential-store=test-cred-store:add-alias(alias=test_smtp__key, secret-value=secure_test_smtp_secret)", - // create the elytron-cs-keystore provider (using the masked form of the credential store password. - "/subsystem=keycloak-server/spi=vault/provider=elytron-cs-keystore/:add(enabled=true, " + - "properties={location => \"${jboss.home.dir}/standalone/configuration/vault/cred-store.jceks\", " + - "secret => \"MASK-2RukbhkyMOXq1WzXkcUcuK;abcd9876;321\", keyStoreType => \"JCEKS\"})"}, - new String[] { - // remove the aliases from the credential store. - "/subsystem=elytron/credential-store=test-cred-store:remove-alias(alias=test_smtp__key)", - "/subsystem=elytron/credential-store=test-cred-store:remove-alias(alias=master_smtp__key)", - // remove the elytron credential store. - "/subsystem=elytron/credential-store=test-cred-store:remove" - }); - + PLAINTEXT("files-plaintext"), + KEYSTORE("files-keystore"); final String name; - final String[] cliInstallationCommands; - final String[] cliRemovalCommands; - PROVIDER_ID(final String name, final String[] cliInstallationCommands, final String[] cliRemovalCommands) { + PROVIDER_ID(final String name) { this.name = name; - this.cliInstallationCommands = cliInstallationCommands; - this.cliRemovalCommands = cliRemovalCommands; } public String getName() { return this.name; } - - public String[] getCliInstallationCommands() { - return this.cliInstallationCommands; - } - - public String[] getCliRemovalCommands() { - return this.cliRemovalCommands; - } }; PROVIDER_ID providerId() default PROVIDER_ID.PLAINTEXT; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/VaultUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/VaultUtils.java index cd5132e751..1d5699d178 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/VaultUtils.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/VaultUtils.java @@ -20,6 +20,10 @@ package org.keycloak.testsuite.util; import org.keycloak.testsuite.arquillian.ContainerInfo; import org.keycloak.testsuite.arquillian.SuiteContext; import org.keycloak.testsuite.arquillian.annotation.EnableVault; +import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer; + +import java.util.ArrayList; +import java.util.List; /** * @author mhajas @@ -32,6 +36,20 @@ public class VaultUtils { if (serverInfo.isUndertow()) { System.setProperty("keycloak.vault." + provider.getName() + ".provider.enabled", "true"); } + else if (serverInfo.isQuarkus()) { + AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); + List additionalArgs = new ArrayList<>(); + + if (provider == EnableVault.PROVIDER_ID.KEYSTORE) { + additionalArgs.add("--vault=keystore"); + additionalArgs.add("--vault-file=../secrets/myks"); + additionalArgs.add("--vault-pass=keystorepassword"); + } else if (provider == EnableVault.PROVIDER_ID.PLAINTEXT) { + additionalArgs.add("--vault=file"); + additionalArgs.add("--vault-dir=../secrets"); + } + container.setAdditionalBuildArgs(additionalArgs); + } } public static void disableVault(SuiteContext suiteContext, EnableVault.PROVIDER_ID provider) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakVaultTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/AbstractKeycloakVaultTest.java similarity index 55% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakVaultTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/AbstractKeycloakVaultTest.java index a0a877f3ea..d4bae13578 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakVaultTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/AbstractKeycloakVaultTest.java @@ -17,12 +17,11 @@ package org.keycloak.testsuite.vault; +import org.jetbrains.annotations.NotNull; import org.junit.Assert; -import org.junit.Test; import org.keycloak.models.KeycloakSession; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; -import org.keycloak.testsuite.arquillian.annotation.EnableVault; import org.keycloak.testsuite.runonserver.RunOnServer; import org.keycloak.testsuite.utils.io.IOUtil; import org.keycloak.vault.VaultStringSecret; @@ -37,22 +36,14 @@ import java.util.Optional; * * @author Stefan Guilhen */ -@EnableVault -public class KeycloakVaultTest extends AbstractKeycloakTest { + +public abstract class AbstractKeycloakVaultTest extends AbstractKeycloakTest { @Override public void addTestRealms(List testRealms) { testRealms.add(IOUtil.loadRealm("/testrealm.json")); } - - @Test - public void testKeycloakVault() throws Exception { - // run the test in two different realms to test the provider's ability to retrieve secrets with the same key in different realms. - testingClient.server().run(new KeycloakVaultServerTest("${vault.smtp_key}", "secure_master_smtp_secret")); - testingClient.server("test").run(new KeycloakVaultServerTest("${vault.smtp_key}", "secure_test_smtp_secret")); - } - static class KeycloakVaultServerTest implements RunOnServer { private String testKey; @@ -65,31 +56,30 @@ public class KeycloakVaultTest extends AbstractKeycloakTest { @Override public void run(KeycloakSession session) { - VaultTranscriber transcriber = session.vault(); - Assert.assertNotNull(transcriber); - - // use the transcriber to obtain a secret from the vault. - try (VaultStringSecret secret = transcriber.getStringSecret(testKey)){ - Optional optional = secret.get(); - Assert.assertTrue(optional.isPresent()); - String secretString = optional.get(); - Assert.assertEquals(expectedSecret, secretString); - } + VaultTranscriber transcriber = getVaultTranscriber(session); + // obtain an existing secret from the vault. + Optional optional = getSecret(transcriber, testKey); + Assert.assertTrue(optional.isPresent()); + Assert.assertEquals(expectedSecret, optional.get()); // try obtaining a secret using a key that does not exist in the vault. - String invalidEntry = "${vault.invalid_entry}"; - try (VaultStringSecret secret = transcriber.getStringSecret(invalidEntry)) { - Optional optional = secret.get(); - Assert.assertFalse(optional.isPresent()); - } + optional = getSecret(transcriber, "${vault.invalid_entry}"); + Assert.assertFalse(optional.isPresent()); // invoke the transcriber using a string that is not a vault expression. - try (VaultStringSecret secret = transcriber.getStringSecret("mysecret")) { - Optional optional = secret.get(); - Assert.assertTrue(optional.isPresent()); - String secretString = optional.get(); - Assert.assertEquals("mysecret", secretString); - } + optional = getSecret(transcriber, "mysecret"); + Assert.assertTrue(optional.isPresent()); + Assert.assertEquals("mysecret", optional.get()); + } + + private Optional getSecret(VaultTranscriber transcriber, String testKey) { + VaultStringSecret secret = transcriber.getStringSecret(testKey); + return secret.get(); } } + + @NotNull + private static VaultTranscriber getVaultTranscriber(KeycloakSession session) throws RuntimeException { + return session.vault(); + } } \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakElytronCSVaultTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakKeystoreVaultTest.java similarity index 57% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakElytronCSVaultTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakKeystoreVaultTest.java index 5edb6c5be1..d7b2038a4c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakElytronCSVaultTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakKeystoreVaultTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.vault; +import org.junit.Test; import org.keycloak.testsuite.arquillian.annotation.EnableVault; import org.keycloak.vault.VaultTranscriber; @@ -24,11 +25,17 @@ import org.keycloak.vault.VaultTranscriber; * Tests the usage of the {@link VaultTranscriber} on the server side. The tests attempt to obtain the transcriber from * the session and then use it to obtain secrets from the configured provider. *

- * This test differs from the superclass in that it uses the {@code elytron-cs-keystore} provider to obtain secrets. + * This test differs from the abstract class in that it uses the {@code files-keystore} provider to obtain secrets. * - * @author Stefan Guilhen + * @author Peter Zaoral */ -@EnableVault(providerId = EnableVault.PROVIDER_ID.ELYTRON_CS_KEYSTORE) -public class KeycloakElytronCSVaultTest extends KeycloakVaultTest { - // run the same tests of the superclass using the elytron credential store provider. +@EnableVault(providerId = EnableVault.PROVIDER_ID.KEYSTORE) +public class KeycloakKeystoreVaultTest extends AbstractKeycloakVaultTest { + + @Test + public void testKeycloakKeystoreVault() { + // run the test in two different realms to test the provider's ability to retrieve secrets with the same key in different realms. + testingClient.server().run(new KeycloakVaultServerTest("${vault.smtp_key}", "secure_master_smtp_secret")); + testingClient.server("test").run(new KeycloakVaultServerTest("${vault.smtp_key}", "secure_test_smtp_secret")); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakPlaintextVaultTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakPlaintextVaultTest.java new file mode 100644 index 0000000000..b2d651bb3d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/vault/KeycloakPlaintextVaultTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.vault; + +import org.junit.Test; +import org.keycloak.testsuite.arquillian.annotation.EnableVault; +import org.keycloak.vault.VaultTranscriber; + +/** + * Tests the usage of the {@link VaultTranscriber} on the server side. The tests attempt to obtain the transcriber from + * the session and then use it to obtain secrets from the configured provider. + *

+ * This test differs from the abstract class in that it uses the {@code files-plaintext} provider to obtain secrets. + * + * @author Peter Zaoral + */ +@EnableVault(providerId = EnableVault.PROVIDER_ID.PLAINTEXT) +public class KeycloakPlaintextVaultTest extends AbstractKeycloakVaultTest { + + @Test + public void testKeycloakPlaintextVault() { + // run the test in two different realms to test the provider's ability to retrieve secrets with the same key in different realms. + testingClient.server().run(new KeycloakVaultServerTest("${vault.smtp_key}", "secure_master_smtp_secret")); + testingClient.server("test").run(new KeycloakVaultServerTest("${vault.smtp_key}", "secure_test_smtp_secret")); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index b8c011a4c8..4ce7f5a0a4 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -368,6 +368,12 @@ "files-plaintext": { "dir": "target/dependency/vault", "enabled": "${keycloak.vault.files-plaintext.provider.enabled:false}" + }, + "files-keystore": { + "file": "target/dependency/vault/myks", + "pass": "keystorepassword", + "type": "PKCS12", + "enabled": "${keycloak.vault.files-keystore.provider.enabled:false}" } }, diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/vault/myks b/testsuite/integration-arquillian/tests/base/src/test/resources/vault/myks new file mode 100644 index 0000000000000000000000000000000000000000..a2eaf70613cf551f18c87e303b6102af178843c8 GIT binary patch literal 706 zcmXqLV%o>V$ZXKWl)=WS)#lOmotKfFaX}MPBuf)hC{Q>Mh<#C|?0`~MKw&e3CPqm% zZm2FEE=JY`jhhV`Hy{f!EofZ8(m2b zPkj4u{@u&a4-d@`GclSLWiXIs<4kDtU`%CZVbo#~=zEjwc5&IQ zp2$`HVJ=Zi@3R;zk_`JAJgF^1WPAPMdL^Uvr)7>Ta@)drcxr?m7styfR`J>k+BdSC zzG<+nb@%quY+DUO4D{iS;S@1cl2c&FWk_TwW+-7uWhi2Z2eNX3>;fPwo*^43R%xJ! z&>(6k!XgxsnVYI$Xl7w(VQ6V)ZfWs)BD)^o{jEGHs4OH zc}+~<*sN@dP&VImG3v3}2LY`;TUiWN75v>gvC5=LnYnLMR!*~Z?EZO+D!7~D3L`&# z+}b?9;zD9{ePZ66oa)TpXfC^?2*UsaZG>M%4CUlxz`+6v98v?u+{n_z(!#*kz|epf z9*0bftPCs)ZuZM=1?qIYnY8KA>yvUj9``tzbZ$-Gs&_Zaa&}#{#MRrF>I9 UEbmdhXgxpmj@0?T^FToW0FD{mD*ylh literal 0 HcmV?d00001