diff --git a/components/common/src/command/package/install.rs b/components/common/src/command/package/install.rs index c5fd9d1010..dc5def7735 100644 --- a/components/common/src/command/package/install.rs +++ b/components/common/src/command/package/install.rs @@ -32,6 +32,7 @@ use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; +use hcore::crypto; use hcore::fs::CACHE_ARTIFACT_PATH; use hcore::package::{PackageArchive, PackageIdent, PackageInstall}; use depot_core::data_object; @@ -75,7 +76,7 @@ pub fn from_archive>(url: &str, path: &P) -> Result<()> { for dep in try!(archive.tdeps()) { try!(install_from_depot(url, &dep, dep.as_ref())); } - try!(install_from_archive(archive, &ident)); + try!(install_from_archive(url, archive, &ident)); Ok(()) } @@ -98,7 +99,7 @@ fn install_from_depot>(url: &str, ident.as_ref(), CACHE_ARTIFACT_PATH)); let ident = try!(archive.ident()); - try!(archive.verify()); + try!(verify(url, &archive, &ident)); try!(archive.unpack()); println!("Installed {}", ident); } @@ -106,15 +107,40 @@ fn install_from_depot>(url: &str, Ok(()) } -fn install_from_archive(archive: PackageArchive, ident: &PackageIdent) -> Result<()> { +fn install_from_archive(url: &str, archive: PackageArchive, ident: &PackageIdent) -> Result<()> { match PackageInstall::load(ident.as_ref(), None) { Ok(_) => { println!("Package {} already installed", ident); } Err(_) => { + try!(verify(url, &archive, &ident)); try!(archive.unpack()); println!("Installed {}", ident); } } Ok(()) } + +/// get the signer for the artifact and see if we have the key locally. +/// If we don't, attempt to download it from the depot. +fn verify(url: &str, archive: &PackageArchive, ident: &PackageIdent) -> Result<()> { + let crypto_ctx = crypto::Context::default(); + let (signer_origin, signer_revision) = try!(crypto_ctx.get_artifact_signer(&archive.path)); + let signer_key_with_rev = format!("{}-{}", signer_origin, signer_revision); + + if let Err(_) = crypto_ctx.get_sig_public_key(&signer_key_with_rev) { + // we don't have the key locally, so try and download it before verification + println!("Can't find {} origin key in local key cache, fetching it from the depot", &signer_key_with_rev); + try!(depot_client::get_origin_key(url, + &signer_origin, + &signer_revision, + &crypto::nacl_key_dir())); + } + + try!(archive.verify()); + println!("Successful verification of {}/{} signed by {}", + &ident.origin, + &ident.name, + &signer_key_with_rev); + Ok(()) +} diff --git a/components/core/src/crypto.rs b/components/core/src/crypto.rs index 1a09bc8d30..62465e4fb6 100644 --- a/components/core/src/crypto.rs +++ b/components/core/src/crypto.rs @@ -15,9 +15,12 @@ //! - All public keys, certificates, and signatures are to be referred to as **public**. //! - All secret or private keys are to be referred to as **secret**. //! - All symmetric encryption keys are to be referred to as **secret**. -//! - The word `key` by itself does not indicate whether it is **public** or **secret**. The only -//! exception is if the word `key` appears as part of a file suffix, where it is then considered as -//! a **secret key** file. +//! - In general, the word `key` by itself does not indicate something as +//! **public** or **secret**. The exceptions to this rule are as follows: +//! - if the word key appears in a URL, then we are referring to a public key to +//! conform to other API's that offer similar public key downloading functionality. +//! - the word `key` appears as part of a file suffix, where it is then considered as +//! a **secret key** file. //! - Referring to keys (by example): //! - A key name: `habitat` //! - A key rev: `201603312016` @@ -313,7 +316,8 @@ pub type SigKeyPair = KeyPair; pub type BoxKeyPair = KeyPair; pub type SymKey = KeyPair<(), SymSecretKey>; -enum KeyType { +#[derive(PartialEq, Eq)] +pub enum KeyType { Sig, Box, Sym, @@ -332,10 +336,83 @@ fn env_var_or_default(env_var: &str, default: &str) -> String { /// Return the canonical location for nacl keys /// This value can be overridden via CACHE_KEY_PATH_ENV_VAR, /// which is useful for testing -fn nacl_key_dir() -> String { +pub fn nacl_key_dir() -> String { env_var_or_default(CACHE_KEY_PATH_ENV_VAR, CACHE_KEY_PATH) } +/// takes a Path to a key, and returns the origin and revision in a tuple +/// ex: /src/foo/core-xyz-20160423193745.pub yields ("core", "20160423193745") +/// TODO DP: this should be in a crypto::utils package +pub fn parse_origin_key_filename>(keyfile: P) -> Result<(String, String)> { + let stem = match keyfile.as_ref().file_stem().and_then(|s| s.to_str()) { + Some(s) => s, + None => return Err(Error::CryptoError("Can't parse key filename".to_string())) + }; + + parse_origin_key_name(stem) +} + +/// takes a string in the form origin-revision and returns a +/// tuple in the form (origin, revision) +pub fn parse_origin_key_name(origin_rev: &str) -> Result<(String, String)> { + let mut chunks: Vec<&str> = origin_rev.split("-").collect(); + if chunks.len() < 2 { + return Err(Error::CryptoError("Invalid origin key name".to_string())) + } + let rev = match chunks.pop() { + Some(r) => r, + None => return Err(Error::CryptoError("Invalid origin key revision".to_string())) + }; + let origin = chunks.join("-").trim().to_owned(); + Ok((origin, rev.trim().to_owned())) +} + +#[test] +fn test_parse_origin_key_name() { + assert!(parse_origin_key_name("foo").is_ok() == false); + match parse_origin_key_name("foo-20160423193745\n") { + Ok((origin, rev)) => { + assert!(origin == "foo"); + assert!(rev == "20160423193745"); + } + Err(_) => panic!("Fail!") + }; + + + match parse_origin_key_name("foo-bar-baz-20160423193745") { + Ok((origin, rev)) => { + assert!(origin == "foo-bar-baz"); + assert!(rev == "20160423193745"); + } + Err(_) => panic!("Fail!") + }; +} + +#[test] +fn test_parse_origin_key_filename() { + if parse_origin_key_filename(Path::new("/tmp/foo.pub")).is_ok() { + panic!("Shouldn't match") + }; + + + match parse_origin_key_filename(Path::new("/tmp/core-20160423193745.pub")) { + Ok((origin, rev)) => { + assert!(origin == "core"); + assert!(rev == "20160423193745"); + } + Err(_) => panic!("Bad filename") + }; + + match parse_origin_key_filename(Path::new("/tmp/multi-dash-origin-20160423193745.pub")) { + Ok((origin, rev)) => { + assert!(origin == "multi-dash-origin"); + assert!(rev == "20160423193745"); + } + Err(_) => panic!("Bad filename") + }; +} + + /// A Context makes crypto operations available centered on a given /// key cache directory. #[derive(Debug)] @@ -446,6 +523,23 @@ impl Context { Ok(reader) } + /// opens up a .hart file and returns the name of the (key, revision) + /// of the signer + pub fn get_artifact_signer>(&self, infilename: P) -> Result<(String, String)> { + let f = try!(File::open(infilename)); + let mut your_format_version = String::new(); + let mut your_key_name = String::new(); + let mut reader = BufReader::new(f); + if try!(reader.read_line(&mut your_format_version)) <= 0 { + return Err(Error::CryptoError("Can't read format version".to_string())); + } + if try!(reader.read_line(&mut your_key_name)) <= 0 { + return Err(Error::CryptoError("Can't read keyname".to_string())); + } + let origin_rev_pair = try!(parse_origin_key_name(&your_key_name)); + Ok(origin_rev_pair) + } + /// verify the crypto signature of a .hart file pub fn artifact_verify(&self, infilename: &str) -> Result<()> { diff --git a/components/depot-client/src/error.rs b/components/depot-client/src/error.rs index 429ac4939a..bc8de999e1 100644 --- a/components/depot-client/src/error.rs +++ b/components/depot-client/src/error.rs @@ -22,6 +22,7 @@ pub enum Error { IO(io::Error), NoFilePart, NoXFilename, + RemoteOriginKeyNotFound(String), RemotePackageNotFound(package::PackageIdent), WriteSyncFailed, } @@ -40,6 +41,7 @@ impl fmt::Display for Error { not have one") } Error::NoXFilename => format!("Invalid download from a Depot - missing X-Filename header"), + Error::RemoteOriginKeyNotFound(ref e) => format!("{}", e), Error::RemotePackageNotFound(ref pkg) => { if pkg.fully_qualified() { format!("Cannot find package in any sources: {}", pkg) @@ -62,6 +64,7 @@ impl error::Error for Error { Error::IO(ref err) => err.description(), Error::NoFilePart => "An invalid path was passed - we needed a filename, and this path does not have one", Error::NoXFilename => "Invalid download from a Depot - missing X-Filename header", + Error::RemoteOriginKeyNotFound(_) => "Remote origin key not found", Error::RemotePackageNotFound(_) => "Cannot find a package in any sources", Error::WriteSyncFailed => "Could not write to destination; bytes written was 0 on a non-0 buffer", } diff --git a/components/depot-client/src/lib.rs b/components/depot-client/src/lib.rs index 756ec555db..1e3cd93915 100644 --- a/components/depot-client/src/lib.rs +++ b/components/depot-client/src/lib.rs @@ -35,9 +35,38 @@ use rustc_serialize::json; /// * Key cannot be found /// * Remote Depot is not available /// * File cannot be created and written to -pub fn fetch_key(depot: &str, key: &str, path: &str) -> Result { - let url = Url::parse(&format!("{}/keys/{}", depot, key)).unwrap(); - download(key, url, path) +pub fn get_origin_key(depot: &str, origin: &str, revision: &str, path: &str) -> Result { + let url = Url::parse(&format!("{}/origins/{}/keys/{}", depot, origin, revision)).unwrap(); + debug!("get_origin_key URL = {}", &url); + let fname = format!("{}/{}-{}.pub", &path, &origin, &revision); + debug!("Output filename = {}", &fname); + download(&fname, url, path) +} + +/// Download all public keys for a given origin. +/// +/// # Failures +/// +/// * Origin cannot be found +/// * Remote Depot is not available +/// * File write errors +pub fn get_origin_keys(depot: &str, origin: &str, path: &str) -> Result<()> { + let url = format!("{}/origins/{}/keys", depot, origin); + debug!("URL = {}", &url); + let client = Client::new(); + let request = client.get(&url); + let mut res = try!(request.send()); + if res.status != hyper::status::StatusCode::Ok { + return Err(Error::RemoteOriginKeyNotFound(origin.to_string())) + }; + + let mut encoded = String::new(); + try!(res.read_to_string(&mut encoded)); + let revisions: Vec = json::decode(&encoded).unwrap(); + for rev in &revisions { + try!(get_origin_key(depot, origin, &rev.revision, path)); + } + Ok(()) } /// Download the latest release of a package. @@ -92,16 +121,15 @@ pub fn show_package(depot: &str, ident: &PackageIdent) -> Result Result<()> { +pub fn post_origin_key(depot: &str, origin: &str, revision: &str, path: &Path) -> Result<()> { let mut file = try!(File::open(path)); - let file_name = try!(path.file_name().ok_or(Error::NoFilePart)); - let url = Url::parse(&format!("{}/keys/{}", depot, file_name.to_string_lossy())).unwrap(); + let url = Url::parse(&format!("{}/origins/{}/keys/{}", depot, origin, revision)).unwrap(); upload(url, &mut file) } diff --git a/components/depot-core/src/data_object.rs b/components/depot-core/src/data_object.rs index 331216f947..9f3783e3ad 100644 --- a/components/depot-core/src/data_object.rs +++ b/components/depot-core/src/data_object.rs @@ -218,3 +218,34 @@ impl AsRef for Package { self.ident.as_ref() } } + + +#[derive(RustcEncodable, RustcDecodable, Eq, PartialEq, Debug, Clone)] +pub struct OriginKeyIdent { + pub origin: String, + pub revision: String, + pub location: String, +} + +impl OriginKeyIdent { + pub fn new(origin: String, revision: String, location: String) -> OriginKeyIdent { + OriginKeyIdent { + origin: origin, + revision: revision, + location: location, + } + } +} + +impl fmt::Display for OriginKeyIdent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}-{}", self.origin, self.revision) + } +} + +impl AsRef for OriginKeyIdent { + fn as_ref(&self) -> &OriginKeyIdent { + self + } +} + diff --git a/components/depot-core/src/lib.rs b/components/depot-core/src/lib.rs index f042f3b6b7..7e2507e2a4 100644 --- a/components/depot-core/src/lib.rs +++ b/components/depot-core/src/lib.rs @@ -13,5 +13,15 @@ extern crate rustc_serialize; pub mod data_object; +use hyper::header::{Headers, ContentDisposition, DispositionType, DispositionParam, Charset}; + header! { (XFileName, "X-Filename") => [String] } header! { (ETag, "ETag") => [String] } + +/// convenience function for setting Content-Disposition +pub fn set_disposition(headers: &mut Headers, filename: String, charset: Charset) -> () { + headers.set(ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::Filename( charset, None, filename.into_bytes())], + }); +} diff --git a/components/depot/doc/api.raml b/components/depot/doc/api.raml new file mode 100644 index 0000000000..4866558106 --- /dev/null +++ b/components/depot/doc/api.raml @@ -0,0 +1,167 @@ +#%RAML 1.0 +title: Depot API +version: v1 +baseUri: http://api.samplehost.com +# in alphabetical order +/origins: + /{origin}: + /keys: + get: + description: Return a list of key revisions for an organization. + responses: + 200: + body: + application/json: + example: | + [ + { + "origin": "core", + "revision": "20160423193732", + "location": "/origins/core/keys/20160423193732" + }, + { + "origin": "core", + "revision": "20160423193733", + "location": "/origins/core/keys/20160423193733" + } + ] + /{revision}: + get: + description: Get a key revision for a specific origin + responses: + 200: + body: + text/plain: + example: | + SIG-PUB-1 + core-20160423193745 + + Jpmj1gD9oTFCgz3wSLltt/QB6RTmNRWoUTe+xhDTIHc= + post: + description: Upload a new key revision for an origin + body: + text/plain: + example: | + SIG-PUB-1 + core-20160423193745 + + Jpmj1gD9oTFCgz3wSLltt/QB6RTmNRWoUTe+xhDTIHc= + +/pkgs: + /{origin}: + get: + description: List packages for an origin + responses: + 200: + 400: + 404: + 500: + /{pkg}: + description: TODO + get: + description: TODO + responses: + 200: + 400: + 404: + 500: + /latest: + get: + description: TODO + responses: + 200: + 404: + 500: + /{version}: + get: + description: TODO + responses: + 200: + 400: + 404: + 500: + /latest: + get: + responses: + 200: + 404: + 500: + /{release}: + get: + responses: + 200: + 404: + 500: + post: + responses: + 200: + 400: + 422: + 409: + /download: + get: + responses: + 200: + 400: + 500: +/views: + get: + description: List all views + responses: + 200: + body: + application/json: + example: | + {} + /{view}: + /pkgs: + /{origin}: + get: + description: List packages for an origin + responses: + 200: + description: Return a list of packages for an origin + 400: + description: Origin not supplied + 404: + description: Origin does not exist + 500: + description: Datastore error + /{pkg}: + get: + description: TODO + responses: + 200: + description: Return a list of packages for an origin + 400: + description: Origin not supplied + 404: + description: Origin does not exist + 500: + description: Datastore error + /latest: + get: + responses: + 200: + 404: + 500: + /{version}: + get: + /latest: + get: + responses: + 200: + 404: + 500: + /{release}: + get: + responses: + 200: + 404: + 500: + /promote: + post: + responses: + 200: + 404: + 500: diff --git a/components/depot/src/data_store.rs b/components/depot/src/data_store.rs index 44028f3d5a..76466342e7 100644 --- a/components/depot/src/data_store.rs +++ b/components/depot/src/data_store.rs @@ -22,6 +22,7 @@ pub struct DataStore { pub pool: Arc, pub packages: PackagesTable, pub views: ViewsTable, + pub origin_keys: OriginKeysTable, } impl DataStore { @@ -32,12 +33,15 @@ impl DataStore { let pool = Arc::new(ConnectionPool::new(pool_cfg, manager).unwrap()); let pool1 = pool.clone(); let pool2 = pool.clone(); + let pool3 = pool.clone(); let packages = PackagesTable::new(pool1); let views = ViewsTable::new(pool2); + let origin_keys = OriginKeysTable::new(pool3); Ok(DataStore { pool: pool, packages: packages, views: views, + origin_keys: origin_keys, }) } @@ -332,3 +336,75 @@ impl Table for ViewPkgIndex { "view:pkg:index" } } + + + +pub struct OriginKeysTable { + pool: Arc, +} + +impl OriginKeysTable { + pub fn new(pool: Arc) -> Self { + OriginKeysTable { pool: pool } + } + + pub fn all(&self, origin: &str) -> Result> { + let conn = self.pool().get().unwrap(); + match conn.smembers::>(Self::key(&origin.to_string())) { + Ok(ids) => { + let ids = ids.iter() + .map(|rev| { + data_object::OriginKeyIdent::new(origin.to_string(), + rev.clone(), + format!("/origins/{}/keys/{}", + &origin, &rev)) + }) + .collect(); + Ok(ids) + } + Err(e) => Err(Error::from(e)), + } + } + + pub fn write(&self, origin: &str, revision: &str) -> Result<()> { + let conn = self.pool().get().unwrap(); + try!(conn.sadd(OriginKeysTable::key(&origin.to_string()), revision)); + Ok(()) + } + + /// return the latest revision for a given origin key + pub fn latest(&self, origin: &str) -> Result { + let conn = self.pool().get().unwrap(); + let key = OriginKeysTable::key(&origin.to_string()); + + match redis::cmd("SORT") + .arg(key) + .arg("LIMIT") + .arg(0) + .arg(1) + .arg("ALPHA") + .arg("DESC") + .query::>(conn.deref()) { + Ok(ids) => { + if ids.is_empty() { + return Err(Error::DataStore(dbcache::Error::EntityNotFound)); + } + Ok(ids[0].to_string()) + } + Err(e) => Err(Error::from(e)), + } + } + +} + +impl Table for OriginKeysTable { + type IdType = String; + + fn pool(&self) -> &ConnectionPool { + &self.pool + } + + fn prefix() -> &'static str { + "origin_keys" + } +} diff --git a/components/depot/src/lib.rs b/components/depot/src/lib.rs index 76091715c8..a471c31c46 100644 --- a/components/depot/src/lib.rs +++ b/components/depot/src/lib.rs @@ -92,8 +92,16 @@ impl Depot { ident.release.as_ref().unwrap())) } - fn key_path(&self, name: &str) -> PathBuf { - self.keys_path().join(format!("{}.asc", name)) + fn key_path(&self, key: &str, rev: &str) -> PathBuf { + let mut digest = Sha256::new(); + let mut output = [0; 64]; + let key_with_rev = format!("{}-{}.pub", key, rev); + digest.input_str(&key_with_rev.to_string()); + digest.result(&mut output); + self.keys_path() + .join(format!("{:x}", output[0])) + .join(format!("{:x}", output[1])) + .join(format!("{}-{}.pub", key, rev)) } fn keys_path(&self) -> PathBuf { diff --git a/components/depot/src/server.rs b/components/depot/src/server.rs index e020a838fa..ecf8f849f6 100644 --- a/components/depot/src/server.rs +++ b/components/depot/src/server.rs @@ -10,7 +10,7 @@ use std::io::{Read, Write, BufWriter}; use std::path::PathBuf; use dbcache; -use depot_core::{ETag, XFileName}; +use depot_core::{ETag, XFileName, set_disposition}; use depot_core::data_object::{self, DataObject}; use iron::prelude::*; use iron::{status, headers, AfterMiddleware}; @@ -55,19 +55,35 @@ fn write_file(filename: &PathBuf, body: &mut Body) -> Result { Ok(true) } -fn upload_key(depot: &Depot, req: &mut Request) -> IronResult { - debug!("Upload Key {:?}", req); - let rext = req.extensions.get::().unwrap(); - let key = rext.find("key").unwrap(); - let file = depot.key_path(&key); +fn upload_origin_key(depot: &Depot, req: &mut Request) -> IronResult { + debug!("Upload Origin Key {:?}", req); + let params = req.extensions.get::().unwrap(); - try!(write_file(&file, &mut req.body)); + let origin = match params.find("origin") { + Some(origin) => origin, + None => return Ok(Response::with(status::BadRequest)), + }; - let short_name = file.file_name().unwrap().to_string_lossy(); - let mut response = Response::with((status::Created, format!("/key/{}", &short_name))); + let revision = match params.find("revision") { + Some(revision) => revision, + None => return Ok(Response::with(status::BadRequest)), + }; + + let origin_keyfile = depot.key_path(&origin, &revision); + debug!("Writing key file {}", origin_keyfile.to_string_lossy()); + if origin_keyfile.is_file() { + return Ok(Response::with(status::Conflict)); + } + + depot.datastore.origin_keys.write(&origin, &revision).unwrap(); + + try!(write_file(&origin_keyfile, &mut req.body)); + + let mut response = Response::with((status::Created, + format!("/origins/{}/keys/{}", &origin, &revision))); let mut base_url = req.url.clone(); - base_url.path = vec![String::from("key"), String::from(key)]; + base_url.path = vec![String::from("key"), format!("{}-{}", &origin, &revision)]; response.headers.set(headers::Location(format!("{}", base_url))); Ok(response) } @@ -140,21 +156,77 @@ fn upload_package(depot: &Depot, req: &mut Request) -> IronResult { } } -fn download_key(depot: &Depot, req: &mut Request) -> IronResult { - debug!("Download {:?}", req); - let rext = req.extensions.get::().unwrap(); +fn download_origin_key(depot: &Depot, req: &mut Request) -> IronResult { + debug!("Download origin key {:?}", req); + let params = req.extensions.get::().unwrap(); - let key = match rext.find("key") { - Some(key) => key, + let origin = match params.find("origin") { + Some(origin) => origin, None => return Ok(Response::with(status::BadRequest)), }; - let short_filename = format!("{}.asc", key); - let filename = depot.keys_path().join(&short_filename); + let revision = match params.find("revision") { + Some(revision) => revision, + None => return Ok(Response::with(status::BadRequest)), + }; + debug!("Trying to retreive origin key {}-{}", &origin, &revision); + let origin_keyfile = depot.key_path(&origin, &revision); + debug!("Looking for {}", &origin_keyfile.to_string_lossy()); + match origin_keyfile.metadata() { + Ok(md) => { + if !md.is_file() { + return Ok(Response::with(status::NotFound)); + }; + } + Err(e) => { + println!("Can't read key file {}: {}", + &origin_keyfile.to_string_lossy(), + e); + return Ok(Response::with(status::NotFound)); + } + }; + + let xfilename = origin_keyfile.file_name().unwrap().to_string_lossy().into_owned(); + let mut response = Response::with((status::Ok, origin_keyfile)); + response.headers.set(XFileName(xfilename.clone())); + set_disposition(&mut response.headers, + xfilename, + headers::Charset::Ext("utf-8".to_string())); + Ok(response) +} + +fn download_latest_origin_key(depot: &Depot, req: &mut Request) -> IronResult { + debug!("Download latest origin key {:?}", req); + let params = req.extensions.get::().unwrap(); - let mut response = Response::with((status::Ok, filename)); - response.headers.set(XFileName(short_filename.clone())); + let origin = match params.find("origin") { + Some(origin) => origin, + None => return Ok(Response::with(status::BadRequest)), + }; + debug!("Trying to retreive latest origin key for {}", &origin); + let latest_rev = depot.datastore.origin_keys.latest(&origin).unwrap(); + let origin_keyfile = depot.key_path(&origin, &latest_rev); + debug!("Looking for {}", &origin_keyfile.to_string_lossy()); + match origin_keyfile.metadata() { + Ok(md) => { + if !md.is_file() { + return Ok(Response::with(status::NotFound)); + }; + } + Err(e) => { + println!("Can't read key file {}: {}", + &origin_keyfile.to_string_lossy(), + e); + return Ok(Response::with(status::NotFound)); + } + }; + let xfilename = origin_keyfile.file_name().unwrap().to_string_lossy().into_owned(); + let mut response = Response::with((status::Ok, origin_keyfile)); + response.headers.set(XFileName(xfilename.clone())); + set_disposition(&mut response.headers, + xfilename, + headers::Charset::Ext("utf-8".to_string())); Ok(response) } @@ -170,6 +242,10 @@ fn download_package(depot: &Depot, req: &mut Request) -> IronResult { Ok(_) => { let mut response = Response::with((status::Ok, archive.path.clone())); response.headers.set(XFileName(archive.file_name())); + + set_disposition(&mut response.headers, + archive.file_name(), + headers::Charset::Ext("utf-8".to_string())); Ok(response) } Err(_) => Ok(Response::with(status::NotFound)), @@ -190,6 +266,26 @@ fn download_package(depot: &Depot, req: &mut Request) -> IronResult { } } +fn list_origin_keys(depot: &Depot, req: &mut Request) -> IronResult { + let params = req.extensions.get::().unwrap(); + let origin = match params.find("origin") { + Some(origin) => origin, + None => return Ok(Response::with(status::BadRequest)), + }; + + match depot.datastore.origin_keys.all(origin) { + Ok(revisions) => { + let body = json::encode(&revisions).unwrap(); + Ok(Response::with((status::Ok, body))) + } + Err(e) => { + error!("list_origin_keys:1, err={:?}", e); + Ok(Response::with(status::InternalServerError)) + } + } + +} + fn list_packages(depot: &Depot, req: &mut Request) -> IronResult { let params = req.extensions.get::().unwrap(); let ident: String = if params.find("pkg").is_none() { @@ -407,31 +503,36 @@ pub fn router(config: Config) -> Result { let depot16 = depot.clone(); let depot17 = depot.clone(); let depot18 = depot.clone(); + let depot19 = depot.clone(); + let depot20 = depot.clone(); let router = router!( get "/views" => move |r: &mut Request| list_views(&depot1, r), - get "/views/:view/pkgs/:origin" => move |r: &mut Request| list_packages(&depot18, r), - get "/views/:view/pkgs/:origin/:pkg" => move |r: &mut Request| list_packages(&depot2, r), - get "/views/:view/pkgs/:origin/:pkg/latest" => move |r: &mut Request| show_package(&depot3, r), - get "/views/:view/pkgs/:origin/:pkg/:version" => move |r: &mut Request| list_packages(&depot4, r), - get "/views/:view/pkgs/:origin/:pkg/:version/latest" => move |r: &mut Request| show_package(&depot5, r), - get "/views/:view/pkgs/:origin/:pkg/:version/:release" => move |r: &mut Request| show_package(&depot6, r), - - post "/views/:view/pkgs/:origin/:pkg/:version/:release/promote" => move |r: &mut Request| promote_package(&depot7, r), - - get "/pkgs/:origin" => move |r: &mut Request| list_packages(&depot8, r), - get "/pkgs/:origin/:pkg" => move |r: &mut Request| list_packages(&depot9, r), - get "/pkgs/:origin/:pkg/latest" => move |r: &mut Request| show_package(&depot10, r), - get "/pkgs/:origin/:pkg/:version" => move |r: &mut Request| list_packages(&depot11, r), - get "/pkgs/:origin/:pkg/:version/latest" => move |r: &mut Request| show_package(&depot12, r), - get "/pkgs/:origin/:pkg/:version/:release" => move |r: &mut Request| show_package(&depot13, r), - - get "/pkgs/:origin/:pkg/:version/:release/download" => move |r: &mut Request| download_package(&depot14, r), - post "/pkgs/:origin/:pkg/:version/:release" => move |r: &mut Request| upload_package(&depot15, r), - - post "/keys/:key" => move |r: &mut Request| upload_key(&depot16, r), - get "/keys/:key" => move |r: &mut Request| download_key(&depot17, r) - ); + get "/views/:view/pkgs/:origin" => move |r: &mut Request| list_packages(&depot2, r), + get "/views/:view/pkgs/:origin/:pkg" => move |r: &mut Request| list_packages(&depot3, r), + get "/views/:view/pkgs/:origin/:pkg/latest" => move |r: &mut Request| show_package(&depot4, r), + get "/views/:view/pkgs/:origin/:pkg/:version" => move |r: &mut Request| list_packages(&depot5, r), + get "/views/:view/pkgs/:origin/:pkg/:version/latest" => move |r: &mut Request| show_package(&depot6, r), + get "/views/:view/pkgs/:origin/:pkg/:version/:release" => move |r: &mut Request| show_package(&depot7, r), + + post "/views/:view/pkgs/:origin/:pkg/:version/:release/promote" => move |r: &mut Request| promote_package(&depot8, r), + + get "/pkgs/:origin" => move |r: &mut Request| list_packages(&depot9, r), + get "/pkgs/:origin/:pkg" => move |r: &mut Request| list_packages(&depot10, r), + get "/pkgs/:origin/:pkg/latest" => move |r: &mut Request| show_package(&depot11, r), + get "/pkgs/:origin/:pkg/:version" => move |r: &mut Request| list_packages(&depot12, r), + get "/pkgs/:origin/:pkg/:version/latest" => move |r: &mut Request| show_package(&depot13, r), + get "/pkgs/:origin/:pkg/:version/:release" => move |r: &mut Request| show_package(&depot14, r), + + get "/pkgs/:origin/:pkg/:version/:release/download" => move |r: &mut Request| download_package(&depot15, r), + post "/pkgs/:origin/:pkg/:version/:release" => move |r: &mut Request| upload_package(&depot16, r), + + + get "/origins/:origin/keys" => move |r: &mut Request| list_origin_keys(&depot17, r), + get "/origins/:origin/keys/latest" => move |r: &mut Request| download_latest_origin_key(&depot19, r), + get "/origins/:origin/keys/:revision" => move |r: &mut Request| download_origin_key(&depot18, r), + post "/origins/:origin/keys/:revision" => move |r: &mut Request| upload_origin_key(&depot20, r) + ); let mut chain = Chain::new(router); chain.link_after(Cors); Ok(chain) diff --git a/components/depot/tests/support/setup.rs b/components/depot/tests/support/setup.rs index 436b40fee0..79a35e8541 100644 --- a/components/depot/tests/support/setup.rs +++ b/components/depot/tests/support/setup.rs @@ -9,7 +9,7 @@ use std::sync::{Once, ONCE_INIT}; use std::env; pub fn origin_setup() { - env::set_var("HABITAT_KEY_CACHE", super::path::key_cache()); + env::set_var("HAB_CACHE_KEY_PATH", super::path::key_cache()); } pub fn simple_service() { @@ -29,15 +29,7 @@ pub fn simple_service() { } pub fn key_install() { - static ONCE: Once = ONCE_INIT; - ONCE.call_once(|| { - let mut cmd = match super::command::sup(&["key", - &super::path::fixture_as_string("chef-public.asc")]) { - Ok(cmd) => cmd, - Err(e) => panic!("{:?}", e), - }; - cmd.wait_with_output(); - }); + // TODO DP: is there a relatively static pub key I can use? } fn dockerize(ident_str: &str) { diff --git a/components/hab/src/cli.rs b/components/hab/src/cli.rs index 273d35ff00..9a86377be0 100644 --- a/components/hab/src/cli.rs +++ b/components/hab/src/cli.rs @@ -91,6 +91,19 @@ pub fn get() -> App<'static, 'static> { (about: "Generates a Habitat origin key") (@arg ORIGIN: "The origin name") ) + (@subcommand download => + (about: "Download origin key(s) to HAB_CACHE_KEY_PATH") + (@arg ORIGIN: +required "The origin name") + (@arg REVISION: "The key revision") + (@arg DEPOT_URL: -u --url +takes_value {valid_url} + "Use a specific Depot URL") + ) + (@subcommand upload => + (about: "Upload a public origin key to the depot") + (@arg FILE: +required {file_exists} "Path to a local public origin key file on disk") + (@arg DEPOT_URL: -u --url +takes_value {valid_url} + "Use a specific Depot URL") + ) ) ) (@subcommand service => diff --git a/components/hab/src/command/artifact/crypto.rs b/components/hab/src/command/artifact/crypto.rs index 3bb2692527..e98045c6fe 100644 --- a/components/hab/src/command/artifact/crypto.rs +++ b/components/hab/src/command/artifact/crypto.rs @@ -4,9 +4,34 @@ // this file ("Licensee") apply to Licensee's use of the Software until such time that the Software // is made available under an open source license such as the Apache 2.0 License. +use std::path::{Path}; + +use depot_client; use error::{Error, Result}; use hcore::crypto; + +pub fn download_origin_keys(depot: &str, origin: &str, revision: Option<&str>) -> Result<()> { + let outdir = crypto::nacl_key_dir(); + match revision { + Some(rev) => { + try!(depot_client::get_origin_key(depot, origin, rev, &outdir)); + } + None => { + try!(depot_client::get_origin_keys(depot, origin, &outdir)); + } + }; + println!("Successfully downloaded origin key(s)"); + Ok(()) +} + +pub fn upload_origin_key(depot: &str, keyfile: &Path) -> Result<()> { + let (origin, revision) = try!(crypto::parse_origin_key_filename(keyfile)); + try!(depot_client::post_origin_key(depot, &origin, &revision, keyfile)); + println!("Successfully uploaded origin key"); + Ok(()) +} + pub fn generate_origin_key(origin: &str) -> Result<()> { let crypto_ctx = crypto::Context::default(); let keyname = try!(crypto_ctx.generate_origin_sig_key(origin)); diff --git a/components/hab/src/main.rs b/components/hab/src/main.rs index 86fcf5a753..d49b34160e 100644 --- a/components/hab/src/main.rs +++ b/components/hab/src/main.rs @@ -96,7 +96,9 @@ fn run_hab() -> Result<()> { match matches.subcommand() { ("key", Some(m)) => { match m.subcommand() { + ("download", Some(sc)) => try!(sub_origin_key_download(sc)), ("generate", Some(sc)) => try!(sub_origin_key_generate(sc)), + ("upload", Some(sc)) => try!(sub_origin_key_upload(sc)), _ => unreachable!(), } } @@ -265,12 +267,30 @@ fn sub_file_upload(m: &ArgMatches) -> Result<()> { Ok(()) } +fn sub_origin_key_download(m: &ArgMatches) -> Result<()> { + let origin = m.value_of("ORIGIN").unwrap(); + let revision = m.value_of("REVISION"); + let env_or_default = henv::var(DEPOT_URL_ENVVAR).unwrap_or(DEFAULT_DEPOT_URL.to_string()); + let url = m.value_of("DEPOT_URL").unwrap_or(&env_or_default); + try!(command::artifact::crypto::download_origin_keys(&url, &origin, revision)); + Ok(()) +} + fn sub_origin_key_generate(m: &ArgMatches) -> Result<()> { let origin = try!(origin_param_or_env(&m)); try!(command::artifact::crypto::generate_origin_key(&origin)); Ok(()) } +fn sub_origin_key_upload(m: &ArgMatches) -> Result<()> { + let env_or_default = henv::var(DEPOT_URL_ENVVAR).unwrap_or(DEFAULT_DEPOT_URL.to_string()); + let url = m.value_of("DEPOT_URL").unwrap_or(&env_or_default); + // required field + let keyfile = Path::new(m.value_of("FILE").unwrap()); + try!(command::artifact::crypto::upload_origin_key(url, &keyfile)); + Ok(()) +} + fn sub_package_install(m: &ArgMatches) -> Result<()> { let env_or_default = henv::var(DEPOT_URL_ENVVAR).unwrap_or(DEFAULT_DEPOT_URL.to_string()); let url = m.value_of("DEPOT_URL").unwrap_or(&env_or_default);