go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/detector/detector_all.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package detector 5 6 import ( 7 "bytes" 8 "io" 9 "regexp" 10 "strconv" 11 "strings" 12 13 "github.com/rs/zerolog/log" 14 "github.com/spf13/afero" 15 "go.mondoo.com/cnquery/providers-sdk/v1/inventory" 16 "go.mondoo.com/cnquery/providers/os/connection/shared" 17 win "go.mondoo.com/cnquery/providers/os/detector/windows" 18 ) 19 20 // Operating Systems 21 var macOS = &PlatformResolver{ 22 Name: "macos", 23 IsFamily: false, 24 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 25 // when we reach here, we know it is darwin 26 // check xml /System/Library/CoreServices/SystemVersion.plist 27 f, err := conn.FileSystem().Open("/System/Library/CoreServices/SystemVersion.plist") 28 if err != nil { 29 return false, nil 30 } 31 defer f.Close() 32 33 c, err := io.ReadAll(f) 34 if err != nil || len(c) == 0 { 35 return false, nil 36 } 37 38 sv, err := ParseMacOSSystemVersion(string(c)) 39 if err != nil || len(c) == 0 { 40 return false, nil 41 } 42 43 pf.Name = "macos" 44 pf.Title = sv["ProductName"] 45 pf.Version = sv["ProductVersion"] 46 pf.Build = sv["ProductBuildVersion"] 47 48 return true, nil 49 }, 50 } 51 52 // is part of the darwin platfrom and fallback for non-known darwin systems 53 var otherDarwin = &PlatformResolver{ 54 Name: "darwin", 55 IsFamily: false, 56 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 57 return true, nil 58 }, 59 } 60 61 var alpine = &PlatformResolver{ 62 Name: "alpine", 63 IsFamily: false, 64 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 65 // check if we are on edge 66 osrd := NewOSReleaseDetector(conn) 67 osr, err := osrd.osrelease() 68 if err != nil { 69 return false, nil 70 } 71 72 if osr["PRETTY_NAME"] == "Alpine Linux edge" { 73 pf.Name = "alpine" 74 pf.Version = "edge" 75 pf.Build = osr["VERSION_ID"] 76 } 77 78 // if we are on alpine, the release was detected properly from parent check 79 if pf.Name == "alpine" { 80 return true, nil 81 } 82 83 f, err := conn.FileSystem().Open("/etc/alpine-release") 84 if err != nil { 85 return false, nil 86 } 87 defer f.Close() 88 89 c, err := io.ReadAll(f) 90 if err != nil || len(c) == 0 { 91 return false, nil 92 } 93 94 pf.Name = "alpine" 95 return true, nil 96 }, 97 } 98 99 var arch = &PlatformResolver{ 100 Name: "arch", 101 IsFamily: false, 102 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 103 if pf.Name == "arch" { 104 return true, nil 105 } 106 return false, nil 107 }, 108 } 109 110 var manjaro = &PlatformResolver{ 111 Name: "manjaro", 112 IsFamily: false, 113 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 114 if pf.Name == "manjaro" { 115 return true, nil 116 } 117 return false, nil 118 }, 119 } 120 121 var debian = &PlatformResolver{ 122 Name: "debian", 123 IsFamily: false, 124 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 125 osrd := NewOSReleaseDetector(conn) 126 127 f, err := conn.FileSystem().Open("/etc/debian_version") 128 if err != nil { 129 return false, nil 130 } 131 defer f.Close() 132 133 c, err := io.ReadAll(f) 134 if err != nil || len(c) == 0 { 135 return false, nil 136 } 137 138 osr, err := osrd.osrelease() 139 if err != nil { 140 return false, nil 141 } 142 143 if osr["ID"] != "debian" { 144 return false, nil 145 } 146 147 pf.Version = strings.TrimSpace(string(c)) 148 149 unamem, err := osrd.unamem() 150 if err == nil { 151 pf.Arch = unamem 152 } 153 154 return true, nil 155 }, 156 } 157 158 var ubuntu = &PlatformResolver{ 159 Name: "ubuntu", 160 IsFamily: false, 161 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 162 if pf.Name == "ubuntu" { 163 return true, nil 164 } 165 return false, nil 166 }, 167 } 168 169 var raspbian = &PlatformResolver{ 170 Name: "raspbian", 171 IsFamily: false, 172 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 173 if pf.Name == "raspbian" { 174 return true, nil 175 } 176 return false, nil 177 }, 178 } 179 180 var kali = &PlatformResolver{ 181 Name: "kali", 182 IsFamily: false, 183 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 184 if pf.Name == "kali" { 185 return true, nil 186 } 187 return false, nil 188 }, 189 } 190 191 var linuxmint = &PlatformResolver{ 192 Name: "linuxmint", 193 IsFamily: false, 194 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 195 if pf.Name == "linuxmint" { 196 return true, nil 197 } 198 return false, nil 199 }, 200 } 201 202 var popos = &PlatformResolver{ 203 Name: "pop", 204 IsFamily: false, 205 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 206 if pf.Name == "pop" { 207 return true, nil 208 } 209 return false, nil 210 }, 211 } 212 213 // rhel PlatformResolver only detects redhat and no derivatives 214 var rhel = &PlatformResolver{ 215 Name: "redhat", 216 IsFamily: false, 217 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 218 // etc redhat release was parsed by the family already, 219 // we reuse that information here 220 // e.g. Red Hat Linux, Red Hat Enterprise Linux Server 221 if strings.Contains(pf.Title, "Red Hat") || pf.Name == "redhat" { 222 pf.Name = "redhat" 223 return true, nil 224 } 225 226 // fallback to /etc/redhat-release file 227 f, err := conn.FileSystem().Open("/etc/redhat-release") 228 if err != nil { 229 return false, nil 230 } 231 defer f.Close() 232 233 c, err := io.ReadAll(f) 234 if err != nil || len(c) == 0 { 235 return false, nil 236 } 237 238 if strings.Contains(string(c), "Red Hat") { 239 pf.Name = "redhat" 240 return true, nil 241 } 242 243 return false, nil 244 }, 245 } 246 247 var eurolinux = &PlatformResolver{ 248 Name: "eurolinux", 249 IsFamily: false, 250 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 251 if pf.Name == "eurolinux" { 252 return true, nil 253 } 254 return false, nil 255 }, 256 } 257 258 // The centos platform resolver finds CentOS and CentOS-like platforms like alma and rocky 259 var centos = &PlatformResolver{ 260 Name: "centos", 261 IsFamily: false, 262 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 263 // works for centos 5+ 264 if strings.Contains(pf.Title, "CentOS") || pf.Name == "centos" { 265 pf.Name = "centos" 266 return true, nil 267 } 268 269 // adapt the name for rocky to align it with amazonlinux, almalinux etc. 270 if pf.Name == "rocky" { 271 pf.Name = "rockylinux" 272 } 273 274 // newer alma linux do not have /etc/centos-release, check for alma linux 275 afs := &afero.Afero{Fs: conn.FileSystem()} 276 if pf.Name == "almalinux" { 277 if ok, err := afs.Exists("/etc/almalinux-release"); err == nil && ok { 278 return true, nil 279 } 280 } 281 282 // newer rockylinux do not have /etc/centos-release 283 if pf.Name == "rockylinux" { 284 if ok, err := afs.Exists("/etc/rocky-release"); err == nil && ok { 285 return true, nil 286 } 287 } 288 289 // NOTE: CentOS 5 does not have /etc/centos-release 290 // fallback to /etc/centos-release file 291 if ok, err := afs.Exists("/etc/centos-release"); err != nil || !ok { 292 return false, nil 293 } 294 295 if len(pf.Name) == 0 { 296 pf.Name = "centos" 297 } 298 299 return true, nil 300 }, 301 } 302 303 var fedora = &PlatformResolver{ 304 Name: "fedora", 305 IsFamily: false, 306 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 307 if strings.Contains(pf.Title, "Fedora") || pf.Name == "fedora" { 308 pf.Name = "fedora" 309 return true, nil 310 } 311 312 // fallback to /etc/fedora-release file 313 f, err := conn.FileSystem().Open("/etc/fedora-release") 314 if err != nil { 315 return false, nil 316 } 317 defer f.Close() 318 319 c, err := io.ReadAll(f) 320 if err != nil || len(c) == 0 { 321 return false, nil 322 } 323 324 if len(pf.Name) == 0 { 325 pf.Name = "fedora" 326 } 327 328 return true, nil 329 }, 330 } 331 332 var oracle = &PlatformResolver{ 333 Name: "oracle", 334 IsFamily: false, 335 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 336 // works for oracle 7+ 337 if pf.Name == "ol" { 338 pf.Name = "oraclelinux" 339 return true, nil 340 } 341 342 // check if we have /etc/centos-release file 343 f, err := conn.FileSystem().Open("/etc/oracle-release") 344 if err != nil { 345 return false, nil 346 } 347 defer f.Close() 348 349 c, err := io.ReadAll(f) 350 if err != nil || len(c) == 0 { 351 return false, nil 352 } 353 354 if len(pf.Name) == 0 { 355 pf.Name = "oraclelinux" 356 } 357 358 return true, nil 359 }, 360 } 361 362 var scientific = &PlatformResolver{ 363 Name: "scientific", 364 IsFamily: false, 365 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 366 // works for oracle 7+ 367 if pf.Name == "scientific" { 368 return true, nil 369 } 370 371 // we only get here if this is a rhel distribution 372 if strings.Contains(pf.Title, "Scientific Linux") { 373 pf.Name = "scientific" 374 return true, nil 375 } 376 377 return false, nil 378 }, 379 } 380 381 var amazonlinux = &PlatformResolver{ 382 Name: "amazonlinux", 383 IsFamily: false, 384 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 385 if pf.Name == "amzn" { 386 pf.Name = "amazonlinux" 387 return true, nil 388 } 389 return false, nil 390 }, 391 } 392 393 var windriver = &PlatformResolver{ 394 Name: "wrlinux", 395 IsFamily: false, 396 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 397 if pf.Name == "wrlinux" { 398 return true, nil 399 } 400 return false, nil 401 }, 402 } 403 404 var opensuse = &PlatformResolver{ 405 Name: "opensuse", 406 IsFamily: false, 407 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 408 if pf.Name == "opensuse" || pf.Name == "opensuse-leap" || pf.Name == "opensuse-tumbleweed" { 409 return true, nil 410 } 411 412 return false, nil 413 }, 414 } 415 416 var sles = &PlatformResolver{ 417 Name: "sles", 418 IsFamily: false, 419 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 420 if pf.Name == "sles" { 421 return true, nil 422 } 423 return false, nil 424 }, 425 } 426 427 var suseMicroOs = &PlatformResolver{ 428 Name: "suse-microos", 429 IsFamily: false, 430 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 431 if pf.Name == "suse-microos" { 432 return true, nil 433 } 434 return false, nil 435 }, 436 } 437 438 var gentoo = &PlatformResolver{ 439 Name: "gentoo", 440 IsFamily: false, 441 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 442 f, err := conn.FileSystem().Open("/etc/gentoo-release") 443 if err != nil { 444 return false, nil 445 } 446 defer f.Close() 447 448 c, err := io.ReadAll(f) 449 if err != nil || len(c) == 0 { 450 log.Debug().Err(err) 451 return false, nil 452 } 453 454 content := strings.TrimSpace(string(c)) 455 name, release, err := ParseRhelVersion(content) 456 if err == nil { 457 // only set title if not already properly detected by lsb or os-release 458 if len(pf.Title) == 0 { 459 pf.Title = name 460 } 461 if len(pf.Version) == 0 { 462 pf.Version = release 463 } 464 } 465 466 return false, nil 467 }, 468 } 469 470 var ubios = &PlatformResolver{ 471 Name: "ubios", 472 IsFamily: false, 473 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 474 if pf.Name == "ubios" { 475 return true, nil 476 } 477 return false, nil 478 }, 479 } 480 481 var busybox = &PlatformResolver{ 482 Name: "busybox", 483 IsFamily: false, 484 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 485 busyboxExists, err := afero.Exists(conn.FileSystem(), "/bin/busybox") 486 if !busyboxExists || err != nil { 487 return false, nil 488 } 489 490 // we need to read this file because all others show up as zero size 491 // This fille seems to be the "original" 492 // all others are hardlinks 493 f, err := conn.FileSystem().Open("/bin/[") 494 if err != nil { 495 return false, nil 496 } 497 defer f.Close() 498 499 content, err := io.ReadAll(f) 500 if err != nil { 501 return false, err 502 } 503 504 // strings are \0 terminated 505 rodataByteStrings := bytes.Split(content, []byte("\x00")) 506 if rodataByteStrings == nil { 507 return false, nil 508 } 509 510 releaseRegex := regexp.MustCompile(`^(.+)\s(v[\d\.]+)\s*\((.*)\).*$`) 511 for _, rodataByteString := range rodataByteStrings { 512 rodataString := string(rodataByteString) 513 m := releaseRegex.FindStringSubmatch(rodataString) 514 if len(m) >= 2 { 515 title := m[1] 516 release := m[2] 517 518 if strings.ToLower(title) == "busybox" { 519 pf.Name = "busybox" 520 pf.Title = title 521 pf.Version = release 522 return true, nil 523 } 524 } 525 } 526 527 return false, nil 528 }, 529 } 530 531 var photon = &PlatformResolver{ 532 Name: "photon", 533 IsFamily: false, 534 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 535 if pf.Name == "photon" { 536 return true, nil 537 } 538 return false, nil 539 }, 540 } 541 542 var openwrt = &PlatformResolver{ 543 Name: "openwrt", 544 IsFamily: false, 545 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 546 // No clue why they are not using either lsb-release or os-release 547 f, err := conn.FileSystem().Open("/etc/openwrt_release") 548 if err != nil { 549 return false, err 550 } 551 defer f.Close() 552 553 content, err := io.ReadAll(f) 554 if err != nil { 555 return false, err 556 } 557 558 lsb, err := ParseLsbRelease(string(content)) 559 if err == nil { 560 if len(lsb["DISTRIB_ID"]) > 0 { 561 pf.Name = strings.ToLower(lsb["DISTRIB_ID"]) 562 pf.Title = lsb["DISTRIB_ID"] 563 } 564 if len(lsb["DISTRIB_RELEASE"]) > 0 { 565 pf.Version = lsb["DISTRIB_RELEASE"] 566 } 567 568 return true, nil 569 } 570 571 return false, nil 572 }, 573 } 574 575 var ( 576 plcnextVersion = regexp.MustCompile(`(?m)^Arpversion:\s+(.*)$`) 577 plcnextBuildVersion = regexp.MustCompile(`(?m)^GIT Commit Hash:\s+(.*)$`) 578 ) 579 580 var plcnext = &PlatformResolver{ 581 Name: "plcnext", 582 IsFamily: false, 583 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 584 // No clue why they are not using either lsb-release or os-release 585 f, err := conn.FileSystem().Open("/etc/plcnext/arpversion") 586 if err != nil { 587 return false, err 588 } 589 defer f.Close() 590 591 content, err := io.ReadAll(f) 592 if err != nil { 593 return false, err 594 } 595 596 m := plcnextVersion.FindStringSubmatch(string(content)) 597 if len(m) >= 2 { 598 pf.Name = "plcnext" 599 pf.Title = "PLCnext" 600 pf.Version = m[1] 601 602 bm := plcnextBuildVersion.FindStringSubmatch(string(content)) 603 if len(bm) >= 2 { 604 pf.Build = bm[1] 605 } 606 607 return true, err 608 } 609 610 return false, nil 611 }, 612 } 613 614 // fallback linux detection, since we do not know the system, the family detection may not be correct 615 var defaultLinux = &PlatformResolver{ 616 Name: "generic-linux", 617 IsFamily: false, 618 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 619 // if we reach here, we know that we detected linux already 620 log.Debug().Msg("platform> we do not know the linux system, but we do our best in guessing") 621 return true, nil 622 }, 623 } 624 625 var netbsd = &PlatformResolver{ 626 Name: "netbsd", 627 IsFamily: false, 628 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 629 if strings.Contains(strings.ToLower(pf.Name), "netbsd") == false { 630 return false, nil 631 } 632 633 osrd := NewOSReleaseDetector(conn) 634 release, err := osrd.unamer() 635 if err == nil { 636 pf.Version = release 637 } 638 639 return true, nil 640 }, 641 } 642 643 var freebsd = &PlatformResolver{ 644 Name: "freebsd", 645 IsFamily: false, 646 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 647 if strings.Contains(strings.ToLower(pf.Name), "freebsd") == false { 648 return false, nil 649 } 650 651 osrd := NewOSReleaseDetector(conn) 652 release, err := osrd.unamer() 653 if err == nil { 654 pf.Version = release 655 } 656 657 return true, nil 658 }, 659 } 660 661 var openbsd = &PlatformResolver{ 662 Name: "openbsd", 663 IsFamily: false, 664 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 665 if strings.Contains(strings.ToLower(pf.Name), "openbsd") == false { 666 return false, nil 667 } 668 669 osrd := NewOSReleaseDetector(conn) 670 release, err := osrd.unamer() 671 if err == nil { 672 pf.Version = release 673 } 674 675 return true, nil 676 }, 677 } 678 679 var dragonflybsd = &PlatformResolver{ 680 Name: "dragonflybsd", 681 IsFamily: false, 682 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 683 if strings.Contains(strings.ToLower(pf.Name), "dragonfly") == false { 684 return false, nil 685 } 686 687 pf.Name = "dragonflybsd" 688 osrd := NewOSReleaseDetector(conn) 689 release, err := osrd.unamer() 690 if err == nil { 691 pf.Version = release 692 } 693 694 return true, nil 695 }, 696 } 697 698 var windows = &PlatformResolver{ 699 Name: "windows", 700 IsFamily: false, 701 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 702 data, err := win.GetWmiInformation(conn) 703 if err != nil { 704 log.Debug().Err(err).Msg("could not gather wmi information") 705 return false, nil 706 } 707 708 pf.Name = "windows" 709 pf.Title = data.Caption 710 711 // instead of using windows major.minor.build.ubr we just use build.ubr since 712 // major and minor can be derived from the build version 713 pf.Version = data.BuildNumber 714 715 // FIXME: we need to ask wmic cpu get architecture 716 pf.Arch = data.OSArchitecture 717 718 if pf.Labels == nil { 719 pf.Labels = map[string]string{} 720 } 721 pf.Labels["windows.mondoo.com/product-type"] = data.ProductType 722 723 // optional: try to get the ubr number (win 10 + 2019) 724 current, err := win.GetWindowsOSBuild(conn) 725 if err == nil && current.UBR > 0 { 726 pf.Build = strconv.Itoa(current.UBR) 727 } else { 728 log.Debug().Err(err).Msg("could not parse windows current version") 729 } 730 731 return true, nil 732 }, 733 } 734 735 var slugRe = regexp.MustCompile("[^a-z0-9]+") 736 737 func slugifyDarwin(s string) string { 738 s = strings.ToLower(s) 739 s = slugRe.ReplaceAllString(s, "_") 740 return strings.Trim(s, "_") 741 } 742 743 // Families 744 var darwinFamily = &PlatformResolver{ 745 Name: inventory.FAMILY_DARWIN, 746 IsFamily: true, 747 Children: []*PlatformResolver{macOS, otherDarwin}, 748 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 749 if strings.Contains(strings.ToLower(pf.Name), "darwin") == false { 750 return false, nil 751 } 752 // from here we know it is a darwin system 753 754 // read information from /usr/bin/sw_vers 755 osrd := NewOSReleaseDetector(conn) 756 dsv, err := osrd.darwin_swversion() 757 // ignore dsv config if we got an error 758 if err == nil { 759 if len(dsv["ProductName"]) > 0 { 760 // name needs to be slugged 761 pf.Name = slugifyDarwin(dsv["ProductName"]) 762 if pf.Name == "mac_os_x" { 763 pf.Name = "macos" 764 } 765 pf.Title = dsv["ProductName"] 766 } 767 if len(dsv["ProductVersion"]) > 0 { 768 pf.Version = dsv["ProductVersion"] 769 } 770 } else { 771 // TODO: we know its darwin, but without swversion support 772 log.Error().Err(err) 773 } 774 775 return true, nil 776 }, 777 } 778 779 var bsdFamily = &PlatformResolver{ 780 Name: inventory.FAMILY_BSD, 781 IsFamily: true, 782 Children: []*PlatformResolver{darwinFamily, netbsd, freebsd, openbsd, dragonflybsd}, 783 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 784 osrd := NewOSReleaseDetector(conn) 785 unames, err := osrd.unames() 786 if err != nil { 787 return false, err 788 } 789 790 unamem, err := osrd.unamem() 791 if err == nil { 792 pf.Arch = unamem 793 } 794 795 if len(unames) > 0 { 796 pf.Name = strings.ToLower(unames) 797 pf.Title = unames 798 return true, nil 799 } 800 return false, nil 801 }, 802 } 803 804 var redhatFamily = &PlatformResolver{ 805 Name: "redhat", 806 IsFamily: true, 807 // NOTE: oracle pretends to be redhat with /etc/redhat-release and Red Hat Linux, therefore we 808 // want to check that platform before redhat 809 Children: []*PlatformResolver{oracle, rhel, centos, fedora, scientific, eurolinux}, 810 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 811 f, err := conn.FileSystem().Open("/etc/redhat-release") 812 if err != nil { 813 log.Debug().Err(err) 814 return false, nil 815 } 816 defer f.Close() 817 818 c, err := io.ReadAll(f) 819 if err != nil || len(c) == 0 { 820 log.Debug().Err(err) 821 return false, nil 822 } 823 824 content := strings.TrimSpace(string(c)) 825 title, release, err := ParseRhelVersion(content) 826 if err == nil { 827 log.Debug().Str("title", title).Str("release", release).Msg("detected rhelish platform") 828 829 // only set title if not already properly detected by lsb or os-release 830 if len(pf.Title) == 0 { 831 pf.Title = title 832 } 833 834 // always override the version from the release file, since it is 835 // more accurate 836 if len(release) > 0 { 837 pf.Version = release 838 } 839 840 return true, nil 841 } 842 843 return false, nil 844 }, 845 } 846 847 var debianFamily = &PlatformResolver{ 848 Name: "debian", 849 IsFamily: true, 850 Children: []*PlatformResolver{debian, ubuntu, raspbian, kali, linuxmint, popos}, 851 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 852 return true, nil 853 }, 854 } 855 856 var suseFamily = &PlatformResolver{ 857 Name: "suse", 858 IsFamily: true, 859 Children: []*PlatformResolver{opensuse, sles, suseMicroOs}, 860 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 861 return true, nil 862 }, 863 } 864 865 var archFamily = &PlatformResolver{ 866 Name: "arch", 867 IsFamily: true, 868 Children: []*PlatformResolver{arch, manjaro}, 869 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 870 // if the file exists, we are on arch or one of its derivatives 871 f, err := conn.FileSystem().Open("/etc/arch-release") 872 if err != nil { 873 return false, nil 874 } 875 defer f.Close() 876 877 c, err := io.ReadAll(f) 878 if err != nil { 879 return false, nil 880 } 881 882 // on arch containers, /etc/os-release may not be present 883 if len(pf.Name) == 0 && strings.Contains(strings.ToLower(string(c)), "manjaro") { 884 pf.Name = "manjaro" 885 pf.Title = strings.TrimSpace(string(c)) 886 return true, nil 887 } 888 889 if len(pf.Name) == 0 { 890 // fallback to arch 891 pf.Name = "arch" 892 pf.Title = "Arch Linux" 893 } 894 return true, nil 895 }, 896 } 897 898 var linuxFamily = &PlatformResolver{ 899 Name: inventory.FAMILY_LINUX, 900 IsFamily: true, 901 Children: []*PlatformResolver{archFamily, redhatFamily, debianFamily, suseFamily, amazonlinux, alpine, gentoo, busybox, photon, windriver, openwrt, ubios, plcnext, defaultLinux}, 902 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 903 detected := false 904 osrd := NewOSReleaseDetector(conn) 905 906 pf.Name = "" 907 pf.Title = "" 908 909 lsb, err := osrd.lsbconfig() 910 // ignore lsb config if we got an error 911 if err == nil { 912 if len(lsb["DISTRIB_ID"]) > 0 { 913 pf.Name = strings.ToLower(lsb["DISTRIB_ID"]) 914 } 915 if len(lsb["DISTRIB_DESCRIPTION"]) > 0 { 916 pf.Title = lsb["DISTRIB_DESCRIPTION"] 917 } else if len(lsb["DISTRIB_ID"]) > 0 { 918 pf.Title = lsb["DISTRIB_ID"] 919 } 920 if len(lsb["DISTRIB_RELEASE"]) > 0 { 921 pf.Version = lsb["DISTRIB_RELEASE"] 922 } 923 924 detected = true 925 } else { 926 log.Debug().Err(err).Msg("platform> cannot parse lsb config on this linux system") 927 } 928 929 osr, err := osrd.osrelease() 930 // ignore os release if we have an error 931 if err != nil { 932 log.Debug().Err(err).Msg("platform> cannot parse os-release on this linux system") 933 } else { 934 if len(osr["ID"]) > 0 { 935 pf.Name = osr["ID"] 936 } 937 if len(osr["PRETTY_NAME"]) > 0 { 938 pf.Title = osr["PRETTY_NAME"] 939 } 940 if len(osr["VERSION_ID"]) > 0 { 941 pf.Version = osr["VERSION_ID"] 942 } 943 944 if len(osr["BUILD_ID"]) > 0 { 945 pf.Build = osr["BUILD_ID"] 946 } 947 948 detected = true 949 } 950 951 // Centos 6 does not include /etc/os-release or /etc/lsb-release, therefore any static analysis 952 // will not be able to detect the system, since the following unamem and unames mechanism is not 953 // available there. Instead the system can be identified by the availability of /etc/redhat-release 954 // If /etc/redhat-release is available, we know its a linux system. 955 f, err := conn.FileSystem().Open("/etc/redhat-release") 956 if f != nil { 957 f.Close() 958 } 959 960 if err == nil { 961 detected = true 962 } 963 964 // BusyBox images do not contain /etc/os-release or /etc/lsb-release, therefore any static analysis 965 // will not be able to detect the system, since the following unamem and unames mechanism is not 966 // available there. Instead the system can be identified by the availability of /bin/busybox 967 // If /bin/busybox is available, we know its a linux system. 968 f, err = conn.FileSystem().Open("/bin/busybox") 969 if f != nil { 970 f.Close() 971 } 972 973 if err == nil { 974 detected = true 975 } 976 977 // try to read the architecture, we cannot assume this works if we use the tar backend where we 978 // just load the filesystem, therefore we do not fail here 979 unamem, err := osrd.unamem() 980 if err == nil { 981 pf.Arch = unamem 982 } 983 984 // abort if os-release or lsb config was available, we don't need uname -s then 985 if detected == true { 986 return true, nil 987 } 988 989 // if we reached here, we have a strange linux distro because it does not ship with 990 // lsb config and/or os release information, lets use the uname test to verify that this 991 // is a linux, it will fail for container images without the ability to run a process 992 unames, err := osrd.unames() 993 if err != nil { 994 return false, err 995 } 996 997 if strings.Contains(strings.ToLower(unames), "linux") == false { 998 return false, nil 999 } 1000 1001 return true, nil 1002 }, 1003 } 1004 1005 var unixFamily = &PlatformResolver{ 1006 Name: inventory.FAMILY_UNIX, 1007 IsFamily: true, 1008 Children: []*PlatformResolver{bsdFamily, linuxFamily, solaris, aix}, 1009 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 1010 // in order to support linux container image detection, we cannot run 1011 // processes here, lets just read files to detect a system 1012 return true, nil 1013 }, 1014 } 1015 1016 var solaris = &PlatformResolver{ 1017 Name: "solaris", 1018 IsFamily: false, 1019 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 1020 osrd := NewOSReleaseDetector(conn) 1021 1022 unames, err := osrd.unames() 1023 if err != nil { 1024 return false, err 1025 } 1026 1027 if strings.Contains(strings.ToLower(unames), "sunos") == false { 1028 return false, nil 1029 } 1030 1031 // try to read the architecture 1032 unamem, err := osrd.unamem() 1033 if err == nil { 1034 pf.Arch = unamem 1035 } 1036 1037 pf.Name = "solaris" 1038 1039 // NOTE: we have only one solaris system here, since we only get here is the family is sunos, we pass 1040 1041 // try to read "/etc/release" for more details 1042 f, err := conn.FileSystem().Open("/etc/release") 1043 if err != nil { 1044 return false, nil 1045 } 1046 defer f.Close() 1047 1048 c, err := io.ReadAll(f) 1049 if err != nil { 1050 return false, nil 1051 } 1052 1053 release, err := ParseSolarisRelease(string(c)) 1054 if err == nil { 1055 pf.Name = release.ID 1056 pf.Title = release.Title 1057 pf.Version = release.Release 1058 } 1059 1060 return true, nil 1061 }, 1062 } 1063 1064 var aixUnameParser = regexp.MustCompile(`(\d+)\s+(\d+)\s+(.*)`) 1065 1066 var aix = &PlatformResolver{ 1067 Name: "aix", 1068 IsFamily: false, 1069 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 1070 osrd := NewOSReleaseDetector(conn) 1071 1072 unames, err := osrd.unames() 1073 if err != nil { 1074 return false, err 1075 } 1076 1077 if strings.Contains(strings.ToLower(unames), "aix") == false { 1078 return false, nil 1079 } 1080 1081 pf.Name = "aix" 1082 pf.Title = "AIX" 1083 1084 // try to read the architecture and version 1085 unamervp, err := osrd.command("uname -rvp") 1086 if err == nil { 1087 m := aixUnameParser.FindStringSubmatch(unamervp) 1088 if len(m) == 4 { 1089 pf.Version = m[2] + "." + m[1] 1090 pf.Version = pf.Version 1091 pf.Arch = m[3] 1092 } 1093 } 1094 1095 return true, nil 1096 }, 1097 } 1098 1099 var esxi = &PlatformResolver{ 1100 Name: "esxi", 1101 IsFamily: false, 1102 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 1103 log.Debug().Msg("check for esxi system") 1104 // at this point, we are already 99% its esxi 1105 cmd, err := conn.RunCommand("vmware -v") 1106 if err != nil { 1107 log.Debug().Err(err).Msg("could not run command") 1108 return false, nil 1109 } 1110 vmware_info, err := io.ReadAll(cmd.Stdout) 1111 if err != nil { 1112 log.Debug().Err(err).Msg("could not run command") 1113 return false, err 1114 } 1115 1116 version, err := ParseEsxiRelease(string(vmware_info)) 1117 if err != nil { 1118 log.Debug().Err(err).Msg("could not run command") 1119 return false, err 1120 } 1121 1122 pf.Version = version 1123 return true, nil 1124 }, 1125 } 1126 1127 var esxFamily = &PlatformResolver{ 1128 Name: "esx", 1129 IsFamily: true, 1130 Children: []*PlatformResolver{esxi}, 1131 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 1132 osrd := NewOSReleaseDetector(conn) 1133 1134 // check if we got vmkernel 1135 unames, err := osrd.unames() 1136 if err != nil { 1137 return false, err 1138 } 1139 1140 if strings.Contains(strings.ToLower(unames), "vmkernel") == false { 1141 return false, nil 1142 } 1143 1144 pf.Name = "esxi" 1145 1146 // try to read the architecture 1147 unamem, err := osrd.unamem() 1148 if err == nil { 1149 pf.Arch = unamem 1150 } 1151 1152 return true, nil 1153 }, 1154 } 1155 1156 var WindowsFamily = &PlatformResolver{ 1157 Name: inventory.FAMILY_WINDOWS, 1158 IsFamily: true, 1159 Children: []*PlatformResolver{windows}, 1160 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 1161 return true, nil 1162 }, 1163 } 1164 1165 var unknownOperatingSystem = &PlatformResolver{ 1166 Name: "unknown-os", 1167 IsFamily: false, 1168 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 1169 // if we reach here, we really do not know the system 1170 log.Debug().Msg("platform> we do not know the operating system, please contact support") 1171 return true, nil 1172 }, 1173 } 1174 1175 var OperatingSystems = &PlatformResolver{ 1176 Name: "os", 1177 IsFamily: true, 1178 Children: []*PlatformResolver{unixFamily, WindowsFamily, esxFamily, unknownOperatingSystem}, 1179 Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { 1180 return true, nil 1181 }, 1182 }