Proxying and deserializing the upstream manifest API

This commit is contained in:
R Tyler Croy 2020-09-12 10:29:42 -07:00
parent b3c953f427
commit 70b5a5371c
2 changed files with 56 additions and 22 deletions

View File

@ -4,9 +4,7 @@
extern crate config;
extern crate pretty_env_logger;
#[macro_use]
extern crate serde;
#[macro_use]
extern crate serde_json;
extern crate surf;
extern crate tide;
@ -83,6 +81,25 @@ impl AppState {
}
}
/**
* Make an authenticated GET request to the given registry
*/
async fn make_get_request(url: &str, authorization: &str, accepts: &str) -> Result<surf::Response, surf::Error> {
debug!("Making GET request to {}", url);
surf::get(url)
.set_header("Authorization", authorization)
.set_header("Accepts", accepts).await
}
/**
* Simple convenience function for grabbing the HeaderValue out of the tide::Request and accessing
* it as a string so contaminate can pass that to surf
*/
fn header_as_str(req: &Request<AppState>, header: &str, default: &str) -> String {
req.header(header).map_or(default, |hv| hv.as_str()).to_string()
}
/**
* Proxy the given response to the upstream registry and return the response
* back to the client request it.
@ -94,26 +111,20 @@ async fn proxy_upstream(req: Request<AppState>) -> Result<Response, tide::Error>
* We need to send the Authorization header along as well, otherwise
* the upstream repository might complain that we're not authorized
*/
let token = req.header("Authorization").map_or("", |h| h.as_str());
let accepts = req.header("Accept").map_or("", |h| h.as_str());
let token = header_as_str(&req, "Authorization", "");
let accepts = header_as_str(&req, "Accept", "");
let outbound = surf::get(full_url)
.set_header("Authorization", token)
.set_header("Accept", accepts);
debug!("Preparing to send request");
if let Ok(mut u_res) = outbound.await {
if let Ok(mut u_res) = make_get_request(&full_url, &token, &accepts).await {
let body = u_res.body_string().await;
debug!("Body received");
debug!("Body received from {}", full_url);
match body {
Ok(body) => {
trace!("upstream response for {}:\n{}", req.url(), body);
/*
* If we don't explicitly set the content type here, the client will think
* that we're sending back a v1 manifest schema and complain about a "missing
* signature key"
*/
//debug!("upstream headers: {:?}", u_res.headers());
debug!("upstream response for {}:\n{}", req.url(), body);
let content_type = u_res.header("Content-Type").map_or("text/plain", |h| h.as_str());
let mut res = Response::new(u_res.status());
res.set_body(body);
@ -139,6 +150,17 @@ async fn proxy_upstream(req: Request<AppState>) -> Result<Response, tide::Error>
*
* This will return a Response to the client which conforms to the manifest
* specification.
*
* The manifests API is defined in the "Pulling an Image" part of the HTTP
* registry API documentation, see here:
* <https://docs.docker.com/registry/spec/api/#pulling-an-image>
*
* This route will:
* - fetch the upstream manifest in order to fetch the image configuration
* - fetch the image configuration, which must have its diff_ids overwritten
* - re-compute the digest of the image configuration
* - grab all the layers which must be inserted into the manifest
* - generate the updated manifest
*/
async fn fetch_digest(req: Request<AppState>) -> Result<Response, tide::Error> {
let org: String = req.param("org").unwrap_or("".to_string());
@ -147,7 +169,19 @@ async fn fetch_digest(req: Request<AppState>) -> Result<Response, tide::Error> {
if req.state().override_exists(&org, &image, &digest) {
error!("Override exists, we should serve this request");
Ok(Response::new(200))
let full_url = format!("{}{}", req.state().upstream(), req.url().path());
let token = header_as_str(&req, "Authorization", "");
let accepts = header_as_str(&req, "Accept", "");
if let Ok(mut up_req) = make_get_request(&full_url, &token, &accepts).await {
let manifest: models::Manifest = up_req.body_json().await.expect("Failed to parse response");
debug!("Manifest: {:?}", manifest);
Ok(Response::new(200))
}
else {
error!("Failed to make an upstream request for {}", full_url);
Ok(Response::new(500))
}
}
else {
error!("Override does not exist for {}/{}/{}, we should proxy upstream", org, image, digest);
@ -204,11 +238,12 @@ fn main() -> Result<(), std::io::Error> {
*/
app.at("/v2/:org/:image/manifests/:digest").get(fetch_digest);
/*
* This route works handles images which look like "official" images,
* This route handles images which look like "official" images,
* such as `alpine:latest`, which _actually_ maps to `library/alpine:latest`
* in DockerHub
*/
//app.at("/v2/:image/manifests/:digest").get(fetch_digest);
app.at("/v2/:org/:image/blobs/:sha").get(fetch_blob);
/*
* The catch-all for the remainder of the v2 API calls should proxy to

View File

@ -7,7 +7,6 @@ extern crate serde;
extern crate serde_json;
use serde::{Deserialize, Serialize};
use serde_json::Result;
/**
* Manifest format, retrieved from: /v2/alpine/manifests/latest
@ -51,8 +50,8 @@ use serde_json::Result;
]
}
*/
#[derive(Serialize, Deserialize)]
struct Manifest {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Manifest {
#[serde(rename = "schemaVersion")]
schema_version: u16,
name: String,
@ -63,14 +62,14 @@ struct Manifest {
signatures: Vec<Signature>,
}
#[derive(Serialize, Deserialize)]
struct Layer {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Layer {
#[serde(rename = "blobSum")]
blob_sum: String,
}
#[derive(Serialize, Deserialize)]
struct Signature {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Signature {
signature: String,
protected: String,
}