diff --git a/.github/workflows/validate-cg-manifest.sh b/.github/workflows/validate-cg-manifest.sh index 3a775b55f0..1a509c425e 100755 --- a/.github/workflows/validate-cg-manifest.sh +++ b/.github/workflows/validate-cg-manifest.sh @@ -23,6 +23,7 @@ ignore_multiple_sources=" \ # List of ignored specs due to no source tarball to scan. ignore_no_source_tarball=" \ + azurelinux-sysinfo \ ca-certificates \ check-restart \ core-packages \ diff --git a/SPECS/LICENSES-AND-NOTICES/LICENSES-MAP.md b/SPECS/LICENSES-AND-NOTICES/LICENSES-MAP.md index 8e15c41d6d..018bd1237f 100644 --- a/SPECS/LICENSES-AND-NOTICES/LICENSES-MAP.md +++ b/SPECS/LICENSES-AND-NOTICES/LICENSES-MAP.md @@ -9,7 +9,7 @@ The CBL-Mariner SPEC files originated from a variety of sources with varying lic | Fedora (Copyright Remi Collet) | [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) | libmemcached-awesome
librabbitmq | | Fedora (ISC) | [ISC License](https://github.com/sarugaku/resolvelib/blob/main/LICENSE) | python-resolvelib | | Magnus Edenhill Open Source | [Magnus Edenhill Open Source BSD License](https://github.com/jemalloc/jemalloc/blob/dev/COPYING) | librdkafka | -| Microsoft | [Microsoft MIT License](/LICENSES-AND-NOTICES/LICENSE.md) | application-gateway-kubernetes-ingress
asc
azcopy
azure-iot-sdk-c
azure-storage-cpp
bazel
blobfuse
blobfuse2
bmon
bpftrace
ccache
cert-manager
cf-cli
check-restart
clamav
cloud-hypervisor
cmake-fedora
coredns
csi-driver-lvm
dcos-cli
debugedit
dejavu-fonts
distroless-packages
doxygen
dtc
elixir
espeak-ng
espeakup
flannel
fluent-bit
freefont
gflags
gh
go-md2man
grpc
grub2-efi-binary-signed
GSL
gtk-update-icon-cache
helm
hvloader
hvloader-signed
installkernel
intel-pf-bb-config
ivykis
jsonbuilder
jx
kata-containers-cc
kata-packages-uvm
keda
keras
kernel-azure-signed
kernel-hci-signed
kernel-mos-signed
kernel-mshv-signed
kernel-signed
KeysInUse-OpenSSL
kpatch
kube-vip-cloud-provider
kubernetes
libacvp
libconfini
libconfuse
libgdiplus
libmaxminddb
libmetalink
libsafec
libuv
libxml++
livepatch-5.15.102.1-1.cm2
livepatch-5.15.102.1-3.cm2
livepatch-5.15.107.1-1.cm2
livepatch-5.15.110.1-1.cm2
livepatch-5.15.111.1-1.cm2
livepatch-5.15.112.1-1.cm2
livepatch-5.15.112.1-2.cm2
livepatch-5.15.116.1-1.cm2
livepatch-5.15.116.1-2.cm2
livepatch-5.15.122.1-2.cm2
livepatch-5.15.125.1-1.cm2
livepatch-5.15.125.1-2.cm2
livepatch-5.15.126.1-1.cm2
livepatch-5.15.131.1-1.cm2
livepatch-5.15.131.1-3.cm2
livepatch-5.15.94.1-1.cm2
livepatch-5.15.94.1-1.cm2-signed
livepatch-5.15.95.1-1.cm2
livepatch-5.15.98.1-1.cm2
livepatching
lld
lld16
local-path-provisioner
lsb-release
ltp
lttng-consume
mariner-release
mariner-repos
mariner-rpm-macros
maven3
mm-common
moby-buildx
moby-cli
moby-compose
moby-containerd
moby-containerd-cc
moby-engine
moby-runc
msgpack
ncompress
networkd-dispatcher
nlohmann-json
nmap
nmi
node-problem-detector
ntopng
opentelemetry-cpp
osslsigncode
packer
pcaudiolib
pcre2
perl-Test-Warnings
perl-Text-Template
pigz
prebuilt-ca-certificates
prebuilt-ca-certificates-base
prometheus-adapter
python-cachetools
python-cherrypy
python-cstruct
python-execnet
python-google-pasta
python-libclang
python-logutils
python-nocasedict
python-opt-einsum
python-pecan
python-pyrpm
python-remoto
python-repoze-lru
python-routes
python-rsa
python-sphinxcontrib-websupport
python-tensorboard
python-tensorboard-plugin-wit
python-tensorflow-estimator
python-yamlloader
R
rabbitmq-server
reaper
rocksdb
rubygem-addressable
rubygem-asciidoctor
rubygem-async
rubygem-async-http
rubygem-async-io
rubygem-async-pool
rubygem-aws-eventstream
rubygem-aws-partitions
rubygem-aws-sdk-core
rubygem-aws-sdk-kms
rubygem-aws-sdk-s3
rubygem-aws-sdk-sqs
rubygem-aws-sigv4
rubygem-bigdecimal
rubygem-bindata
rubygem-concurrent-ruby
rubygem-connection_pool
rubygem-console
rubygem-cool.io
rubygem-deep_merge
rubygem-digest-crc
rubygem-elastic-transport
rubygem-elasticsearch
rubygem-elasticsearch-api
rubygem-eventmachine
rubygem-excon
rubygem-faraday
rubygem-faraday-em_http
rubygem-faraday-em_synchrony
rubygem-faraday-excon
rubygem-faraday-httpclient
rubygem-faraday-multipart
rubygem-faraday-net_http
rubygem-faraday-net_http_persistent
rubygem-faraday-patron
rubygem-faraday-rack
rubygem-faraday-retry
rubygem-ffi
rubygem-fiber-local
rubygem-fluent-config-regexp-type
rubygem-fluent-logger
rubygem-fluent-plugin-elasticsearch
rubygem-fluent-plugin-kafka
rubygem-fluent-plugin-prometheus
rubygem-fluent-plugin-prometheus_pushgateway
rubygem-fluent-plugin-record-modifier
rubygem-fluent-plugin-rewrite-tag-filter
rubygem-fluent-plugin-s3
rubygem-fluent-plugin-systemd
rubygem-fluent-plugin-td
rubygem-fluent-plugin-webhdfs
rubygem-fluent-plugin-windows-exporter
rubygem-fluentd
rubygem-hirb
rubygem-hocon
rubygem-hoe
rubygem-http_parser.rb
rubygem-httpclient
rubygem-io-event
rubygem-jmespath
rubygem-ltsv
rubygem-mini_portile2
rubygem-minitest
rubygem-mocha
rubygem-msgpack
rubygem-multi_json
rubygem-multipart-post
rubygem-net-http-persistent
rubygem-nio4r
rubygem-nokogiri
rubygem-oj
rubygem-parallel
rubygem-power_assert
rubygem-prometheus-client
rubygem-protocol-hpack
rubygem-protocol-http
rubygem-protocol-http1
rubygem-protocol-http2
rubygem-public_suffix
rubygem-puppet-resource_api
rubygem-rdiscount
rubygem-rdkafka
rubygem-rexml
rubygem-ruby-kafka
rubygem-ruby-progressbar
rubygem-rubyzip
rubygem-semantic_puppet
rubygem-serverengine
rubygem-sigdump
rubygem-strptime
rubygem-systemd-journal
rubygem-td
rubygem-td-client
rubygem-td-logger
rubygem-test-unit
rubygem-thor
rubygem-timers
rubygem-tzinfo
rubygem-tzinfo-data
rubygem-webhdfs
rubygem-webrick
rubygem-yajl-ruby
rubygem-zip-zip
sdbus-cpp
sgx-backwards-compatability
shim
shim-unsigned
shim-unsigned-aarch64
shim-unsigned-x64
skopeo
span-lite
sriov-network-device-plugin
swupdate
SymCrypt
SymCrypt-OpenSSL
tensorflow
terraform
tinyxml2
toml11
tracelogging
umoci
usrsctp
vala
verity-read-only-root
vnstat
zstd | +| Microsoft | [Microsoft MIT License](/LICENSES-AND-NOTICES/LICENSE.md) | application-gateway-kubernetes-ingress
asc
azcopy
azure-iot-sdk-c
azure-storage-cpp
azurelinux-sysinfo
bazel
blobfuse
blobfuse2
bmon
bpftrace
ccache
cert-manager
cf-cli
check-restart
clamav
cloud-hypervisor
cmake-fedora
coredns
csi-driver-lvm
dcos-cli
debugedit
dejavu-fonts
distroless-packages
doxygen
dtc
elixir
espeak-ng
espeakup
flannel
fluent-bit
freefont
gflags
gh
go-md2man
grpc
grub2-efi-binary-signed
GSL
gtk-update-icon-cache
helm
hvloader
hvloader-signed
installkernel
intel-pf-bb-config
ivykis
jsonbuilder
jx
kata-containers-cc
kata-packages-uvm
keda
keras
kernel-azure-signed
kernel-hci-signed
kernel-mos-signed
kernel-mshv-signed
kernel-signed
KeysInUse-OpenSSL
kpatch
kube-vip-cloud-provider
kubernetes
libacvp
libconfini
libconfuse
libgdiplus
libmaxminddb
libmetalink
libsafec
libuv
libxml++
livepatch-5.15.102.1-1.cm2
livepatch-5.15.102.1-3.cm2
livepatch-5.15.107.1-1.cm2
livepatch-5.15.110.1-1.cm2
livepatch-5.15.111.1-1.cm2
livepatch-5.15.112.1-1.cm2
livepatch-5.15.112.1-2.cm2
livepatch-5.15.116.1-1.cm2
livepatch-5.15.116.1-2.cm2
livepatch-5.15.122.1-2.cm2
livepatch-5.15.125.1-1.cm2
livepatch-5.15.125.1-2.cm2
livepatch-5.15.126.1-1.cm2
livepatch-5.15.131.1-1.cm2
livepatch-5.15.131.1-3.cm2
livepatch-5.15.94.1-1.cm2
livepatch-5.15.94.1-1.cm2-signed
livepatch-5.15.95.1-1.cm2
livepatch-5.15.98.1-1.cm2
livepatching
lld
lld16
local-path-provisioner
lsb-release
ltp
lttng-consume
mariner-release
mariner-repos
mariner-rpm-macros
maven3
mm-common
moby-buildx
moby-cli
moby-compose
moby-containerd
moby-containerd-cc
moby-engine
moby-runc
msgpack
ncompress
networkd-dispatcher
nlohmann-json
nmap
nmi
node-problem-detector
ntopng
opentelemetry-cpp
osslsigncode
packer
pcaudiolib
pcre2
perl-Test-Warnings
perl-Text-Template
pigz
prebuilt-ca-certificates
prebuilt-ca-certificates-base
prometheus-adapter
python-cachetools
python-cherrypy
python-cstruct
python-execnet
python-google-pasta
python-libclang
python-logutils
python-nocasedict
python-opt-einsum
python-pecan
python-pyrpm
python-remoto
python-repoze-lru
python-routes
python-rsa
python-sphinxcontrib-websupport
python-tensorboard
python-tensorboard-plugin-wit
python-tensorflow-estimator
python-yamlloader
R
rabbitmq-server
reaper
rocksdb
rubygem-addressable
rubygem-asciidoctor
rubygem-async
rubygem-async-http
rubygem-async-io
rubygem-async-pool
rubygem-aws-eventstream
rubygem-aws-partitions
rubygem-aws-sdk-core
rubygem-aws-sdk-kms
rubygem-aws-sdk-s3
rubygem-aws-sdk-sqs
rubygem-aws-sigv4
rubygem-bigdecimal
rubygem-bindata
rubygem-concurrent-ruby
rubygem-connection_pool
rubygem-console
rubygem-cool.io
rubygem-deep_merge
rubygem-digest-crc
rubygem-elastic-transport
rubygem-elasticsearch
rubygem-elasticsearch-api
rubygem-eventmachine
rubygem-excon
rubygem-faraday
rubygem-faraday-em_http
rubygem-faraday-em_synchrony
rubygem-faraday-excon
rubygem-faraday-httpclient
rubygem-faraday-multipart
rubygem-faraday-net_http
rubygem-faraday-net_http_persistent
rubygem-faraday-patron
rubygem-faraday-rack
rubygem-faraday-retry
rubygem-ffi
rubygem-fiber-local
rubygem-fluent-config-regexp-type
rubygem-fluent-logger
rubygem-fluent-plugin-elasticsearch
rubygem-fluent-plugin-kafka
rubygem-fluent-plugin-prometheus
rubygem-fluent-plugin-prometheus_pushgateway
rubygem-fluent-plugin-record-modifier
rubygem-fluent-plugin-rewrite-tag-filter
rubygem-fluent-plugin-s3
rubygem-fluent-plugin-systemd
rubygem-fluent-plugin-td
rubygem-fluent-plugin-webhdfs
rubygem-fluent-plugin-windows-exporter
rubygem-fluentd
rubygem-hirb
rubygem-hocon
rubygem-hoe
rubygem-http_parser.rb
rubygem-httpclient
rubygem-io-event
rubygem-jmespath
rubygem-ltsv
rubygem-mini_portile2
rubygem-minitest
rubygem-mocha
rubygem-msgpack
rubygem-multi_json
rubygem-multipart-post
rubygem-net-http-persistent
rubygem-nio4r
rubygem-nokogiri
rubygem-oj
rubygem-parallel
rubygem-power_assert
rubygem-prometheus-client
rubygem-protocol-hpack
rubygem-protocol-http
rubygem-protocol-http1
rubygem-protocol-http2
rubygem-public_suffix
rubygem-puppet-resource_api
rubygem-rdiscount
rubygem-rdkafka
rubygem-rexml
rubygem-ruby-kafka
rubygem-ruby-progressbar
rubygem-rubyzip
rubygem-semantic_puppet
rubygem-serverengine
rubygem-sigdump
rubygem-strptime
rubygem-systemd-journal
rubygem-td
rubygem-td-client
rubygem-td-logger
rubygem-test-unit
rubygem-thor
rubygem-timers
rubygem-tzinfo
rubygem-tzinfo-data
rubygem-webhdfs
rubygem-webrick
rubygem-yajl-ruby
rubygem-zip-zip
sdbus-cpp
sgx-backwards-compatability
shim
shim-unsigned
shim-unsigned-aarch64
shim-unsigned-x64
skopeo
span-lite
sriov-network-device-plugin
swupdate
SymCrypt
SymCrypt-OpenSSL
tensorflow
terraform
tinyxml2
toml11
tracelogging
umoci
usrsctp
vala
verity-read-only-root
vnstat
zstd | | Netplan source | [GPLv3](https://github.com/canonical/netplan/blob/main/COPYING) | netplan | | Numad source | [LGPLv2 License](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) | numad | | NVIDIA | [ASL 2.0 License and spec specific licenses](http://www.apache.org/licenses/LICENSE-2.0) | knem
libnvidia-container
mlnx-ofa_kernel
mlnx-tools
mlx-bootctl
nvidia-container-runtime
nvidia-container-toolkit
nvidia-docker2
ofed-scripts
perftest | diff --git a/SPECS/LICENSES-AND-NOTICES/data/licenses.json b/SPECS/LICENSES-AND-NOTICES/data/licenses.json index 012261b1ae..eff809859d 100644 --- a/SPECS/LICENSES-AND-NOTICES/data/licenses.json +++ b/SPECS/LICENSES-AND-NOTICES/data/licenses.json @@ -2152,6 +2152,7 @@ "azcopy", "azure-iot-sdk-c", "azure-storage-cpp", + "azurelinux-sysinfo", "bazel", "blobfuse", "blobfuse2", diff --git a/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.service b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.service new file mode 100644 index 0000000000..5434f98f45 --- /dev/null +++ b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.service @@ -0,0 +1,13 @@ +[Unit] +Description=Azure Linux Sysinfo Service +After=cloud-init.target multi-user.target + +[Service] +Environment=PYTHONUNBUFFERED=1 +Type=simple +ExecStart=/usr/bin/python3 /usr/bin/collect-sysinfo +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.signatures.json b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.signatures.json new file mode 100644 index 0000000000..0ca8108b64 --- /dev/null +++ b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.signatures.json @@ -0,0 +1,9 @@ +{ + "Signatures": { + "collect-sysinfo": "b47df8a856c49e4bc02b36d1c3dd2825b75b9d8449b5dae8af401fc6818131c9", + "sysinfo-schema-v1.json": "67b541239416bd5f9a77a0799881f21c2e5eea686dc7a3ccaffe6bd7219a4798", + "azurelinux-sysinfo.service": "c719ab2238d0412b7ac6a793cd83e5be7879023161f86fb29d1c0ca18e70631c", + "sysinfo-selinuxpolicies.cil": "1f0df94a09f4db09093743339b6162735b6f1c81108cd3b857a6dbc729630400" + } +} + \ No newline at end of file diff --git a/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.spec b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.spec new file mode 100644 index 0000000000..07e5f7b0a5 --- /dev/null +++ b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.spec @@ -0,0 +1,69 @@ +Summary: Package to deploy azurelinux-sysinfo service +Name: azurelinux-sysinfo +Version: 2.0 +Release: 1%{?dist} +License: MIT +Vendor: Microsoft Corporation +Distribution: Azure Linux +Group: System Environment/Base +URL: https://aka.ms/azurelinux +Source0: collect-sysinfo +Source1: sysinfo-schema-v1.json +Source2: azurelinux-sysinfo.service +Source3: sysinfo-selinuxpolicies.cil +Requires: systemd +Requires: python3-psutil + +%description +Deploys a systemd service that gathers system information related to the device, operating system, cloud-init, boot +time, resource utilization, installed packages, and SELinux mode. Collected information is written in JSON format to +a log file on the user's system for easy access and analysis. The systemd service runs at boot time if installed during +image creation. + +%install +# Copy collection python script to /usr/bin/ +mkdir -p %{buildroot}%{_bindir}/ +install -m 755 %{SOURCE0} %{buildroot}%{_bindir}/ + +# Copy data schema to /usr/share/azurelinux-sysinfo/ +mkdir -p %{buildroot}%{_datadir}/azurelinux-sysinfo/ +install -m 755 %{SOURCE1} %{buildroot}%{_datadir}/azurelinux-sysinfo/ + +# Copy service to /etc/systemd/system/ +mkdir -p %{buildroot}%{_sysconfdir}/systemd/system/ +install -m 755 %{SOURCE2} %{buildroot}%{_sysconfdir}/systemd/system/ + +# Copy the sysinfo-selinuxpolicies file to /usr/share/selinux/packages/ +mkdir -p %{buildroot}%{_datadir}/selinux/packages/ +install -m 755 %{SOURCE3} %{buildroot}%{_datadir}/selinux/packages/ + +%files +%{_bindir}/collect-sysinfo +%dir %{_datadir}/azurelinux-sysinfo/ +%{_datadir}/azurelinux-sysinfo/sysinfo-schema-v1.json +%{_sysconfdir}/systemd/system/azurelinux-sysinfo.service +%{_datadir}/selinux/packages/sysinfo-selinuxpolicies.cil + +%post +#!/bin/sh +# Enable the systemd service +systemctl enable azurelinux-sysinfo.service + +# Apply required SElinux policies only if selinux-policy is present +if rpm -q selinux-policy &> /dev/null; then + semodule -i %{_datadir}/selinux/packages/sysinfo-selinuxpolicies.cil +fi + +%postun +# If selinux-policy is present, remove the sysinfo-selinuxpolicies module +if rpm -q selinux-policy &> /dev/null; then + semodule -r sysinfo-selinuxpolicies +fi + +%changelog +* Thu Apr 04 2024 Amrita Kohli - 2.0-1 +- License verified. +- Implementation of package that deploys azurelinux-sysinfo service. +- Original version for CBL-Mariner. + + diff --git a/SPECS/azurelinux-sysinfo/collect-sysinfo b/SPECS/azurelinux-sysinfo/collect-sysinfo new file mode 100755 index 0000000000..2188c0561e --- /dev/null +++ b/SPECS/azurelinux-sysinfo/collect-sysinfo @@ -0,0 +1,293 @@ +#!/usr/bin/python3 + +import argparse +import json +import shutil +import jsonschema +import psutil +import os +import re +import subprocess + +DATA_SCHEMA_DIR = "/usr/share/azurelinux-sysinfo" +DATA_SCHEMA_VERSION = "v1" +DATA_SCHEMA_FILENAME = f"sysinfo-schema-{DATA_SCHEMA_VERSION}.json" +LOG_FILE_PATH = "/var/log/azurelinux-sysinfo.log" +SERVICE_NAME = "azurelinux-sysinfo-service" + + +# This function converts a string that matches +# regex = r"(\d+(\.\d+)?)(min|s|ms)" to seconds +def convert_to_secs(line): + regex = r"(\d+(?:\.\d+)?)(min|s|ms)" + time_secs = 0 + for match in re.findall(regex, line): + time = float(match[0]) + unit = match[1] + if unit == "min": + time *= 60 + elif unit == "ms": + time /= 1000 + time_secs += time + return time_secs + + +def collect_os_info(): + print("Collecting os info...") + + release_data = {} + release_info = subprocess.run( + ["cat", "/etc/os-release"], capture_output=True, text=True + ) + + kernel_info = subprocess.run(["uname", "-r"], capture_output=True, text=True) + + for line in release_info.stdout.strip().splitlines(): + name, value = line.split("=", maxsplit=1) + release_data[name] = value.strip('"') + + + os_info = { + "kernel_version": kernel_info.stdout.strip(), + "release_version": release_data["VERSION"], + "release_version_id": release_data["VERSION_ID"], + } + + return os_info + + +def collect_boot_info(): + print("Collecting boot info...") + # Known issue: In SELinux enforcing mode, systemd-analyze commands are expected to fail until required policies are added. + # In this case, the boot times will be 0 and longest running processes will be empty. + + # Collect boot time + result = subprocess.run(["systemd-analyze", "time"], capture_output=True, text=True) + + # Sample output for livecd image: + # Startup finished in 153ms (firmware) + 554ms (loader) + 1.413s (kernel) + 908ms (userspace) = 3.030s + # multi-user.target reached after 897ms in userspace + # Sample output for host images: + # Startup finished in 12.688s (kernel) + 8.082s (initrd) + 1min 1.458s (userspace) = 1min 22.230s + # multi-user.target reached after 1min 966ms in userspace + + lines = result.stdout.strip().splitlines() + + # In a test setup on qemu, systemd-analyze returns empty + if len(lines) < 1 or not(lines[0].startswith("Startup finished in")): + boot_info = { + "boot_time": { + "kernel_boot_time_secs": 0, + "userspace_boot_time_secs": 0, + "total_boot_time_secs": 0, + }, + "longest_running_processes": [], + } + return boot_info + + # Define regular expression to extract times + timeRegex = r"((?:\d+)(?:\d*min\s?)?(?:\d*\.?\d*s\s?)?(?:\d*\.?\d*ms)?)" + # Define regular expression to extract values between parentheses + betweenParenthesesRegex = r"\((.*?)\)" + + boot_time_keys = re.findall(betweenParenthesesRegex, lines[0]) + boot_times = re.findall(timeRegex, lines[0]) + boot_times = [t.strip() for t in boot_times] + boot_times_secs = [convert_to_secs(time) for time in boot_times] + + boot_time = dict() + suffix = "_boot_time_secs" + for i in range(len(boot_time_keys)): + bootTimeKey = boot_time_keys[i] + suffix + bootTimeValue = boot_times_secs[i] + boot_time[bootTimeKey] = bootTimeValue + boot_time["total_boot_time_secs"] = boot_times_secs[-1] + + # Collect boot time longest running processes + top_n = 3 + result = subprocess.run( + ["systemd-analyze", "blame"], capture_output=True, text=True + ) + filtered_result = subprocess.run( + ["head", f"-{top_n}"], input=result.stdout, capture_output=True, text=True + ) + + # Sample output: + # 43.642s systemd-networkd-wait-online.service + lines = filtered_result.stdout.strip().splitlines() + + process_list = [] + for line in lines: + process = re.search(r"\S+\s*$", line).group().strip() + process_list.append({process: convert_to_secs(line)}) + + boot_info = {"boot_time": boot_time, "longest_running_processes": process_list} + + return boot_info + + +def collect_resource_utilization(): + print("Collecting disk and memory usage...") + + # disk + os_disk_usage = shutil.disk_usage("/") + disk_usage = { + "disk_size_gib": f"{os_disk_usage.total/1024**3:.2f}", + "disk_usage_gib": f"{os_disk_usage.used/1024**3:.2f}", + } + + # memory + memory_info = psutil.virtual_memory() + total_memory = memory_info.total // (1024**3) + available_memory = memory_info.available // (1024**3) + + memory_usage = { + "total_memory_gib": total_memory, + "available_memory_gib": available_memory, + } + + physical_cpu_count = psutil.cpu_count(logical=False) + logical_cpu_count = psutil.cpu_count(logical=True) + cpu_percent = psutil.cpu_percent() + + cpu_usage = { + "physical_cpu_count": physical_cpu_count, + "logical_cpu_count": logical_cpu_count, + "cpu_percent": cpu_percent, + } + + resource_utilization = { + "disk_usage": disk_usage, + "memory_usage": memory_usage, + "cpu_usage": cpu_usage, + } + + return resource_utilization + + +def collect_package_info(): + print("Collecting package info...") + get_package_list = subprocess.run( + ["rpm", "-qa"], capture_output=True, text=True, check=True + ) + + package_list = get_package_list.stdout.strip().splitlines() + + # TASK 4917: Adding package list resulted in hitting the size limit for the log, + # so only logging package count until an alternative is implemented. + package_info = {"package_count": len(package_list)} + + return package_info + + +def collect_cloud_init_info(): + print("Collecting cloud-init info...") + + # Collect cloud-init longest running processes + result = subprocess.run( + ["cloud-init", "analyze", "blame"], capture_output=True, text=True, check=True + ) + + lines = result.stdout.strip().splitlines() + process_list = [] + top_n = 5 + + # Skipping the first line as it is "-- Boot Record 01 --" + # Skipping the last line as it is "x boot records analyzed" + range = min(top_n + 1, len(lines) - 1) + + for line in lines[1:range]: + record_details = line.split() + if len(record_details) > 1: + process_info = {} + process_info["time"], process_info["process"] = record_details + process_list.append(process_info) + + get_hostname = subprocess.run( + ["hostname"], capture_output=True, text=True, check=True + ) + + cloud_init_info = { + "hostname": get_hostname.stdout.strip(), + "longest_running_processes": process_list, + } + + return cloud_init_info + + +def get_selinux_mode(): + return subprocess.run( + ["getenforce"], capture_output=True, text=True, check=True + ).stdout.strip() + + +def collect_system_info(): + print("Collecting system info...") + system_info = {"selinux_mode": get_selinux_mode()} + return system_info + + +def get_asset_id(): + print("Collecting asset id...") + + return subprocess.run( + ["cat", "/sys/devices/virtual/dmi/id/product_uuid"], capture_output=True, text=True + ).stdout.lower().strip() + + +def has_valid_schema(data): + schema_file = os.path.join(DATA_SCHEMA_DIR, DATA_SCHEMA_FILENAME) + with open(schema_file, "r") as file: + schema = json.load(file) + + try: + jsonschema.validate(data, schema) + except jsonschema.exceptions.ValidationError as err: + print(f"Schema validation failed: {err}") + return False + return True + + +def main(): + print("Running azurelinux sysinfo collection...") + asset_id = get_asset_id() + os_info = collect_os_info() + cloud_init_info = collect_cloud_init_info() + boot_info = collect_boot_info() + resource_utilization = collect_resource_utilization() + package_info = collect_package_info() + system_info = collect_system_info() + + # Use json as a data structure to store the data + # since it is supported by Kusto + data = { + "$schema": f"{DATA_SCHEMA_VERSION}", + "source": f"{SERVICE_NAME}", + "asset_id": asset_id, + "os_info": os_info, + "cloud_init_info": cloud_init_info, + "boot_info": boot_info, + "resource_utilization": resource_utilization, + "package_info": package_info, + "system_info": system_info, + } + + print(data) + + if has_valid_schema(data): + # Dump the data to a log file, this path is added to fluentd config + # and will be picked up by fluentd and sent through Geneva Agents + with open(LOG_FILE_PATH, "w") as file: + json.dump(data, file, separators=(',', ':')) + + # Add newline so that the fluentd tail plug-in consumes the log + # line. + file.write("\n") + print("Azure Linux sysinfo collection completed successfully.") + else: + print("Azure Linux sysinfo collection failed.") + exit(1) + + +if __name__ == "__main__": + main() diff --git a/SPECS/azurelinux-sysinfo/sysinfo-schema-v1.json b/SPECS/azurelinux-sysinfo/sysinfo-schema-v1.json new file mode 100644 index 0000000000..64377ab4f5 --- /dev/null +++ b/SPECS/azurelinux-sysinfo/sysinfo-schema-v1.json @@ -0,0 +1,272 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "SPECS/azurelinux-sysinfo/sysinfo-schema-v1.json", + "title": "Azure Linux Sysinfo Schema", + "description": "This defines the schema for the collected Azure Linux system information", + "type": "object", + "properties": { + "source": { + "$id": "#/properties/source", + "type": "string", + "title": "Name of the service", + "description": "Service the logs are obtained from", + "required": [ + "source" + ], + "additionalProperties": false + }, + "asset_id": { + "$id": "#/properties/asset_id", + "type": "string", + "title": "Asset ID", + "description": "Unique identifier (uuid) of the device", + "required": [ + "asset_id" + ], + "additionalProperties": false + }, + "os_info": { + "$id": "#/properties/os_info", + "type": "object", + "title": "Operating system information", + "description": "Information about the Azure Linux operating system", + "properties": { + "kernel_version": { + "$id": "#/properties/os_info/properties/kernel_version", + "type": "string", + "title": "Linux kernel version", + "description": "The kernel version of Linux" + }, + "release_version": { + "$id": "#/properties/os_info/properties/release_version", + "type": "string", + "title": "Azure Linux operating system release version", + "description": "The release version of upstream Azure Linux" + }, + "release_version_id": { + "$id": "#/properties/os_info/properties/release_version_id", + "type": "string", + "title": "Azure Linux operating system release version id", + "description": "The release version id of upstream Azure Linux" + } + }, + "required": [ + "kernel_version", + "release_version", + "release_version_id" + ], + "additionalProperties": false + }, + "cloud_init_info": { + "$id": "#/properties/cloud_init_info", + "type": "object", + "title": "Cloud-init information", + "description": "Information about cloud-init applied to the Azure Linux operating system", + "properties": { + "hostname": { + "$id": "#/properties/cloud_init_info/properties/hostname", + "type": "string", + "title": "Hostname", + "description": "The hostname of the system" + }, + "longest_running_processes": { + "$id": "#/properties/cloud_init_info/properties/longest_running_processes", + "type": "array", + "title": "Longest Running Processes", + "description": "List of top running processes that took the most time during cloud-init" + } + }, + "required": [ + "hostname", + "longest_running_processes" + ], + "additionalProperties": false + }, + "boot_info": { + "$id": "#/properties/boot_info", + "type": "object", + "title": "Operating system boot information", + "description": "Information about Azure Linux operating system booting", + "properties": { + "boot_time": { + "$id": "#/properties/boot_info/properties/boot_time", + "type": "object", + "title": "Boot time", + "description": "Boot time information", + "properties": { + "kernel_boot_time_secs": { + "$id": "#/properties/boot_info/properties/boot_time/properties/kernel_boot_time_secs", + "type": "number", + "title": "Kernel boot time in seconds", + "description": "Time spent in the kernel before userspace has been reached" + }, + "initrd_boot_time_secs": { + "$id": "#/properties/boot_info/properties/boot_time/properties/initrd_boot_time_secs", + "type": "number", + "title": "Initrd boot time in seconds", + "description": "Time spent in the initrd before userspace has been reached" + }, + "userspace_boot_time_secs": { + "$id": "#/properties/boot_info/properties/boot_time/properties/userspace_boot_time_secs", + "type": "number", + "title": "Userspace boot time in seconds", + "description": "Time spent in userspace before the system is ready to use" + }, + "firmware_boot_time_secs": { + "$id": "#/properties/boot_info/properties/boot_time/properties/firmware_boot_time_secs", + "type": "number", + "title": "Firmware boot time in seconds", + "description": "Time spent in firmware before the system is ready to use" + }, + "loader_boot_time_secs": { + "$id": "#/properties/boot_info/properties/boot_time/properties/loader_boot_time_secs", + "type": "number", + "title": "Loader boot time in seconds", + "description": "Time spent in loader before the system is ready to use" + }, + "total_boot_time_secs": { + "$id": "#/properties/boot_info/properties/boot_time/properties/total_boot_time_secs", + "type": "number", + "title": "Total boot time in seconds", + "description": "Total time spent in the boot process" + } + }, + "required": [ + "kernel_boot_time_secs", + "userspace_boot_time_secs", + "total_boot_time_secs" + ] + }, + "longest_running_processes": { + "$id": "#/properties/boot_info/properties/longest_running_processes", + "type": "array", + "title": "Longest running processes", + "description": "List of top running processes that took the most time during boot" + } + }, + "required": [ + "boot_time", + "longest_running_processes" + ] + }, + "resource_utilization": { + "$id": "#/properties/resource_utilization", + "type": "object", + "title": "System resources utilization", + "description": "Information about resources usage", + "properties": { + "disk_usage": { + "$id": "#/properties/resource_utilization/properties/disk_usage", + "type": "object", + "title": "Disk usage", + "description": "Disk usage information", + "properties": { + "disk_size_gib": { + "$id": "#/properties/resource_utilization/properties/disk_usage/properties/disk_size_gib", + "type": "string", + "title": "Os disk size", + "description": "Os disk size in GiB when the system was booted" + }, + "disk_usage_gib": { + "$id": "#/properties/resource_utilization/properties/disk_usage/properties/disk_usage_gib", + "type": "string", + "title": "Os disk usage", + "description": "Os disk usage in GiB when the system was booted" + } + }, + "required": [ + "disk_size_gib", + "disk_usage_gib" + ] + }, + "memory_usage": { + "$id": "#/properties/resource_utilization/properties/memory_usage", + "type": "object", + "title": "Memory usage", + "description": "Memory usage information", + "properties": { + "total_memory_gib": { + "$id": "#properties/resource_utilization/properties/memory_usage/properties/total_memory_gib", + "type": "integer", + "title": "Total memory", + "description": "Total memory in GiB when the system was booted" + }, + "available_memory_gib": { + "$id": "#properties/resource_utilization/properties/memory_usage/properties/available_memory_gib", + "type": "integer", + "title": "Available memory", + "description": "Available memory in GiB when the system was booted" + } + } + }, + "cpu_usage": { + "$id": "#/properties/resource_utilization/properties/memory_usage", + "type": "object", + "title": "cpu usage & info", + "description": "Cpu usage information", + "properties": { + "physical_cpu_count": { + "$id": "#properties/resource_utilization/properties/cpu_usage/properties/physical_cpu_count", + "type": "integer", + "title": "Physical cpu count", + "description": "Physical cpu count" + }, + "logical_cpu_count": { + "$id": "#properties/resource_utilization/properties/cpu_usage/properties/logical_cpu_count", + "type": "integer", + "title": "Logical cpu count", + "description": "Logical cpu count" + }, + "cpu_usage_percent": { + "$id": "#properties/resource_utilization/properties/cpu_usage/properties/cpu_usage_percent", + "type": "number", + "title": "Cpu usage percent", + "description": "Cpu usage percent" + } + } + } + }, + "required": [ + "disk_usage", + "memory_usage", + "cpu_usage" + ] + }, + "package_info": { + "$id": "#/properties/package_info", + "type": "object", + "title": "Package Information", + "description": "Information about the packages installed on Azure Linux", + "properties": { + "package_count": { + "$id": "#/properties/package_info/properties/package_count", + "type": "integer", + "title": "Package Count", + "description": "The number of packages installed on Azure Linux" + } + }, + "required": [ + "package_count" + ], + "additionalProperties": false + }, + "system_info": { + "$id": "#/properties/system_info", + "type": "object", + "title": "System Information", + "description": "Information about the system-wide settings", + "properties": { + "selinux_mode": { + "$id": "#/properties/package_info/properties/package_count", + "type": "string", + "title": "SELinux Mode", + "description": "Enforced or Permissive" + } + }, + "required": [ + "selinux_mode" + ], + "additionalProperties": false + } + } +} diff --git a/SPECS/azurelinux-sysinfo/sysinfo-selinuxpolicies.cil b/SPECS/azurelinux-sysinfo/sysinfo-selinuxpolicies.cil new file mode 100644 index 0000000000..d66205f386 --- /dev/null +++ b/SPECS/azurelinux-sysinfo/sysinfo-selinuxpolicies.cil @@ -0,0 +1,14 @@ +(allow systemd_analyze_t sysctl_kernel_t (dir (search))) +(allow systemd_analyze_t locale_t (dir (search))) +(allow systemd_analyze_t init_runtime_t (dir (search))) +(allow systemd_analyze_t sysctl_kernel_t (file (read))) +(allow systemd_analyze_t locale_t (file (read))) +(allow systemd_analyze_t systemd_analyze_t (capability (net_admin))) +(allow systemd_analyze_t init_t (unix_stream_socket (connectto))) +(allow systemd_analyze_t system_dbusd_runtime_t (dir (search))) +(allow systemd_analyze_t security_t (filesystem (getattr))) +(allow systemd_analyze_t selinux_config_t (dir (search))) +(allow systemd_analyze_t init_t (system (status))) +(allow systemd_analyze_t init_t (service (status))) +(allow systemd_analyze_t systemdunit (service (status))) +(allow systemd_analyze_t etc_t (service (status)))