aws_config/imds/client/
error.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Error types for [`ImdsClient`](crate::imds::client::Client)
7
8use aws_smithy_runtime_api::client::orchestrator::HttpResponse;
9use aws_smithy_runtime_api::client::result::SdkError;
10use std::error::Error;
11use std::fmt;
12
13/// Error context for [`ImdsError::FailedToLoadToken`]
14#[derive(Debug)]
15pub struct FailedToLoadToken {
16    source: SdkError<TokenError, HttpResponse>,
17}
18
19impl FailedToLoadToken {
20    /// Returns `true` if a dispatch failure caused the token to fail to load
21    pub fn is_dispatch_failure(&self) -> bool {
22        matches!(self.source, SdkError::DispatchFailure(_))
23    }
24
25    pub(crate) fn into_source(self) -> SdkError<TokenError, HttpResponse> {
26        self.source
27    }
28}
29
30/// Error context for [`ImdsError::ErrorResponse`]
31#[derive(Debug)]
32pub struct ErrorResponse {
33    raw: HttpResponse,
34}
35
36impl ErrorResponse {
37    /// Returns the raw response from IMDS
38    pub fn response(&self) -> &HttpResponse {
39        &self.raw
40    }
41}
42
43/// Error context for [`ImdsError::IoError`]
44#[derive(Debug)]
45pub struct IoError {
46    source: Box<dyn Error + Send + Sync + 'static>,
47}
48
49/// Error context for [`ImdsError::Unexpected`]
50#[derive(Debug)]
51pub struct Unexpected {
52    source: Box<dyn Error + Send + Sync + 'static>,
53}
54
55/// An error retrieving metadata from IMDS
56#[derive(Debug)]
57#[non_exhaustive]
58pub enum ImdsError {
59    /// An IMDSv2 Token could not be loaded
60    ///
61    /// Requests to IMDS must be accompanied by a token obtained via a `PUT` request. This is handled
62    /// transparently by the [`Client`](crate::imds::client::Client).
63    FailedToLoadToken(FailedToLoadToken),
64
65    /// An error response was returned from IMDS
66    ErrorResponse(ErrorResponse),
67
68    /// IO Error
69    ///
70    /// An error occurred communication with IMDS
71    IoError(IoError),
72
73    /// An unexpected error occurred communicating with IMDS
74    Unexpected(Unexpected),
75}
76
77impl ImdsError {
78    pub(super) fn failed_to_load_token(source: SdkError<TokenError, HttpResponse>) -> Self {
79        Self::FailedToLoadToken(FailedToLoadToken { source })
80    }
81
82    pub(super) fn error_response(raw: HttpResponse) -> Self {
83        Self::ErrorResponse(ErrorResponse { raw })
84    }
85
86    pub(super) fn io_error(source: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
87        Self::IoError(IoError {
88            source: source.into(),
89        })
90    }
91
92    pub(super) fn unexpected(source: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
93        Self::Unexpected(Unexpected {
94            source: source.into(),
95        })
96    }
97}
98
99impl fmt::Display for ImdsError {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        match self {
102            ImdsError::FailedToLoadToken(_) => {
103                write!(f, "failed to load IMDS session token")
104            }
105            ImdsError::ErrorResponse(context) => write!(
106                f,
107                "error response from IMDS (code: {}). {:?}",
108                context.raw.status().as_u16(),
109                context.raw
110            ),
111            ImdsError::IoError(_) => {
112                write!(f, "an IO error occurred communicating with IMDS")
113            }
114            ImdsError::Unexpected(_) => {
115                write!(f, "an unexpected error occurred communicating with IMDS",)
116            }
117        }
118    }
119}
120
121impl Error for ImdsError {
122    fn source(&self) -> Option<&(dyn Error + 'static)> {
123        match &self {
124            ImdsError::FailedToLoadToken(context) => Some(&context.source),
125            ImdsError::IoError(context) => Some(context.source.as_ref()),
126            ImdsError::Unexpected(context) => Some(context.source.as_ref()),
127            ImdsError::ErrorResponse(_) => None,
128        }
129    }
130}
131
132#[derive(Debug)]
133pub(super) enum InnerImdsError {
134    BadStatus,
135    InvalidUtf8,
136}
137
138impl fmt::Display for InnerImdsError {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
140        match self {
141            InnerImdsError::BadStatus => write!(f, "failing status code returned from IMDS"),
142            InnerImdsError::InvalidUtf8 => write!(f, "IMDS did not return valid UTF-8"),
143        }
144    }
145}
146
147impl Error for InnerImdsError {}
148
149/// Invalid Endpoint Mode
150#[derive(Debug)]
151pub struct InvalidEndpointMode {
152    mode: String,
153}
154
155impl InvalidEndpointMode {
156    pub(super) fn new(mode: impl Into<String>) -> Self {
157        Self { mode: mode.into() }
158    }
159}
160
161impl fmt::Display for InvalidEndpointMode {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        write!(
164            f,
165            "`{}` is not a valid endpoint mode. Valid values are [`IPv4`, `IPv6`]",
166            &self.mode
167        )
168    }
169}
170
171impl Error for InvalidEndpointMode {}
172
173#[derive(Debug)]
174#[allow(clippy::enum_variant_names)]
175enum BuildErrorKind {
176    /// The endpoint mode was invalid
177    InvalidEndpointMode(InvalidEndpointMode),
178
179    /// The specified endpoint was not a valid URI
180    InvalidEndpointUri(Box<dyn Error + Send + Sync + 'static>),
181}
182
183/// Error constructing IMDSv2 Client
184#[derive(Debug)]
185pub struct BuildError {
186    kind: BuildErrorKind,
187}
188
189impl BuildError {
190    pub(super) fn invalid_endpoint_mode(source: InvalidEndpointMode) -> Self {
191        Self {
192            kind: BuildErrorKind::InvalidEndpointMode(source),
193        }
194    }
195
196    pub(super) fn invalid_endpoint_uri(
197        source: impl Into<Box<dyn Error + Send + Sync + 'static>>,
198    ) -> Self {
199        Self {
200            kind: BuildErrorKind::InvalidEndpointUri(source.into()),
201        }
202    }
203}
204
205impl fmt::Display for BuildError {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
207        use BuildErrorKind::*;
208        write!(f, "failed to build IMDS client: ")?;
209        match self.kind {
210            InvalidEndpointMode(_) => write!(f, "invalid endpoint mode"),
211            InvalidEndpointUri(_) => write!(f, "invalid URI"),
212        }
213    }
214}
215
216impl Error for BuildError {
217    fn source(&self) -> Option<&(dyn Error + 'static)> {
218        use BuildErrorKind::*;
219        match &self.kind {
220            InvalidEndpointMode(e) => Some(e),
221            InvalidEndpointUri(e) => Some(e.as_ref()),
222        }
223    }
224}
225
226#[derive(Debug)]
227pub(super) enum TokenErrorKind {
228    /// The token was invalid
229    ///
230    /// Because tokens must be eventually sent as a header, the token must be a valid header value.
231    InvalidToken,
232
233    /// No TTL was sent
234    ///
235    /// The token response must include a time-to-live indicating the lifespan of the token.
236    NoTtl,
237
238    /// The TTL was invalid
239    ///
240    /// The TTL must be a valid positive integer.
241    InvalidTtl,
242
243    /// Invalid Parameters
244    ///
245    /// The request to load a token was malformed. This indicates an SDK bug.
246    InvalidParameters,
247
248    /// Forbidden
249    ///
250    /// IMDS is disabled or has been disallowed via permissions.
251    Forbidden,
252}
253
254/// Error retrieving token from IMDS
255#[derive(Debug)]
256pub struct TokenError {
257    kind: TokenErrorKind,
258}
259
260impl fmt::Display for TokenError {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        use TokenErrorKind::*;
263        match self.kind {
264            InvalidToken => write!(f, "invalid token"),
265            NoTtl => write!(f, "token response did not contain a TTL header"),
266            InvalidTtl => write!(f, "the returned TTL was invalid"),
267            InvalidParameters => {
268                write!(f, "invalid request parameters. This indicates an SDK bug.")
269            }
270            Forbidden => write!(
271                f,
272                "request forbidden: IMDS is disabled or the caller has insufficient permissions."
273            ),
274        }
275    }
276}
277
278impl Error for TokenError {}
279
280impl From<TokenErrorKind> for TokenError {
281    fn from(kind: TokenErrorKind) -> Self {
282        Self { kind }
283    }
284}