go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/os.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package resources 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "strings" 10 "time" 11 12 "github.com/cockroachdb/errors" 13 "github.com/rs/zerolog/log" 14 "go.mondoo.com/cnquery/llx" 15 "go.mondoo.com/cnquery/providers-sdk/v1/inventory" 16 "go.mondoo.com/cnquery/providers/os/connection" 17 "go.mondoo.com/cnquery/providers/os/connection/shared" 18 "go.mondoo.com/cnquery/providers/os/id/hostname" 19 "go.mondoo.com/cnquery/providers/os/id/platformid" 20 "go.mondoo.com/cnquery/providers/os/resources/reboot" 21 "go.mondoo.com/cnquery/providers/os/resources/systemd" 22 "go.mondoo.com/cnquery/providers/os/resources/updates" 23 "go.mondoo.com/cnquery/providers/os/resources/uptime" 24 "go.mondoo.com/cnquery/providers/os/resources/windows" 25 ) 26 27 func (p *mqlOs) rebootpending() (bool, error) { 28 switch p.MqlRuntime.Connection.(type) { 29 case *connection.DockerSnapshotConnection: 30 return false, nil 31 case *connection.TarConnection: 32 return false, nil 33 } 34 35 // check photon 36 conn := p.MqlRuntime.Connection.(shared.Connection) 37 asset := conn.Asset() 38 39 if asset.Platform.Name == "photon" { 40 // get installed kernel and check if the found one is running 41 k, err := CreateResource(p.MqlRuntime, "kernel", map[string]*llx.RawData{}) 42 if err != nil { 43 return false, err 44 } 45 kernel := k.(*mqlKernel) 46 47 kernelInstalled := kernel.GetInstalled() 48 if kernelInstalled.Error != nil { 49 return false, kernelInstalled.Error 50 } 51 52 kernels := []KernelVersion{} 53 data, err := json.Marshal(kernelInstalled) 54 if err != nil { 55 return false, err 56 } 57 err = json.Unmarshal([]byte(data), &kernels) 58 if err != nil { 59 return false, err 60 } 61 62 // we should only have one kernel here 63 if len(kernels) != 1 { 64 return false, errors.New("unexpected kernel list result for photon os") 65 } 66 67 return !kernels[0].Running, nil 68 } 69 70 // TODO: move more logic into MQL to leverage its cache 71 // try to collect if a reboot is required, fails for static images 72 rb, err := reboot.New(conn) 73 if err != nil { 74 return false, err 75 } 76 return rb.RebootPending() 77 } 78 79 func (p *mqlOs) getUnixEnv() (map[string]interface{}, error) { 80 rawCmd, err := CreateResource(p.MqlRuntime, "command", map[string]*llx.RawData{ 81 "command": llx.StringData("env"), 82 }) 83 if err != nil { 84 return nil, err 85 } 86 cmd := rawCmd.(*mqlCommand) 87 88 out := cmd.GetStdout() 89 if out.Error != nil { 90 return nil, out.Error 91 } 92 93 res := map[string]interface{}{} 94 lines := strings.Split(out.Data, "\n") 95 for i := range lines { 96 parts := strings.SplitN(lines[i], "=", 2) 97 if len(parts) != 2 { 98 continue 99 } 100 res[parts[0]] = parts[1] 101 } 102 103 return res, nil 104 } 105 106 func (p *mqlOs) getWindowsEnv() (map[string]interface{}, error) { 107 rawCmd, err := CreateResource(p.MqlRuntime, "powershell", map[string]*llx.RawData{ 108 "script": llx.StringData("Get-ChildItem Env:* | ConvertTo-Json"), 109 }) 110 if err != nil { 111 return nil, err 112 } 113 cmd := rawCmd.(*mqlPowershell) 114 115 out := cmd.GetStdout() 116 if out.Error != nil { 117 return nil, out.Error 118 } 119 120 return windows.ParseEnv(strings.NewReader(out.Data)) 121 } 122 123 func (p *mqlOs) env() (map[string]interface{}, error) { 124 conn := p.MqlRuntime.Connection.(shared.Connection) 125 platform := conn.Asset().Platform 126 127 if platform.IsFamily("windows") { 128 return p.getWindowsEnv() 129 } 130 return p.getUnixEnv() 131 } 132 133 func (p *mqlOs) path(env map[string]interface{}) ([]interface{}, error) { 134 rawPath, ok := env["PATH"] 135 if !ok { 136 return []interface{}{}, nil 137 } 138 139 path := rawPath.(string) 140 parts := strings.Split(path, ":") 141 res := make([]interface{}, len(parts)) 142 for i := range parts { 143 res[i] = parts[i] 144 } 145 146 return res, nil 147 } 148 149 func MqlTime(t time.Time) *time.Time { 150 return &t 151 } 152 153 // returns uptime in nanoseconds 154 func (p *mqlOs) uptime() (*time.Time, error) { 155 uptime, err := uptime.New(p.MqlRuntime.Connection.(shared.Connection)) 156 if err != nil { 157 return MqlTime(llx.DurationToTime(0)), err 158 } 159 160 t, err := uptime.Duration() 161 if err != nil { 162 return MqlTime(llx.DurationToTime(0)), err 163 } 164 165 // we get nano seconds but duration to time only takes seconds 166 bootTime := time.Now().Add(-t) 167 up := time.Now().Unix() - bootTime.Unix() 168 return MqlTime(llx.DurationToTime(up)), nil 169 } 170 171 func (p *mqlOsUpdate) id() (string, error) { 172 return p.Name.Data, nil 173 } 174 175 func (p *mqlOs) updates() ([]interface{}, error) { 176 // find suitable system updates 177 conn := p.MqlRuntime.Connection.(shared.Connection) 178 um, err := updates.ResolveSystemUpdateManager(conn) 179 if um == nil || err != nil { 180 return nil, fmt.Errorf("could not detect suitable update manager for platform") 181 } 182 183 // retrieve all system updates 184 updates, err := um.List() 185 if err != nil { 186 return nil, fmt.Errorf("could not retrieve updates list for platform") 187 } 188 189 // create MQL update resources for each update 190 osupdates := make([]interface{}, len(updates)) 191 log.Debug().Int("updates", len(updates)).Msg("mql[updates]> found system updates") 192 for i, update := range updates { 193 194 o, err := CreateResource(p.MqlRuntime, "os.update", map[string]*llx.RawData{ 195 "name": llx.StringData(update.Name), 196 "severity": llx.StringData(update.Severity), 197 "category": llx.StringData(update.Category), 198 "restart": llx.BoolData(update.Restart), 199 "format": llx.StringData(update.Format), 200 }) 201 if err != nil { 202 return nil, err 203 } 204 205 osupdates[i] = o.(*mqlOsUpdate) 206 } 207 208 // return the packages as new entries 209 return osupdates, nil 210 } 211 212 func (s *mqlOs) hostname() (string, error) { 213 conn := s.MqlRuntime.Connection.(shared.Connection) 214 platform := conn.Asset().Platform 215 216 if res, ok := hostname.Hostname(conn, platform); ok { 217 return res, nil 218 } 219 return "", errors.New("cannot determine hostname") 220 } 221 222 func (p *mqlOs) name() (string, error) { 223 conn := p.MqlRuntime.Connection.(shared.Connection) 224 platform := conn.Asset().Platform 225 226 if !platform.IsFamily(inventory.FAMILY_UNIX) && !platform.IsFamily(inventory.FAMILY_WINDOWS) { 227 return "", errors.New("your platform is not supported by operating system resource") 228 } 229 230 if platform.IsFamily(inventory.FAMILY_LINUX) { 231 lf, err := CreateResource(p.MqlRuntime, "file", map[string]*llx.RawData{ 232 "path": llx.StringData("/etc/machine-info"), 233 }) 234 if err != nil { 235 return "", err 236 } 237 file := lf.(*mqlFile) 238 239 exists := file.GetExists() 240 if exists.Error != nil { 241 return "", exists.Error 242 } 243 // if the file does not exist, the pretty hostname is just empty 244 // fallback to hostname 245 if !exists.Data { 246 hn := p.GetHostname() 247 return hn.Data, hn.Error 248 } 249 250 // gather content 251 data := file.GetContent() 252 if data.Error != nil { 253 return "", data.Error 254 } 255 256 mi, err := systemd.ParseMachineInfo(strings.NewReader(data.Data)) 257 if err != nil { 258 return "", err 259 } 260 261 if mi.PrettyHostname != "" { 262 return mi.PrettyHostname, nil 263 } 264 } 265 266 // return plain hostname, this also happens for linux if no pretty name was found 267 if platform.IsFamily(inventory.FAMILY_UNIX) { 268 hn := p.GetHostname() 269 return hn.Data, hn.Error 270 } 271 272 if platform.IsFamily(inventory.FAMILY_WINDOWS) { 273 274 // try to get the computer name from env 275 env, err := p.getWindowsEnv() 276 if err == nil { 277 val, ok := env["COMPUTERNAME"] 278 if ok { 279 return val.(string), nil 280 } 281 } 282 283 // fallback to hostname 284 hn := p.GetHostname() 285 return hn.Data, hn.Error 286 } 287 288 return "", errors.New("your platform is not supported by operating system resource") 289 } 290 291 // returns the OS native machine UUID/GUID 292 func (s *mqlOs) machineid() (string, error) { 293 conn := s.MqlRuntime.Connection.(shared.Connection) 294 platform := conn.Asset().Platform 295 296 uuidProvider, err := platformid.MachineIDProvider(conn, platform) 297 if err != nil { 298 return "", errors.Wrap(err, "cannot determine platform uuid") 299 } 300 301 if uuidProvider == nil { 302 return "", errors.New("cannot determine platform uuid") 303 } 304 305 id, err := uuidProvider.ID() 306 if err != nil { 307 return "", errors.Wrap(err, "cannot determine platform uuid") 308 } 309 310 return id, nil 311 } 312 313 func (p *mqlOsBase) id() (string, error) { 314 conn := p.MqlRuntime.Connection.(shared.Connection) 315 asset := conn.Asset() 316 317 ident := asset.GetMrn() 318 if ident == "" { 319 ident = strings.Join(asset.GetPlatformIds(), ",") 320 } 321 return "os.base(" + ident + ")", nil 322 } 323 324 func (p *mqlOsBase) rebootpending() (bool, error) { 325 // it is a container image, a reboot is never required 326 switch p.MqlRuntime.Connection.(type) { 327 case *connection.DockerSnapshotConnection: 328 return false, nil 329 case *connection.TarConnection: 330 return false, nil 331 } 332 333 // check photon 334 conn := p.MqlRuntime.Connection.(shared.Connection) 335 platform := conn.Asset().Platform 336 337 if platform.Name == "photon" { 338 // get installed kernel and check if the found one is running 339 raw, err := CreateResource(p.MqlRuntime, "kernel", map[string]*llx.RawData{}) 340 if err != nil { 341 return false, err 342 } 343 kernel := raw.(*mqlKernel) 344 installed := kernel.GetInstalled() 345 if installed.Error != nil { 346 return false, installed.Error 347 } 348 349 kernels := []KernelVersion{} 350 data, err := json.Marshal(installed) 351 if err != nil { 352 return false, err 353 } 354 err = json.Unmarshal([]byte(data), &kernels) 355 if err != nil { 356 return false, err 357 } 358 359 // we should only have one kernel here 360 if len(kernels) != 1 { 361 return false, errors.New("unexpected kernel list result for photon os") 362 } 363 364 return !kernels[0].Running, nil 365 } 366 367 // TODO: move more logic into MQL to leverage its cache 368 // try to collect if a reboot is required, fails for static images 369 rb, err := reboot.New(conn) 370 if err != nil { 371 return false, err 372 } 373 return rb.RebootPending() 374 } 375 376 func (p *mqlOsBase) getUnixEnv() (map[string]interface{}, error) { 377 rawCmd, err := CreateResource(p.MqlRuntime, "command", map[string]*llx.RawData{ 378 "command": llx.StringData("env"), 379 }) 380 if err != nil { 381 return nil, err 382 } 383 cmd := rawCmd.(*mqlCommand) 384 385 out := cmd.GetStdout() 386 if out.Error != nil { 387 return nil, out.Error 388 } 389 390 res := map[string]interface{}{} 391 lines := strings.Split(out.Data, "\n") 392 for i := range lines { 393 parts := strings.SplitN(lines[i], "=", 2) 394 if len(parts) != 2 { 395 continue 396 } 397 res[parts[0]] = parts[1] 398 } 399 400 return res, nil 401 } 402 403 func (p *mqlOsBase) getWindowsEnv() (map[string]interface{}, error) { 404 rawCmd, err := CreateResource(p.MqlRuntime, "powershell", map[string]*llx.RawData{ 405 "script": llx.StringData("Get-ChildItem Env:* | ConvertTo-Json"), 406 }) 407 if err != nil { 408 return nil, err 409 } 410 cmd := rawCmd.(*mqlPowershell) 411 412 out := cmd.GetStdout() 413 if out.Error != nil { 414 return nil, out.Error 415 } 416 417 return windows.ParseEnv(strings.NewReader(out.Data)) 418 } 419 420 func (p *mqlOsBase) env() (map[string]interface{}, error) { 421 conn := p.MqlRuntime.Connection.(shared.Connection) 422 platform := conn.Asset().Platform 423 424 if platform.IsFamily("windows") { 425 return p.getWindowsEnv() 426 } 427 return p.getUnixEnv() 428 } 429 430 func (p *mqlOsBase) path(env map[string]interface{}) ([]interface{}, error) { 431 rawPath, ok := env["PATH"] 432 if !ok { 433 return []interface{}{}, nil 434 } 435 436 path := rawPath.(string) 437 parts := strings.Split(path, ":") 438 res := make([]interface{}, len(parts)) 439 for i := range parts { 440 res[i] = parts[i] 441 } 442 443 return res, nil 444 } 445 446 // returns uptime in nanoseconds 447 func (p *mqlOsBase) uptime() (*time.Time, error) { 448 conn := p.MqlRuntime.Connection.(shared.Connection) 449 uptime, err := uptime.New(conn) 450 if err != nil { 451 return MqlTime(llx.DurationToTime(0)), err 452 } 453 454 t, err := uptime.Duration() 455 if err != nil { 456 return MqlTime(llx.DurationToTime(0)), err 457 } 458 459 // we get nano seconds but duration to time only takes seconds 460 bootTime := time.Now().Add(-t) 461 up := time.Now().Unix() - bootTime.Unix() 462 return MqlTime(llx.DurationToTime(up)), nil 463 } 464 465 func (p *mqlOsBase) updates() ([]interface{}, error) { 466 // find suitable system updates 467 conn := p.MqlRuntime.Connection.(shared.Connection) 468 um, err := updates.ResolveSystemUpdateManager(conn) 469 if um == nil || err != nil { 470 return nil, fmt.Errorf("could not detect suitable update manager for platform") 471 } 472 473 // retrieve all system updates 474 updates, err := um.List() 475 if err != nil { 476 return nil, fmt.Errorf("could not retrieve updates list for platform") 477 } 478 479 // create MQL update resources for each update 480 osupdates := make([]interface{}, len(updates)) 481 log.Debug().Int("updates", len(updates)).Msg("mql[updates]> found system updates") 482 for i, update := range updates { 483 484 o, err := CreateResource(p.MqlRuntime, "os.update", map[string]*llx.RawData{ 485 "name": llx.StringData(update.Name), 486 "severity": llx.StringData(update.Severity), 487 "category": llx.StringData(update.Category), 488 "restart": llx.BoolData(update.Restart), 489 "format": llx.StringData(update.Format), 490 }) 491 if err != nil { 492 return nil, err 493 } 494 495 osupdates[i] = o.(*mqlOsUpdate) 496 } 497 498 // return the packages as new entries 499 return osupdates, nil 500 } 501 502 func (s *mqlOsBase) hostname() (string, error) { 503 conn := s.MqlRuntime.Connection.(shared.Connection) 504 platform := conn.Asset().Platform 505 506 if res, ok := hostname.Hostname(conn, platform); ok { 507 return res, nil 508 } 509 return "", errors.New("cannot determine hostname") 510 } 511 512 func (p *mqlOsBase) name() (string, error) { 513 conn := p.MqlRuntime.Connection.(shared.Connection) 514 platform := conn.Asset().Platform 515 516 if !platform.IsFamily(inventory.FAMILY_UNIX) && !platform.IsFamily(inventory.FAMILY_WINDOWS) { 517 return "", errors.New("your platform is not supported by operating system resource") 518 } 519 520 if platform.IsFamily(inventory.FAMILY_LINUX) { 521 lf, err := CreateResource(p.MqlRuntime, "file", map[string]*llx.RawData{ 522 "path": llx.StringData("/etc/machine-info"), 523 }) 524 if err != nil { 525 return "", err 526 } 527 file := lf.(*mqlFile) 528 529 exists := file.GetExists() 530 if exists.Error != nil { 531 return "", exists.Error 532 } 533 // if the file does not exist, the pretty hostname is just empty 534 if !exists.Data { 535 return "", nil 536 } 537 538 // gather content 539 data := file.GetContent() 540 if data.Error != nil { 541 return "", data.Error 542 } 543 544 mi, err := systemd.ParseMachineInfo(strings.NewReader(data.Data)) 545 if err != nil { 546 return "", err 547 } 548 549 if mi.PrettyHostname != "" { 550 return mi.PrettyHostname, nil 551 } 552 } 553 554 // return plain hostname, this also happens for linux if no pretty name was found 555 if platform.IsFamily(inventory.FAMILY_UNIX) { 556 if res, ok := hostname.Hostname(conn, platform); ok { 557 return res, nil 558 } 559 return "", errors.New("cannot determine hostname") 560 } 561 562 if platform.IsFamily(inventory.FAMILY_WINDOWS) { 563 564 // try to get the computer name from env 565 env, err := p.getWindowsEnv() 566 if err == nil { 567 val, ok := env["COMPUTERNAME"] 568 if ok { 569 return val.(string), nil 570 } 571 } 572 573 // fallback to hostname 574 if res, ok := hostname.Hostname(conn, platform); ok { 575 return res, nil 576 } 577 return "", errors.New("cannot determine hostname") 578 } 579 580 return "", errors.New("your platform is not supported by operating system resource") 581 } 582 583 // returns the OS native machine UUID/GUID 584 func (s *mqlOsBase) machineid() (string, error) { 585 conn := s.MqlRuntime.Connection.(shared.Connection) 586 platform := conn.Asset().Platform 587 588 uuidProvider, err := platformid.MachineIDProvider(conn, platform) 589 if err != nil { 590 return "", errors.Wrap(err, "cannot determine platform uuid") 591 } 592 593 if uuidProvider == nil { 594 return "", errors.New("cannot determine platform uuid") 595 } 596 597 id, err := uuidProvider.ID() 598 if err != nil { 599 return "", errors.Wrap(err, "cannot determine platform uuid") 600 } 601 602 return id, nil 603 } 604 605 func (s *mqlOsBase) machine() (*mqlMachine, error) { 606 res, err := CreateResource(s.MqlRuntime, "machine", map[string]*llx.RawData{}) 607 if err != nil { 608 return nil, err 609 } 610 return res.(*mqlMachine), nil 611 } 612 613 func (s *mqlOsBase) groups() (*mqlGroups, error) { 614 res, err := CreateResource(s.MqlRuntime, "groups", map[string]*llx.RawData{}) 615 if err != nil { 616 return nil, err 617 } 618 return res.(*mqlGroups), nil 619 } 620 621 func (s *mqlOsBase) users() (*mqlUsers, error) { 622 res, err := CreateResource(s.MqlRuntime, "users", map[string]*llx.RawData{}) 623 if err != nil { 624 return nil, err 625 } 626 return res.(*mqlUsers), nil 627 } 628 629 func (s *mqlOsUnix) id() (string, error) { 630 conn := s.MqlRuntime.Connection.(shared.Connection) 631 asset := conn.Asset() 632 633 ident := asset.GetMrn() 634 if ident == "" { 635 ident = strings.Join(asset.GetPlatformIds(), ",") 636 } 637 return "os.unix(" + ident + ")", nil 638 } 639 640 func (s *mqlOsUnix) base() (*mqlOsBase, error) { 641 res, err := CreateResource(s.MqlRuntime, "os.base", map[string]*llx.RawData{}) 642 if err != nil { 643 return nil, err 644 } 645 return res.(*mqlOsBase), nil 646 } 647 648 func (s *mqlOsLinux) id() (string, error) { 649 conn := s.MqlRuntime.Connection.(shared.Connection) 650 asset := conn.Asset() 651 652 ident := asset.GetMrn() 653 if ident == "" { 654 ident = strings.Join(asset.GetPlatformIds(), ",") 655 } 656 return "os.linux(" + ident + ")", nil 657 } 658 659 func (s *mqlOsLinux) unix() (*mqlOsUnix, error) { 660 res, err := CreateResource(s.MqlRuntime, "os.unix", map[string]*llx.RawData{}) 661 if err != nil { 662 return nil, err 663 } 664 return res.(*mqlOsUnix), nil 665 } 666 667 func (s *mqlOsLinux) iptables() (*mqlIptables, error) { 668 res, err := CreateResource(s.MqlRuntime, "iptables", map[string]*llx.RawData{}) 669 if err != nil { 670 return nil, err 671 } 672 return res.(*mqlIptables), nil 673 } 674 675 func (s *mqlOsLinux) ip6tables() (*mqlIp6tables, error) { 676 res, err := CreateResource(s.MqlRuntime, "ip6tables", map[string]*llx.RawData{}) 677 if err != nil { 678 return nil, err 679 } 680 return res.(*mqlIp6tables), nil 681 }