Commit
Author: Jason Mobarak [jason@swift-nav.com]
Committer: GitHub [noreply@github.com] Sat, 13 May 2023 22:57:39 +0000
Hash: c8f95f3a3503786541d59d7c4040e3f1e3930b21
Timestamp: Sat, 13 May 2023 22:57:39 +0000 (1 year ago)

+36 -4 +/-2 browse
Reflect auth headers back into response objects (#59)
Reflect auth headers back into response objects (#59)

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.
1diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
2index 40dfd6b..802c290 100644
3--- a/.github/workflows/ci.yml
4+++ b/.github/workflows/ci.yml
5 @@ -77,6 +77,8 @@ jobs:
6 - uses: actions-rs/clippy-check@v1
7 with:
8 token: ${{ secrets.GITHUB_TOKEN }}
9+ args: --all-features -- --allow dead_code
10+
11
12 publish_crate:
13 name: Publish Crate
14 diff --git a/src/app.rs b/src/app.rs
15index c881d5e..42b8de4 100644
16--- a/src/app.rs
17+++ b/src/app.rs
18 @@ -17,6 +17,7 @@
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 // SOFTWARE.
22+ use std::collections::BTreeMap;
23 use std::fmt;
24 use std::io;
25
26 @@ -27,6 +28,7 @@ use futures::{
27 future::{self, BoxFuture},
28 stream::TryStreamExt,
29 };
30+ use http::HeaderMap;
31 use http::{self, header, StatusCode, Uri};
32 use hyper::{self, body::Body, service::Service, Method, Request, Response};
33 use serde::{Deserialize, Serialize};
34 @@ -248,6 +250,7 @@ where
35 ) -> Result<Response<Body>, Error> {
36 // Get the host name and scheme.
37 let uri = req.base_uri().path_and_query("/").build().unwrap();
38+ let headers = req.headers().clone();
39
40 match from_json::<lfs::BatchRequest>(req.into_body()).await {
41 Ok(val) => {
42 @@ -264,7 +267,8 @@ where
43
44 let (namespace, _) = key.into_parts();
45 Ok(basic_response(
46- uri, &storage, object, operation, size, namespace,
47+ uri, &headers, &storage, object, operation, size,
48+ namespace,
49 )
50 .await)
51 }
52 @@ -298,6 +302,7 @@ where
53
54 async fn basic_response<E, S>(
55 uri: Uri,
56+ headers: &HeaderMap,
57 storage: &S,
58 object: lfs::RequestObject,
59 op: lfs::Operation,
60 @@ -389,7 +394,7 @@ where
61 uri, namespace, object.oid
62 )
63 }),
64- header: None,
65+ header: extract_auth_header(headers),
66 expires_in: Some(upload_expiry_secs),
67 expires_at: None,
68 }),
69 @@ -398,7 +403,7 @@ where
70 "{}api/{}/objects/verify",
71 uri, namespace
72 ),
73- header: None,
74+ header: extract_auth_header(headers),
75 expires_in: None,
76 expires_at: None,
77 }),
78 @@ -428,7 +433,7 @@ where
79 uri, namespace, object.oid
80 )
81 }),
82- header: None,
83+ header: extract_auth_header(headers),
84 expires_in: None,
85 expires_at: None,
86 }),
87 @@ -451,6 +456,31 @@ where
88 }
89 }
90
91+ /// Extracts the authorization headers so that they can be reflected back to the
92+ /// `git-lfs` client. If we're behind a reverse proxy that provides
93+ /// authentication, the `git-lfs` client will send an `Authorization` header on
94+ /// the first connection, however in order for subsequent requests to also be
95+ /// authenticated, the `header` field in the `lfs::ResponseObject` must be
96+ /// populated.
97+ fn extract_auth_header(
98+ headers: &HeaderMap,
99+ ) -> Option<BTreeMap<String, String>> {
100+ let headers = headers.iter().filter_map(|(k, v)| {
101+ if k == http::header::AUTHORIZATION {
102+ let value = String::from_utf8_lossy(v.as_bytes()).to_string();
103+ Some((k.to_string(), value))
104+ } else {
105+ None
106+ }
107+ });
108+ let map = BTreeMap::from_iter(headers);
109+ if map.is_empty() {
110+ None
111+ } else {
112+ Some(map)
113+ }
114+ }
115+
116 impl<S> Service<Request<Body>> for App<S>
117 where
118 S: Storage + Clone + Send + Sync + 'static,