diff --git a/server-spi-private/src/main/java/org/keycloak/vault/VaultProvider.java b/server-spi-private/src/main/java/org/keycloak/vault/VaultProvider.java new file mode 100644 index 0000000000..259c84aead --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/vault/VaultProvider.java @@ -0,0 +1,50 @@ +/* + * 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.vault; + +import org.keycloak.provider.Provider; + +/** + * Provider interface for a vault. The only purpose of a vault is retrieval of secrets. + */ +public interface VaultProvider extends Provider { + + /** + * Retrieves a secret from vault. The implementation should respect + * at least the realm ID to separate the secrets within the vault. + * If the secret is retrieved successfully, it is returned; + * otherwise this method results into an empty {@link VaultRawSecret#getRawSecret()}. + * + * This method is intended to be used within a try-with-resources block so that + * the secret is destroyed immediately after use. + * + * Note that it is responsibility of the implementor to provide a way + * to destroy the secret in the returned {@link VaultRawSecret#close()} method. + * + * @param vaultSecretId Identifier of the secret. It corresponds to the value + * entered by user in the respective configuration, which in turn + * is obtained from the vault when storing the secret. + * + * @return Always a non-{@code null} value with the raw secret. + * Within the returned value, the secret or {@code null} is stored in the + * {@link VaultRawSecret#getRawSecret()} return value if the secret was successfully + * resolved, or an empty {@link java.util.Optional} if the secret has not been found in the vault. + */ + VaultRawSecret obtainSecret(String vaultSecretId); + +} diff --git a/server-spi-private/src/main/java/org/keycloak/vault/VaultProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/vault/VaultProviderFactory.java new file mode 100644 index 0000000000..3aa458b682 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/vault/VaultProviderFactory.java @@ -0,0 +1,25 @@ +/* + * 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.vault; + +import org.keycloak.provider.ProviderFactory; + +/** + */ +public interface VaultProviderFactory extends ProviderFactory { +} diff --git a/server-spi-private/src/main/java/org/keycloak/vault/VaultRawSecret.java b/server-spi-private/src/main/java/org/keycloak/vault/VaultRawSecret.java new file mode 100644 index 0000000000..e417c608db --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/vault/VaultRawSecret.java @@ -0,0 +1,43 @@ +/* + * 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.vault; + +import java.nio.ByteBuffer; +import java.util.Optional; + +/** + * Raw representation of the secret obtained from vault that supports automated cleanup of memory. + * + * @author hmlnarik + */ +public interface VaultRawSecret extends AutoCloseable { + + /** + * Returns the raw secret bytes. + * @return If the secret was successfully resolved by vault, returns + * an {@link Optional} containing the value returned by the vault + * (a valid value can be {@code null}), or an empty {@link Optional} + */ + Optional getRawSecret(); + + /** + * Destroys the secret in memory by e.g. overwriting it with random garbage. + */ + @Override + void close(); + +} diff --git a/server-spi-private/src/main/java/org/keycloak/vault/VaultSpi.java b/server-spi-private/src/main/java/org/keycloak/vault/VaultSpi.java new file mode 100644 index 0000000000..4e3996973f --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/vault/VaultSpi.java @@ -0,0 +1,48 @@ +/* + * 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.vault; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * SPI for a low-level vault access. + */ +public class VaultSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "vault"; + } + + @Override + public Class getProviderClass() { + return VaultProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return VaultProviderFactory.class; + } +} diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index bb4c6c3924..803848c600 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -74,4 +74,5 @@ org.keycloak.keys.KeySpi org.keycloak.storage.client.ClientStorageProviderSpi org.keycloak.crypto.SignatureSpi org.keycloak.crypto.ClientSignatureVerifierSpi -org.keycloak.crypto.HashSpi \ No newline at end of file +org.keycloak.crypto.HashSpi +org.keycloak.vault.VaultSpi \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/vault/DefaultVaultRawSecret.java b/services/src/main/java/org/keycloak/vault/DefaultVaultRawSecret.java new file mode 100644 index 0000000000..1c18378a28 --- /dev/null +++ b/services/src/main/java/org/keycloak/vault/DefaultVaultRawSecret.java @@ -0,0 +1,64 @@ +/* + * 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.vault; + +import java.nio.ByteBuffer; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Default raw secret implementation for {@code byte[]}. + * @author hmlnarik + */ +public class DefaultVaultRawSecret implements VaultRawSecret { + + private static final VaultRawSecret EMPTY_VAULT_SECRET = new VaultRawSecret() { + @Override + public Optional getRawSecret() { + return Optional.empty(); + } + + @Override + public void close() { + } + }; + + private final ByteBuffer rawSecret; + + public static VaultRawSecret forBuffer(Optional buffer) { + if (buffer == null || ! buffer.isPresent()) { + return EMPTY_VAULT_SECRET; + } + return new DefaultVaultRawSecret(buffer.get()); + } + + private DefaultVaultRawSecret(ByteBuffer rawSecret) { + this.rawSecret = rawSecret; + } + + @Override + public Optional getRawSecret() { + return Optional.of(this.rawSecret); + } + + @Override + public void close() { + if (rawSecret.hasArray()) { + ThreadLocalRandom.current().nextBytes(rawSecret.array()); + } + } +}