Skip to content

Commit

Permalink
Reflect auth headers back into response objects (#59)
Browse files Browse the repository at this point in the history
If an Authentication header is present in the request to the server, then reflect it in the response object from the server. If this is not present the git-lfs client will attempt to send unauthenticated requests to the server. With this change rudolfs can be protected behind a Kubernetes NGINX Ingress resource that uses the nginx.ingress.kubernetes.io/auth-url annotation.
  • Loading branch information
Jason Mobarak committed May 13, 2023
1 parent 80ba538 commit c8f95f3
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ jobs:
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features -- --allow dead_code


publish_crate:
name: Publish Crate
Expand Down
38 changes: 34 additions & 4 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::collections::BTreeMap;
use std::fmt;
use std::io;

Expand All @@ -27,6 +28,7 @@ use futures::{
future::{self, BoxFuture},
stream::TryStreamExt,
};
use http::HeaderMap;
use http::{self, header, StatusCode, Uri};
use hyper::{self, body::Body, service::Service, Method, Request, Response};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -248,6 +250,7 @@ where
) -> Result<Response<Body>, Error> {
// Get the host name and scheme.
let uri = req.base_uri().path_and_query("/").build().unwrap();
let headers = req.headers().clone();

match from_json::<lfs::BatchRequest>(req.into_body()).await {
Ok(val) => {
Expand All @@ -264,7 +267,8 @@ where

let (namespace, _) = key.into_parts();
Ok(basic_response(
uri, &storage, object, operation, size, namespace,
uri, &headers, &storage, object, operation, size,
namespace,
)
.await)
}
Expand Down Expand Up @@ -298,6 +302,7 @@ where

async fn basic_response<E, S>(
uri: Uri,
headers: &HeaderMap,
storage: &S,
object: lfs::RequestObject,
op: lfs::Operation,
Expand Down Expand Up @@ -389,7 +394,7 @@ where
uri, namespace, object.oid
)
}),
header: None,
header: extract_auth_header(headers),
expires_in: Some(upload_expiry_secs),
expires_at: None,
}),
Expand All @@ -398,7 +403,7 @@ where
"{}api/{}/objects/verify",
uri, namespace
),
header: None,
header: extract_auth_header(headers),
expires_in: None,
expires_at: None,
}),
Expand Down Expand Up @@ -428,7 +433,7 @@ where
uri, namespace, object.oid
)
}),
header: None,
header: extract_auth_header(headers),
expires_in: None,
expires_at: None,
}),
Expand All @@ -451,6 +456,31 @@ where
}
}

/// Extracts the authorization headers so that they can be reflected back to the
/// `git-lfs` client. If we're behind a reverse proxy that provides
/// authentication, the `git-lfs` client will send an `Authorization` header on
/// the first connection, however in order for subsequent requests to also be
/// authenticated, the `header` field in the `lfs::ResponseObject` must be
/// populated.
fn extract_auth_header(
headers: &HeaderMap,
) -> Option<BTreeMap<String, String>> {
let headers = headers.iter().filter_map(|(k, v)| {
if k == http::header::AUTHORIZATION {
let value = String::from_utf8_lossy(v.as_bytes()).to_string();
Some((k.to_string(), value))
} else {
None
}
});
let map = BTreeMap::from_iter(headers);
if map.is_empty() {
None
} else {
Some(map)
}
}

impl<S> Service<Request<Body>> for App<S>
where
S: Storage + Clone + Send + Sync + 'static,
Expand Down

0 comments on commit c8f95f3

Please sign in to comment.