1 //! Support Krill upgrades, e.g.:
2 //! - Updating the format of commands or events
3 //! - Export / Import data
4 
5 use std::{fmt, path::Path, str::FromStr, sync::Arc};
6 
7 use serde::de::DeserializeOwned;
8 
9 use crate::{
10     commons::{
11         api::Handle,
12         crypto::KrillSigner,
13         error::KrillIoError,
14         eventsourcing::{AggregateStoreError, CommandKey, KeyStoreKey, KeyValueError, KeyValueStore},
15         util::{file, KrillVersion},
16     },
17     daemon::{config::Config, krillserver::KrillServer},
18     pubd::RepositoryManager,
19     upgrades::v0_9_0::{CaObjectsMigration, PubdObjectsMigration},
20 };
21 
22 pub mod v0_9_0;
23 
24 pub type UpgradeResult<T> = Result<T, UpgradeError>;
25 
26 pub const MIGRATION_SCOPE: &str = "migration";
27 
28 //------------ UpgradeError --------------------------------------------------
29 
30 #[derive(Debug)]
31 #[allow(clippy::large_enum_variant)]
32 pub enum UpgradeError {
33     AggregateStoreError(AggregateStoreError),
34     KeyStoreError(KeyValueError),
35     IoError(KrillIoError),
36     Unrecognised(String),
37     CannotLoadAggregate(Handle),
38     Custom(String),
39 }
40 
41 impl fmt::Display for UpgradeError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result42     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43         match self {
44             UpgradeError::AggregateStoreError(e) => e.fmt(f),
45             UpgradeError::KeyStoreError(e) => e.fmt(f),
46             UpgradeError::IoError(e) => e.fmt(f),
47             UpgradeError::Unrecognised(s) => write!(f, "Unrecognised command summary: {}", s),
48             UpgradeError::CannotLoadAggregate(handle) => write!(f, "Cannot load: {}", handle),
49             UpgradeError::Custom(s) => s.fmt(f),
50         }
51     }
52 }
53 impl UpgradeError {
custom(msg: impl fmt::Display) -> Self54     pub fn custom(msg: impl fmt::Display) -> Self {
55         UpgradeError::Custom(msg.to_string())
56     }
57 
unrecognised(msg: impl fmt::Display) -> Self58     pub fn unrecognised(msg: impl fmt::Display) -> Self {
59         UpgradeError::Unrecognised(msg.to_string())
60     }
61 }
62 
63 impl From<AggregateStoreError> for UpgradeError {
from(e: AggregateStoreError) -> Self64     fn from(e: AggregateStoreError) -> Self {
65         UpgradeError::AggregateStoreError(e)
66     }
67 }
68 
69 impl From<KeyValueError> for UpgradeError {
from(e: KeyValueError) -> Self70     fn from(e: KeyValueError) -> Self {
71         UpgradeError::KeyStoreError(e)
72     }
73 }
74 
75 impl From<KrillIoError> for UpgradeError {
from(e: KrillIoError) -> Self76     fn from(e: KrillIoError) -> Self {
77         UpgradeError::IoError(e)
78     }
79 }
80 
81 impl From<crate::commons::error::Error> for UpgradeError {
from(e: crate::commons::error::Error) -> Self82     fn from(e: crate::commons::error::Error) -> Self {
83         UpgradeError::Custom(e.to_string())
84     }
85 }
86 
87 impl std::error::Error for UpgradeError {}
88 
89 //------------ UpgradeStore --------------------------------------------------
90 
91 /// Implement this for automatic upgrades to key stores
92 pub trait UpgradeStore {
needs_migrate(&self) -> Result<bool, UpgradeError>93     fn needs_migrate(&self) -> Result<bool, UpgradeError>;
migrate(&self) -> Result<(), UpgradeError>94     fn migrate(&self) -> Result<(), UpgradeError>;
95 
version_before(kv: &KeyValueStore, later: KrillVersion) -> Result<bool, UpgradeError>96     fn version_before(kv: &KeyValueStore, later: KrillVersion) -> Result<bool, UpgradeError> {
97         kv.version_is_before(later).map_err(UpgradeError::KeyStoreError)
98     }
99 
store(&self) -> &KeyValueStore100     fn store(&self) -> &KeyValueStore;
101 
102     // Find all command keys and sort them by sequence.
103     // Then turn them back into key store keys for further processing.
command_keys(&self, scope: &str) -> Result<Vec<KeyStoreKey>, UpgradeError>104     fn command_keys(&self, scope: &str) -> Result<Vec<KeyStoreKey>, UpgradeError> {
105         let store = self.store();
106         let keys = store.keys(Some(scope.to_string()), "command--")?;
107         let mut cmd_keys: Vec<CommandKey> = vec![];
108         for key in keys {
109             let cmd_key = CommandKey::from_str(key.name()).map_err(|_| {
110                 UpgradeError::Custom(format!("Found invalid command key: {} for ca: {}", key.name(), scope))
111             })?;
112             cmd_keys.push(cmd_key);
113         }
114         cmd_keys.sort_by_key(|k| k.sequence);
115         let cmd_keys = cmd_keys
116             .into_iter()
117             .map(|ck| KeyStoreKey::scoped(scope.to_string(), format!("{}.json", ck)))
118             .collect();
119 
120         Ok(cmd_keys)
121     }
122 
get<V: DeserializeOwned>(&self, key: &KeyStoreKey) -> Result<V, UpgradeError>123     fn get<V: DeserializeOwned>(&self, key: &KeyStoreKey) -> Result<V, UpgradeError> {
124         self.store()
125             .get(key)?
126             .ok_or_else(|| UpgradeError::Custom(format!("Cannot read key: {}", key)))
127     }
128 
event_key(scope: &str, nr: u64) -> KeyStoreKey129     fn event_key(scope: &str, nr: u64) -> KeyStoreKey {
130         KeyStoreKey::scoped(scope.to_string(), format!("delta-{}.json", nr))
131     }
132 
archive_snapshots(&self, scope: &str) -> Result<(), UpgradeError>133     fn archive_snapshots(&self, scope: &str) -> Result<(), UpgradeError> {
134         let snapshot_key = KeyStoreKey::scoped(scope.to_string(), "snapshot.json".to_string());
135         let snapshot_bk_key = KeyStoreKey::scoped(scope.to_string(), "snapshot-bk.json".to_string());
136 
137         if self.store().has(&snapshot_key)? {
138             self.archive_to_migration_scope(&snapshot_key)?;
139         }
140 
141         if self.store().has(&snapshot_bk_key)? {
142             self.archive_to_migration_scope(&snapshot_bk_key)?;
143         }
144 
145         Ok(())
146     }
147 
archive_to_migration_scope(&self, key: &KeyStoreKey) -> Result<(), UpgradeError>148     fn archive_to_migration_scope(&self, key: &KeyStoreKey) -> Result<(), UpgradeError> {
149         self.store()
150             .archive_to(key, MIGRATION_SCOPE)
151             .map_err(UpgradeError::KeyStoreError)
152     }
153 
drop_migration_scope(&self, scope: &str) -> Result<(), UpgradeError>154     fn drop_migration_scope(&self, scope: &str) -> Result<(), UpgradeError> {
155         let scope = format!("{}/{}", scope, MIGRATION_SCOPE);
156         self.store().drop_scope(&scope).map_err(UpgradeError::KeyStoreError)
157     }
158 }
159 
160 /// Should be called when Krill starts, before the KrillServer is initiated
pre_start_upgrade(config: Arc<Config>) -> Result<(), UpgradeError>161 pub fn pre_start_upgrade(config: Arc<Config>) -> Result<(), UpgradeError> {
162     upgrade_data_to_0_9_0(config)
163 }
164 
165 /// Should be called when the KrillServer is initiated, before the webserver is started
166 /// and operators can make changes.
post_start_upgrade(config: &Config, server: &KrillServer) -> Result<(), UpgradeError>167 pub async fn post_start_upgrade(config: &Config, server: &KrillServer) -> Result<(), UpgradeError> {
168     if needs_upgrade(&config.data_dir, "cas", KrillVersion::candidate(0, 9, 3, 2)) {
169         info!("Reissue ROAs on upgrade to force short EE certificate subjects in the objects");
170         server.force_renew_roas().await.map_err(|e| e.into())
171     } else {
172         Ok(())
173     }
174 }
175 
update_storage_version(work_dir: &Path) -> Result<(), UpgradeError>176 pub async fn update_storage_version(work_dir: &Path) -> Result<(), UpgradeError> {
177     let current = KrillVersion::current();
178 
179     if needs_v0_9_0_upgrade(work_dir, "cas") {
180         debug!("Updating version file for cas");
181         file::save_json(&current, &work_dir.join("cas/version"))?;
182     }
183 
184     if needs_v0_9_0_upgrade(work_dir, "pubd") {
185         debug!("Updating version file for pubd");
186         file::save_json(&current, &work_dir.join("pubd/version"))?;
187     }
188 
189     Ok(())
190 }
191 
upgrade_data_to_0_9_0(config: Arc<Config>) -> Result<(), UpgradeError>192 fn upgrade_data_to_0_9_0(config: Arc<Config>) -> Result<(), UpgradeError> {
193     let work_dir = &config.data_dir;
194     if needs_v0_9_0_upgrade(work_dir, "pubd") {
195         PubdObjectsMigration::migrate(config.clone())?;
196     }
197 
198     if needs_v0_9_0_upgrade(work_dir, "cas") {
199         let signer = Arc::new(KrillSigner::build(work_dir)?);
200         let repo_manager = RepositoryManager::build(config.clone(), signer)?;
201 
202         CaObjectsMigration::migrate(config, repo_manager)?;
203     }
204 
205     Ok(())
206 }
207 
needs_v0_9_0_upgrade(work_dir: &Path, ns: &str) -> bool208 fn needs_v0_9_0_upgrade(work_dir: &Path, ns: &str) -> bool {
209     needs_upgrade(work_dir, ns, KrillVersion::release(0, 9, 0))
210 }
211 
needs_upgrade(work_dir: &Path, ns: &str, before: KrillVersion) -> bool212 fn needs_upgrade(work_dir: &Path, ns: &str, before: KrillVersion) -> bool {
213     let keystore_path = work_dir.join(ns);
214     if keystore_path.exists() {
215         let version_path = keystore_path.join("version");
216         let version_found = file::load_json(&version_path).unwrap_or_else(|_| KrillVersion::v0_5_0_or_before());
217         version_found < before
218     } else {
219         false
220     }
221 }
222 
223 //------------ Tests ---------------------------------------------------------
224 
225 #[cfg(test)]
226 mod tests {
227 
228     use std::{fs, path::PathBuf};
229 
230     use crate::commons::util::file;
231     use crate::test::tmp_dir;
232 
233     use super::*;
234 
235     #[test]
test_upgrade_0_8_1()236     fn test_upgrade_0_8_1() {
237         let work_dir = tmp_dir();
238         let source = PathBuf::from("test-resources/migrations/v0_8_1/");
239         file::backup_dir(&source, &work_dir).unwrap();
240 
241         let config = Arc::new(Config::test(&work_dir, false, false, false));
242         let _ = config.init_logging();
243 
244         upgrade_data_to_0_9_0(config).unwrap();
245 
246         let _ = fs::remove_dir_all(work_dir);
247     }
248 
249     #[test]
test_upgrade_0_6_0()250     fn test_upgrade_0_6_0() {
251         let work_dir = tmp_dir();
252         let source = PathBuf::from("test-resources/migrations/v0_6_0/");
253         file::backup_dir(&source, &work_dir).unwrap();
254 
255         let config = Arc::new(Config::test(&work_dir, false, false, false));
256         let _ = config.init_logging();
257 
258         upgrade_data_to_0_9_0(config).unwrap();
259 
260         let _ = fs::remove_dir_all(work_dir);
261     }
262 }
263