Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
inspec / lib / resources / groups.rb
Size: Mime:
# encoding: utf-8

require 'utils/filter'

module Inspec::Resources
  # This file contains two resources, the `group` and `groups` resource.
  # The `group` resource is optimized for requests that verify specific groups
  # that you know upfront for testing. If you need to query all groups or search
  # specific groups with certain properties, use the `groups` resource.
  module GroupManagementSelector
    # select group provider based on the operating system
    # returns nil, if no group manager was found for the operating system
    def select_group_manager(os)
      @group_provider = if os.darwin?
                          DarwinGroup.new(inspec)
                        elsif os.unix?
                          UnixGroup.new(inspec)
                        elsif os.windows?
                          WindowsGroup.new(inspec)
                        end
    end
  end

  class Groups < Inspec.resource(1)
    include GroupManagementSelector

    name 'groups'
    supports platform: 'unix'
    supports platform: 'windows'
    desc 'Use the group InSpec audit resource to test groups on the system. Groups can be filtered.'
    example "
      describe groups.where { name == 'root'} do
        its('names') { should eq ['root'] }
        its('gids') { should eq [0] }
      end

      describe groups.where { name == 'Administrators'} do
        its('names') { should eq ['Administrators'] }
        its('gids') { should eq ['S-1-5-32-544'] }
      end
    "

    def initialize
      # select group manager
      @group_provider = select_group_manager(inspec.os)
      return skip_resource 'The `groups` resource is not supported on your OS yet.' if @group_provider.nil?
    end

    filter = FilterTable.create
    filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
    filter.register_column(:names,     field: 'name')
          .register_column(:gids,      field: 'gid')
          .register_column(:domains,   field: 'domain')
          .register_column(:members,   field: 'members')
    filter.install_filter_methods_on_resource(self, :collect_group_details)

    def to_s
      'Groups'
    end

    private

    # collects information about every group
    def collect_group_details
      return @groups_cache ||= @group_provider.groups unless @group_provider.nil?
      []
    end
  end

  # Usage:
  # describe group('root') do
  #   it { should exist }
  #   its('gid') { should eq 0 }
  # end
  #
  # deprecated has matcher
  # describe group('root') do
  #  it { should have_gid 0 }
  # end
  class Group < Inspec.resource(1)
    include GroupManagementSelector

    name 'group'
    supports platform: 'unix'
    supports platform: 'windows'
    desc 'Use the group InSpec audit resource to test groups on the system.'
    example "
      describe group('root') do
        it { should exist }
        its('gid') { should eq 0 }
      end

      describe group('Administrators') do
        its('members') { should include 'Administrator' }
      end
    "

    def initialize(groupname)
      @group = groupname

      # select group manager
      @group_provider = select_group_manager(inspec.os)
      return skip_resource 'The `group` resource is not supported on your OS yet.' if @group_provider.nil?
    end

    # verifies if a group exists
    def exists?
      !group_info.entries.empty?
    end

    def gid
      flatten_entry(group_info, 'gid')
    end

    # implements rspec has matcher, to be compatible with serverspec
    def has_gid?(compare_gid)
      gid == compare_gid
    end

    def members
      return unless inspec.os.windows?
      flatten_entry(group_info, 'members')
    end

    def local
      # at this point the implementation only returns local groups
      true
    end

    def to_s
      "Group #{@group}"
    end

    private

    def flatten_entry(group_info, prop)
      entries = group_info.entries
      if entries.empty?
        nil
      elsif entries.size == 1
        entries.first.send(prop)
      else
        raise 'found more than one group with the same name, please use `groups` resource'
      end
    end

    def group_info
      # we need a local copy for the block
      group = @group.dup
      @groups_cache ||= inspec.groups.where { name == group }
    end
  end

  class GroupInfo
    attr_reader :inspec
    def initialize(inspec)
      @inspec = inspec
    end

    def groups
      raise 'group provider must implement the `groups` method'
    end
  end

  # implements generic unix groups via /etc/group
  class UnixGroup < GroupInfo
    def groups
      inspec.etc_group.entries
    end
  end

  # OSX uses opendirectory for groups, so `/etc/group` may not be fully accurate
  # This uses `dscacheutil` to get the group info instead of `etc_group`
  class DarwinGroup < GroupInfo
    def groups
      group_info = inspec.command('dscacheutil -q group').stdout.split("\n\n")

      groups = []
      regex = /^([^:]*?)\s*:\s(.*?)\s*$/
      group_info.each do |data|
        groups << inspec.parse_config(data, assignment_regex: regex).params
      end

      # Convert the `dscacheutil` groups to match `inspec.etc_group.entries`
      groups.each { |g| g['gid'] = g['gid'].to_i }
      groups.each do |g|
        next if g['users'].nil?
        g['members'] = g.delete('users')
        g['members'].tr!(' ', ',')
      end
    end
  end

  class WindowsGroup < GroupInfo
    # returns all local groups
    def groups
      script = <<-EOH
        Function ConvertTo-SID { Param([byte[]]$BinarySID)
          (New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value
        }
        $Computername = $Env:Computername
        $adsi = [ADSI]"WinNT://$Computername"
        $groups = $adsi.Children | where {$_.SchemaClassName -eq 'group'} | ForEach {
          $name = $_.Name[0]
          $sid = ConvertTo-SID -BinarySID $_.ObjectSID[0]
          $group =[ADSI]$_.Path
          $members = $_.Members() | Foreach-Object { $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null) }
          # An empty collection of these objects isn't properly converted to an empty array by ConvertTo-Json
          if(-not [bool]$members) {
            $members = @()
          }
          new-object psobject -property @{name = $group.Name[0]; gid = $sid; domain = $Computername; members = $members}
        }
        $groups | ConvertTo-Json -Depth 3
      EOH
      cmd = inspec.powershell(script)
      # cannot rely on exit code for now, successful command returns exit code 1
      # return nil if cmd.exit_status != 0, try to parse json
      begin
        groups = JSON.parse(cmd.stdout)
      rescue JSON::ParserError => _e
        return []
      end

      # ensure we have an array of groups
      groups = [groups] if !groups.is_a?(Array)
      groups
    end
  end
end