async_snmp/client/
auth.rs1use crate::v3::UsmConfig;
26
27#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub enum CommunityVersion {
30 V1,
32 #[default]
34 V2c,
35}
36
37#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
42pub enum Auth {
43 Community {
45 version: CommunityVersion,
47 community: String,
49 },
50 Usm(UsmConfig),
52}
53
54impl Default for Auth {
55 fn default() -> Self {
57 Auth::v2c("public")
58 }
59}
60
61impl Auth {
62 pub fn v1(community: impl Into<String>) -> Self {
76 Auth::Community {
77 version: CommunityVersion::V1,
78 community: community.into(),
79 }
80 }
81
82 pub fn v2c(community: impl Into<String>) -> Self {
100 Auth::Community {
101 version: CommunityVersion::V2c,
102 community: community.into(),
103 }
104 }
105
106 pub fn usm(username: impl Into<String>) -> UsmConfig {
138 UsmConfig::new(bytes::Bytes::from(username.into()))
139 }
140}
141
142impl From<UsmConfig> for Auth {
143 fn from(config: UsmConfig) -> Self {
144 Self::Usm(config)
145 }
146}
147
148const REDACTED: &str = "[REDACTED]";
150
151impl std::fmt::Debug for Auth {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 match self {
154 Auth::Community { version, .. } => f
155 .debug_struct("Auth::Community")
156 .field("version", version)
157 .field("community", &REDACTED)
158 .finish(),
159 Auth::Usm(usm) => f.debug_tuple("Auth::Usm").field(usm).finish(),
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use crate::message::SecurityLevel;
168 use crate::v3::{AuthProtocol, PrivProtocol};
169
170 #[test]
171 fn test_default_auth() {
172 let auth = Auth::default();
173 match auth {
174 Auth::Community { version, community } => {
175 assert_eq!(version, CommunityVersion::V2c);
176 assert_eq!(community, "public");
177 }
178 Auth::Usm(_) => panic!("expected Community variant"),
179 }
180 }
181
182 #[test]
183 fn test_v1_auth() {
184 let auth = Auth::v1("private");
185 match auth {
186 Auth::Community { version, community } => {
187 assert_eq!(version, CommunityVersion::V1);
188 assert_eq!(community, "private");
189 }
190 Auth::Usm(_) => panic!("expected Community variant"),
191 }
192 }
193
194 #[test]
195 fn test_v2c_auth() {
196 let auth = Auth::v2c("secret");
197 match auth {
198 Auth::Community { version, community } => {
199 assert_eq!(version, CommunityVersion::V2c);
200 assert_eq!(community, "secret");
201 }
202 Auth::Usm(_) => panic!("expected Community variant"),
203 }
204 }
205
206 #[test]
207 fn test_community_version_default() {
208 let version = CommunityVersion::default();
209 assert_eq!(version, CommunityVersion::V2c);
210 }
211
212 #[test]
213 fn test_usm_no_auth_no_priv() {
214 let auth: Auth = Auth::usm("readonly").into();
215 match auth {
216 Auth::Usm(usm) => {
217 assert_eq!(usm.username().as_ref(), b"readonly");
218 assert_eq!(usm.security_level(), SecurityLevel::NoAuthNoPriv);
219 assert!(usm.configured_context_name().is_empty());
220 }
221 Auth::Community { .. } => panic!("expected Usm variant"),
222 }
223 }
224
225 #[test]
226 fn test_usm_auth_no_priv() {
227 let auth: Auth = Auth::usm("admin")
228 .auth(AuthProtocol::Sha256, "authpass123")
229 .into();
230 match auth {
231 Auth::Usm(usm) => {
232 assert_eq!(usm.username().as_ref(), b"admin");
233 assert_eq!(usm.security_level(), SecurityLevel::AuthNoPriv);
234 }
235 Auth::Community { .. } => panic!("expected Usm variant"),
236 }
237 }
238
239 #[test]
240 fn test_usm_auth_priv() {
241 let auth: Auth = Auth::usm("admin")
242 .auth_priv(
243 AuthProtocol::Sha256,
244 "authpass",
245 PrivProtocol::Aes128,
246 "privpass",
247 )
248 .into();
249 match auth {
250 Auth::Usm(usm) => {
251 assert_eq!(usm.username().as_ref(), b"admin");
252 assert_eq!(usm.security_level(), SecurityLevel::AuthPriv);
253 }
254 Auth::Community { .. } => panic!("expected Usm variant"),
255 }
256 }
257
258 #[test]
259 fn test_usm_with_context_name() {
260 let auth: Auth = Auth::usm("admin")
261 .auth(AuthProtocol::Sha256, "authpass")
262 .context_name("vlan100")
263 .into();
264 match auth {
265 Auth::Usm(usm) => {
266 assert_eq!(usm.username().as_ref(), b"admin");
267 assert_eq!(usm.configured_context_name().as_ref(), b"vlan100");
268 }
269 Auth::Community { .. } => panic!("expected Usm variant"),
270 }
271 }
272
273 #[test]
274 fn test_usm_builder_chaining() {
275 let auth: Auth = Auth::usm("user")
277 .auth_priv(AuthProtocol::Sha512, "auth", PrivProtocol::Aes256, "priv")
278 .context_name("ctx")
279 .into();
280
281 match auth {
282 Auth::Usm(usm) => {
283 assert_eq!(usm.username().as_ref(), b"user");
284 assert_eq!(usm.security_level(), SecurityLevel::AuthPriv);
285 assert_eq!(usm.configured_context_name().as_ref(), b"ctx");
286 }
287 Auth::Community { .. } => panic!("expected Usm variant"),
288 }
289 }
290
291 #[test]
292 fn test_debug_redacts_secrets() {
293 let community = Auth::v2c("supersecretcommunity");
295 let rendered = format!("{community:?}");
296 assert!(!rendered.contains("supersecretcommunity"), "{rendered}");
297 assert!(rendered.contains("[REDACTED]"), "{rendered}");
298
299 let config = Auth::usm("admin")
301 .auth_priv(
302 AuthProtocol::Sha256,
303 "authpassword123",
304 PrivProtocol::Aes128,
305 "privpassword456",
306 )
307 .context_name("vlan100");
308 let config_rendered = format!("{config:?}");
309 assert!(
310 !config_rendered.contains("authpassword123"),
311 "{config_rendered}"
312 );
313 assert!(
314 !config_rendered.contains("privpassword456"),
315 "{config_rendered}"
316 );
317 assert!(config_rendered.contains("admin"), "{config_rendered}");
319 assert!(config_rendered.contains("vlan100"), "{config_rendered}");
320
321 let usm: Auth = config.into();
322 let usm_rendered = format!("{usm:?}");
323 assert!(!usm_rendered.contains("authpassword123"), "{usm_rendered}");
324 assert!(!usm_rendered.contains("privpassword456"), "{usm_rendered}");
325 assert!(usm_rendered.contains("[REDACTED]"), "{usm_rendered}");
326 assert!(usm_rendered.contains("admin"), "{usm_rendered}");
327 }
328}