Plugin Development Guide¶
Overview¶
opnDossier uses a plugin-based architecture for compliance standards, allowing developers to create custom compliance plugins that integrate seamlessly with the core audit engine. Plugins can be either statically registered (baked into the binary) or dynamically loaded at runtime as Go plugins (.so files). This guide explains how to create, implement, and integrate new compliance plugins.
Plugin Architecture¶
Core Components¶
CompliancePluginInterface: Defines the contract that all plugins must implementPluginRegistry: Manages plugin registration, dynamic loading, and lifecyclePluginManager: Coordinates plugin operations and provides high-level APIsControlStruct: Represents individual compliance controls within a standard
Plugin Interface¶
All plugins must implement the CompliancePlugin interface:
import "github.com/EvilBit-Labs/opnDossier/internal/plugin"
type CompliancePlugin interface {
Name() string // Unique plugin identifier
Version() string // Plugin version
Description() string // Human-readable description
RunChecks(config *model.OpnSenseDocument) []plugin.Finding // Execute compliance checks
GetControls() []plugin.Control // Return all controls
GetControlByID(id string) (*plugin.Control, error) // Get specific control
ValidateConfiguration() error // Validate plugin config
}
The Finding struct is generic and uses References, Tags, and Metadata fields:
// plugin.Finding
Type string // e.g. "compliance"
Title string
Description string
Recommendation string
Component string
Reference string
References []string // Control IDs or external references
Tags []string // Arbitrary tags for filtering/categorization
Metadata map[string]string // Optional extra data
Creating a New Plugin¶
Step 1: Plugin Structure¶
For static plugins, create a new directory in internal/plugins/:
internal/plugins/
├── stig/
│ └── stig.go
├── sans/
│ └── sans.go
├── firewall/
│ └── firewall.go
└── your_plugin/
└── your_plugin.go
For dynamic plugins, create a new Go module or directory with a main package.
Step 2: Plugin Implementation¶
Static Plugin Example¶
package plugins
import (
"fmt"
"github.com/EvilBit-Labs/opnDossier/internal/plugin"
"github.com/EvilBit-Labs/opnDossier/internal/model"
)
type CustomPlugin struct {
controls []plugin.Control
}
func NewCustomPlugin() *CustomPlugin {
return &CustomPlugin{
controls: []plugin.Control{
{
ID: "CUSTOM-001",
Title: "Custom Security Control",
Description: "Description of the custom security control",
Category: "Security",
Severity: "high",
Rationale: "Why this control is important",
Remediation: "How to fix compliance issues",
Tags: []string{"custom", "security", "compliance"},
},
},
}
}
func (cp *CustomPlugin) Name() string { return "custom" }
func (cp *CustomPlugin) Version() string { return "1.0.0" }
func (cp *CustomPlugin) Description() string { return "Custom compliance checks for specific security requirements" }
func (cp *CustomPlugin) GetControls() []plugin.Control { return cp.controls }
func (cp *CustomPlugin) GetControlByID(id string) (*plugin.Control, error) {
for _, control := range cp.controls {
if control.ID == id {
return &control, nil
}
}
return nil, fmt.Errorf("control '%s' not found", id)
}
func (cp *CustomPlugin) ValidateConfiguration() error {
if len(cp.controls) == 0 {
return fmt.Errorf("no controls defined")
}
return nil
}
func (cp *CustomPlugin) RunChecks(config *model.OpnSenseDocument) []plugin.Finding {
var findings []plugin.Finding
// Implement your compliance checks here
// Example:
findings = append(findings, plugin.Finding{
Type: "compliance",
Title: "Missing Custom Security Feature",
Description: "The configuration is missing required custom security feature",
Recommendation: "Enable the custom security feature in the configuration",
Component: "security",
Reference: "CUSTOM-001",
References: []string{"CUSTOM-001"},
Tags: []string{"custom", "security", "compliance"},
})
return findings
}
Dynamic Plugin Example¶
package main
import (
"github.com/EvilBit-Labs/opnDossier/internal/plugin"
"github.com/EvilBit-Labs/opnDossier/internal/model"
)
type MyDynamicPlugin struct{}
// Implement CompliancePlugin methods...
var Plugin plugin.CompliancePlugin = &MyDynamicPlugin{}
Build with:
Step 3: Plugin Registration¶
- Static plugins: Register in the plugin manager as before.
- Dynamic plugins: Drop
.sofiles into the plugin directory (default:./plugins). They will be loaded automatically at startup.
Dynamic Plugin Loading¶
- The audit engine will scan a configurable directory for
.sofiles and load any plugin that exportsvar Plugin plugin.CompliancePlugin. - Dynamic plugins must be built with the same Go version and dependencies as the main binary.
- Both static and dynamic plugins are supported and can coexist.
Plugin Development Best Practices¶
- Use unique, descriptive control IDs and titles.
- Provide actionable remediation and clear rationale.
- Use the
ReferencesandTagsfields for all findings. - Write comprehensive tests for your plugin.
- Document your controls and plugin usage.
Troubleshooting¶
- Plugin not loaded? Ensure it is built as a Go plugin (
-buildmode=plugin), exportsvar Plugin, and is in the correct directory. - Go version mismatch? All plugins and the main binary must be built with the exact same Go version and dependencies.
- Platform support: Go plugins are supported on Linux and macOS, not Windows.
Examples¶
- See
internal/plugins/for static plugin examples. - See the above dynamic plugin example for external plugins.
Conclusion¶
The opnDossier plugin system is flexible: you can extend compliance coverage by adding new plugins statically or dynamically, with a simple, generic interface and robust integration with the audit engine.