Skip to content

Commit 4062cd7

Browse files
haipham23igorlukanin
authored andcommitted
feat(cubestore): Support IAM role authentication for S3
1 parent 39190e0 commit 4062cd7

File tree

1 file changed

+108
-14
lines changed
  • rust/cubestore/cubestore/src/remotefs

1 file changed

+108
-14
lines changed

rust/cubestore/cubestore/src/remotefs/s3.rs

Lines changed: 108 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,56 @@ impl S3RemoteFs {
4848
bucket_name: String,
4949
sub_path: Option<String>,
5050
) -> Result<Arc<Self>, CubeError> {
51-
// Incorrect naming for ENV variables...
52-
let access_key = env::var("CUBESTORE_AWS_ACCESS_KEY_ID").ok();
53-
let secret_key = env::var("CUBESTORE_AWS_SECRET_ACCESS_KEY").ok();
54-
51+
let role_name = env::var("CUBESTORE_AWS_IAM_ROLE").ok();
52+
let (access_key, secret_key) = match role_name {
53+
Some(role_name) => {
54+
let region = env::var("CUBESTORE_AWS_REGION").expect("CUBESTORE_AWS_REGION must be set");
55+
let account_id = Command::new("aws")
56+
.args(&["sts", "get-caller-identity", "--query", "Account", "--output", "text"])
57+
.output()
58+
.expect("Failed to get account ID")
59+
.stdout
60+
.trim()
61+
.to_string();
62+
63+
let assume_role_output = Command::new("aws")
64+
.args(&[
65+
"sts",
66+
"assume-role",
67+
"--role-arn",
68+
&format!("arn:aws:iam::{}:role/{}", account_id, role_name),
69+
"--role-session-name",
70+
&format!("session-{}", role_name),
71+
"--query",
72+
"Credentials",
73+
"--output",
74+
"text",
75+
])
76+
.output()
77+
.expect("Failed to assume role")
78+
.stdout
79+
.trim()
80+
.to_string();
81+
82+
let access_key = assume_role_output.split_whitespace().nth(1).unwrap().trim_matches('"');
83+
let secret_key = assume_role_output.split_whitespace().nth(3).unwrap().trim_matches('"');
84+
85+
(Some(access_key), Some(secret_key))
86+
}
87+
None => (
88+
env::var("CUBESTORE_AWS_ACCESS_KEY_ID").ok(),
89+
env::var("CUBESTORE_AWS_SECRET_ACCESS_KEY").ok(),
90+
),
91+
};
92+
5593
let credentials = Credentials::new(
5694
access_key.as_deref(),
5795
secret_key.as_deref(),
5896
None,
5997
None,
6098
None,
6199
)
62-
.map_err(|err| {
100+
.map_err(|err| {
63101
CubeError::internal(format!(
64102
"Failed to create S3 credentials: {}",
65103
err.to_string()
@@ -79,15 +117,22 @@ impl S3RemoteFs {
79117
sub_path,
80118
delete_mut: Mutex::new(()),
81119
});
82-
spawn_creds_refresh_loop(access_key, secret_key, bucket_name, region, &fs);
83-
120+
121+
spawn_creds_refresh_loop(
122+
role_name.or(access_key.clone()),
123+
role_name.is_some(),
124+
bucket_name,
125+
region,
126+
&fs,
127+
);
128+
84129
Ok(fs)
85130
}
86131
}
87132

88133
fn spawn_creds_refresh_loop(
89-
access_key: Option<String>,
90-
secret_key: Option<String>,
134+
role_or_access_key: Option<String>,
135+
is_role: bool,
91136
bucket_name: String,
92137
region: Region,
93138
fs: &Arc<S3RemoteFs>,
@@ -110,6 +155,43 @@ fn spawn_creds_refresh_loop(
110155
}
111156
Some(fs) => fs,
112157
};
158+
let (access_key, secret_key) = if is_role {
159+
let region = env::var("CUBESTORE_AWS_REGION").expect("CUBESTORE_AWS_REGION must be set");
160+
let account_id = Command::new("aws")
161+
.args(&["sts", "get-caller-identity", "--query", "Account", "--output", "text"])
162+
.output()
163+
.expect("Failed to get account ID")
164+
.stdout
165+
.trim()
166+
.to_string();
167+
168+
let assume_role_output = Command::new("aws")
169+
.args(&[
170+
"sts",
171+
"assume-role",
172+
"--role-arn",
173+
&format!("arn:aws:iam::{}:role/{}", account_id, role_or_access_key.as_ref().unwrap()),
174+
"--role-session-name",
175+
&format!("session-{}", role_or_access_key.as_ref().unwrap()),
176+
"--query",
177+
"Credentials",
178+
"--output",
179+
"text",
180+
])
181+
.output()
182+
.expect("Failed to assume role")
183+
.stdout
184+
.trim()
185+
.to_string();
186+
187+
let access_key = assume_role_output.split_whitespace().nth(1).unwrap().trim_matches('"');
188+
let secret_key = assume_role_output.split_whitespace().nth(3).unwrap().trim_matches('"');
189+
190+
(Some(access_key), Some(secret_key))
191+
} else {
192+
(role_or_access_key, None)
193+
};
194+
113195
let c = match Credentials::new(
114196
access_key.as_deref(),
115197
secret_key.as_deref(),
@@ -138,12 +220,24 @@ fn spawn_creds_refresh_loop(
138220

139221
fn refresh_interval_from_env() -> Duration {
140222
let mut mins = 180; // 3 hours by default.
141-
if let Ok(s) = std::env::var("CUBESTORE_AWS_CREDS_REFRESH_EVERY_MINS") {
142-
match s.parse::<u64>() {
143-
Ok(i) => mins = i,
144-
Err(e) => log::error!("Could not parse CUBESTORE_AWS_CREDS_REFRESH_EVERY_MINS. Refreshing every {} minutes. Error: {}", mins, e),
223+
if let Ok(_) = std::env::var("CUBESTORE_AWS_IAM_ROLE") {
224+
if let Ok(s) = std::env::var("CUBESTORE_AWS_IAM_REFRESH_EVERY_MINS") {
225+
// 14 mins by default if IAM role present
226+
match s.parse::<u64>() {
227+
Ok(i) => mins = i,
228+
Err(e) => log::error!("Could not parse CUBESTORE_AWS_IAM_REFRESH_EVERY_MINS. Refreshing every {} minutes. Error: {}", mins, e),
229+
};
230+
} else {
231+
mins = 14;
232+
}
233+
} else {
234+
if let Ok(s) = std::env::var("CUBESTORE_AWS_CREDS_REFRESH_EVERY_MINS") {
235+
match s.parse::<u64>() {
236+
Ok(i) => mins = i,
237+
Err(e) => log::error!("Could not parse CUBESTORE_AWS_CREDS_REFRESH_EVERY_MINS. Refreshing every {} minutes. Error: {}", mins, e),
238+
};
145239
};
146-
};
240+
}
147241
Duration::from_secs(60 * mins)
148242
}
149243

0 commit comments

Comments
 (0)