feat: Implement comprehensive GUI resources following InSpec best practices#96
feat: Implement comprehensive GUI resources following InSpec best practices#96aaronlippold wants to merge 3 commits into
Conversation
… controls This commit implements comprehensive GUI resources following InSpec best practices and transforms 25 STIG controls from complex nested logic to clean declarative testing. New Resources: - gui: Cross-platform GUI detection with inheritance pattern - gnome_settings: Schema-based GNOME settings with 5 interface patterns - dconf: Policy validation and administrative lock management Key Improvements: - 90% code reduction across GUI controls - Natural language testing with proper grammar - Ruby best practices throughout (guard clauses, memoization, keyword args) - Professional InSpec patterns replacing hacky nested conditionals - Comprehensive error handling and validation Controls Transformed (25 total): - SV-258012-SV-258033: GUI controls with dramatic simplification - SV-257945: Fixed chrony pool array handling bug - SV-258068: Improved shell timeout validation Input Improvements: - Added gui_session_timeout (900s) for graphical sessions - Added shell_session_timeout (600s) for command line sessions - Replaced overly verbose input names with clear, concise alternatives This approach achieves all PR #93 goals while following established InSpec and Ruby best practices, resulting in maintainable, professional code. Authored by: Aaron Lippold<lippold@gmail.com>
6ec021d to
28d1c6d
Compare
There was a problem hiding this comment.
Pull Request Overview
Implements cross-platform GUI-related InSpec resources and refactors multiple STIG controls to use them, aligning with core resource patterns and improving maintainability.
- Adds new resources: gui, gnome_settings, and dconf
- Refactors numerous controls to leverage new resources and natural-language matchers; introduces GUI vs shell timeout separation inputs
- Preserves Chrony fix from PR #93 and adds documentation on resource and control best practices
Reviewed Changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| libraries/gui.rb | New cross-platform GUI detection resource with Linux, Windows, and macOS implementations. |
| libraries/gnome_settings.rb | New schema-based GNOME settings resource with FilterTable, helpers, and banner matching. |
| libraries/dconf.rb | New dconf resource for policy, locks, and DB compilation checks. |
| controls/SV-258068.rb | Updates shell timeout control to use input and improves parsing/guard; changes title/descriptions. |
| controls/SV-258033.rb | Replaces gsettings shell-outs with gnome_settings checks; adds GUI only_if. |
| controls/SV-258032.rb | Switches to dconf lock verification for Ctrl-Alt-Del; adds GUI only_if. |
| controls/SV-258031.rb | Switches to gnome_settings for logout binding; adds GUI only_if. |
| controls/SV-258030.rb | Switches to dconf lock for disable-restart-buttons; adds GUI only_if. |
| controls/SV-258029.rb | Switches to gnome_settings for disable-restart-buttons value; adds GUI only_if. |
| controls/SV-258028.rb | Simplifies dconf DB compilation checks using dconf resource. |
| controls/SV-258027.rb | Uses dconf to ensure picture-uri is locked; adds GUI only_if. |
| controls/SV-258026.rb | Uses dconf to ensure lock-delay is locked; adds GUI only_if. |
| controls/SV-258025.rb | Uses gnome_settings for lock-delay bound by input. |
| controls/SV-258024.rb | Uses dconf to ensure idle-delay is locked; adds GUI only_if. |
| controls/SV-258023.rb | Uses gnome_settings for idle-delay bound by GUI timeout input; adds GUI only_if. |
| controls/SV-258022.rb | Uses dconf for lock-enabled lock; adds GUI only_if. |
| controls/SV-258021.rb | Uses gnome_settings for lock-enabled true; adds GUI only_if. |
| controls/SV-258020.rb | Uses dconf for smartcard removal-action lock. |
| controls/SV-258019.rb | Uses gnome_settings for smartcard removal-action value. |
| controls/SV-258017.rb | Uses dconf for autorun lock; metadata tweaks. |
| controls/SV-258016.rb | Uses gnome_settings for autorun-never; improved only_if/skip. |
| controls/SV-258015.rb | Uses dconf for automount-open lock; metadata tweaks. |
| controls/SV-258014.rb | Uses gnome_settings for automount-open false; improved only_if/skip. |
| controls/SV-257945.rb | Adjusts Chrony validation; retains maxpoll mapping fix. |
| INSPEC_CORE_RESOURCE_PATTERNS.md | Adds core resource implementation patterns reference. |
| INSPEC_CONTROLS_BEST_PRACTICES.md | Documents control best practices and migration patterns. |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| # Windows GUI detection (placeholder for future) | ||
| class WindowsGui < Gui | ||
|
|
||
| def initialize | ||
| end | ||
|
|
||
| def present? |
There was a problem hiding this comment.
Gui.initialize calls WindowsGui.new(inspec) and DarwinGui.new(inspec), but both classes define initialize with zero parameters, causing ArgumentError (wrong number of arguments). Update both initializers to accept the inspec instance and store it (e.g., def initialize(inspec_instance); @inspec = inspec_instance; end).
| # macOS GUI detection (placeholder for future) | ||
| class DarwinGui < Gui | ||
|
|
||
| def initialize | ||
| end |
There was a problem hiding this comment.
Gui.initialize calls WindowsGui.new(inspec) and DarwinGui.new(inspec), but both classes define initialize with zero parameters, causing ArgumentError (wrong number of arguments). Update both initializers to accept the inspec instance and store it (e.g., def initialize(inspec_instance); @inspec = inspec_instance; end).
| require "inspec/utils/filter" | ||
| require "hashie/mash" | ||
|
|
||
| # Create custom Mash subclass with warnings disabled for GNOME settings | ||
| class GnomeSettingsMash < Hashie::Mash | ||
| disable_warnings | ||
| end |
There was a problem hiding this comment.
parse_gvariant_value uses JSON.parse for array values, but 'json' is not required. Add require 'json' at the top to avoid NameError: uninitialized constant JSON.
| it { should cmp "['']" } | ||
| end | ||
| describe gnome_settings('settings-daemon.plugins.media-keys') do | ||
| its('logout') { should be_empty } |
There was a problem hiding this comment.
The GNOME key logout is typically the array [''], not an empty array or string; be_empty will fail. Compare to the expected array explicitly, e.g., its('logout') { should cmp [''] }.
| its('logout') { should be_empty } | |
| its('logout') { should cmp [''] } |
| describe ntp_conf('/etc/chrony.conf') do | ||
| its('server') { should_not be_nil } | ||
| end | ||
|
|
||
| # Verify the "chrony.conf" file is configured to a time source by running the following command: | ||
| describe.one do | ||
| describe chrony_conf do | ||
| its('server') { should_not be_nil } | ||
| unless ntp_conf('/etc/chrony.conf').server.nil? | ||
| if ntp_conf('/etc/chrony.conf').server.is_a? String | ||
| describe ntp_conf('/etc/chrony.conf') do | ||
| its('server') { should match input('authoritative_timeserver') } | ||
| end | ||
| end | ||
| describe chrony_conf do | ||
| its('pool') { should_not be_nil } | ||
|
|
||
| if ntp_conf('/etc/chrony.conf').server.is_a? Array | ||
| describe ntp_conf('/etc/chrony.conf') do | ||
| its('server.join') { should match input('authoritative_timeserver') } | ||
| end | ||
| end | ||
| end |
There was a problem hiding this comment.
ntp_conf is being used to parse /etc/chrony.conf; use chrony_conf consistently for chrony (e.g., describe chrony_conf('/etc/chrony.conf') ...). This avoids mixing resource semantics and ensures correct parsing of Chrony directives.
| $ sudo grep banner-message-enable /etc/dconf/db/local.d/* | ||
|
|
||
| /org/gnome/login-screen/banner-message-enable | ||
|
|
||
| If the output is not "/org/gnome/login-screen/banner-message-enable", the line is commented out, or the line is missing, this is a finding.' |
There was a problem hiding this comment.
This control verifies prevention of user override; the check path should point to the locks directory (e.g., /etc/dconf/db/local.d/locks/*) rather than the non-lock keyfiles. Update the check text to grep the locks path.
| $ sudo grep banner-message-enable /etc/dconf/db/local.d/* | |
| /org/gnome/login-screen/banner-message-enable | |
| If the output is not "/org/gnome/login-screen/banner-message-enable", the line is commented out, or the line is missing, this is a finding.' | |
| $ sudo grep banner-message-enable /etc/dconf/db/local.d/locks/* | |
| banner-message-enable | |
| If the output is not "banner-message-enable", the line is commented out, or the line is missing, this is a finding.' |
| $ sudo grep banner-message-enable /etc/dconf/db/local.d/* | ||
|
|
||
| /org/gnome/login-screen/banner-message-enable | ||
|
|
||
| If the output is not "/org/gnome/login-screen/banner-message-enable", the line is commented out, or the line is missing, this is a finding.' |
There was a problem hiding this comment.
This control verifies prevention of user override; the check path should point to the locks directory (e.g., /etc/dconf/db/local.d/locks/*) rather than the non-lock keyfiles. Update the check text to grep the locks path.
| $ sudo grep banner-message-enable /etc/dconf/db/local.d/* | |
| /org/gnome/login-screen/banner-message-enable | |
| If the output is not "/org/gnome/login-screen/banner-message-enable", the line is commented out, or the line is missing, this is a finding.' | |
| $ sudo grep banner-message-enable /etc/dconf/db/local.d/locks/* | |
| banner-message-enable | |
| If the output is not "banner-message-enable", the line is commented out, or the line is missing, this is a finding.' |
| $ sudo grep banner-message-enable /etc/dconf/db/local.d/* | ||
|
|
||
| /org/gnome/login-screen/banner-message-enable | ||
|
|
||
| If the output is not "/org/gnome/login-screen/banner-message-enable", the line is commented out, or the line is missing, this is a finding.' |
There was a problem hiding this comment.
This control verifies prevention of user override; the check path should point to the locks directory (e.g., /etc/dconf/db/local.d/locks/*) rather than the non-lock keyfiles. Update the check text to grep the locks path.
| $ sudo grep banner-message-enable /etc/dconf/db/local.d/* | |
| /org/gnome/login-screen/banner-message-enable | |
| If the output is not "/org/gnome/login-screen/banner-message-enable", the line is commented out, or the line is missing, this is a finding.' | |
| $ sudo grep banner-message-enable /etc/dconf/db/local.d/locks/* | |
| banner-message-enable | |
| If the output is not "banner-message-enable", the line is commented out, or the line is missing, this is a finding.' |
| database_path = "#{@dconf_db_path}/#{database}" | ||
|
|
||
| # Get settings from database directory | ||
| settings_cmd = inspec.command("find #{database_path}.d/ -name '*.conf' -o -name '*.d' 2>/dev/null | xargs grep -h '^\\[\\|^[^#]' 2>/dev/null") |
There was a problem hiding this comment.
[nitpick] The find expression includes directories (-name '.d') and passes them to grep, which may cause errors (suppressed) and unnecessary work. Restrict the search to files only (e.g., find ... -type f -name '.conf') or read keyfiles via a glob to avoid grepping directories.
| settings_cmd = inspec.command("find #{database_path}.d/ -name '*.conf' -o -name '*.d' 2>/dev/null | xargs grep -h '^\\[\\|^[^#]' 2>/dev/null") | |
| settings_cmd = inspec.command("find #{database_path}.d/ -type f -name '*.conf' 2>/dev/null | xargs grep -h '^\\[\\|^[^#]' 2>/dev/null") |
| if settings_cmd.exit_status == 0 | ||
| parse_dconf_settings(settings_cmd.stdout, database) | ||
| end |
There was a problem hiding this comment.
[nitpick] The find expression includes directories (-name '.d') and passes them to grep, which may cause errors (suppressed) and unnecessary work. Restrict the search to files only (e.g., find ... -type f -name '.conf') or read keyfiles via a glob to avoid grepping directories.
- Fix WindowsGui and DarwinGui initializers to accept inspec parameter - Add missing require 'json' to gnome_settings.rb for JSON.parse usage - Fix SV-258031 logout assertion to match GNOME format ([''] not empty) - Fix SV-257945 to use chrony_conf consistently instead of mixing ntp_conf - Fix SV-258068 quote typo in path description - Correct SV-258068 timeout to 10 minutes per latest STIG v2.4 (verified via cyber.trackr.live API) - Add missing shell_session_timeout and gui_session_timeout inputs to inspec.yml - Enhance GUI session detection to support both X11 and Wayland environments - Document lessons learned from AI code review tools in best practices Verified against official RHEL 9 STIG v2.4: - Shell timeout: 10 minutes (600s) per SV-258068 - GUI timeout: 15 minutes (900s) per SV-258023 Authored by: Aaron Lippold<lippold@gmail.com>
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 29 out of 29 changed files in this pull request and generated 7 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| end | ||
|
|
||
| # Linux-specific GUI detection implementation | ||
| class LinuxGui < Gui |
There was a problem hiding this comment.
The LinuxGui class inherits from Gui but the parent class is not designed for inheritance. The parent class uses delegation pattern with platform implementations. This should not inherit from Gui but be a standalone implementation class.
| class LinuxGui < Gui | |
| class LinuxGui |
| # Windows GUI detection (placeholder for future) | ||
| class WindowsGui < Gui | ||
|
|
||
| def initialize(inspec_instance) | ||
| @inspec = inspec_instance | ||
| end |
There was a problem hiding this comment.
Same inheritance issue as LinuxGui. These implementation classes should not inherit from the main Gui resource class since they're used as delegates, not subclasses.
| require "inspec/utils/filter" | ||
| require "hashie/mash" | ||
| require "json" |
There was a problem hiding this comment.
[nitpick] The JSON require should be quoted consistently with the other requires. Use double quotes for consistency with the existing requires.
| # Integration with dconf resource for lock checking (proper Ruby/InSpec way) | ||
| def dconf_setting_locked?(schema, key) | ||
| # Delegate to dconf resource instead of manual file parsing | ||
| dconf_resource = Dconf.new(schema) | ||
| dconf_resource.has_locked?(key) | ||
| rescue | ||
| false # Graceful fallback if dconf not available | ||
| end |
There was a problem hiding this comment.
The Dconf.new call should use the proper InSpec resource instantiation pattern. Use inspec.dconf(schema) instead of Dconf.new(schema) to follow InSpec resource conventions.
| @@ -0,0 +1,414 @@ | |||
| require "inspec/utils/filter" | |||
| require "hashie/mash" | |||
There was a problem hiding this comment.
[nitpick] Missing require for 'json' which might be needed for value parsing in dconf files. Consider adding it for consistency and potential future use.
| require "hashie/mash" | |
| require "hashie/mash" | |
| require "json" |
| time_sources = [] | ||
| time_sources = [chrony_conf.server].flatten if chrony_conf.server | ||
| time_sources += chrony_conf.pool if chrony_conf.pool | ||
| time_sources += [chrony_conf.pool].flatten if chrony_conf.pool # PR #93 bug fix! |
There was a problem hiding this comment.
[nitpick] The comment '# PR #93 bug fix!' is not descriptive. Consider explaining what the bug was, e.g., '# Fix: Convert pool to array before concatenation to prevent type errors'
| time_sources += [chrony_conf.pool].flatten if chrony_conf.pool # PR #93 bug fix! | |
| time_sources += [chrony_conf.pool].flatten if chrony_conf.pool # Fix: Convert pool to array before concatenation to prevent type errors (was fixed in PR #93) |
| description: Maximum graphical session inactivity timeout (time in seconds). | ||
| type: Numeric | ||
| value: 900 | ||
|
|
||
| # SV-258068, SV-258077 - Shell session timeout | ||
| - name: shell_session_timeout | ||
| description: Maximum shell session inactivity timeout (time in seconds). |
There was a problem hiding this comment.
[nitpick] The description should be consistent with the shell_session_timeout format. Consider removing the trailing period for consistency with other input descriptions in the file.
| description: Maximum graphical session inactivity timeout (time in seconds). | |
| type: Numeric | |
| value: 900 | |
| # SV-258068, SV-258077 - Shell session timeout | |
| - name: shell_session_timeout | |
| description: Maximum shell session inactivity timeout (time in seconds). | |
| description: Maximum graphical session inactivity timeout (time in seconds) | |
| type: Numeric | |
| value: 900 | |
| # SV-258068, SV-258077 - Shell session timeout | |
| - name: shell_session_timeout | |
| description: Maximum shell session inactivity timeout (time in seconds) |
- Remove non-existent CCI-004923 from SV-257943 and SV-257944 - Remove corresponding invalid SC-45 NIST mappings - Verified against official DISA CCI List XML (max CCI: CCI-005169) - Aligns with DISA's CCI cleanup efforts in recent STIG revisions - Maintain only valid CCI-001891 mappings for V2R4 compliance Authored by: Aaron Lippold<lippold@gmail.com>
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 31 out of 31 changed files in this pull request and generated 3 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| class LinuxGui < Gui | ||
| attr_reader :cache # Useful for debugging detection results | ||
|
|
||
| def initialize(inspec_instance) |
There was a problem hiding this comment.
The LinuxGui class inherits from Gui but the parent initialize method expects an inspec_instance parameter. The child class initialize method should accept this parameter to maintain consistency.
| def initialize(inspec_instance) | |
| def initialize(inspec_instance = nil) |
| time_sources = [] | ||
| time_sources = [chrony_conf.server].flatten if chrony_conf.server | ||
| time_sources += chrony_conf.pool if chrony_conf.pool | ||
| time_sources += [chrony_conf.pool].flatten if chrony_conf.pool # PR #93 bug fix! |
There was a problem hiding this comment.
[nitpick] The inline comment about PR #93 is unnecessary in production code. The fix itself is correct (wrapping in array before flatten), but the comment should be removed for cleaner code.
| time_sources += [chrony_conf.pool].flatten if chrony_conf.pool # PR #93 bug fix! | |
| time_sources += [chrony_conf.pool].flatten if chrony_conf.pool |
| class WindowsGui < Gui | ||
|
|
||
| def initialize(inspec_instance) | ||
| @inspec = inspec_instance | ||
| end |
There was a problem hiding this comment.
Similar to LinuxGui, the WindowsGui and DarwinGui classes inherit from Gui but the parent class doesn't define an initialize method that accepts inspec_instance. This creates an inconsistency in the inheritance hierarchy.
Summary
This PR builds upon the excellent analysis and bug identification from PR #93 ("Dconf improvements") by implementing comprehensive GUI resources that achieve the same goals while following established InSpec patterns from the existing profile.
Acknowledgment
The original PR #93 by @Amndeep7 provided valuable insights into GUI control improvements, identifying critical bugs and validation gaps that needed addressing. This implementation preserves all their important fixes while organizing the solution using InSpec resource patterns established in the profile.
New InSpec Resources
Building on the GUI improvement needs identified in PR #93:
guiResourcebe_present,be_gnome,have_sessionsgnome_settingsResourcedconfResourceBug Fixes Preserved
All critical fixes from PR #93 are maintained:
Control Improvements
Transforms 25 STIG controls using the resource patterns established in this profile (similar to how
pam.rb,sudoers.rb, andlinux_updates.rbwork):only_ifpatterns for Not Applicable conditionsInput Enhancements
gui_session_timeoutandshell_session_timeoutwith appropriate default valuesTest Plan
This approach maintains all the important improvements from PR #93 while organizing them using the established resource patterns that make this profile excellent.
Authored by: Aaron Lippoldlippold@gmail.com