aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorLibravatar Rutger Broekhoff2024-03-30 15:19:49 +0100
committerLibravatar Rutger Broekhoff2024-03-30 15:19:49 +0100
commit5e5cde1624b1b4ffd00efa73935c48e547a5a8d3 (patch)
tree67bceba2d25cf3298cd83b2f4e876752684dd704 /server
parentf033c6889e0071b29e75c551586e8e5da1b556a3 (diff)
downloadgitolfs3-5e5cde1624b1b4ffd00efa73935c48e547a5a8d3.tar.gz
gitolfs3-5e5cde1624b1b4ffd00efa73935c48e547a5a8d3.zip
Restructure authorization flow
Tried to write verify_claims such that the happy path is to the left as much as possible.
Diffstat (limited to 'server')
-rw-r--r--server/src/main.rs97
1 files changed, 56 insertions, 41 deletions
diff --git a/server/src/main.rs b/server/src/main.rs
index 4a88dcd..e615d19 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -762,28 +762,46 @@ fn authorize_batch(
762 specific_claims: common::SpecificClaims::BatchApi(operation), 762 specific_claims: common::SpecificClaims::BatchApi(operation),
763 repo_path, 763 repo_path,
764 }; 764 };
765 if verify_claims(conf, &claims, headers)? { 765 if !verify_claims(conf, &claims, headers)? {
766 return Ok(Trusted(true)); 766 return authorize_batch_unauthenticated(conf, public, operation, headers);
767 } 767 }
768 return Ok(Trusted(true));
769}
768 770
771fn authorize_batch_unauthenticated(
772 conf: &AuthorizationConfig,
773 public: bool,
774 operation: common::Operation,
775 headers: &HeaderMap,
776) -> Result<Trusted, GitLfsErrorResponse<'static>> {
769 let trusted = forwarded_from_trusted_host(headers, &conf.trusted_forwarded_hosts)?; 777 let trusted = forwarded_from_trusted_host(headers, &conf.trusted_forwarded_hosts)?;
770 if operation != common::Operation::Download { 778 match operation {
771 if trusted { 779 common::Operation::Upload => {
780 // Trusted users can clone all repositories (by virtue of accessing the server via a
781 // trusted network). However, they can not push without proper authentication. Untrusted
782 // users who are also not authenticated should not need to know which repositories exists.
783 // Therefore, we tell untrusted && unauthenticated users that the repo doesn't exist, but
784 // tell trusted users that they need to authenticate.
785 if !trusted {
786 return Err(REPO_NOT_FOUND);
787 }
772 return Err(make_error_resp( 788 return Err(make_error_resp(
773 StatusCode::FORBIDDEN, 789 StatusCode::FORBIDDEN,
774 "Authentication required to upload", 790 "Authentication required to upload",
775 )); 791 ));
792 },
793 common::Operation::Download => {
794 // Again, trusted users can see all repos. For untrusted users, we first need to check
795 // whether the repo is public before we authorize. If the user is untrusted and the
796 // repo isn't public, we just act like it doesn't even exist.
797 if !trusted {
798 if !public {
799 return Err(REPO_NOT_FOUND)
800 }
801 return Ok(Trusted(false))
802 }
803 return Ok(Trusted(true));
776 } 804 }
777 return Err(REPO_NOT_FOUND);
778 }
779 if trusted {
780 return Ok(Trusted(true));
781 }
782
783 if public {
784 Ok(Trusted(false))
785 } else {
786 Err(REPO_NOT_FOUND)
787 } 805 }
788} 806}
789 807
@@ -909,35 +927,32 @@ fn verify_claims(
909 const INVALID_AUTHZ_HEADER: GitLfsErrorResponse = 927 const INVALID_AUTHZ_HEADER: GitLfsErrorResponse =
910 make_error_resp(StatusCode::BAD_REQUEST, "Invalid authorization header"); 928 make_error_resp(StatusCode::BAD_REQUEST, "Invalid authorization header");
911 929
912 if let Some(authz) = headers.get(header::AUTHORIZATION) { 930 let Some(authz) = headers.get(header::AUTHORIZATION) else {
913 if let Ok(authz) = authz.to_str() { 931 return Ok(false);
914 if let Some(val) = authz.strip_prefix("Gitolfs3-Hmac-Sha256 ") { 932 };
915 let (tag, expires_at) = val.split_once(' ').ok_or(INVALID_AUTHZ_HEADER)?; 933 let authz = authz.to_str().map_err(|_| INVALID_AUTHZ_HEADER)?;
916 let tag: common::Digest<32> = tag.parse().map_err(|_| INVALID_AUTHZ_HEADER)?; 934 let val = authz.strip_prefix("Gitolfs3-Hmac-Sha256 ").ok_or(INVALID_AUTHZ_HEADER)?;
917 let expires_at: i64 = expires_at.parse().map_err(|_| INVALID_AUTHZ_HEADER)?; 935 let (tag, expires_at) = val.split_once(' ').ok_or(INVALID_AUTHZ_HEADER)?;
918 let expires_at = 936 let tag: common::Digest<32> = tag.parse().map_err(|_| INVALID_AUTHZ_HEADER)?;
919 DateTime::<Utc>::from_timestamp(expires_at, 0).ok_or(INVALID_AUTHZ_HEADER)?; 937 let expires_at: i64 = expires_at.parse().map_err(|_| INVALID_AUTHZ_HEADER)?;
920 let Some(expected_tag) = common::generate_tag( 938 let expires_at =
921 common::Claims { 939 DateTime::<Utc>::from_timestamp(expires_at, 0).ok_or(INVALID_AUTHZ_HEADER)?;
922 specific_claims: claims.specific_claims, 940 let expected_tag = common::generate_tag(
923 repo_path: claims.repo_path, 941 common::Claims {
924 expires_at, 942 specific_claims: claims.specific_claims,
925 }, 943 repo_path: claims.repo_path,
926 &conf.key, 944 expires_at,
927 ) else { 945 },
928 return Err(make_error_resp( 946 &conf.key,
929 StatusCode::INTERNAL_SERVER_ERROR, 947 ).ok_or_else(|| make_error_resp(
930 "Internal server error", 948 StatusCode::INTERNAL_SERVER_ERROR,
931 )); 949 "Internal server error",
932 }; 950 ))?;
933 if tag == expected_tag { 951 if tag != expected_tag {
934 return Ok(true);
935 }
936 }
937 }
938 return Err(INVALID_AUTHZ_HEADER); 952 return Err(INVALID_AUTHZ_HEADER);
939 } 953 }
940 Ok(false) 954
955 Ok(true)
941} 956}
942 957
943fn authorize_get( 958fn authorize_get(