Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"MD013": false,
"MD053": false
}
40 changes: 37 additions & 3 deletions Gatekeeper/Configuration.psd1
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
# Configuration settings for the Gatekeeper module
@{
# Configuration settings for the Gatekeeper module
ModuleName = 'Gatekeeper'
ModuleVersion = '0.1.0'
# This is the version of the configuration, this will
# allow safe upgrades in the future.
# It is not the version of the module itself.
Version = '0.1.0'
FilePaths = @{
Schemas = "$PSScriptRoot\Schemas"
}
Logging = @{
Allow = @{
# We leave this disabled by default to avoid cluttering the console
Enabled = $false
Script = {
param($Rule)
Write-Host "✅ Rule [$($Rule.Name)] matched and is allowed."
}
}
Deny = @{
# We leave this disabled by default to avoid cluttering the console
Enabled = $false
Script = {
param($Rule)
Write-Host "⛔ Rule [$($Rule.Name)] matched and is denied."
}
}
Warning = @{
Enabled = $true
Script = {
param($Rule)
Write-Warning "⚠️ Rule [$($Rule.Name)] matched."
}
}
Audit = @{
Enabled = $true
Script = {
param($Rule)
Write-Host "Audit: $($Rule.Name)"
}
}
}
}
17 changes: 17 additions & 0 deletions Gatekeeper/Public/Get-PropertySet.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
function Get-PropertySet {
<#
.SYNOPSIS
Retrieve property sets from the Gatekeeper configuration.

.DESCRIPTION
This function retrieves property sets from the Gatekeeper configuration.
It can return all property sets or a specific one by name. Property sets are
stored in a cache to avoid multiple reads from disk, improving performance.

.PARAMETER Name
The name of the property set to retrieve.
If not specified, all property sets will be returned.
.EXAMPLE
$propertySet = Get-PropertySet -Name 'MyPropertySet'

This retrieves the property set with the name 'MyPropertySet'.
#>
param (
[Parameter()]
[string]
Expand Down
35 changes: 34 additions & 1 deletion Gatekeeper/Public/Import-GatekeeperConfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,43 @@ function Import-GatekeeperConfig {
}
}
process {
$script:GatekeeperConfiguration = Import-Configuration
if ($script:GatekeeperConfiguration) {
Write-Verbose "Using cached Gatekeeper configuration."
} else {
Write-Verbose "Loading Gatekeeper configuration from disk."
$script:GatekeeperConfiguration = Import-Configuration
}
# Check if the configuration was imported successfully
if (-not $script:GatekeeperConfiguration) {
throw "Failed to import Gatekeeper configuration."
}

#region Parse the logging configuration
if ($script:GatekeeperConfiguration.Logging) {
Write-Verbose "Parsing logging configuration."
$script:GatekeeperLogging = @{}
foreach ($level in $script:GatekeeperConfiguration.Logging.Keys) {
if (-not $script:GatekeeperConfiguration.Logging[$level].Enabled) {
Write-Verbose "Logging level '$level' is disabled, skipping."
continue
}
# Handle if the script is file or script block
if ($script:GatekeeperConfiguration.Logging[$level].Script -is [string]) {
$scriptPath = $script:GatekeeperConfiguration.Logging[$level].Script
if (-not (Test-Path -Path $scriptPath)) {
throw "Logging script file not found: $scriptPath"
}
Write-Verbose "Loading logging script from file: $scriptPath"
$script:GatekeeperLogging[$level] = [scriptblock]::Create((Get-Content -Path $scriptPath -Raw))
} elseif ($script:GatekeeperConfiguration.Logging[$level].Script -is [scriptblock]) {
Write-Verbose "Using inline script block for logging level: $level"
$script:GatekeeperLogging[$level] = $script:GatekeeperConfiguration.Logging[$level].Script
} else {
Write-Warning "No valid script found for logging level: $level"
}
}
}
#endregion Parse the logging configuration
}
end {
Write-Verbose "Gatekeeper configuration imported successfully."
Expand Down
1 change: 0 additions & 1 deletion Gatekeeper/Public/New-Condition.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ function New-Condition {

Write-Verbose "Initializing new condition for property '$Property' with operator '$Operator' and value '$Value'."
# Test if Property is a known property
# TODO: Configure Get-PropertySet to return a list of all properties
if ($property -notin (Get-PropertySet).Properties.Keys) {
Write-Warning "Property '$Property' is not defined in any property set."
}
Expand Down
9 changes: 6 additions & 3 deletions Gatekeeper/Public/Test-FeatureFlag.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

begin {
$finalResult = $False
$config = Import-GatekeeperConfig
}

process {
Expand All @@ -51,22 +52,24 @@
Condition = $rule.Conditions
}
if (Test-Condition @testConditionSplat) {
Write-Verbose "✅ Rule [$($rule.Name)] matched. Effect: $($rule.Effect)" -ForegroundColor Green
Write-Verbose "✅ Rule [$($rule.Name)] matched. Effect: $($rule.Effect)"
# Check effect
switch ($rule.Effect) {
'Allow' {
. $script:GatekeeperLogging['Allow'] -Rule $rule
$finalResult = $true
break
}
'Deny' {
. $script:GatekeeperLogging['Deny'] -Rule $rule
$finalResult = $false
break
}
'Audit' {
# TODO: Implement auditing function
. $script:GatekeeperLogging['Audit'] -Rule $rule
}
'Warn' {
Write-Warning "⚠️ Rule [$($rule.Name)] matched."
. $script:GatekeeperLogging['Warning'] -Rule $rule
}
default {
throw 'Unknown effect'
Expand Down
113 changes: 102 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,17 +314,108 @@
> effect is not an approval. This protects against accidentally opening a
> feature when your default is warn or audit.

## To Do
## Configuration

| Key | Description | Default |
|------------|-----------------------------------------------------------------------------------------------------| -|
| `Version` | The version of the configuration file, used for safe upgrades. | `0.1.0` |
| `FilePaths` | An object containing paths to important folders, such as Schemas. | Objects |
| `FilePaths.Schemas` | The path to the the Schemas on disk. | `Schemas` in Module directory |
| `FilePaths.FeatureFlags` | The path to the the FeatureFlags on disk. | `$null`. [^1] |
| `FilePaths.PropertySet` | The path to the the PropertySet's on disk. | `$null`.[^1] |
| `Logging` | An object defining logging behaviors for different rule outcomes (Allow, Deny, Warning, Audit). | Object with `Allow`, `Deny`, `Warning`, and `Audit` defined. |
| `Logging.Allow` | Logging settings for allowed rules, including whether logging is enabled and the script to execute. | See the [Logging](#logging) table |
| `Logging.Deny` | Logging settings for denied rules, including whether logging is enabled and the script to execute. | See the [Logging](#logging) table |
| `Logging.Warning` | Logging settings for warning rules, including whether logging is enabled and the script to execute. | See the [Logging](#logging) table |
| `Logging.Audit` | Logging settings for audit rules, including whether logging is enabled and the script to execute. | See the [Logging](#logging) table |

[^1]: These folders are evaluated during a run and the configuration is saved
to disk. These default to the same folder as the machine wide configuration.

### Loading Precedent

Configuration begins by loading the
[Configuration.psd1](config) from the module.
Then it loads the machine-wide settings (e.g. `$Env:ProgramData` or
`/etc/xdg/`). Then it imports the users' enterprise roaming settings (e.g. from
`$Env:AppData` (the roaming path) or `~/.local/share/`). Finally it imports the
users' local settings (from `$Env:LocalAppData` or `~/.config/`).

> [!NOTE]
> All the logic of placing configuration is thanks to the Configuration module.

## Logging

Logging is defined in the configuration file and can be a a path on disk or a
scriptblock. Either should accept a `$Rule` parameter (but don't necessarily
need to use it).

The default configuration has the Allow and Deny logging rule set to disable to
avoid cluttering the screen. The default warning will `Write-Warning` to let you
know the rule would have passed.

| Logging Level | Enabled | Default |
|---------------|----------|--------------------------------------------------------------|
| Allow | Disabled | `Write-Host "✅ Rule [$($Rule.Name)] matched and is allowed"` |
| Deny | Disabled | `Write-Host "⛔ Rule [$($Rule.Name)] matched and is denied."` |
| Warning | Enabled | `Write-Warning "⚠️ Rule [$($Rule.Name)] matched."` |
| Audit | Enabled | `Write-Host "Audit: $($Rule.Name)"` |

The most obvious logging method to overwrite will be `Audit`. In your
[configuration file](#configuration) you will need to overwrite the script
block.

### Changing Auditing Function

To change your auditing function you need to update your `Configuration.psd1` to
contain something like the following:

```powershell
Logging = @{
... # Your other logging functions (if any)
Audit = @{
# Ensure it's enabled
Enabled = $true
Script = {
param($Rule)
$line = "✅ Rule [$($Rule.Name)] matched and is allowed."
$line | Out-File C:\Contoso\Logs\Gatekeeper.log -Append

Check warning on line 382 in README.md

View workflow job for this annotation

GitHub Actions / Continuous Integration / Run Linters

Unknown word (Contoso) Suggestions: (contos, conto, cantos, centos, condos)
}
}
}
```

Here is an example if you prefer to use a script from disk.

Let's say you have a script called `C:\Contoso\Logging.ps1`. That script writes

Check warning on line 390 in README.md

View workflow job for this annotation

GitHub Actions / Continuous Integration / Run Linters

Unknown word (Contoso) Suggestions: (contos, conto, cantos, centos, condos)
to a log file. The script could look something like :

```powershell
param($Rule)
$line = "✅ Rule [$($Rule.Name)] matched and is allowed."
$line | Out-File C:\Contoso\Logs\Gatekeeper.log -Append

Check warning on line 396 in README.md

View workflow job for this annotation

GitHub Actions / Continuous Integration / Run Linters

Unknown word (Contoso) Suggestions: (contos, conto, cantos, centos, condos)
```

Then you would update your configuration to look like:

```powershell
Logging = @{
... # Your other logging functions (if any)
Audit = @{
# Ensure it's enabled
Enabled = $true
Script = 'C:\Contoso\Logging.ps1'

Check warning on line 407 in README.md

View workflow job for this annotation

GitHub Actions / Continuous Integration / Run Linters

Unknown word (Contoso) Suggestions: (contos, conto, cantos, centos, condos)
}
}
```

> [!IMPORTANT]
> If you supply a string, it must be a valid path to a script file
> that accepts the `$Rule` parameter.

# ToDo List

These are items that may or may not be setup.

- [ ] Function to create PropertySet
- [ ] Class for FeatureFlag
- [ ] Evaluate performance
- [ ] Handle fetching/caching feature flags
- [ ] Script level variables for defining where to get/set json files.
- [ ] Function to create property in PropertySet
- [ ] Ability to create PropertySet in memory and then ability to save to disk.
- [ ] Define auditing method that users can overwrite
- [ ] Publish schemas somewhere consistent with some type of versioning
- [ ] CRUD for creating condition
- [ ] TUI?
- [ ] Support for evaluating remote device
Loading