Skip to content

Commit 666e240

Browse files
committed
mk-oracle: use the ASM credentials when connecting to an ASM instance
The `asm_*` fields were parsed but never read, so ASM instances were contacted with the regular database credentials. `Target::connection_auth()` now picks the `asm_*` values for ASM targets and the regular ones for everything else; the per-field fallbacks are unchanged, so a config without `asm_*` behaves as before. The wallet setup is decided per target too, so `asm_type: wallet` works with standard database credentials. CMK-37729 Change-Id: I14084b1633dcd7e8d357bdcc6b2463c1a162add1
1 parent 30bea6e commit 666e240

5 files changed

Lines changed: 244 additions & 31 deletions

File tree

packages/mk-oracle/README.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,54 @@ authentication:
166166
password: 'secret' # mandatory for standard auth
167167
role: 'sysdba' # optional, e.g. sysdba, sysasm
168168
type: 'standard' # optional, default: "standard", values: standard, wallet
169+
asm_username: 'asm_user' # optional, only used for ASM instances
170+
asm_password: 'asm_pass' # optional, only used for ASM instances
171+
asm_role: 'sysasm' # optional, only used for ASM instances
172+
asm_type: 'standard' # optional, only used for ASM instances
169173
```
170174

171175
Set `type: wallet` to use Oracle Wallet authentication instead of username/password (see [Oracle Wallet Authentication](#oracle-wallet-authentication) below).
172176

177+
#### ASM Authentication
178+
179+
ASM instances (a SID or instance name starting with `+`, e.g. `+ASM`) are usually
180+
reached with different credentials than a normal database. The `asm_*` fields
181+
hold those credentials and replace their regular counterparts whenever the plugin
182+
connects to an ASM instance; normal database connections ignore them. This
183+
mirrors `ASMUSER` of the legacy `mk_oracle` plugin.
184+
185+
Each field falls back to its regular counterpart when it is not set:
186+
187+
| Field | Replaces | Fallback when unset |
188+
| -------------- | ---------- | --------------------------------------------------------------------------------------- |
189+
| `asm_username` | `username` | `username` |
190+
| `asm_password` | `password` | `password` (only when `asm_username` is unset, so an ASM user never reuses a DB secret) |
191+
| `asm_role` | `role` | `role` |
192+
| `asm_type` | `type` | derived from `asm_username`/`asm_password`, see below |
193+
194+
When `asm_type` is omitted it is derived: `asm_username: '/'` or an
195+
`asm_username` without `asm_password` means external authentication (`wallet`),
196+
an ASM user with a password means `standard`, and without any `asm_username` the
197+
regular `type` applies.
198+
199+
Set `asm_role` (typically `sysasm`, `sysdba` also works) — an ASM instance has no
200+
data dictionary reachable from an unprivileged session.
201+
202+
```yaml
203+
oracle:
204+
main:
205+
authentication:
206+
username: 'checkmk'
207+
password: 'secret'
208+
role: 'sysdba'
209+
asm_username: 'asm_user'
210+
asm_password: 'asm_pass'
211+
asm_role: 'sysasm'
212+
instances:
213+
- sid: 'ORCL' # connects as checkmk/secret as sysdba
214+
- sid: '+ASM' # connects as asm_user/asm_pass as sysasm
215+
```
216+
173217
### Connection
174218

175219
Defines the network-level connection parameters shared by all instances unless overridden.
@@ -926,7 +970,7 @@ The generated file is a single YAML document consisting of:
926970
| ------------------------------------------ | ------------------------------------------------------------------------------------------------- |
927971
| `DBUSER` (required) | Top-level `connection:` (hostname, port) and `authentication:`, plus the first `instances:` entry |
928972
| `DBUSER_<SID>` | An `instances:` entry with per-instance `connection:` and `authentication:` |
929-
| `ASMUSER` | `asm_username`, `asm_password`, `asm_role` under `authentication:` |
973+
| `ASMUSER` | `asm_username`, `asm_password`, `asm_role`, `asm_type` under `authentication:` |
930974
| `REMOTE_INSTANCE_<ID>` | An `instances:` entry including `piggyback_host:` (Linux/AIX only) |
931975
| `SYNC_SECTIONS` / `ASYNC_SECTIONS` | `sections:` entries with `is_async: false` / `true` |
932976
| `SYNC_ASM_SECTIONS` / `ASYNC_ASM_SECTIONS` | `sections:` entries with `affinity: "asm"` (`"all"` if the section is also a normal section) |

packages/mk-oracle/src/config/authentication.rs

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,35 @@ impl Authentication {
280280
pub fn asm_role(&self) -> Option<&Role> {
281281
self.asm_role.as_ref().or(self.role.as_ref())
282282
}
283+
284+
pub fn db_auth(&self) -> ConnectionAuth {
285+
ConnectionAuth {
286+
auth_type: self.auth_type.clone(),
287+
username: self.username().to_owned(),
288+
password: self.password().map(str::to_owned),
289+
role: self.role().cloned(),
290+
}
291+
}
292+
293+
pub fn asm_auth(&self) -> ConnectionAuth {
294+
ConnectionAuth {
295+
auth_type: self.asm_auth_type(),
296+
username: self.asm_username().to_owned(),
297+
password: self.asm_password().map(str::to_owned),
298+
role: self.asm_role().cloned(),
299+
}
300+
}
301+
}
302+
303+
/// The credentials, role and auth type used to open one connection, resolved
304+
/// out of an [`Authentication`] by [`Authentication::db_auth`] /
305+
/// [`Authentication::asm_auth`].
306+
#[derive(PartialEq, Debug, Clone)]
307+
pub struct ConnectionAuth {
308+
pub auth_type: AuthType,
309+
pub username: String,
310+
pub password: Option<String>,
311+
pub role: Option<Role>,
283312
}
284313

285314
fn _extract_username_if_env_var<T: AsRef<str> + Sized>(value: T) -> String {
@@ -448,6 +477,16 @@ authentication:
448477
username: "foo"
449478
_password: "bar"
450479
_type: "system"
480+
"#;
481+
pub const AUTHENTICATION_ASM: &str = r#"
482+
authentication:
483+
username: "foo"
484+
password: "bar"
485+
type: "standard"
486+
role: sysdba
487+
asm_username: "asm_user"
488+
asm_password: "asm_pass"
489+
asm_role: sysasm
451490
"#;
452491
}
453492

@@ -516,22 +555,67 @@ authentication:
516555

517556
#[test]
518557
fn test_authentication_from_yaml_asm_fields() {
519-
let yaml_str = r#"
558+
let a = Authentication::from_yaml(&create_yaml(data::AUTHENTICATION_ASM))
559+
.unwrap()
560+
.unwrap();
561+
assert_eq!(a.asm_username(), "asm_user");
562+
assert_eq!(a.asm_password(), Some("asm_pass"));
563+
assert_eq!(a.asm_role(), Some(&Role::SysASM));
564+
}
565+
566+
#[test]
567+
fn test_asm_auth_uses_asm_fields() {
568+
let a = Authentication::from_yaml(&create_yaml(data::AUTHENTICATION_ASM))
569+
.unwrap()
570+
.unwrap();
571+
assert_eq!(
572+
a.asm_auth(),
573+
ConnectionAuth {
574+
auth_type: AuthType::Standard,
575+
username: "asm_user".to_owned(),
576+
password: Some("asm_pass".to_owned()),
577+
role: Some(Role::SysASM),
578+
}
579+
);
580+
// The regular connection is unaffected by the `asm_*` fields.
581+
assert_eq!(
582+
a.db_auth(),
583+
ConnectionAuth {
584+
auth_type: AuthType::Standard,
585+
username: "foo".to_owned(),
586+
password: Some("bar".to_owned()),
587+
role: Some(Role::SysDba),
588+
}
589+
);
590+
}
591+
592+
#[test]
593+
fn test_asm_auth_falls_back_to_regular_fields() {
594+
let a = Authentication::from_yaml(&create_yaml(data::AUTHENTICATION_NO_ASM))
595+
.unwrap()
596+
.unwrap();
597+
assert_eq!(a.asm_auth(), a.db_auth());
598+
}
599+
600+
#[test]
601+
fn test_asm_auth_wallet_with_standard_regular_auth() {
602+
let a = Authentication::from_yaml(&create_yaml(
603+
r#"
520604
authentication:
521605
username: "foo"
522606
password: "bar"
523607
type: "standard"
524608
role: sysdba
525-
asm_username: "asm_user"
526-
asm_password: "asm_pass"
609+
asm_username: "/"
527610
asm_role: sysasm
528-
"#;
529-
let a = Authentication::from_yaml(&create_yaml(yaml_str))
530-
.unwrap()
531-
.unwrap();
532-
assert_eq!(a.asm_username(), "asm_user");
533-
assert_eq!(a.asm_password(), Some("asm_pass"));
534-
assert_eq!(a.asm_role(), Some(&Role::SysASM));
611+
"#,
612+
))
613+
.unwrap()
614+
.unwrap();
615+
// `asm_username: "/"` is external auth: no password is passed on.
616+
assert_eq!(a.asm_auth().auth_type, AuthType::Wallet);
617+
assert_eq!(a.asm_auth().role, Some(Role::SysASM));
618+
assert_eq!(a.db_auth().auth_type, AuthType::Standard);
535619
}
536620

537621
#[test]

packages/mk-oracle/src/ora_sql/backend.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,24 @@ impl OraDbEngine for StdEngine {
6363
let connection_string = target
6464
.make_connection_string(instance_name, ConnectionStringType::Tns)
6565
.context("Target is not defined")?;
66+
// An ASM instance is reached with the `asm_*` credentials from the config.
67+
let auth = target.connection_auth();
6668
log::info!(
67-
"Connection string: {}, auth type {:?}",
69+
"Connection string: {}, asm {}, auth type {:?}",
6870
connection_string,
69-
target.auth.auth_type()
71+
target.is_asm(),
72+
auth.auth_type
7073
);
7174

72-
let mut connector = match target.auth.auth_type() {
75+
let mut connector = match &auth.auth_type {
7376
AuthType::Standard => {
7477
// Standard authentication with username and password
75-
let username = target.auth.username();
76-
let password = target.auth.password().unwrap_or("");
77-
log::info!("Using standard authentication with user: {}", username);
78-
Connector::new(username, password, &connection_string)
78+
log::info!("Using standard authentication with user: {}", auth.username);
79+
Connector::new(
80+
&auth.username,
81+
auth.password.as_deref().unwrap_or(""),
82+
&connection_string,
83+
)
7984
}
8085
AuthType::Os | AuthType::Wallet => {
8186
// OS/Wallet authentication - use external auth with empty credentials
@@ -86,7 +91,7 @@ impl OraDbEngine for StdEngine {
8691
}
8792
};
8893

89-
if let Some(role) = target.auth.role() {
94+
if let Some(role) = &auth.role {
9095
log::info!("Using role: {}", role);
9196
connector.privilege(_to_privilege(role));
9297
}

packages/mk-oracle/src/ora_sql/instance.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,6 @@ pub async fn generate_data(
8585
// we need to set TNS_ADMIN for Oracle client for the case alias is used
8686
add_tns_admin_to_env(ora_sql.conn());
8787

88-
// Set up wallet environment (creates sqlnet.ora with wallet location)
89-
// Only if tns_admin is NOT explicitly set in config
90-
let tns_admin_explicitly_set = ora_sql.conn().tns_admin().is_some();
91-
if ora_sql.auth().auth_type() == &AuthType::Wallet && !tns_admin_explicitly_set {
92-
if let Err(e) = setup_wallet_environment(None) {
93-
log::error!("Failed to setup wallet environment: {}", e);
94-
return Err(e).context("Failed to setup wallet environment");
95-
}
96-
}
97-
9888
// TODO: detect instances
9989
// TODO: apply to config detected instances
10090
// TODO: customize instances
@@ -103,6 +93,22 @@ pub async fn generate_data(
10393
let all = calc_all_spots(vec![ora_sql.endpoint()], ora_sql.instances());
10494
let all = filter_spots(all, ora_sql.discovery());
10595

96+
// Set up wallet environment (creates sqlnet.ora with wallet location)
97+
// Only if tns_admin is NOT explicitly set in config.
98+
// The auth type is asked per spot: an ASM instance may use wallet auth
99+
// (`asm_type`) while the regular credentials are standard, and vice versa.
100+
let tns_admin_explicitly_set = ora_sql.conn().tns_admin().is_some();
101+
let uses_wallet = ora_sql.auth().auth_type() == &AuthType::Wallet
102+
|| all
103+
.iter()
104+
.any(|spot| spot.target().connection_auth().auth_type == AuthType::Wallet);
105+
if uses_wallet && !tns_admin_explicitly_set {
106+
if let Err(e) = setup_wallet_environment(None) {
107+
log::error!("Failed to setup wallet environment: {}", e);
108+
return Err(e).context("Failed to setup wallet environment");
109+
}
110+
}
111+
106112
let sections = ora_sql
107113
.product()
108114
.sections()

packages/mk-oracle/src/ora_sql/types.rs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ use crate::types::{
1919
ServiceType, Sid,
2020
};
2121

22-
use crate::config::{authentication::Authentication, target::TargetId};
22+
use crate::config::{
23+
authentication::{Authentication, ConnectionAuth},
24+
target::TargetId,
25+
};
2326
use std::time::Duration;
2427

2528
#[derive(Debug, Clone)]
@@ -89,6 +92,16 @@ impl Target {
8992
.map(|sid| InstanceName::from(sid).is_asm())
9093
.unwrap_or(false)
9194
}
95+
96+
/// Credentials to connect to this target with: an ASM instance is reached
97+
/// with the `asm_*` credentials, everything else with the regular ones.
98+
pub fn connection_auth(&self) -> ConnectionAuth {
99+
if self.is_asm() {
100+
self.auth.asm_auth()
101+
} else {
102+
self.auth.db_auth()
103+
}
104+
}
92105
}
93106

94107
impl Target {
@@ -221,7 +234,7 @@ impl Target {
221234
#[cfg(test)]
222235
mod tests {
223236
use super::*;
224-
use crate::config::authentication::Authentication;
237+
use crate::config::authentication::{AuthType, Authentication, Role};
225238
use crate::config::target::TargetIdBuilder;
226239
use crate::config::yaml::test_tools::create_yaml;
227240
use crate::types::HostName;
@@ -256,6 +269,67 @@ authentication:
256269
assert!(!target_with_target_id(TargetIdBuilder::new().build()).is_asm());
257270
}
258271

272+
const ASM_AUTH_YAML: &str = r"
273+
authentication:
274+
username: 'user'
275+
password: 'pass'
276+
type: 'standard'
277+
role: 'sysdba'
278+
asm_username: 'asm_user'
279+
asm_password: 'asm_pass'
280+
asm_role: 'sysasm'
281+
asm_type: 'standard'";
282+
283+
fn target_with_asm_auth(sid: &str) -> Target {
284+
Target {
285+
host: HostName::from("localhost".to_owned()),
286+
timeout: Duration::from_secs(0),
287+
target_id: TargetIdBuilder::new().sid(Some(sid)).build(),
288+
port: Port(1521),
289+
auth: Authentication::from_yaml(&create_yaml(ASM_AUTH_YAML))
290+
.unwrap()
291+
.unwrap(),
292+
}
293+
}
294+
295+
#[test]
296+
fn test_connection_auth_for_asm_instance() {
297+
let target = target_with_asm_auth("+ASM");
298+
let auth = target.connection_auth();
299+
assert_eq!(auth.username, "asm_user");
300+
assert_eq!(auth.password.as_deref(), Some("asm_pass"));
301+
assert_eq!(auth.role, Some(Role::SysASM));
302+
assert_eq!(auth.auth_type, AuthType::Standard);
303+
}
304+
305+
#[test]
306+
fn test_connection_auth_for_db_instance_ignores_asm_fields() {
307+
let target = target_with_asm_auth("ORCL");
308+
let auth = target.connection_auth();
309+
assert_eq!(auth.username, "user");
310+
assert_eq!(auth.password.as_deref(), Some("pass"));
311+
assert_eq!(auth.role, Some(Role::SysDba));
312+
assert_eq!(auth.auth_type, AuthType::Standard);
313+
}
314+
315+
#[test]
316+
fn test_connection_auth_without_asm_fields_stays_regular() {
317+
// No `asm_*` in the config: an ASM instance uses the regular credentials.
318+
let target = Target {
319+
host: HostName::from("localhost".to_owned()),
320+
timeout: Duration::from_secs(0),
321+
target_id: TargetIdBuilder::new().sid(Some("+ASM")).build(),
322+
port: Port(1521),
323+
auth: Authentication::from_yaml(&create_yaml(AUTH_YAML))
324+
.unwrap()
325+
.unwrap(),
326+
};
327+
let auth = target.connection_auth();
328+
assert_eq!(auth.username, "user");
329+
assert_eq!(auth.password.as_deref(), Some("pass"));
330+
assert_eq!(auth.auth_type, AuthType::Standard);
331+
}
332+
259333
#[test]
260334
fn test_make_connection_string_service_type_instance() {
261335
let target = Target {

0 commit comments

Comments
 (0)