aws_runtime/env_config/file.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
//! Config structs to programmatically customize the profile files that get loaded
use std::fmt;
use std::path::PathBuf;
/// Provides the ability to programmatically override the profile files that get loaded by the SDK.
///
/// The [`Default`] for `EnvConfigFiles` includes the default SDK config and credential files located in
/// `~/.aws/config` and `~/.aws/credentials` respectively.
///
/// Any number of config and credential files may be added to the `EnvConfigFiles` file set, with the
/// only requirement being that there is at least one of them. Custom file locations that are added
/// will produce errors if they don't exist, while the default config/credentials files paths are
/// allowed to not exist even if they're included.
///
/// # Example: Using a custom profile file path
///
/// ```no_run,ignore
/// use aws_runtime::env_config::file::{EnvConfigFiles, SharedConfigFileKind};
/// use std::sync::Arc;
///
/// # async fn example() {
/// let profile_files = EnvConfigFiles::builder()
/// .with_file(SharedConfigFileKind::Credentials, "some/path/to/credentials-file")
/// .build();
/// let sdk_config = aws_config::from_env()
/// .profile_files(profile_files)
/// .load()
/// .await;
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct EnvConfigFiles {
pub(crate) files: Vec<EnvConfigFile>,
}
impl EnvConfigFiles {
/// Returns a builder to create `EnvConfigFiles`
pub fn builder() -> Builder {
Builder::new()
}
}
impl Default for EnvConfigFiles {
fn default() -> Self {
Self {
files: vec![
EnvConfigFile::Default(EnvConfigFileKind::Config),
EnvConfigFile::Default(EnvConfigFileKind::Credentials),
],
}
}
}
/// Profile file type (config or credentials)
#[derive(Copy, Clone, Debug)]
pub enum EnvConfigFileKind {
/// The SDK config file that typically resides in `~/.aws/config`
Config,
/// The SDK credentials file that typically resides in `~/.aws/credentials`
Credentials,
}
impl EnvConfigFileKind {
pub(crate) fn default_path(&self) -> &'static str {
match &self {
EnvConfigFileKind::Credentials => "~/.aws/credentials",
EnvConfigFileKind::Config => "~/.aws/config",
}
}
pub(crate) fn override_environment_variable(&self) -> &'static str {
match &self {
EnvConfigFileKind::Config => "AWS_CONFIG_FILE",
EnvConfigFileKind::Credentials => "AWS_SHARED_CREDENTIALS_FILE",
}
}
}
/// A single config file within a [`EnvConfigFiles`] file set.
#[derive(Clone)]
pub(crate) enum EnvConfigFile {
/// One of the default profile files (config or credentials in their default locations)
Default(EnvConfigFileKind),
/// A profile file at a custom location
FilePath {
kind: EnvConfigFileKind,
path: PathBuf,
},
/// The direct contents of a profile file
FileContents {
kind: EnvConfigFileKind,
contents: String,
},
}
impl fmt::Debug for EnvConfigFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Default(kind) => f.debug_tuple("Default").field(kind).finish(),
Self::FilePath { kind, path } => f
.debug_struct("FilePath")
.field("kind", kind)
.field("path", path)
.finish(),
// Security: Redact the file contents since they may have credentials in them
Self::FileContents { kind, contents: _ } => f
.debug_struct("FileContents")
.field("kind", kind)
.field("contents", &"** redacted **")
.finish(),
}
}
}
/// Builder for [`EnvConfigFiles`].
#[derive(Clone, Default, Debug)]
pub struct Builder {
with_config: bool,
with_credentials: bool,
custom_sources: Vec<EnvConfigFile>,
}
impl Builder {
/// Creates a new builder instance.
pub fn new() -> Self {
Default::default()
}
/// Include the default SDK config file in the list of profile files to be loaded.
///
/// The default SDK config typically resides in `~/.aws/config`. When this flag is enabled,
/// this config file will be included in the profile files that get loaded in the built
/// [`EnvConfigFiles`] file set.
///
/// This flag defaults to `false` when using the builder to construct [`EnvConfigFiles`].
pub fn include_default_config_file(mut self, include_default_config_file: bool) -> Self {
self.with_config = include_default_config_file;
self
}
/// Include the default SDK credentials file in the list of profile files to be loaded.
///
/// The default SDK credentials typically reside in `~/.aws/credentials`. When this flag is enabled,
/// this credentials file will be included in the profile files that get loaded in the built
/// [`EnvConfigFiles`] file set.
///
/// This flag defaults to `false` when using the builder to construct [`EnvConfigFiles`].
pub fn include_default_credentials_file(
mut self,
include_default_credentials_file: bool,
) -> Self {
self.with_credentials = include_default_credentials_file;
self
}
/// Include a custom `file` in the list of profile files to be loaded.
///
/// The `kind` informs the parser how to treat the file. If it's intended to be like
/// the SDK credentials file typically in `~/.aws/config`, then use [`EnvConfigFileKind::Config`].
/// Otherwise, use [`EnvConfigFileKind::Credentials`].
pub fn with_file(mut self, kind: EnvConfigFileKind, file: impl Into<PathBuf>) -> Self {
self.custom_sources.push(EnvConfigFile::FilePath {
kind,
path: file.into(),
});
self
}
/// Include custom file `contents` in the list of profile files to be loaded.
///
/// The `kind` informs the parser how to treat the file. If it's intended to be like
/// the SDK credentials file typically in `~/.aws/config`, then use [`EnvConfigFileKind::Config`].
/// Otherwise, use [`EnvConfigFileKind::Credentials`].
pub fn with_contents(mut self, kind: EnvConfigFileKind, contents: impl Into<String>) -> Self {
self.custom_sources.push(EnvConfigFile::FileContents {
kind,
contents: contents.into(),
});
self
}
/// Build the [`EnvConfigFiles`] file set.
pub fn build(self) -> EnvConfigFiles {
let mut files = self.custom_sources;
if self.with_credentials {
files.insert(0, EnvConfigFile::Default(EnvConfigFileKind::Credentials));
}
if self.with_config {
files.insert(0, EnvConfigFile::Default(EnvConfigFileKind::Config));
}
if files.is_empty() {
panic!("At least one profile file must be included in the `EnvConfigFiles` file set.");
}
EnvConfigFiles { files }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn redact_file_contents_in_profile_file_debug() {
let shared_config_file = EnvConfigFile::FileContents {
kind: EnvConfigFileKind::Config,
contents: "sensitive_contents".into(),
};
let debug = format!("{shared_config_file:?}");
assert!(!debug.contains("sensitive_contents"));
assert!(debug.contains("** redacted **"));
}
#[test]
fn build_correctly_orders_default_config_credentials() {
let shared_config_files = EnvConfigFiles::builder()
.with_file(EnvConfigFileKind::Config, "foo")
.include_default_credentials_file(true)
.include_default_config_file(true)
.build();
assert_eq!(3, shared_config_files.files.len());
assert!(matches!(
shared_config_files.files[0],
EnvConfigFile::Default(EnvConfigFileKind::Config)
));
assert!(matches!(
shared_config_files.files[1],
EnvConfigFile::Default(EnvConfigFileKind::Credentials)
));
assert!(matches!(
shared_config_files.files[2],
EnvConfigFile::FilePath {
kind: EnvConfigFileKind::Config,
path: _
}
));
}
#[test]
#[should_panic]
fn empty_builder_panics() {
EnvConfigFiles::builder().build();
}
}