diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..3e0aa57e65c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# CODEOWNERS +# Last-match-wins: put broader patterns above more specific ones. +# Docs: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners +# Supersedes the root CODEOWNERS, which still names upstream @osquery/* teams. +/.github/ @flamingo-stack/devops-engineers +/.github/CODEOWNERS @flamingo-stack/devops-engineers diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1daa7e42f10..3f1765a1873 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,9 +33,38 @@ env: # ============================================================================= jobs: + # cmake needs 3 semver components and no leading "v" + version: + name: "Resolve Version" + runs-on: ubuntu-latest + outputs: + version: ${{ steps.resolve.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Resolve version + id: resolve + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + v="${INPUT_VERSION:-}" + [ -n "$v" ] || v="$(git describe --tags --abbrev=0 2>/dev/null || true)" + v="${v#v}" + case "$v" in + [0-9]*.[0-9]*.[0-9]*) ;; + *) echo "::warning::Unusable version '$v'; falling back to 0.0.0" ; v="0.0.0" ;; + esac + echo "Resolved version: $v" + echo "version=$v" >> "$GITHUB_OUTPUT" + build_macos: name: "Build C Client for (${{ matrix.os }} ${{ matrix.os_arch }})" runs-on: ${{ matrix.os }} + needs: [version] if: | github.event_name == 'push' || github.event_name == 'workflow_dispatch' @@ -52,7 +81,7 @@ jobs: cmake_arch: x86_64 artifact_name: osquery-macos-x64 - name: "macOS ARM64" - os: macos-latest + os: macos-15 # pinned: Xcode 26 on macos-latest fails to compile boost mpl os_arch: arm64 cmake_arch: arm64 artifact_name: osquery-macos-arm64 @@ -77,31 +106,24 @@ jobs: done fi - - name: Cache Homebrew - uses: actions/cache@v4 - with: - path: | - ~/Library/Caches/Homebrew - /usr/local/Homebrew - /opt/homebrew - key: ${{ runner.os }}-${{ runner.arch }}-homebrew-${{ hashFiles('.github/workflows/*.yml') }} - restore-keys: | - ${{ runner.os }}-${{ runner.arch }}-homebrew- - - name: Install macOS Dependencies run: brew install ccache git git-lfs cmake python clang-format flex bison cppcheck shell: bash - - - name: Cache CMake Build for faster rebuilds + + # ccache 4.x defaults to ~/Library/Caches/ccache on macOS + - name: Configure ccache + run: | + echo "CCACHE_DIR=$HOME/.ccache" >> $GITHUB_ENV + echo "CCACHE_MAXSIZE=2G" >> $GITHUB_ENV + echo "CCACHE_COMPRESS=1" >> $GITHUB_ENV + + - name: Cache ccache uses: actions/cache@v4 with: - path: | - build/ - ~/.ccache - key: ${{ runner.os }}-${{ matrix.os_arch }}-cmake-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt') }}-${{ github.sha }} + path: ~/.ccache + key: ${{ runner.os }}-${{ matrix.os_arch }}-ccache-${{ github.sha }} restore-keys: | - ${{ runner.os }}-${{ matrix.os_arch }}-cmake-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt') }} - ${{ runner.os }}-${{ matrix.os_arch }}-cmake- + ${{ runner.os }}-${{ matrix.os_arch }}-ccache- - name: Setup Build Environment run: | @@ -126,7 +148,7 @@ jobs: -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_OSX_SYSROOT=$SDKROOT \ -DCMAKE_OSX_ARCHITECTURES=${{ matrix.cmake_arch }} \ - -DOSQUERY_VERSION="5.9.1" \ + -DOSQUERY_VERSION="${{ needs.version.outputs.version }}" \ .. echo "CMake configuration completed" @@ -142,6 +164,10 @@ jobs: echo "Build completed successfully" + - name: ccache statistics + if: always() + run: ccache --show-stats + - name: Upload client artifact uses: actions/upload-artifact@v4 with: @@ -216,6 +242,7 @@ jobs: build_windows: name: "Build C Client for (${{ matrix.os }} ${{ matrix.os_arch }})" runs-on: ${{ matrix.os }} + needs: [version] if: | github.event_name == 'push' || github.event_name == 'workflow_dispatch' @@ -227,7 +254,7 @@ jobs: matrix: include: - name: "Windows x64" - os: windows-latest + os: windows-2022 # pinned: windows-latest is VS 2026; generator below needs VS 17 os_arch: x64 artifact_name: osquery-windows-amd64 @@ -249,7 +276,7 @@ jobs: run: | mkdir build cd build - cmake -G "Visual Studio 17 2022" -A ${{ matrix.os_arch }} -DOSQUERY_VERSION="5.9.1" .. + cmake -G "Visual Studio 17 2022" -A ${{ matrix.os_arch }} -DOSQUERY_VERSION="${{ needs.version.outputs.version }}" .. - name: Build OSQuery for Windows ${{ matrix.os_arch }} with MSBuild run: | @@ -272,7 +299,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact_name }} - path: "build/osquery/RelWithDebInfo/${{ env.BINARY_NAME }}${{ matrix.os == 'windows-latest' && '.exe' || '' }}" + path: "build/osquery/RelWithDebInfo/${{ env.BINARY_NAME }}.exe" if-no-files-found: warn retention-days: 30 compression-level: 9 @@ -293,7 +320,6 @@ jobs: uses: actions/download-artifact@v4 with: path: release-artifacts - continue-on-error: true - name: Prepare release artifacts run: | @@ -311,6 +337,14 @@ jobs: zip "${GITHUB_WORKSPACE}/final-artifacts/clients/osquery-windows-amd64.zip" ${{ env.BINARY_NAME }}.exe) fi + MISSING="" + [ -f "final-artifacts/clients/osquery-macos-universal.tar.gz" ] || MISSING="$MISSING macOS" + [ -f "final-artifacts/clients/osquery-windows-amd64.zip" ] || MISSING="$MISSING Windows" + if [ -n "$MISSING" ]; then + echo "::error::Missing release artifacts:$MISSING" + exit 1 + fi + ls -lh final-artifacts/clients/ - name: Generate release header diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af6d8988a49..98c462a6e92 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ on: concurrency: group: pr-${{ github.head_ref || github.ref }} - cancel-in-progress: false + cancel-in-progress: true env: REGISTRY: "ghcr.io" @@ -49,7 +49,7 @@ jobs: cmake_arch: x86_64 artifact_name: osquery-macos-x64 - name: "macOS ARM64" - os: macos-latest + os: macos-15 # pinned: Xcode 26 on macos-latest fails to compile boost mpl os_arch: arm64 cmake_arch: arm64 artifact_name: osquery-macos-arm64 @@ -73,31 +73,24 @@ jobs: done fi - - name: Cache Homebrew - uses: actions/cache@v4 - with: - path: | - ~/Library/Caches/Homebrew - /usr/local/Homebrew - /opt/homebrew - key: ${{ runner.os }}-${{ runner.arch }}-homebrew-${{ hashFiles('.github/workflows/*.yml') }} - restore-keys: | - ${{ runner.os }}-${{ runner.arch }}-homebrew- - - name: Install macOS Dependencies run: brew install ccache git git-lfs cmake python clang-format flex bison cppcheck shell: bash - - - name: Cache CMake Build for faster rebuilds + + # ccache 4.x defaults to ~/Library/Caches/ccache on macOS + - name: Configure ccache + run: | + echo "CCACHE_DIR=$HOME/.ccache" >> $GITHUB_ENV + echo "CCACHE_MAXSIZE=2G" >> $GITHUB_ENV + echo "CCACHE_COMPRESS=1" >> $GITHUB_ENV + + - name: Cache ccache uses: actions/cache@v4 with: - path: | - build/ - ~/.ccache - key: ${{ runner.os }}-${{ matrix.os_arch }}-cmake-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt') }}-${{ github.sha }} + path: ~/.ccache + key: ${{ runner.os }}-${{ matrix.os_arch }}-ccache-${{ github.sha }} restore-keys: | - ${{ runner.os }}-${{ matrix.os_arch }}-cmake-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt') }} - ${{ runner.os }}-${{ matrix.os_arch }}-cmake- + ${{ runner.os }}-${{ matrix.os_arch }}-ccache- - name: Setup Build Environment run: | @@ -138,6 +131,10 @@ jobs: echo "Build completed successfully" + - name: ccache statistics + if: always() + run: ccache --show-stats + build_windows: name: "Build C Client for (${{ matrix.os }} ${{ matrix.os_arch }})" runs-on: ${{ matrix.os }} @@ -150,7 +147,7 @@ jobs: matrix: include: - name: "Windows x64" - os: windows-latest + os: windows-2022 # pinned: windows-latest is VS 2026; generator below needs VS 17 os_arch: x64 artifact_name: osquery-windows-x64 steps: @@ -177,3 +174,13 @@ jobs: run: | cd build cmake --build . --config RelWithDebInfo -j10 + + all-checks: + name: "All Checks" + needs: [build_macos, build_windows] + runs-on: ubuntu-latest + if: always() + steps: + - if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} + run: exit 1 + - run: echo "All checks passed" diff --git a/openframe/CMakeLists.txt b/openframe/CMakeLists.txt index cbe8acc40db..316e1e4c6fe 100644 --- a/openframe/CMakeLists.txt +++ b/openframe/CMakeLists.txt @@ -15,6 +15,7 @@ function(generateOsqueryOpenframeLibrary) openframe_token_extractor.cpp openframe_token_refresher.cpp openframe_authorization_manager.cpp + openframe_machine_id_provider.cpp ) target_link_libraries(osquery_openframe PUBLIC @@ -33,6 +34,7 @@ function(generateOsqueryOpenframeLibrary) openframe_token_refresher.h openframe_authorization_manager.h openframe_authorization_manager_provider.h + openframe_machine_id_provider.h ) generateIncludeNamespace(osquery_openframe "openframe" "FILE_ONLY" ${public_header_files}) diff --git a/openframe/openframe_machine_id_provider.cpp b/openframe/openframe_machine_id_provider.cpp new file mode 100644 index 00000000000..daad4a111b6 --- /dev/null +++ b/openframe/openframe_machine_id_provider.cpp @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2014-present, The osquery authors + * + * This source code is licensed as defined by the LICENSE file found in the + * root directory of this source tree. + * + * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) + */ + +#include "openframe_machine_id_provider.h" + +#include +#include + +#include +#include + +namespace osquery { + +const std::string kOpenframeMachineIdHeader = "x-machine-id"; + +namespace { + +// Must match the openframe-client's app-support dir on each platform. +std::string machineIdFilePath() { +#ifdef WIN32 + const char* program_data = std::getenv("ProgramData"); + std::string base = + (program_data != nullptr) ? program_data : "C:\\ProgramData"; + return base + "\\OpenFrame\\machine_id"; +#elif defined(__APPLE__) + return "/Library/Application Support/OpenFrame/machine_id"; +#else + return "/var/lib/openframe/machine_id"; +#endif +} + +} // namespace + +OpenframeMachineIdProvider& OpenframeMachineIdProvider::getInstance() { + static OpenframeMachineIdProvider instance; + return instance; +} + +std::string OpenframeMachineIdProvider::getMachineId() { + std::lock_guard lock(mutex_); + if (!machine_id_.empty()) { + return machine_id_; + } + + auto path = machineIdFilePath(); + std::string machine_id; + std::ifstream machine_id_file(path); + if (machine_id_file.is_open()) { + std::getline(machine_id_file, machine_id); + boost::algorithm::trim(machine_id); + } + + if (machine_id.empty()) { + if (!warned_) { + LOG(WARNING) << "Could not read OpenFrame machine id from: " << path; + warned_ = true; + } + return ""; + } + + machine_id_ = machine_id; + LOG(INFO) << "OpenFrame machine id loaded from: " << path; + return machine_id_; +} + +} // namespace osquery diff --git a/openframe/openframe_machine_id_provider.h b/openframe/openframe_machine_id_provider.h new file mode 100644 index 00000000000..d9696513986 --- /dev/null +++ b/openframe/openframe_machine_id_provider.h @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2014-present, The osquery authors + * + * This source code is licensed as defined by the LICENSE file found in the + * root directory of this source tree. + * + * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) + */ + +#pragma once + +#include +#include +#include + +namespace osquery { + +/// HTTP header carrying the locally generated OpenFrame machine id. +extern const std::string kOpenframeMachineIdHeader; + +/** + * @brief Provides the locally generated OpenFrame machine id + * + * Reads the machine id written by the openframe-client to the shared + * OpenFrame data directory and caches it for the process lifetime. + */ +class OpenframeMachineIdProvider : private boost::noncopyable { + public: + /** + * @brief Get the singleton instance of OpenframeMachineIdProvider + * + * @return Reference to the singleton instance + */ + static OpenframeMachineIdProvider& getInstance(); + + /** + * @brief Get the machine id, reading it from disk on first success + * + * @return The machine id, or an empty string if not available yet + */ + std::string getMachineId(); + + private: + OpenframeMachineIdProvider() = default; + ~OpenframeMachineIdProvider() = default; + + std::mutex mutex_; + std::string machine_id_; + bool warned_ = false; +}; + +} // namespace osquery diff --git a/osquery/remote/transports/tls.cpp b/osquery/remote/transports/tls.cpp index e10ee3ac9ac..d56761ecd77 100644 --- a/osquery/remote/transports/tls.cpp +++ b/osquery/remote/transports/tls.cpp @@ -10,6 +10,7 @@ #include "tls.h" #include "openframe/openframe_authorization_manager.h" #include "openframe/openframe_authorization_manager_provider.h" +#include "openframe/openframe_machine_id_provider.h" #include #include @@ -103,6 +104,11 @@ void TLSTransport::decorateRequest(http::Request& r) { if (!token.empty()) { r << http::Request::Header("Authorization", "Bearer " + token); } + + auto machine_id = OpenframeMachineIdProvider::getInstance().getMachineId(); + if (!machine_id.empty()) { + r << http::Request::Header(kOpenframeMachineIdHeader, machine_id); + } } }