| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // 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.cloud.utils.crypt; |
| |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Properties; |
| |
| import org.apache.commons.configuration.ConfigurationException; |
| import org.apache.commons.configuration.PropertiesConfiguration; |
| import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; |
| import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig; |
| import org.jasypt.exceptions.EncryptionOperationNotPossibleException; |
| import org.jasypt.properties.EncryptableProperties; |
| |
| import com.cloud.utils.PropertiesUtil; |
| import com.cloud.utils.db.TransactionLegacy; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| |
| /* |
| * EncryptionSecretKeyChanger updates Management Secret Key / DB Secret Key or both. |
| * DB secret key is validated against the key in db.properties |
| * db.properties is updated with values encrypted using new MS secret key |
| * DB data migrated using new DB secret key |
| */ |
| public class EncryptionSecretKeyChanger { |
| |
| private StandardPBEStringEncryptor oldEncryptor = new StandardPBEStringEncryptor(); |
| private StandardPBEStringEncryptor newEncryptor = new StandardPBEStringEncryptor(); |
| private static final String keyFile = "/etc/cloudstack/management/key"; |
| |
| public static void main(String[] args) { |
| List<String> argsList = Arrays.asList(args); |
| Iterator<String> iter = argsList.iterator(); |
| String oldMSKey = null; |
| String oldDBKey = null; |
| String newMSKey = null; |
| String newDBKey = null; |
| |
| //Parse command-line args |
| while (iter.hasNext()) { |
| String arg = iter.next(); |
| // Old MS Key |
| if (arg.equals("-m")) { |
| oldMSKey = iter.next(); |
| } |
| // Old DB Key |
| if (arg.equals("-d")) { |
| oldDBKey = iter.next(); |
| } |
| // New MS Key |
| if (arg.equals("-n")) { |
| newMSKey = iter.next(); |
| } |
| // New DB Key |
| if (arg.equals("-e")) { |
| newDBKey = iter.next(); |
| } |
| } |
| |
| if (oldMSKey == null || oldDBKey == null) { |
| System.out.println("Existing MS secret key or DB secret key is not provided"); |
| usage(); |
| return; |
| } |
| |
| if (newMSKey == null && newDBKey == null) { |
| System.out.println("New MS secret key and DB secret are both not provided"); |
| usage(); |
| return; |
| } |
| |
| final File dbPropsFile = PropertiesUtil.findConfigFile("db.properties"); |
| final Properties dbProps; |
| EncryptionSecretKeyChanger keyChanger = new EncryptionSecretKeyChanger(); |
| StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); |
| keyChanger.initEncryptor(encryptor, oldMSKey); |
| dbProps = new EncryptableProperties(encryptor); |
| PropertiesConfiguration backupDBProps = null; |
| |
| System.out.println("Parsing db.properties file"); |
| try(FileInputStream db_prop_fstream = new FileInputStream(dbPropsFile);) { |
| dbProps.load(db_prop_fstream); |
| backupDBProps = new PropertiesConfiguration(dbPropsFile); |
| } catch (FileNotFoundException e) { |
| System.out.println("db.properties file not found while reading DB secret key" + e.getMessage()); |
| } catch (IOException e) { |
| System.out.println("Error while reading DB secret key from db.properties" + e.getMessage()); |
| } catch (ConfigurationException e) { |
| e.printStackTrace(); |
| } |
| |
| String dbSecretKey = null; |
| try { |
| dbSecretKey = dbProps.getProperty("db.cloud.encrypt.secret"); |
| } catch (EncryptionOperationNotPossibleException e) { |
| System.out.println("Failed to decrypt existing DB secret key from db.properties. " + e.getMessage()); |
| return; |
| } |
| |
| if (!oldDBKey.equals(dbSecretKey)) { |
| System.out.println("Incorrect MS Secret Key or DB Secret Key"); |
| return; |
| } |
| |
| System.out.println("Secret key provided matched the key in db.properties"); |
| final String encryptionType = dbProps.getProperty("db.cloud.encryption.type"); |
| |
| if (newMSKey == null) { |
| System.out.println("No change in MS Key. Skipping migrating db.properties"); |
| } else { |
| if (!keyChanger.migrateProperties(dbPropsFile, dbProps, newMSKey, newDBKey)) { |
| System.out.println("Failed to update db.properties"); |
| return; |
| } else { |
| //db.properties updated successfully |
| if (encryptionType.equals("file")) { |
| //update key file with new MS key |
| try (FileWriter fwriter = new FileWriter(keyFile); |
| BufferedWriter bwriter = new BufferedWriter(fwriter);) |
| { |
| bwriter.write(newMSKey); |
| } catch (IOException e) { |
| System.out.println("Failed to write new secret to file. Please update the file manually"); |
| } |
| } |
| } |
| } |
| |
| boolean success = false; |
| if (newDBKey == null || newDBKey.equals(oldDBKey)) { |
| System.out.println("No change in DB Secret Key. Skipping Data Migration"); |
| } else { |
| EncryptionSecretKeyChecker.initEncryptorForMigration(oldMSKey); |
| try { |
| success = keyChanger.migrateData(oldDBKey, newDBKey); |
| } catch (Exception e) { |
| System.out.println("Error during data migration"); |
| e.printStackTrace(); |
| success = false; |
| } |
| } |
| |
| if (success) { |
| System.out.println("Successfully updated secret key(s)"); |
| } else { |
| System.out.println("Data Migration failed. Reverting db.properties"); |
| //revert db.properties |
| try { |
| backupDBProps.save(); |
| } catch (ConfigurationException e) { |
| e.printStackTrace(); |
| } |
| if (encryptionType.equals("file")) { |
| //revert secret key in file |
| try (FileWriter fwriter = new FileWriter(keyFile); |
| BufferedWriter bwriter = new BufferedWriter(fwriter);) |
| { |
| bwriter.write(oldMSKey); |
| } catch (IOException e) { |
| System.out.println("Failed to revert to old secret to file. Please update the file manually"); |
| } |
| } |
| } |
| } |
| |
| private boolean migrateProperties(File dbPropsFile, Properties dbProps, String newMSKey, String newDBKey) { |
| System.out.println("Migrating db.properties.."); |
| StandardPBEStringEncryptor msEncryptor = new StandardPBEStringEncryptor(); |
| ; |
| initEncryptor(msEncryptor, newMSKey); |
| |
| try { |
| PropertiesConfiguration newDBProps = new PropertiesConfiguration(dbPropsFile); |
| if (newDBKey != null && !newDBKey.isEmpty()) { |
| newDBProps.setProperty("db.cloud.encrypt.secret", "ENC(" + msEncryptor.encrypt(newDBKey) + ")"); |
| } |
| String prop = dbProps.getProperty("db.cloud.password"); |
| if (prop != null && !prop.isEmpty()) { |
| newDBProps.setProperty("db.cloud.password", "ENC(" + msEncryptor.encrypt(prop) + ")"); |
| } |
| prop = dbProps.getProperty("db.usage.password"); |
| if (prop != null && !prop.isEmpty()) { |
| newDBProps.setProperty("db.usage.password", "ENC(" + msEncryptor.encrypt(prop) + ")"); |
| } |
| newDBProps.save(dbPropsFile.getAbsolutePath()); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| return false; |
| } |
| System.out.println("Migrating db.properties Done."); |
| return true; |
| } |
| |
| private boolean migrateData(String oldDBKey, String newDBKey) { |
| System.out.println("Begin Data migration"); |
| initEncryptor(oldEncryptor, oldDBKey); |
| initEncryptor(newEncryptor, newDBKey); |
| System.out.println("Initialised Encryptors"); |
| |
| TransactionLegacy txn = TransactionLegacy.open("Migrate"); |
| txn.start(); |
| try { |
| Connection conn; |
| try { |
| conn = txn.getConnection(); |
| } catch (SQLException e) { |
| throw new CloudRuntimeException("Unable to migrate encrypted data in the database", e); |
| } |
| |
| migrateConfigValues(conn); |
| migrateHostDetails(conn); |
| migrateVNCPassword(conn); |
| migrateUserCredentials(conn); |
| |
| txn.commit(); |
| } finally { |
| txn.close(); |
| } |
| System.out.println("End Data migration"); |
| return true; |
| } |
| |
| private void initEncryptor(StandardPBEStringEncryptor encryptor, String secretKey) { |
| encryptor.setAlgorithm("PBEWithMD5AndDES"); |
| SimpleStringPBEConfig stringConfig = new SimpleStringPBEConfig(); |
| stringConfig.setPassword(secretKey); |
| encryptor.setConfig(stringConfig); |
| } |
| |
| private String migrateValue(String value) { |
| if (value == null || value.isEmpty()) { |
| return value; |
| } |
| String decryptVal = oldEncryptor.decrypt(value); |
| return newEncryptor.encrypt(decryptVal); |
| } |
| |
| private void migrateConfigValues(Connection conn) { |
| System.out.println("Begin migrate config values"); |
| try(PreparedStatement select_pstmt = conn.prepareStatement("select name, value from configuration where category in ('Hidden', 'Secure')"); |
| ResultSet rs = select_pstmt.executeQuery(); |
| PreparedStatement update_pstmt = conn.prepareStatement("update configuration set value=? where name=?"); |
| ) { |
| while (rs.next()) { |
| String name = rs.getString(1); |
| String value = rs.getString(2); |
| if (value == null || value.isEmpty()) { |
| continue; |
| } |
| String encryptedValue = migrateValue(value); |
| update_pstmt.setBytes(1, encryptedValue.getBytes("UTF-8")); |
| update_pstmt.setString(2, name); |
| update_pstmt.executeUpdate(); |
| } |
| } catch (SQLException e) { |
| throw new CloudRuntimeException("Unable to update configuration values ", e); |
| } catch (UnsupportedEncodingException e) { |
| throw new CloudRuntimeException("Unable to update configuration values ", e); |
| } |
| System.out.println("End migrate config values"); |
| } |
| |
| private void migrateHostDetails(Connection conn) { |
| System.out.println("Begin migrate host details"); |
| |
| try( PreparedStatement sel_pstmt = conn.prepareStatement("select id, value from host_details where name = 'password'"); |
| ResultSet rs = sel_pstmt.executeQuery(); |
| PreparedStatement pstmt = conn.prepareStatement("update host_details set value=? where id=?"); |
| ) { |
| while (rs.next()) { |
| long id = rs.getLong(1); |
| String value = rs.getString(2); |
| if (value == null || value.isEmpty()) { |
| continue; |
| } |
| String encryptedValue = migrateValue(value); |
| pstmt.setBytes(1, encryptedValue.getBytes("UTF-8")); |
| pstmt.setLong(2, id); |
| pstmt.executeUpdate(); |
| } |
| } catch (SQLException e) { |
| throw new CloudRuntimeException("Unable update host_details values ", e); |
| } catch (UnsupportedEncodingException e) { |
| throw new CloudRuntimeException("Unable update host_details values ", e); |
| } |
| System.out.println("End migrate host details"); |
| } |
| |
| private void migrateVNCPassword(Connection conn) { |
| System.out.println("Begin migrate VNC password"); |
| try(PreparedStatement select_pstmt = conn.prepareStatement("select id, vnc_password from vm_instance"); |
| ResultSet rs = select_pstmt.executeQuery(); |
| PreparedStatement pstmt = conn.prepareStatement("update vm_instance set vnc_password=? where id=?"); |
| ) { |
| while (rs.next()) { |
| long id = rs.getLong(1); |
| String value = rs.getString(2); |
| if (value == null || value.isEmpty()) { |
| continue; |
| } |
| String encryptedValue = migrateValue(value); |
| |
| pstmt.setBytes(1, encryptedValue.getBytes("UTF-8")); |
| pstmt.setLong(2, id); |
| pstmt.executeUpdate(); |
| } |
| } catch (SQLException e) { |
| throw new CloudRuntimeException("Unable update vm_instance vnc_password ", e); |
| } catch (UnsupportedEncodingException e) { |
| throw new CloudRuntimeException("Unable update vm_instance vnc_password ", e); |
| } |
| System.out.println("End migrate VNC password"); |
| } |
| |
| private void migrateUserCredentials(Connection conn) { |
| System.out.println("Begin migrate user credentials"); |
| try(PreparedStatement select_pstmt = conn.prepareStatement("select id, secret_key from user"); |
| ResultSet rs = select_pstmt.executeQuery(); |
| PreparedStatement pstmt = conn.prepareStatement("update user set secret_key=? where id=?"); |
| ) { |
| while (rs.next()) { |
| long id = rs.getLong(1); |
| String secretKey = rs.getString(2); |
| if (secretKey == null || secretKey.isEmpty()) { |
| continue; |
| } |
| String encryptedSecretKey = migrateValue(secretKey); |
| pstmt.setBytes(1, encryptedSecretKey.getBytes("UTF-8")); |
| pstmt.setLong(2, id); |
| pstmt.executeUpdate(); |
| } |
| } catch (SQLException e) { |
| throw new CloudRuntimeException("Unable update user secret key ", e); |
| } catch (UnsupportedEncodingException e) { |
| throw new CloudRuntimeException("Unable update user secret key ", e); |
| } |
| System.out.println("End migrate user credentials"); |
| } |
| |
| private static void usage() { |
| System.out.println("Usage: \tEncryptionSecretKeyChanger \n" + "\t\t-m <Mgmt Secret Key> \n" + "\t\t-d <DB Secret Key> \n" + "\t\t-n [New Mgmt Secret Key] \n" |
| + "\t\t-e [New DB Secret Key]"); |
| } |
| } |