Allow plugins to store encypted settings in etc/$PLUGIN.secure.config
Change-Id: I82dd2fdd33c53aa2db7cb136c79548e0fc8d0ede
This commit is contained in:
@@ -737,6 +737,18 @@ this:
|
||||
reviewer = My Info Developers
|
||||
----
|
||||
|
||||
Plugins that have sensitive configuration settings can store those settings in
|
||||
an own secure configuration file. The plugin's secure configuration file must be
|
||||
named after the plugin and must be located in the `etc` folder of the review
|
||||
site. For example a secure configuration file for a `default-reviewer` plugin
|
||||
could look like this:
|
||||
|
||||
.$site_path/etc/default-reviewer.secure.config
|
||||
----
|
||||
[auth]
|
||||
password = secret
|
||||
----
|
||||
|
||||
Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
|
||||
plugin can easily access its configuration:
|
||||
|
||||
@@ -749,6 +761,8 @@ private com.google.gerrit.server.config.PluginConfigFactory cfg;
|
||||
|
||||
String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
|
||||
.getStringList("branch", "refs/heads/master", "reviewer");
|
||||
String password = cfg.getGlobalPluginConfig("default-reviewer")
|
||||
.getString("auth", null, "password");
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -127,6 +127,12 @@ public class UpgradeFrom2_0_xTest extends InitTestCase {
|
||||
return cfg.getStringList(section, subsection, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getListForPlugin(String pluginName, String section,
|
||||
String subsection, String name) {
|
||||
throw new UnsupportedOperationException("not used by tests");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setList(String section, String subsection, String name,
|
||||
List<String> values) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// 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 com.google.gerrit.server.config;
|
||||
|
||||
import com.google.gerrit.server.securestore.SecureStore;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
|
||||
/**
|
||||
* Plugin configuration in etc/$PLUGIN.config and etc/$PLUGIN.secure.config.
|
||||
*/
|
||||
public class GlobalPluginConfig extends Config {
|
||||
private final SecureStore secureStore;
|
||||
private final String pluginName;
|
||||
|
||||
GlobalPluginConfig(String pluginName, Config baseConfig,
|
||||
SecureStore secureStore) {
|
||||
super(baseConfig);
|
||||
this.pluginName = pluginName;
|
||||
this.secureStore = secureStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String section, String subsection, String name) {
|
||||
String secure = secureStore.getForPlugin(
|
||||
pluginName, section, subsection, name);
|
||||
if (secure != null) {
|
||||
return secure;
|
||||
}
|
||||
return super.getString(section, subsection, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getStringList(String section, String subsection, String name) {
|
||||
String[] secure = secureStore.getListForPlugin(
|
||||
pluginName, section, subsection, name);
|
||||
if (secure != null && secure.length > 0) {
|
||||
return secure;
|
||||
}
|
||||
return super.getStringList(section, subsection, name);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.google.gerrit.server.plugins.ReloadPluginListener;
|
||||
import com.google.gerrit.server.project.NoSuchProjectException;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.server.securestore.SecureStore;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
@@ -49,6 +50,7 @@ public class PluginConfigFactory implements ReloadPluginListener {
|
||||
private final Provider<Config> cfgProvider;
|
||||
private final ProjectCache projectCache;
|
||||
private final ProjectState.Factory projectStateFactory;
|
||||
private final SecureStore secureStore;
|
||||
private final Map<String, Config> pluginConfigs;
|
||||
|
||||
private volatile FileSnapshot cfgSnapshot;
|
||||
@@ -59,13 +61,15 @@ public class PluginConfigFactory implements ReloadPluginListener {
|
||||
SitePaths site,
|
||||
@GerritServerConfig Provider<Config> cfgProvider,
|
||||
ProjectCache projectCache,
|
||||
ProjectState.Factory projectStateFactory) {
|
||||
ProjectState.Factory projectStateFactory,
|
||||
SecureStore secureStore) {
|
||||
this.site = site;
|
||||
this.cfgProvider = cfgProvider;
|
||||
this.projectCache = projectCache;
|
||||
this.projectStateFactory = projectStateFactory;
|
||||
this.pluginConfigs = new HashMap<>();
|
||||
this.secureStore = secureStore;
|
||||
|
||||
this.pluginConfigs = new HashMap<>();
|
||||
this.cfgSnapshot = FileSnapshot.save(site.gerrit_config.toFile());
|
||||
this.cfg = cfgProvider.get();
|
||||
}
|
||||
@@ -260,10 +264,12 @@ public class PluginConfigFactory implements ReloadPluginListener {
|
||||
Path pluginConfigFile = site.etc_dir.resolve(pluginName + ".config");
|
||||
FileBasedConfig cfg =
|
||||
new FileBasedConfig(pluginConfigFile.toFile(), FS.DETECTED);
|
||||
pluginConfigs.put(pluginName, cfg);
|
||||
GlobalPluginConfig pluginConfig =
|
||||
new GlobalPluginConfig(pluginName, cfg, secureStore);
|
||||
pluginConfigs.put(pluginName, pluginConfig);
|
||||
if (!cfg.getFile().exists()) {
|
||||
log.info("No " + pluginConfigFile.toAbsolutePath() + "; assuming defaults");
|
||||
return cfg;
|
||||
return pluginConfig;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -272,7 +278,7 @@ public class PluginConfigFactory implements ReloadPluginListener {
|
||||
log.warn("Failed to load " + pluginConfigFile.toAbsolutePath(), e);
|
||||
}
|
||||
|
||||
return cfg;
|
||||
return pluginConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.google.gerrit.server.config.SitePaths;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
import org.eclipse.jgit.internal.storage.file.LockFile;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.storage.file.FileBasedConfig;
|
||||
@@ -27,20 +28,26 @@ import org.eclipse.jgit.util.FS;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class DefaultSecureStore extends SecureStore {
|
||||
private final FileBasedConfig sec;
|
||||
private final Map<String, FileBasedConfig> pluginSec;
|
||||
private final SitePaths site;
|
||||
|
||||
@Inject
|
||||
DefaultSecureStore(SitePaths site) {
|
||||
this.site = site;
|
||||
sec = new FileBasedConfig(site.secure_config.toFile(), FS.DETECTED);
|
||||
try {
|
||||
sec.load();
|
||||
} catch (Exception e) {
|
||||
} catch (IOException | ConfigInvalidException e) {
|
||||
throw new RuntimeException("Cannot load secure.config", e);
|
||||
}
|
||||
this.pluginSec = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,6 +55,28 @@ public class DefaultSecureStore extends SecureStore {
|
||||
return sec.getStringList(section, subsection, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String[] getListForPlugin(String pluginName, String section,
|
||||
String subsection, String name) {
|
||||
FileBasedConfig cfg = null;
|
||||
if (pluginSec.containsKey(pluginName)) {
|
||||
cfg = pluginSec.get(pluginName);
|
||||
} else {
|
||||
String filename = pluginName + ".secure.config";
|
||||
File pluginConfigFile = site.etc_dir.resolve(filename).toFile();
|
||||
if (pluginConfigFile.exists()) {
|
||||
cfg = new FileBasedConfig(pluginConfigFile, FS.DETECTED);
|
||||
try {
|
||||
cfg.load();
|
||||
pluginSec.put(pluginName, cfg);
|
||||
} catch (IOException | ConfigInvalidException e) {
|
||||
throw new RuntimeException("Cannot load " + filename, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cfg != null ? cfg.getStringList(section, subsection, name) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setList(String section, String subsection, String name,
|
||||
List<String> values) {
|
||||
|
||||
@@ -74,6 +74,38 @@ public abstract class SecureStore {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract decrypted value of stored plugin config property from SecureStore
|
||||
* or {@code null} when property was not found.
|
||||
*
|
||||
* @param pluginName
|
||||
* @param section
|
||||
* @param subsection
|
||||
* @param name
|
||||
* @return decrypted String value or {@code null} if not found
|
||||
*/
|
||||
public final String getForPlugin(String pluginName, String section,
|
||||
String subsection, String name) {
|
||||
String[] values = getListForPlugin(pluginName, section, subsection, name);
|
||||
if (values != null && values.length > 0) {
|
||||
return values[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract list of plugin config values from SecureStore and decrypt every
|
||||
* value in that list, or {@code null} when property was not found.
|
||||
*
|
||||
* @param pluginName
|
||||
* @param section
|
||||
* @param subsection
|
||||
* @param name
|
||||
* @return decrypted list of string values or {@code null}
|
||||
*/
|
||||
public abstract String[] getListForPlugin(String pluginName, String section,
|
||||
String subsection, String name);
|
||||
|
||||
/**
|
||||
* Extract list of values from SecureStore and decrypt every value in that
|
||||
* list or {@code null} when property was not found.
|
||||
|
||||
Reference in New Issue
Block a user