# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/patching/policy/policy_node' require 'contrast/api/decorators/trace_taint_range_tags' module Contrast module Agent module Assess module Policy # This class functions to translate our policy.json into an actionable # Ruby object, allowing for dynamic patching over hardcoded patching. class PolicyNode < Contrast::Agent::Patching::Policy::PolicyNode attr_accessor :tags, :type attr_reader :sources, :targets, :source_string, :target_string def initialize policy_hash = {} super(policy_hash) @source_string = policy_hash[JSON_SOURCE] @target_string = policy_hash[JSON_TARGET] @tags = Set.new(policy_hash[JSON_TAGS]) @sources = convert_policy_markers(source_string) @targets = convert_policy_markers(target_string) end def feature 'Assess' end def node_class 'Node' end def node_type :TYPE_METHOD end def target_string= value @target_string = value @targets = convert_policy_markers(value) end # Sometimes we need to tie information to an event. We'll add a # properties section to the patch node, which can pass them along to # the pre-dtm event def add_property name, value return unless name && value @properties ||= {} @properties[name] = value end def get_property name return unless @properties @properties[name] end # Don't let nodes be created that will be missing things we need # later on. Really, if they don't have these things, they couldn't have # done their jobs anyway. def validate super validate_tags end # TeamServer is picky. The tags here match to ENUMs there. If there # isn't a matching ENUM in TS land, the database gets got. We really # don't want to get them, so we're going to prevent the node from being # made. def validate_tags return unless tags tags.each do |tag| next if Contrast::Api::Decorators::TraceTaintRangeTags::VALID_TAGS.include?(tag) || Contrast::Api::Decorators::TraceTaintRangeTags::VALID_SOURCE_TAGS.include?(tag) raise(ArgumentError, "#{ node_class } #{ id } had an invalid tag. #{ tag } is not a known value.") end end ALL_TYPE = 'A' TO_MARKER = '2' # Convert our action, built from our source and target, into # the TS appropriate action. That's a single source to single # target marker (A,O,P,R) # # Creation (source nodes) don't have sources (they are the source) # Trigger (trigger nodes) don't have targets (they are the target) # Everything else (propagation nodes) are Source2Target def build_action @event_action ||= case node_class when Contrast::Agent::Assess::Policy::SourceNode::SOURCE :CREATION when Contrast::Agent::Assess::Policy::TriggerNode::TRIGGER :TRIGGER else if source_string.nil? :CREATION elsif target_string.nil? :TRIGGER else # TeamServer can't handle the multi-source or multi-target values. Give it some help # by changing them to 'A' source = all_type?(source_string) ? ALL_TYPE : source_string target = all_type?(target_string) ? ALL_TYPE : target_string str = source[0] + TO_MARKER + target[0] str.to_sym end end @event_action end # The keys used to read from policy.json to create the individual # policy nodes. These are common across node types JSON_SOURCE = 'source' JSON_TARGET = 'target' JSON_TAGS = 'tags' JSON_DATAFLOW = 'dataflow' private # Given a policy string in the format A,B,C, populate the given array # 1) Split on ',' # 2) If 'O' or 'R', add the array, else it's P and needs to be # converted. P type will either be P# where # is the index # of the parameter. Drop the P and store the # as an int. # # @param markers [String] the String from the policy to parse # @return [Array] the array generated by converting the marker string def convert_policy_markers markers return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless markers return Contrast::Utils::ObjectShare::EMPTY_ARRAY if markers.empty? converted = [] markers.split(Contrast::Utils::ObjectShare::COMMA).each do |t| converted << case t when Contrast::Utils::ObjectShare::OBJECT_KEY, Contrast::Utils::ObjectShare::RETURN_KEY t else Integer(t[1..-1]) end end converted end def all_type? marker marker.include?(Contrast::Utils::ObjectShare::COMMA) end end end end end end