Skip to main content

multistore_sts/
request.rs

1//! STS request parsing.
2//!
3//! Extracts `AssumeRoleWithWebIdentity` parameters from query strings.
4
5use multistore::error::ProxyError;
6
7/// Parsed STS `AssumeRoleWithWebIdentity` request parameters.
8#[derive(Debug, Clone)]
9pub struct StsRequest {
10    /// The ARN of the IAM role to assume.
11    pub role_arn: String,
12    /// The OIDC identity token provided by the caller.
13    pub web_identity_token: String,
14    /// Optional session duration in seconds.
15    pub duration_seconds: Option<u64>,
16}
17
18/// Try to parse an STS request from the query string.
19///
20/// Returns `None` if the query does not contain `Action=AssumeRoleWithWebIdentity`
21/// (i.e., this is not an STS request). Returns `Some(Ok(..))` on success or
22/// `Some(Err(..))` if it is an STS request but required parameters are missing.
23pub fn try_parse_sts_request(query: Option<&str>) -> Option<Result<StsRequest, ProxyError>> {
24    let q = query?;
25    let params: Vec<(String, String)> = url::form_urlencoded::parse(q.as_bytes())
26        .map(|(k, v)| (k.to_string(), v.to_string()))
27        .collect();
28
29    let action = params.iter().find(|(k, _)| k == "Action");
30    match action {
31        Some((_, value)) if value == "AssumeRoleWithWebIdentity" => {}
32        _ => return None,
33    }
34
35    Some(parse_sts_params(&params))
36}
37
38fn parse_sts_params(params: &[(String, String)]) -> Result<StsRequest, ProxyError> {
39    let role_arn = params
40        .iter()
41        .find(|(k, _)| k == "RoleArn")
42        .map(|(_, v)| v.clone())
43        .ok_or_else(|| ProxyError::InvalidRequest("missing RoleArn".into()))?;
44
45    let web_identity_token = params
46        .iter()
47        .find(|(k, _)| k == "WebIdentityToken")
48        .map(|(_, v)| v.clone())
49        .ok_or_else(|| ProxyError::InvalidRequest("missing WebIdentityToken".into()))?;
50
51    let duration_seconds = params
52        .iter()
53        .find(|(k, _)| k == "DurationSeconds")
54        .and_then(|(_, v)| v.parse().ok());
55
56    Ok(StsRequest {
57        role_arn,
58        web_identity_token,
59        duration_seconds,
60    })
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn test_not_sts_request() {
69        assert!(try_parse_sts_request(None).is_none());
70        assert!(try_parse_sts_request(Some("prefix=foo/")).is_none());
71        assert!(try_parse_sts_request(Some("Action=ListBuckets")).is_none());
72    }
73
74    #[test]
75    fn test_valid_sts_request() {
76        let query = "Action=AssumeRoleWithWebIdentity&RoleArn=my-role&WebIdentityToken=tok123";
77        let result = try_parse_sts_request(Some(query)).unwrap().unwrap();
78        assert_eq!(result.role_arn, "my-role");
79        assert_eq!(result.web_identity_token, "tok123");
80        assert_eq!(result.duration_seconds, None);
81    }
82
83    #[test]
84    fn test_sts_request_with_duration() {
85        let query =
86            "Action=AssumeRoleWithWebIdentity&RoleArn=r&WebIdentityToken=t&DurationSeconds=7200";
87        let result = try_parse_sts_request(Some(query)).unwrap().unwrap();
88        assert_eq!(result.duration_seconds, Some(7200));
89    }
90
91    #[test]
92    fn test_missing_role_arn() {
93        let query = "Action=AssumeRoleWithWebIdentity&WebIdentityToken=tok";
94        let result = try_parse_sts_request(Some(query)).unwrap();
95        assert!(result.is_err());
96    }
97
98    #[test]
99    fn test_missing_web_identity_token() {
100        let query = "Action=AssumeRoleWithWebIdentity&RoleArn=role";
101        let result = try_parse_sts_request(Some(query)).unwrap();
102        assert!(result.is_err());
103    }
104}