Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/Evolveum/midpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
matusmacik committed Feb 9, 2024
2 parents 0cdbdc0 + 9acbce2 commit 93eaceb
Show file tree
Hide file tree
Showing 72 changed files with 1,663 additions and 231 deletions.
4 changes: 4 additions & 0 deletions infra/common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2010-2023 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.common.secrets;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.SystemUtils;
import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.DockerSecretsProviderType;

public class DockerSecretsProvider extends SecretsProviderImpl<DockerSecretsProviderType> {

private Charset charset;

public DockerSecretsProvider(DockerSecretsProviderType configuration) {
super(configuration);
}

@Override
public void initialize() {
super.initialize();

DockerSecretsProviderType config = getConfiguration();
charset = config.getCharset() != null ? Charset.forName(config.getCharset()) : StandardCharsets.UTF_8;
}

@Override
protected <ST> ST resolveSecret(@NotNull String key, @NotNull Class<ST> type) throws EncryptionException {
File parent;
if (SystemUtils.IS_OS_WINDOWS) {
parent = new File("C:\\ProgramData\\Docker\\secrets");
} else {
parent = new File("/run/secrets");
}

// to avoid path traversal
String filename = new File(key).getName();
File valueFile = new File(parent, filename);
ST value = null;
if (valueFile.exists() && valueFile.isFile() && valueFile.canRead()) {
try (InputStream is = new FileInputStream(valueFile)) {
String content = IOUtils.toString(is, charset);

value = mapValue(content, type);
} catch (IOException ex) {
throw new EncryptionException("Couldn't read secret from " + valueFile.getAbsolutePath(), ex);
}
}

return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2010-2024 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.common.secrets;

import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.EnvironmentVariablesSecretsProviderType;

public class EnvironmentVariablesSecretsProvider extends SecretsProviderImpl<EnvironmentVariablesSecretsProviderType> {

public EnvironmentVariablesSecretsProvider(EnvironmentVariablesSecretsProviderType configuration) {
super(configuration);
}

@Override
protected <ST> ST resolveSecret(@NotNull String key, @NotNull Class<ST> type) throws EncryptionException {
String prefix = getConfiguration().getPrefix();

if (prefix != null && !key.startsWith(prefix)) {
throw new EncryptionException("Key not available in provider " + getIdentifier() + ": " + key);
}

String value = System.getenv(key);

return mapValue(value, type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2010-2024 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.common.secrets;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.PropertiesSecretsProviderType;

public class PropertiesSecretsProvider extends SecretsProviderImpl<PropertiesSecretsProviderType> {

private Charset charset;
private File properties;

public PropertiesSecretsProvider(PropertiesSecretsProviderType configuration) {
super(configuration);
}

@Override
public void initialize() {
super.initialize();

PropertiesSecretsProviderType config = getConfiguration();
charset = config.getCharset() != null ? Charset.forName(config.getCharset()) : StandardCharsets.UTF_8;

String path = config.getPropertiesFile();
if (StringUtils.isEmpty(path)) {
throw new IllegalArgumentException("No properties file specified in the properties secret provider "
+ config.getIdentifier());
}
properties = new File(config.getPropertiesFile());
if (!properties.exists() || !properties.isFile()) {
throw new IllegalArgumentException(
"Properties file '" + path + "' specified in the properties secret provider " + config.getIdentifier()
+ " does not exist or is not file");
}
}

@Override
protected <ST> ST resolveSecret(@NotNull String key, @NotNull Class<ST> type) throws EncryptionException {
try (Reader reader = new FileReader(properties, charset)) {
Properties props = new Properties();
props.load(reader);

String value = props.getProperty(key);

return mapValue(value, type);
} catch (IOException ex) {
throw new EncryptionException("Couldn't read properties file in provider " + getIdentifier(), ex);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (C) 2010-2023 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.common.secrets;

import java.nio.ByteBuffer;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.datatype.Duration;

import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.common.Clock;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.prism.crypto.SecretsProvider;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SecretsProviderType;

public abstract class SecretsProviderImpl<T extends SecretsProviderType> implements SecretsProvider<T> {

private static final String[] EMPTY_DEPENDENCIES = new String[0];

private static final long DEFAULT_TTL = 0;

private final T configuration;

private final Map<String, CacheValue<?>> cache = new ConcurrentHashMap<>();

private long ttl;

public SecretsProviderImpl(@NotNull T configuration) {
this.configuration = configuration;
}

@Override
public @NotNull T getConfiguration() {
return configuration;
}

@Override
public void initialize() {
Duration duration = configuration.getCache();

ttl = duration != null ? duration.getTimeInMillis(new Date()) : DEFAULT_TTL;
}

@Override
public void destroy() {
cache.clear();
}

@Override
public @NotNull String getIdentifier() {
return configuration.getIdentifier();
}

@Override
public @NotNull String[] getDependencies() {
return EMPTY_DEPENDENCIES;
}

@Override
public String getSecretString(@NotNull String key) throws EncryptionException {
return getOrResolveSecret(key, String.class);
}

@Override
public ByteBuffer getSecretBinary(@NotNull String key) throws EncryptionException {
return getOrResolveSecret(key, ByteBuffer.class);
}

private <ST> ST getOrResolveSecret(String key, Class<ST> type) throws EncryptionException {
if (ttl <= 0) {
return resolveSecret(key, type);
}

CacheValue<?> value = cache.get(key);
if (value != null) {
if (Clock.get().currentTimeMillis() <= ttl) {
if (value.value == null) {
return null;
}

Class<?> clazz = value.value().getClass();
if (!(type.isAssignableFrom(clazz))) {
throw new IllegalStateException(
"Secret value for key " + key + " is not a " + type + ", but " + clazz);
}
return (ST) value.value();
} else {
cache.remove(key);
}
}

ST secret = resolveSecret(key, type);
cache.put(key, new CacheValue<>(secret, System.currentTimeMillis() + ttl));

return secret;
}

/**
* Should return secret value for given key or null if the secret does not exist.
*
* @throws EncryptionException if the secret cannot be resolved (e.g. due to network problems, or unforeseen error)
*/
protected abstract <ST> ST resolveSecret(@NotNull String key, @NotNull Class<ST> type) throws EncryptionException;

protected <ST> ST mapValue(String value, Class<ST> type) {
if (value == null) {
return null;
}

if (type == String.class) {
return (ST) value;
} else if (type == ByteBuffer.class) {
return (ST) ByteBuffer.wrap(value.getBytes());
}

throw new IllegalStateException("Unsupported type " + type);
}

private record CacheValue<T>(T value, long ttl) {

@Override
public String toString() {
return "CacheKey{" +
"key='" + value + '\'' +
", ttl=" + ttl +
'}';
}
}
}

0 comments on commit 93eaceb

Please sign in to comment.