/*
* Copyright 2016 ThoughtWorks, Inc.
*
* 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.thoughtworks.go.config;
import com.thoughtworks.go.config.materials.MaterialConfigs;
import com.thoughtworks.go.config.materials.perforce.P4MaterialConfig;
import com.thoughtworks.go.config.materials.svn.SvnMaterialConfig;
import com.thoughtworks.go.config.materials.tfs.TfsMaterialConfig;
import com.thoughtworks.go.config.registry.ConfigElementImplementationRegistrar;
import com.thoughtworks.go.config.registry.ConfigElementImplementationRegistry;
import com.thoughtworks.go.config.registry.NoPluginsInstalled;
import com.thoughtworks.go.security.CipherProvider;
import com.thoughtworks.go.security.GoCipher;
import com.thoughtworks.go.util.FileUtil;
import com.thoughtworks.go.util.ReflectionUtil;
import com.thoughtworks.go.util.SystemEnvironment;
import com.thoughtworks.go.util.TimeProvider;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.joda.time.DateTime;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
public class ConfigCipherUpdaterTest {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private SystemEnvironment systemEnvironment = new SystemEnvironment();
private ConfigCipherUpdater updater;
private String timestamp;
private ConfigCache configCache;
private ConfigElementImplementationRegistry registry;
private final String passwordEncryptedWithFlawedCipher = "ruRUF0mi2ia/BWpWMISbjQ==";
private MagicalGoConfigXmlLoader magicalGoConfigXmlLoader;
private final String password = "password";
private File originalConfigFile;
@Before
public void setUp() throws Exception {
systemEnvironment.setProperty(SystemEnvironment.CONFIG_DIR_PROPERTY, temporaryFolder.newFolder().getAbsolutePath());
final Date currentTime = new DateTime().toDate();
timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(currentTime);
updater = new ConfigCipherUpdater(systemEnvironment, new TimeProvider() {
@Override
public Date currentTime() {
return currentTime;
}
});
configCache = new ConfigCache();
registry = new ConfigElementImplementationRegistry(new NoPluginsInstalled());
ConfigElementImplementationRegistrar registrar = new ConfigElementImplementationRegistrar(registry);
registrar.initialize();
magicalGoConfigXmlLoader = new MagicalGoConfigXmlLoader(configCache, registry);
File configFileEncryptedWithFlawedCipher = new ClassPathResource("cruise-config-with-encrypted-with-flawed-cipher.xml").getFile();
FileUtil.writeContentToFile(ConfigCipherUpdater.FLAWED_VALUE, systemEnvironment.getCipherFile());
ReflectionUtil.setStaticField(CipherProvider.class, "cachedKey", null);
String xml = ConfigMigrator.migrate(FileUtil.readContentFromFile(configFileEncryptedWithFlawedCipher));
originalConfigFile = new File(systemEnvironment.getCruiseConfigFile());
FileUtils.writeStringToFile(originalConfigFile, xml);
}
@After
public void tearDown() throws Exception {
new SystemEnvironment().clearProperty(SystemEnvironment.CONFIG_DIR_PROPERTY);
}
@Test
public void shouldNotMigrateAnythingIfCipherFileIsNotPresent_FreshInstalls() throws IOException {
FileUtils.deleteQuietly(systemEnvironment.getCipherFile());
updater.migrate();
File cipherFile = systemEnvironment.getCipherFile();
assertThat(cipherFile.exists(), is(false));
}
@Test
public void shouldMigrateEncryptedPasswordsThatWereEncryptedWithFlawedCipher() throws Exception {
String originalConfig = FileUtil.readContentFromFile(originalConfigFile);
assertThat(originalConfig.contains("encryptedPassword=\"" + passwordEncryptedWithFlawedCipher + "\""), is(true));
updater.migrate();
File copyOfOldConfig = new File(systemEnvironment.getConfigDir(), "cipher.original." + timestamp);
assertThat(copyOfOldConfig.exists(), is(true));
assertThat(FileUtil.readContentFromFile(copyOfOldConfig).equals(ConfigCipherUpdater.FLAWED_VALUE), is(true));
String newCipher = FileUtil.readContentFromFile(systemEnvironment.getCipherFile());
assertThat(newCipher.equals(ConfigCipherUpdater.FLAWED_VALUE), is(false));
File editedConfigFile = new File(systemEnvironment.getCruiseConfigFile());
String editedConfig = FileUtil.readContentFromFile(editedConfigFile);
assertThat(editedConfig.contains("encryptedPassword=\"" + passwordEncryptedWithFlawedCipher + "\""), is(false));
String passwordEncryptedWithNewCipher = new GoCipher().encrypt(password);
CruiseConfig config = magicalGoConfigXmlLoader.loadConfigHolder(editedConfig).config;
MaterialConfigs materialConfigs = config.getAllPipelineConfigs().get(0).materialConfigs();
SvnMaterialConfig svnMaterial = materialConfigs.getSvnMaterial();
assertThat(svnMaterial.getPassword(), is(password));
assertThat(svnMaterial.getEncryptedPassword(), is(passwordEncryptedWithNewCipher));
P4MaterialConfig p4Material = materialConfigs.getP4Material();
assertThat(p4Material.getPassword(), is(password));
assertThat(p4Material.getEncryptedPassword(), is(passwordEncryptedWithNewCipher));
TfsMaterialConfig tfsMaterial = materialConfigs.getTfsMaterial();
assertThat(tfsMaterial.getPassword(), is(password));
assertThat(tfsMaterial.getEncryptedPassword(), is(passwordEncryptedWithNewCipher));
}
@Test
public void shouldMigrateEncryptedManagerPasswordsEncryptedWithFlawedCipher() throws Exception {
String originalConfig = FileUtil.readContentFromFile(originalConfigFile);
assertThat(originalConfig.contains("encryptedPassword=\"" + passwordEncryptedWithFlawedCipher + "\""), is(true));
updater.migrate();
File copyOfOldConfig = new File(systemEnvironment.getConfigDir(), "cipher.original." + timestamp);
assertThat(copyOfOldConfig.exists(), is(true));
assertThat(FileUtil.readContentFromFile(copyOfOldConfig).equals(ConfigCipherUpdater.FLAWED_VALUE), is(true));
assertThat(FileUtil.readContentFromFile(systemEnvironment.getCipherFile()).equals(ConfigCipherUpdater.FLAWED_VALUE), is(false));
File editedConfigFile = new File(systemEnvironment.getCruiseConfigFile());
String editedConfig = FileUtil.readContentFromFile(editedConfigFile);
assertThat(editedConfig.contains("encryptedManagerPassword=\"" + passwordEncryptedWithFlawedCipher + "\""), is(false));
CruiseConfig config = magicalGoConfigXmlLoader.loadConfigHolder(editedConfig).config;
assertThat(config.server().security().ldapConfig().isEnabled(), is(false));
SecurityAuthConfig migratedLdapConfig = config.server().security().securityAuthConfigs().get(0);
assertThat(migratedLdapConfig.getProperty("Password").getValue(), is(password));
assertThat(migratedLdapConfig.getProperty("Password").getEncryptedValue(), is(new GoCipher().encrypt(password)));
}
@Test
public void shouldMigrateEncryptedValuesEncryptedWithFlawedCipher() throws Exception {
String originalConfig = FileUtil.readContentFromFile(originalConfigFile);
assertThat(originalConfig.contains("<encryptedValue>" + passwordEncryptedWithFlawedCipher + "</encryptedValue>"), is(true));
updater.migrate();
File copyOfOldConfig = new File(systemEnvironment.getConfigDir(), "cipher.original." + timestamp);
assertThat(copyOfOldConfig.exists(), is(true));
assertThat(FileUtil.readContentFromFile(copyOfOldConfig).equals(ConfigCipherUpdater.FLAWED_VALUE), is(true));
assertThat(FileUtil.readContentFromFile(systemEnvironment.getCipherFile()).equals(ConfigCipherUpdater.FLAWED_VALUE), is(false));
File editedConfigFile = new File(systemEnvironment.getCruiseConfigFile());
String editedConfig = FileUtil.readContentFromFile(editedConfigFile);
assertThat(editedConfig.contains("<encryptedValue>" + passwordEncryptedWithFlawedCipher + "</encryptedValue>"), is(false));
CruiseConfig config = magicalGoConfigXmlLoader.loadConfigHolder(editedConfig).config;
EnvironmentVariablesConfig secureVariables = config.getAllPipelineConfigs().get(0).getSecureVariables();
assertThat(secureVariables.first().getValue(), is(password));
assertThat(secureVariables.first().getEncryptedValue(), is(new GoCipher().encrypt(password)));
}
@Test
public void shouldNotTryMigratingOlderConfigsWhichWereNotEncryptedWithFlawedCipher() throws IOException, InvalidCipherTextException {
String goodCipher = "269298bc31c44620";
FileUtil.writeContentToFile(goodCipher, systemEnvironment.getCipherFile());
File originalConfigFile = new File(systemEnvironment.getCruiseConfigFile());
FileUtils.copyFile(new ClassPathResource("cruise-config-with-encrypted-with-safe-cipher.xml").getFile(), originalConfigFile);
String originalConfig = FileUtil.readContentFromFile(originalConfigFile);
assertThat(originalConfig.contains("encryptedPassword=\"pVyuW5ny9I6YT4Ou+KLZhQ==\""), is(true));
updater.migrate();
File copyOfOldConfig = new File(systemEnvironment.getConfigDir(), "cipher.original." + timestamp);
assertThat(copyOfOldConfig.exists(), is(false));
assertThat(FileUtil.readContentFromFile(systemEnvironment.getCipherFile()).equals(goodCipher), is(true));
File configFile = new File(systemEnvironment.getCruiseConfigFile());
String editedConfig = FileUtil.readContentFromFile(configFile);
assertThat(editedConfig.contains("encryptedPassword=\"pVyuW5ny9I6YT4Ou+KLZhQ==\""), is(true));
}
}