Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 0 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,5 @@
}
},
"workspaceFolder": "/workspaces/azure-osconfig",
"mounts": [
"source=${localWorkspaceFolder}/../azcorelinux-Compliance-AugmentationEngine,target=/workspaces/azcorelinux-Compliance-AugmentationEngine,type=bind,consistency=cached"
],
"remoteUser": "root"
}
186 changes: 6 additions & 180 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ The architecture has three layers: **Adapters** (IoT Hub PnP agent, RC/DC watche
```
src/ # All source code
CMakeLists.txt # Root CMake file (project config, vcpkg integration, build options)
vcpkg.json # Dependencies: openssl, curl, lua, sqlite3, nlohmann-json, gtest
vcpkg.json # Dependencies: openssl, curl, sqlite3, nlohmann-json, gtest
vcpkg-configuration.json # vcpkg registry baseline
adapters/ # Agent adapters (PnP IoT Hub client, MC machine config)
pnp/ # PnP agent (main binary: /usr/bin/osconfig)
azure-iot-sdk-c/ # Git submodule - Azure IoT C SDK
mc/ # Machine configuration adapters (ASB, SSH, compliance engine)
mc/ # Machine configuration adapters (ASB, SSH)
platform/ # Management Platform daemon (/usr/bin/osconfig-platform)
inc/Mpi.h # MPI interface header
Main.c, MpiServer.c, ModulesManager.c, MmiClient.c
Expand All @@ -39,7 +39,6 @@ src/ # All source code
securitybaseline/ # SecurityBaseline module
configuration/ # Configuration module
deviceinfo/ # DeviceInfo module
complianceengine/ # ComplianceEngine module (Lua-based evaluator)
test/ # Module test harness (moduletest tool)
recipes/ # Test recipe JSON files
samples/ # Sample module (C++)
Expand Down Expand Up @@ -88,7 +87,7 @@ ctest --test-dir . --output-on-failure -j$(nproc)

- ~960 unit tests using Google Test. Some tests requiring root or special filesystem permissions will be **skipped** (not failed) when run as non-root. ~10 tests involving file access may fail without root.
- Tests are registered per-module and per-library in their respective `tests/` subdirectories.
- Test binaries are in: `build/common/commonutils/`, `build/platform/tests/`, `build/modules/commandrunner/tests/`, `build/modules/complianceengine/tests/`, `build/modules/configuration/tests/`.
- Test binaries are in: `build/common/commonutils/`, `build/platform/tests/`, `build/modules/commandrunner/tests/`, `build/modules/configuration/tests/`.
- Total test time: ~10 seconds.

### Formatting and Linting (required before PR)
Expand All @@ -98,9 +97,9 @@ Always run pre-commit before submitting changes:
python3 -m pre_commit run --all-files
```

This runs: trailing whitespace fix, end-of-file fix, LF line endings, clang-format (v14, on complianceengine + telemetry files only), clang-tidy (on complianceengine + telemetry C++ files only), and compliance engine interface generation.
This runs: trailing whitespace fix, end-of-file fix, LF line endings, clang-format (v14, on telemetry files only), and clang-tidy (on telemetry C++ files only).

**Important**: clang-format and clang-tidy in pre-commit only apply to files in `src/modules/complianceengine/`, `src/compliance-engine-assessor/`, and `src/common/telemetry/`. Other C/C++ files are not auto-formatted but must follow the style in `docs/style.md`.
**Important**: clang-format and clang-tidy in pre-commit only apply to files in `src/common/telemetry/`. Other C/C++ files are not auto-formatted but must follow the style in `docs/style.md`.

## CI Checks on Pull Requests

Expand All @@ -127,177 +126,4 @@ Each module is a shared library (`.so`) implementing the MMI API (`MmiOpen`, `Mm
3. Register in `src/modules/CMakeLists.txt` using the `add_module()` function
4. Add test recipes in `src/modules/test/recipes/`

**Only 5 modules are actively built:** commandrunner, securitybaseline, configuration, deviceinfo, complianceengine. Other module directories (adhs, firewall, hostname, networking, pmc, tpm, ztsi) exist but are not included in the default build.

## ComplianceEngine Module (Important Module)

The ComplianceEngine is the most complex and important module. It evaluates security compliance rules defined as **rule payloads**, using a combination of **logical combinators** (`allOf`, `anyOf`, `not`), **built-in C++ procedures**, and **Lua scripts**. It supports both **audit** (check compliance) and **remediation** (fix non-compliance) actions.

**Upstream producer**: Rule payloads are generated by the **Compliance Augmentation Engine** (`azcorelinux-Compliance-AugmentationEngine` repo), which transforms CIS XCCDF benchmarks into these JSON structures and base64-encodes them into MOF files. The same concept is referred to as "JSON conditionals", "calling convention", or "mofJson" in that repo. The payload structure (`{audit, remediate, parameters}`) is identical—produced upstream, consumed here.

### Architecture

```
src/modules/complianceengine/
src/
lib/ # Core library (complianceenginelib)
Engine.h/.cpp # Top-level MMI handler, manages rule database
Evaluator.h/.cpp # Recursive rule evaluator (allOf/anyOf/not/Lua/builtin dispatch)
Procedure.h/.cpp # Stores a single rule's audit/remediate JSON + parameters
ProcedureMap.h/.cpp # AUTO-GENERATED - maps procedure names → function pointers
Bindings.h # Template framework connecting params structs → string args
BindingParsers.h/.cpp # Type parsers (string, int, bool, mode_t, regex, Pattern)
GenInterface.py # Code generator: parses .h files → generates ProcedureMap.h/.cpp
Indicators.h/.cpp # Tree of compliance/non-compliance status messages
ContextInterface.h/.cpp # Abstract interface for system access (commands, files, logging)
LuaEvaluator.h/.cpp # Lua script execution engine
payload.schema.json # JSON Schema for rule payloads (allOf/anyOf/not/Lua/procedures)
procedures/ # Built-in procedure implementations (one triad per procedure)
so/ # Module .so entry point (ComplianceEngineModule.c)
assessor/ # CLI assessor tool
lua-evaluator/ # Lua evaluator subdirectory
tests/ # Unit tests
procedures/ # Per-procedure test files
```

### How Rule Evaluation Works

A **rule payload** is a JSON object with `audit`, optional `remediate`, and optional `parameters` fields. This is the exact structure produced by the Compliance Augmentation Engine (where it is called "JSON conditionals" or "calling convention") and base64-encoded into the MOF `ProcedureObjectValue` field. The `audit` and `remediate` fields contain a recursive expression tree:

```json
{
"audit": {
"allOf": [
{ "EnsureFilePermissions": { "filename": "/etc/passwd", "permissions": "0644" } },
{ "anyOf": [
{ "PackageInstalled": { "packageName": "openssh-server" } },
{ "not": { "EnsureFileExists": { "filename": "/etc/ssh/sshd_config" } } }
]},
{ "Lua": { "script": "return Compliant('ok')" } }
]
},
"parameters": { "myParam": "defaultValue" }
}
```

The `Evaluator` recursively processes each node in the expression tree (`Evaluator.cpp`):
- **`allOf`**: Array of sub-expressions. Returns `NonCompliant` on first failure (short-circuit AND).
- **`anyOf`**: Array of sub-expressions. Returns `Compliant` on first success (short-circuit OR).
- **`not`**: Inverts the result. Audit-only (no remediation through `not`).
- **`Lua`**: Runs an inline Lua script via `LuaEvaluator`.
- **Any other key**: Looked up as a built-in procedure name in `Evaluator::mProcedureMap`.

Procedure arguments support **parameter substitution**: a value starting with `$` (e.g., `"$myParam"`) is replaced with the value from the rule's `parameters` map. The Compliance Augmentation Engine produces these `$paramName` placeholders with default values in the `parameters` dict; user overrides arrive via `DesiredObjectValue` in the MOF (`key=value` pairs) and are applied by `Procedure::UpdateUserParameters()`.

### Built-in Procedures

Built-in procedure source files live in `src/modules/complianceengine/src/lib/procedures/`. A single file can contain multiple related procedures — for example, `EnsureFilePermissions.h/.cpp` defines both `EnsureFilePermissions` and `EnsureFilePermissionsCollection`. The file naming reflects the logical grouping, not a 1:1 mapping to procedure names.

Each source file consists of up to three parts:

| File | Purpose |
|------|---------|
| `.h` | Params struct(s) + audit/remediate function declarations for one or more procedures |
| `.cpp` | Implementation(s) |
| `.schema.json` | JSON Schema fragment(s) with `definitions.audit` and `definitions.remediation` sections for each procedure in the file |

Each procedure must implement an Audit function and may optionally implement a Remediate function:
- `Result<Status> Audit<Name>(const <Name>Params& params, IndicatorsTree& indicators, ContextInterface& context)` — **required**
- `Result<Status> Remediate<Name>(const <Name>Params& params, IndicatorsTree& indicators, ContextInterface& context)` — **optional**

Functions return `Status::Compliant` or `Status::NonCompliant` via `indicators.Compliant("msg")` / `indicators.NonCompliant("msg")`, or `Error(...)` on failure.

**Important:** A single file can contain multiple related procedures (e.g., `EnsureFilePermissions.h` defines both `EnsureFilePermissions` and `EnsureFilePermissionsCollection`). The filename does not need to match any individual procedure name — group related procedures together when it makes sense. When adding a new procedure, consider whether it logically belongs in an existing file before creating a new one.

### How to Add a New Procedure

1. **Add the procedure to a header** in `procedures/` (new or existing `.h` file): Define a params struct and declare audit/remediate functions.
- Struct fields use types: `std::string`, `int`, `bool`, `mode_t`, `regex`, `Pattern`, `Optional<T>`, `Separated<T, char>`, or enum types.
- Document each field with `///` comments (used by GenInterface.py). Add `/// pattern: <regex>` for validation.

```cpp
struct Audit<Name>Params
{
/// Description of the parameter
std::string requiredParam;

/// Optional parameter description
Optional<int> optionalParam;
};

Result<Status> Audit<Name>(const Audit<Name>Params& params, IndicatorsTree& indicators, ContextInterface& context);
```

2. **Add the implementation** to a `.cpp` file in `procedures/` (matching the header file, not necessarily the procedure name).

3. **Add or update the schema** in a `.schema.json` file in `procedures/` with `definitions.audit` and `definitions.remediation` sections for the new procedure.

4. **Run `GenInterface.py`** (or `pre-commit`): The script parses all procedure headers and **auto-generates** `ProcedureMap.h` and `ProcedureMap.cpp`. These files:
- Include all procedure headers.
- Define `Bindings<Params>` specializations (field name arrays + member pointer tuples).
- Define `MapEnum<E>()` specializations for any custom enums.
- Populate `Evaluator::mProcedureMap` with `{name, {MakeHandler(Audit...), MakeHandler(Remediate...)}}`.

**NEVER edit `ProcedureMap.h` or `ProcedureMap.cpp` manually** — they are regenerated by `GenInterface.py`.

5. **Register in CMakeLists.txt**: Add the `.cpp` to the `PROCEDURES` list and the `.schema.json` to the `SCHEMAS` list in `src/modules/complianceengine/src/lib/CMakeLists.txt`. The build enforces that every `.cpp` in `procedures/` is listed and every procedure has a matching schema.

6. **Add unit tests** in `tests/procedures/<Name>Test.cpp`. Use `MockContext` from `tests/MockContext.h` to mock file/command access.

7. **Run pre-commit** to regenerate the ProcedureMap files and validate formatting:
```bash
python3 -m pre_commit run --all-files
```

### Key Types for Procedure Development

- `IndicatorsTree`: Call `indicators.Compliant("message")` or `indicators.NonCompliant("message")` to record results.
- `ContextInterface`: Use `context.ExecuteCommand(cmd)` and `context.GetFileContents(path)` for system access. Never call system functions directly — this enables unit testing with `MockContext`.
- `Result<T>`: Either holds a value (`HasValue()`) or an `Error`. Return `Error("msg", errno_code)` on failure.
- `Optional<T>`: For optional procedure parameters. `HasValue()` checks if set.
- `Separated<T, delimiter>`: For pipe/comma-separated list parameters (e.g., `Separated<Pattern, '|'>`).

### Enum Parameters

Enum types allow procedure parameters to accept a fixed set of string labels from JSON. To add an enum parameter:

1. **Define the enum** in the procedure header file, placing it **before** the params struct that uses it. Each enum value must have a `/// label: <json_label>` comment specifying the string used in JSON rule payloads:

```cpp
enum class PackageManagerType
{
/// label: autodetect
Autodetect,

/// label: rpm
RPM,

/// label: dpkg
DPKG,
};
```

2. **Use the enum type** in the params struct:

```cpp
struct PackageInstalledParams
{
/// Package name
std::string packageName;

/// Package manager, autodetected by default
Optional<PackageManagerType> packageManager;
};
```

3. **Run `GenInterface.py`** (or `pre-commit`): It parses the `/// label:` comments and auto-generates a `MapEnum<PackageManagerType>()` specialization in `ProcedureMap.h` that maps JSON string labels to C++ enum values. **Do not write this mapping manually.**

In rule payloads, the enum value is specified by its label string: `{ "PackageInstalled": { "packageName": "nftables", "packageManager": "rpm" } }`.

### Testing ComplianceEngine

Unit tests are in `src/modules/complianceengine/tests/` and `tests/procedures/`. Integration tests use recipe JSON files in `src/modules/test/recipes/complianceengine/`. Run:

```bash
cd build && ctest -R complianceengine --output-on-failure -j$(nproc)
```
**Only 4 modules are actively built:** commandrunner, securitybaseline, configuration, deviceinfo. Other module directories (adhs, firewall, hostname, networking, pmc, tpm, ztsi) exist but are not included in the default build.
27 changes: 0 additions & 27 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,44 +47,17 @@ repos:
additional_dependencies: [clang-tidy]
files: |
(?x)(
^src/modules/complianceengine/src/.*\.h$|
^src/modules/complianceengine/src/.*\.cpp$|
^src/compliance-engine-assessor/.*\.hpp$|
^src/compliance-engine-assessor/.*\.cpp$|
^src/common/telemetry/.*\.h$|
^src/common/telemetry/.*\.hpp$|
^src/common/telemetry/.*\.c$
)
- repo: local
hooks:
- id: compliance-engine-interface
name: Generate symbolic compliance engine interface
types_or: [c++, json]
entry: src/modules/complianceengine/src/lib/GenInterface.py
language: python
require_serial: true
files: |
(?x)(
^src/modules/complianceengine/src/lib/payload.schema.json$|
^src/modules/complianceengine/src/lib/procedures/.*\.h$|
^src/modules/complianceengine/src/lib/procedures/.*\.schema.json$
)
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v14.0.6
hooks:
- id: clang-format
files: |
(?x)(
^src/modules/complianceengine/.*\.h$|
^src/modules/complianceengine/.*\.cpp$|
^src/compliance-engine-assessor/.*\.hpp$|
^src/compliance-engine-assessor/.*\.cpp$|
^src/common/telemetry/.*\.h$|
^src/common/telemetry/.*\.hpp$|
^src/common/telemetry/.*\.c$
)
exclude: |
(?x)(
^src/modules/complianceengine/src/lib/ProcedureMap.h$|
^src/modules/complianceengine/src/lib/ProcedureMap.cpp$
)
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ Source | Destination | Description
[src/modules/commandrunner/](src/modules/commandrunner/) | /usr/lib/osconfig/commandrunner.so | The CommandRunner module binary
[src/modules/configuration/](src/modules/configuration/) | /usr/lib/osconfig/configuration.so | The Configuration module binary
[src/modules/securitybaseline/](src/modules/securitybaseline/) | /usr/lib/osconfig/securitybaseline.so | The SecurityBaseline module binary
[src/modules/complianceengine/](src/modules/complianceengine/) | /usr/lib/osconfig/complianceengine.so | The ComplianceEngine module binary
[src/common/telemetry/](src/common/telemetry/) | /var/lib/osconfig/telemetry | The OSConfig telemetry directory

### Enable and start OSConfig for the first time
Expand Down
1 change: 0 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ option(BUILD_TELEMETRY "Build telemetry" ON)
option(BUILD_SAMPLES "Build samples" OFF)
option(COVERAGE "Enable code coverage" OFF)
option(BUILD_FUZZER "Build fuzzer" OFF)
option(BUILD_COMPLIANCE_ENGINE_ASSESSOR "Build the compliance engine assessor tool" ON)

add_compile_options("-Wno-psabi;-fPIC")
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
Expand Down
1 change: 0 additions & 1 deletion src/adapters/mc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,3 @@ add_compile_options("-Wall;-Wextra;-Wunused;-Werror;-Wformat;-Wformat-security;-

add_subdirectory(ssh)
add_subdirectory(asb)
add_subdirectory(complianceengine)
74 changes: 0 additions & 74 deletions src/adapters/mc/complianceengine/Baseline.c

This file was deleted.

Loading
Loading