Is there a reason why audit rules files wouldn't u...
# compliance
r
Is there a reason why audit rules files wouldn't update for example if privileged commands changed?
h
No, the list of privileged commands should be enumerated at runtime. The resource that manages them is
Sce_audit_rule[SCE audit privileged commands]
. If you look at the puppetserver debug logs and agent run debug logs it may give more info as to what's going on
r
I'm still investigating as I found some of my code in the way. But one file that was an SCE file, still had a file mentioned in it that didn't exist anymore. Surprised me that Puppet wasn't trying to get rid of it.
h
Ah, ok. It will catch new privileged commands and start auditing them, however we intentionally don't remove auditing rules from the system. The reason for this is that some customers may have auditing rules on systems that are not managed by SCE, such as auditing rules baked into base images, that they expect to always be there. Because auditing is a critical service, we opted for an additive-only approach in SCE. I will concede that this creates a bit more work for sysads, but we thought it was a worthy tradeoff.
r
I wouldn't expect a customer to be editing a file with sce_ in the name though
They should be punished for that 😄
h
We actually don't look at the rules on a per-file basis on the backend, we look at the rules themselves regardless of what files they belong too. We decided to go this direction so we wouldn't double up on rules or contradict other rules.
it's a little weird and unintuitive now that I'm explaining it lol
r
Interesting... I had to delete some of my old files I had created because of doubled up rules
h
they must have slipped through our comparison. For posterity, let me get the comparison logic for you
👍 1
Copy code
# This class provides a set of heuristics for determining if a rule is
        # roughly equivalent to another rule. This is used by the `sce_audit_rule`
        # provider class method `#find_provider_for_resource` to determine if a
        # rule on the system is already managed by a resource.
        # Currently, this is robust enough to handle the rules created when
        # specifying `$for_all_privileged_commands = true`, but may not be
        # robust enough to handle all cases.
        class RuleHeuristics
          SIMILARITY_ATTRS = %i[key dir path].freeze

          def initialize(rule)
            @rule = Rules::Parser.parse_rule(rule)
          end

          # @param rule [String, Hash, PuppetX::Puppetlabs::Sce::AuditdHelper::Rules::Parser::Rule]
          #  The rule to compare against. If a string or hash is given, it will be parsed
          #  into a rule object. If a rule object is given, it will be used as-is.
          def similar?(rule)
            other_h = RuleHeuristics.new(rule)
            SIMILARITY_ATTRS.all? { |attr| attr_eq?(attr, other_h) }
          end

          def key
            @key ||= find_flag_or_field_value('key')
          end

          def dir
            @dir ||= find_field_value('dir')
          end

          def path
            @path ||= find_field_value('path')
          end

          private

          def attr_eq?(attribute, other_h)
            send(attribute) == other_h.send(attribute)
          end

          def find_field_value(field_name)
            find_flag_value do |f|
              f.option_name == 'field' && f.value_parts[:field_name] == field_name
            end
          end

          # @yield [PuppetX::Puppetlabs::Sce::AuditdHelper::Rules::Parser::Flag] A flag object
          def find_flag_value
            return unless block_given?

            value(@rule.flags.find { |f| yield(f) })
          end

          # @param flag_name [String] The name of the flag to find
          # @param field_name [String] The name of the field to find, if different from the flag name
          # @return [Any] The value of the flag or field, if found
          def find_flag_or_field_value(flag_name, field_name = nil)
            field_name ||= flag_name
            found = @rule.flags.find do |f|
              f.option_name == flag_name || (f.option_name == 'field' && f.value_parts[:field_name] == field_name)
            end
            value(found)
          end

          def value(flag_obj)
            flag_obj&.value
          end
        end
Basically, if the path, dir, and key of the audit rule are the same, they are considered equal
r
interesting
h
Another catch is that if the audit rules are configured to be immutable, as CIS directs by default, we actually can no longer manage the rules unless the system is rebooted. If this is the case, you will see a warning log message in either puppetserver.log or an agent debug run that says
'Auditd is configured to be immutable, unable to load rules without a machine reboot.'
r
Ah ok
h
We will still edit the files, but the rules won't go live until reboot
r
supposedly we're compliant with that... but I've still been able to run augenrules --load to make them go live
h
we ran into issues with it, so we changed up the logic in our code to include that warning
it may be an artifact of how different operating systems handle loading the rules, though