From 617cedff1ec0ca1cfbce81cbb307486dc661f9c3 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Wed, 6 Dec 2017 16:31:35 -0600 Subject: [PATCH 01/14] Port eso-client gem to nexpose-client --- lib/eso.rb | 24 ++ lib/eso/conductor.rb | 227 ++++++++++++++++++ lib/eso/configuration/configuration.rb | 104 ++++++++ .../configuration/configuration_manager.rb | 136 +++++++++++ lib/eso/filter.rb | 137 +++++++++++ lib/eso/integration_option.rb | 88 +++++++ lib/eso/integration_options_manager.rb | 153 ++++++++++++ lib/eso/nexpose.rb | 209 ++++++++++++++++ lib/eso/service.rb | 82 +++++++ lib/eso/step.rb | 166 +++++++++++++ lib/eso/step_configuration.rb | 73 ++++++ lib/eso/version.rb | 3 + lib/eso/workflow.rb | 151 ++++++++++++ 13 files changed, 1553 insertions(+) create mode 100644 lib/eso.rb create mode 100644 lib/eso/conductor.rb create mode 100644 lib/eso/configuration/configuration.rb create mode 100644 lib/eso/configuration/configuration_manager.rb create mode 100644 lib/eso/filter.rb create mode 100644 lib/eso/integration_option.rb create mode 100644 lib/eso/integration_options_manager.rb create mode 100644 lib/eso/nexpose.rb create mode 100644 lib/eso/service.rb create mode 100644 lib/eso/step.rb create mode 100644 lib/eso/step_configuration.rb create mode 100644 lib/eso/version.rb create mode 100644 lib/eso/workflow.rb diff --git a/lib/eso.rb b/lib/eso.rb new file mode 100644 index 00000000..dd1933e0 --- /dev/null +++ b/lib/eso.rb @@ -0,0 +1,24 @@ +require 'date' +require 'json' +require 'net/http' +require 'net/https' +require 'nexpose' +require 'time' +require 'uri' + +require 'eso/conductor' +require 'eso/configuration/configuration' +require 'eso/configuration/configuration_manager' +require 'eso/filter' +require 'eso/integration_option' +require 'eso/integration_options_manager' +require 'eso/nexpose' +require 'eso/service' +require 'eso/step' +require 'eso/step_configuration' +require 'eso/version' +require 'eso/workflow' + +module Eso + +end diff --git a/lib/eso/conductor.rb b/lib/eso/conductor.rb new file mode 100644 index 00000000..694f9c4d --- /dev/null +++ b/lib/eso/conductor.rb @@ -0,0 +1,227 @@ +require 'eso/service' + +module Eso + class Conductor < Service + + # Constructor for Conductor. + # + # @param [String] host Hostname or IP address where this conductor resides. + # @param [Integer] port The TCP port to connect to this conductor on. + # @param [Nexpose::Connection] nsc A logged-in Nexpose::Connection object with a valid session used to authenticate. + # @return [Eso::Conductor] The newly created conductor object + # + def initialize(host:, port: 3780, nsc:) + super(host: host, port: port, nsc: nsc) + @url = "https://#{@host}:#{@port}/eso/conductor-service/api/" + end + + # Return all of the workflows that currently exist on this conductor. + # + # @return [Array] An array containing all of the current workflows on the conductor in Eso::Workflow object format. Returns an empty array if no workflows are present. + # + def workflows + rv = [] + json_data = get(url: "#{@url}workflows/") + json_data.each do |wf| + workflow = Workflow.new(id: wf[:id], name: wf[:name]) + steps = wf[:steps] + steps.each do |step| + workflow_step = Step.new(uuid: step[:uuid], + service_name: step[:serviceName], + workflow: workflow, + type_name: step[:stepConfiguration][:typeName], + previous_type_name: step[:stepConfiguration][:previousTypeName], + configuration_params: step[:stepConfiguration][:configurationParams]) + workflow.steps << workflow_step + end + rv << workflow + end + rv + end + + # Return the workflow histories with only the state histories for the given date range. + # + # @param [Fixnum] starttime The time in milliseconds since epoch for which you want the workflow histories + # @param [Fixnum] endtime The time in milliseconds since epoch for which you want the workflow histories + # @return [Array[Eso::Workflow::History]] An array containing all of the workflow histories from the + # Conductor, which has StateHistory objects containing startTime's within the specified time range. Only the + # StateHistories within that range are returned in the WorkflowHistory object. Returns an empty array if none are present. + def workflow_histories(starttime, endtime) + histories = [] + json_data = get(url: "#{@url}workflows/history/#{starttime}/#{endtime}") + json_data.each do |wf| + # Initialize WorkflowHistory elements + workflow_steps = [] + state_histories = [] + + # Create a new WorkflowHistory with the details we already know + workflow_history = Eso::Workflow::History.new(id: wf[:id], + name: wf[:name], + timeCreated: wf[:timeCreated], + state: wf[:state], + message: wf[:message], + steps: workflow_steps, + history: state_histories + ) + + # Parse the steps out of the response to be returned with the WorkflowHistory + wf[:steps].each do |step| + workflow_steps << Step.new(uuid: step[:uuid], + service_name: step[:serviceName], + workflow: workflow_history, + type_name: step[:stepConfiguration][:typeName], + previous_type_name: step[:stepConfiguration][:previousTypeName], + configuration_params: step[:stepConfiguration][:configurationParams]) + end + workflow_history.steps = workflow_steps + + # Parse the histories out of the response, to be returned with the WorkflowHistory. For some reason. + # this failed with named parameters. For now I returned it to positional. + wf[:history].each do |history| + state_histories << Eso::Workflow::StateHistory.new(history[:message], + history[:state], + history[:startTime]) + end + workflow_history.state_histories = state_histories + + # Add the Workflow History we just built to the list to be returned. + histories << workflow_history + end + histories + end + + # Get the state of the specified workflow. + # + # @param [String] workflow_id The ID of the workflow to retrieve the state of. + # @return [String] The current state of the workflow. + # + def workflow_state(workflow_id:) + get(url: "#{@url}workflows/#{workflow_id}/state") + end + + # Get the count of items in a state of the specified workflow. + # + # @param [Eso::Workflow::State] state The state of the workflows to retrieve the count of. + # @return [Integer] The number of workflows in the requested state. + # + def workflows_state_count(state) + get(url: "#{@url}workflows/count/#{state}") + end + + # Retrieve the states for all of the workflows created on the conductor. + # + # @return [Hash] A hash containing the states of all existing workflows, keyed by workflow ID. + # + def workflow_states + wfs = workflows + states = {} + wfs.each { |wf| states[wf.id] = workflow_state(workflow_id: wf.id) } + states + end + + # Create a new workflow on this conductor. + # + # @param [String] name The user-facing name the workflow will be created with. + # @param [Array] steps An array containing each of the steps that the workflow will be created with, in Eso::Step format. + # @return [Eso::Workflow] The newly created workflow object + # + def create_workflow(name:, steps:) + workflow = Workflow.new(name: name, steps: steps) + + resp = post(url: "#{@url}workflows/", payload: workflow.to_json) + created_workflow = Workflow.load(self, resp[:id]) + + created_workflow + end + + # Update an existing workflow on the conductor to have the configuration of the workflow object passed into this method. + # + # @param [Eso::Workflow] updated_workflow A workflow object that has already had all required changes made to it. This workflow must have an ID set. + # + def update_workflow(updated_workflow:) + payload = updated_workflow.to_json + put(url: "#{@url}workflows/#{updated_workflow.id}", payload: payload) + end + + # Delete an existing workflow from the conductor. + # + # @param [String] workflow_id The ID of the workflow to be deleted. + # + def delete_workflow(workflow_id:) + delete(url: "#{@url}workflows/#{workflow_id}") + end + + # Delete all current workflows on the conductor. + # + def delete_all_workflows + wfs = workflows + wfs.each { |wf| delete_workflow(workflow_id: wf.id) } + end + + # Start the specified workflow. + # + # @param [String] workflow_id The ID of the workflow to be started. + # + def start_workflow(workflow_id:) + post(url: "#{@url}workflows/#{workflow_id}/state") + end + + # Stop the specified workflow. + # + # @param [String] workflow_id The ID of the workflow to be stopped. + # + def stop_workflow(workflow_id:) + delete(url: "#{@url}workflows/#{workflow_id}/state") + end + + # Start all workflows that exist on the conductor. + # + # @return [Hash] A hash containing the states of all existing workflows, keyed by workflow ID. + # + def start_all_workflows + wf_states = workflow_states + + wf_states.each { |wf_id, state| start_workflow(workflow_id: wf_id) if state[:workflow_state] == "STOPPED" } + workflow_states + end + + # Stop all workflows that exist on the conductor. + # + # @return [Hash] A hash containing the states of all existing workflows, keyed by workflow ID. + # + def stop_all_workflows + wf_states = workflow_states + + wf_states.each { |wf_id, state| stop_workflow(workflow_id: wf_id) if state[:workflow_state] == "RUNNING" } + workflow_states + end + + # Returns the translated value of the specified key for a step type (defined in Eso::StepNames). + # The translated value will be based on the language settings the user has configured. + # + # @param [String] step_type The step type to query metadata for. Valid values defined in Eso::StepNames + # @param [String] key The key value in the metadata that maps to the desired label. + # @return [String] The translated value of the key. + # + def get_translation_label(step_type, key) + json_data = get(url: "#{@url}services/nexpose/metadata/#{step_type}") + + target_hash = json_data[:labels].values.find { |label_hash| label_hash.has_key?(key) } + target_hash[key] if target_hash + end + + # Returns the metadata key for a specified translated string. + # The translated value needs to be in language that the user has configured. + # + # @param [String] step_type The step type to query metadata for. Valid values defined in Eso::StepNames + # @param [String] label The translated value of which you are requesting the key for. + # @return [String] The metadata key corresponding to this label. + # + def get_translation_key(step_type, label) + json_data = get(url: "#{@url}services/nexpose/metadata/#{step_type}") + + target_hash = json_data[:labels].values.find { |label_hash| label_hash.values.include?(label) } + target_hash.key(label).to_s if target_hash + end + end +end diff --git a/lib/eso/configuration/configuration.rb b/lib/eso/configuration/configuration.rb new file mode 100644 index 00000000..ccdd82f3 --- /dev/null +++ b/lib/eso/configuration/configuration.rb @@ -0,0 +1,104 @@ +module Eso + # This class represents the Configuration that is sent to the server for new + # style Discovery Connections. + class Configuration + attr_accessor :service_name, :config_name, :config_id, :properties + + def initialize(service_name:, config_name:, properties:[], config_id:) + @service_name = service_name + @config_name = config_name + @properties = properties + @config_id = config_id + end + + # Convert the Configuration to a JSON string for sending to Nexpose + # + # @return [String] A JSON String representation of the Configuration + def to_json + self.to_hash.to_json + end + + # Convert the Configuration to a Hash + # + # @return [Hash] A Hash representation of the Configuration + def to_hash + hash = {:configId => @config_id, + :serviceName => @service_name, + :configName => @config_name, + :configurationAttributes => {:valueClass => 'Object', + :objectType => 'service_configuration', + :properties => []}} + properties.each {|prop| hash[:configurationAttributes][:properties] << prop.to_hash} + end + + # Retrieve a Configuration attribute property value given the name of the property + # + # @param [String] name The name of the property to retrieve + # @return [String] The value of the property + def property(name) + properties.find{|attr| attr.property == name}.value + end + + # Update a Configuration attribute property value given the name of the property + # + # @param [String] name The name of the property to update + # @param [String] value The value of the property to update + # @return [String] The value of the property + def update_property(name, value) + properties.find{|attr| attr.property == name}.value = value + end + + # Load a Configuration object from a Hash + # + # @param [Hash] hash The Hash containing the Configuration object + # @return [Configuration] The Configuration object which was in the Hash + def self.load(hash) + configuration = self.new(service_name: hash[:serviceName], + config_name: hash[:configName], + config_id: hash[:configID]) + hash[:configurationAttributes][:properties].each do |prop| + configuration.properties << ConfigurationAttribute.load(prop) + end + configuration + end + end + + # The ConfigurationAttribute is a property of the Configuration + class ConfigurationAttribute + attr_accessor :property, :value_class, :value + + def initialize(property, value_class, value) + @property = property + @value_class = value_class + @value = value + end + + # Convert the ConfigurationAttribute to a JSON string for sending to Nexpose + # + # @return [String] A JSON String representation of the ConfigurationAttribute + def to_json + self.to_hash.to_json + end + + # Convert the ConfigurationAttribute to a Hash + # + # @return [Hash] A Hash representation of the ConfigurationAttribute + def to_hash + prop = @property.to_sym + hash = {prop => {}} + hash[prop]['valueClass'] = @value_class + hash[prop]['value'] = @value + end + + # Load a ConfigurationAttribute object from an Array + # + # @param [Array] array The Array containing the ConfigurationAttribute object + # @return [ConfigurationAttribute] The ConfigurationAttribute object which was in the Array + def self.load(array) + property = array.first + value_class = array.last['valueClass'] + value = array.last['value'] + self.new(property, value_class, value) + end + end +end diff --git a/lib/eso/configuration/configuration_manager.rb b/lib/eso/configuration/configuration_manager.rb new file mode 100644 index 00000000..190cf36c --- /dev/null +++ b/lib/eso/configuration/configuration_manager.rb @@ -0,0 +1,136 @@ +module Eso +## +# This class represents a configuration manager service, which manages a number of configurations (ie a hostname, +# port, username, and password) used to connect to services, and the services they connect to (ie, ePO, dxl, palo-alto). +# + class ConfigurationManager + attr_accessor :url, :nexpose_console + + ## + # Constructor for ConfigurationManager. + # + # @param [Nexpose::Connection] nsc A logged-in Nexpose::Connection object with a valid session used to authenticate. + # @return [Eso::ConfigurationManager] The newly created configurationManager object + # + def initialize(nsc) + @nexpose_console = nsc + @url = "https://#{nsc.host}:#{nsc.port}/eso/configuration-manager/api/" + end + + ## + # Return all of the services that are currently supported by this configuration manager. + # + # @return [Array] An array containing all of services in the configuration manager in String object form. + # Returns an empty array if no services have been configured. + # + def services + json_data = ::Nexpose::AJAX.get(@nexpose_console, "#{@url}service/", ::Nexpose::AJAX::CONTENT_TYPE::JSON) + JSON.parse(json_data) + end + + ## + # Return all of the configurations of a particular service type. + # + # @param [String] service_name The name of a service to find configurations of. + # @return [Array] An array containing all the configurations of the given service type. + # + def service_configurations(service_name) + json_data = ::Nexpose::AJAX.get(@nexpose_console, + "#{@url}service/configuration/#{service_name}/", + ::Nexpose::AJAX::CONTENT_TYPE::JSON) + JSON.parse(json_data, :symbolize_names => true) + end + + ## + # Return the configuration of a particular service type with a particular name. + # + # @param [String] service_name The name of a service to find configurations of. + # @param [String] config_name The name of the Configuration. + # @return [Eso::Configuration] A Configuration object which matches the service name and config name requested. + def configuration_by_name(service_name, config_name) + service_configs_by_type = service_configurations(service_name) + config_hash = service_configs_by_type.find { |config| config[:configName] == config_name } + Eso::Configuration.load(config_hash) + end + + def configuration_type(service_name:) + json_data = ::Nexpose::AJAX.get(@nexpose_console, + "#{@url}service/configurationType/#{service_name.downcase}", + ::Nexpose::AJAX::CONTENT_TYPE::JSON) + JSON.parse(json_data) + end + + ## + # Get a configuration by id. Runs a GET call against the eso/configuration-manager/api/service/configuration/CONFIGURATION_ID endpoint + # @param [String] configuration_id The id of the configuration to get + # return [JSON] A json object representing a configuration + # TODO : Update to use an Eso::Configuration + def get_configuration(configuration_id) + json_data = ::Nexpose::AJAX.get(@nexpose_console, "#{@url}/service/configuration/id/#{configuration_id}", ::Nexpose::AJAX::CONTENT_TYPE::JSON) + JSON.parse(json_data, :symbolize_names => true) + end + + ## + # Create a new configuration. + # + # @param [String] payload The JSON representation of a configuration. + # @return [Integer] The configID (>= 1) of the newly created configuration. Raises error on failure. + # TODO: Update to use an Eso::Configuration + def post_service_configuration(payload) + # TODO retry if the post fails on timeout + response_body = ::Nexpose::AJAX.post(@nexpose_console, "#{@url}service/configuration", payload, ::Nexpose::AJAX::CONTENT_TYPE::JSON) + config_id = Integer(JSON.parse(response_body)['data']) + raise Exception.new("API returned invalid configID (#{config_id}) while attempting to create configuration.") unless config_id >= 1 + config_id + end + + ## + # Test a configuration. + # + # @param [String] payload The JSON representation of a configuration. + # @return [String] The response from the call or an APIError + # TODO: Update to use an Eso::Configuration + def test_service_configuration(payload) + ::Nexpose::AJAX.post(@nexpose_console, + "#{@url}service/configuration/test", + payload, + ::Nexpose::AJAX::CONTENT_TYPE::JSON) + end + + ## + # Delete a configuration. Runs a DELETE call against the eso/configuration-manager/api/service/configuration/CONFIGURATION_ID endpoint + # + # @param [String] configuration_id The id of the configuration to delete + # return [Boolean] Return true if the api reports a successful delete. Raises an error on failure. + def delete(configuration_id) + response_body = ::Nexpose::AJAX.delete(@nexpose_console, "#{@url}service/configuration/#{configuration_id}") + raise Exception.new("Failed to delete configuration with ID: #{configuration_id}") unless 'success' == response_body + true + end + + ## + # Preview assets for a configuration. Calls a POST to the eso/configuration-manager/api/service/configuration/preview endpoint + # + # @param configuration The configuration to preview + # return [Array] previewed assets + # TODO: Update to use an Eso::Configuration + def preview_assets(configuration) + response_body = ::Nexpose::AJAX.post(@nexpose_console, + "#{@url}service/configuration/preview", + configuration, + ::Nexpose::AJAX::CONTENT_TYPE::JSON) + @preview_assets = JSON.parse(response_body)["previewAssets"] + end + end + + module ConfigManagerMessages + module TestConfig + CONNECTION_SUCCESSFUL = 'The connection to the external service was successful.' + + # Applies to invalid user, password, wrong protocol, can't reach server, bad base or search query + CONNECTION_FAILED = 'The connection to the external service failed.' + + INVALID_FIELDS = 'The configuration had invalid fields.' + end + end +end diff --git a/lib/eso/filter.rb b/lib/eso/filter.rb new file mode 100644 index 00000000..ec992583 --- /dev/null +++ b/lib/eso/filter.rb @@ -0,0 +1,137 @@ +module Eso + class Filter + # These are defined in Eso::Filters which reside in the respective service they are related to. + attr_accessor :type + + # These are the individual filter items + attr_accessor :filter_items + + # Constructor for Filter. + # + # @param [String] type The type of filter this is. They are based on the service this filter exists in. These are defined in Eso::Filters which reside in the respective service they are related to. + # @param [Array] items Array of filters of this type + # @return [Eso::Filter] The newly created filter object + # + def initialize(type:, items: []) + @type = type + @filter_items = items + end + + # Append a filter_item later + def <<(filter_item) + @filter_items << filter_item + end + + def to_json + self.to_hash.to_json + end + + def to_hash + hash = {} + hash[@type.to_sym] = { + valueClass: 'Array', + items: @filter_items.map{|item| item.to_hash} + } + hash + end + alias_method :to_h, :to_hash + + class FilterItem + attr_accessor :type + # Examples are "OR", "IN", "CONTAINS". These should probably be constantized somewhere. + attr_accessor :operator + + # Array containing the values to filter on + attr_accessor :operands + + def initialize(type:, operator:, operands:) + @type = "#{type}_ITEM" + @operator = operator + process_operands(operands) + end + + def process_operands(operands) + @operands = + if ["IS_EMPTY", "IS_NOT_EMPTY"].include? @operator + nil + elsif @type == "#{Eso::Filters::IP_ADDRESS}_ITEM" || + @type == "#{Eso::Filters::IP_RANGE}_ITEM" || + @type == "#{Eso::Filters::OPEN_PORT}_ITEM" || + @type == "#{Eso::Filters::RISK_SCORE}_ITEM" || + @type == "#{Eso::Filters::CVSS_SCORE}_ITEM" + operands.first.split('-') + else + operands + end + + if @operands == nil + return + end + @operands.map! do |value| + # This regex is used to determine if the string is actually a float. + # http://stackoverflow.com/questions/1034418/determine-if-a-string-is-a-valid-float-value + if value =~ /^\s*[+-]?((\d+_?)*\d+(\.(\d+_?)*\d+)?|\.(\d+_?)*\d+)(\s*|([eE][+-]?(\d+_?)*\d+)\s*)$/ + if (@type == "#{Eso::Filters::OPEN_PORT}_ITEM") + value.to_i + else + value.to_f + end + # If it's not a float, let's see if it's an integer. + elsif value.to_i.to_s == value + value.to_i + # Guess not, so lets keep the original value. + else + value + end + end + end + + def to_hash + hash = { + valueClass: "Object", + objectType: @type, + properties: { + operator: { + valueClass: "String", + value: @operator + } + } + } + # Currently there are no standards that say how many operands a filter can have + operand_hash = {} + operand_counter = 1 + unless @operands.nil? + @operands.each do |operand| + label = "operand#{operand_counter}".to_sym + + # A correct value class is required because Jackson expects it. + # A Jackson processor for Ruby would probably make this much nicer + # Also, defaulting to Number is probably a bad idea, but based on current possible values in ESO this works. + case operand.class.to_s + + when "String" + value_class = "String" + when "Array" + value_class = "Array" + when "Fixnum" + value_class = "Integer" + else + value_class = "Number" + end + + operand_hash[label] = { + valueClass: value_class, + value: operand + } + operand_counter += 1 + end + + hash[:properties].merge! operand_hash + end + + hash + end + end + end +end + diff --git a/lib/eso/integration_option.rb b/lib/eso/integration_option.rb new file mode 100644 index 00000000..feaa73ed --- /dev/null +++ b/lib/eso/integration_option.rb @@ -0,0 +1,88 @@ +module Eso + + module IntegrationOptionNames + IMPORT_AD_ASSETS = 'import_ad_assets' + IMPORT_EPO_ASSETS = 'import_epo_assets' + SYNC_AZURE_ASSETS = 'sync_azure_assets' + SYNC_AZURE_ASSETS_WITH_TAGS = 'sync_azure_assets_with_tags' + end + + # IntegrationOptionTypes is a way to categorize what various Integration Options do. + module IntegrationOptionTypes + # The IMPORT_TO_SITE Array tracks Integration Options which load Assets into a Site. + IMPORT_TO_SITE = [ + IntegrationOptionNames::IMPORT_AD_ASSETS, + IntegrationOptionNames::IMPORT_EPO_ASSETS, + IntegrationOptionNames::SYNC_AZURE_ASSETS, + IntegrationOptionNames::SYNC_AZURE_ASSETS_WITH_TAGS + ] + end + + class IntegrationOption + attr_accessor :name + attr_accessor :steps + attr_accessor :id + + def initialize(id: nil, name:, steps: []) + @id = id + @name = name + @steps = steps + end + + def site_id=(site_id) + # As of now, the site is always in the last Step of the IntegrationOption. Might change. + @steps.last.add_property(StepConfiguration::ConfigParamProperties::SITE_ID, site_id) + end + + def site_id + # As of now, the site is always in the last Step of the IntegrationOption. Might change. + @steps.last.site_id + end + + # Return this object and the associated steps in a digestible JSON format. + # + # @return [String] JSON interpretation of this workflow. + # + def to_json + # Convert Object to Hash + hash = self.to_hash + + # Grab the Step objects and convert to Hashes + steps = hash['steps'] + hashified_steps = [] + steps.each {|step| hashified_steps << step.to_hash} + hash['steps'] = hashified_steps + + # Convert Hash to JSON + hash.to_json + end + + # Return this object as a Hash. The corresponding Steps will still be objects. + # + # @return [Hash] Hash interpretation of this IntegrationOption. + def to_hash + hash = {} + instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var)} + hash + end + + # Load a Hash of an IntegrationOption into an actual IntegrationOption. Probably didn't need to + # break out separately, but might be useful + # + # @param [Hash] raw_integration_option is a Hash representation of an IntegrationOption + # @return [IntegrationOption] The IntegrationOption version of the Hash + def self.load(raw_integration_option) + integration_option = IntegrationOption.new(id: raw_integration_option[:id], name: raw_integration_option[:name]) + steps = raw_integration_option[:steps] + steps.each do |step| + step_config = step[:stepConfiguration] + integration_option.steps << Step.new(uuid: step[:uuid], + service_name: step[:serviceName], + type_name: step_config[:typeName], + previous_type_name: step_config[:previousTypeName], + configuration_params: step_config[:configurationParams]) + end + integration_option + end + end +end diff --git a/lib/eso/integration_options_manager.rb b/lib/eso/integration_options_manager.rb new file mode 100644 index 00000000..eb0a3f37 --- /dev/null +++ b/lib/eso/integration_options_manager.rb @@ -0,0 +1,153 @@ +require 'nexpose' + +module Eso + ## + # This class is a manager for the integration options api. Integration options match epo/dxl/etc steps + # (ie discover-epo-assets) to nexpose steps (ie import-external-assets). + + class IntegrationOptionsManager + + ## + # Constructor for IntegrationOptionsManager. + # + # @param [Nexpose::Connection] nsc A logged-in Nexpose::Connection object with a valid session used to authenticate. + # @return [Eso::IntegrationOptionsManager] The newly created IntegrationOptionManager object + # + def initialize(nsc) + @nexpose_console = nsc + @url = "https://#{nsc.host}:#{nsc.port}/eso/integration-manager-service/api/integration-options/" + end + + ## + # Create a new or Update existing integration option. + # + # @param [String] payload The JSON representation of an integration option. + # @return [String] The integrationOptionID (a UUID) of the newly created configuration. Raises error on failure. + # + def create(payload) + # TODO retry if the post fails on timeout + response_body = ::Nexpose::AJAX.post(@nexpose_console, "#{@url}", payload, ::Nexpose::AJAX::CONTENT_TYPE::JSON) + JSON.parse(response_body)['data']['id'] + end + alias_method :update, :create + + # Deleting and stopping are the same thing + def delete(integration_option_id) + ::Nexpose::AJAX.delete(@nexpose_console, "#{@url}#{integration_option_id}/state") + end + alias_method :stop, :delete + + ## + # Get an existing integration option. + # + # @param [String] integration_option_id The integration_option_id of the integration option. + # @return IntegrationOption for that id, or nil + # + def get(integration_option_id) + # Gets all integration options + response_body = ::Nexpose::AJAX.get(@nexpose_console, "#{@url}", ::Nexpose::AJAX::CONTENT_TYPE::JSON) + response = JSON.parse(response_body, symbolize_names: true) + + # Find the desired one + raw_integration_option = response.find{|raw| raw[:id] == integration_option_id} + raise "No IntegrationOption with ID #{integration_option_id}" if raw_integration_option.nil? + + # Load it to an object + IntegrationOption.load(raw_integration_option) + end + + ## + # Get the status of an integration option. + # + # @param [String] integration_option_id The integration_option_id of the integration option. + # @return the state (READY, STOPPED, etc) + # + def status(integration_option_id) + response_body = ::Nexpose::AJAX.get(@nexpose_console, "#{@url}#{integration_option_id}/status", ::Nexpose::AJAX::CONTENT_TYPE::JSON) + response = JSON.parse(response_body) + response['state'] + end + + def start(integration_option_id) + response_body = ::Nexpose::AJAX.post(@nexpose_console, "#{@url}#{integration_option_id}/state", ::Nexpose::AJAX::CONTENT_TYPE::JSON) + JSON.parse(response_body) + end + + # TODO: These build_* methods must die. + def self.build_import_epo_assets_option(name:, discovery_conn_id:, site_id: nil) + step1 = Step.new(service_name: ServiceNames::EPO, type_name: StepNames::DISCOVER_EPO) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::IMPORT_EXTERNAL, previous_type_name: step1.type_name) + + #This isn't always known immediately, which is why we have IntegrationOption.site_id= + step2.add_property(StepConfiguration::ConfigParamProperties::SITE_ID, site_id) if site_id + IntegrationOption.new(name: name, steps: [step1, step2]) + end + + def self.build_import_ad_assets_option(name:, discovery_conn_id:, site_id: nil) + step1 = Step.new(service_name: ServiceNames::ACTIVE_DIRECTORY, type_name: StepNames::DISCOVER_ACTIVE_DIRECTORY) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::IMPORT_EXTERNAL, previous_type_name: step1.type_name) + + #This isn't always known immediately, which is why we have IntegrationOption.site_id= + step2.add_property(StepConfiguration::ConfigParamProperties::SITE_ID, site_id) if site_id + IntegrationOption.new(name: name, steps: [step1, step2]) + end + + def self.build_sync_aws_assets_option(name:, discovery_conn_id:, site_id: nil) + step1 = Step.new(service_name: ServiceNames::AWS, type_name: StepNames::DISCOVER_AWS_ASSETS) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::SYNC_EXTERNAL, previous_type_name: step1.type_name) + + #This isn't always known immediately, which is why we have IntegrationOption.site_id= + step2.add_property(StepConfiguration::ConfigParamProperties::SITE_ID, site_id) if site_id + IntegrationOption.new(name: name, steps: [step1, step2]) + end + + def self.build_sync_azure_assets_option(name:, discovery_conn_id:, site_id: nil) + step1 = Step.new(service_name: ServiceNames::AZURE, type_name: StepNames::DISCOVER_AZURE_ASSETS) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::SYNC_EXTERNAL, previous_type_name: step1.type_name) + + #This isn't always known immediately, which is why we have IntegrationOption.site_id= + step2.add_property(StepConfiguration::ConfigParamProperties::SITE_ID, site_id) if site_id + IntegrationOption.new(name: name, steps: [step1, step2]) + end + + def self.build_sync_azure_assets_with_tags_option(name:, discovery_conn_id:, site_id: nil, tags: '') + step1 = Step.new(service_name: ServiceNames::AZURE, type_name: StepNames::DISCOVER_AZURE_ASSETS) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + .add_property(StepConfiguration::ConfigParamProperties::IMPORT_TAGS, true) + .add_property(StepConfiguration::ConfigParamProperties::EXCLUDE_ASSETS_WITH_TAGS, "") + .add_property(StepConfiguration::ConfigParamProperties::ONLY_IMPORT_THESE_TAGS, tags) + step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::SYNC_EXTERNAL, previous_type_name: step1.type_name) + + #This isn't always known immediately, which is why we have IntegrationOption.site_id= + step2.add_property(StepConfiguration::ConfigParamProperties::SITE_ID, site_id) if site_id + IntegrationOption.new(name: name, steps: [step1, step2]) + end + + def self.build_export_risk_scores_option(name:, discovery_conn_id:) + step1 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::RISK_SCORE_UPDATED) + step2 = Step.new(service_name: ServiceNames::EPO, type_name: StepNames::PUSH_RISK_SCORE, previous_type_name: step1.type_name) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + IntegrationOption.new(name: name, steps: [step1, step2]) + end + + def self.build_find_vuln_details_option(name:, discovery_conn_id:) + step1 = Step.new(service_name: ServiceNames::DXL, type_name: StepNames::VULN_DETAILS_REQUEST) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::VULN_DETAILS, previous_type_name: step1.type_name) + step3 = Step.new(service_name: ServiceNames::DXL, type_name: StepNames::VULN_DETAILS_REQUEST, previous_type_name: step2.type_name) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + IntegrationOption.new(name: name, steps: [step1, step2, step3]) + end + + def self.build_publish_vulnerabilities_option(name:, discovery_conn_id:) + step1 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::NEW_ASSET_VULN) + step2 = Step.new(service_name: ServiceNames::DXL, type_name: StepNames::PUBLISH_VULN_INT_TYPE, previous_type_name: step1.type_name) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + IntegrationOption.new(name: name, steps: [step1, step2]) + end + end +end diff --git a/lib/eso/nexpose.rb b/lib/eso/nexpose.rb new file mode 100644 index 00000000..e151790c --- /dev/null +++ b/lib/eso/nexpose.rb @@ -0,0 +1,209 @@ +require 'nexpose' + +module Eso + module ServiceNames + ACTIVE_DIRECTORY = 'active-directory' + AWS = 'amazon-web-services' + AZURE = 'azure' + DXL = 'dxl' + EPO = 'epo' + NEXPOSE = 'nexpose' + end + + module StepNames + ADD_TO_SITE = 'add-to-site' + ADD_VULN_AND_SCAN = 'add-vulnerabilities-to-site-and-scan' + DISCOVER_ACTIVE_DIRECTORY = 'discover-ad-assets' + DISCOVER_AWS_ASSETS = 'discover-aws-assets' + DISCOVER_AZURE_ASSETS = 'discover-azure-assets' + DISCOVER_EPO = 'discover-epo-assets' + DISCOVER_KNOWN = 'discover-known-assets' + DISCOVER_NEW = 'discover-new-assets' + DISCOVERY_CONFIG_METADATA = 'discoveryConfigMetadata' + EMPTY = '' + FILE_REPUTATION_TRIGGER = 'tie-file-reputation-trigger' + IMPORT_EXTERNAL = 'import-external-assets' + NEW_ASSET_VULN = 'new-asset-vulnerability' + NEW_VULN = 'new-vulnerabilities' + PUBLISH_VULN_INT_TYPE = 'publish-vulnerability-integration-type' + PUSH_RISK_SCORE = 'push-risk-score' + RISK_SCORE_UPDATED = 'risk-score-updated' + SCAN = 'scan' + SCAN_IN_SITE = 'scan-in-site' + SYNC_EXTERNAL = 'sync-external-assets' + TAG = 'tag' + VULN_DETAILS = 'vulnerability-details' + VULN_DETAILS_REQUEST = 'vulnerability-details-request' + end + + module Values + ARRAY = 'Array' + BOOLEAN = 'Boolean' + INTEGER = 'Integer' + OBJECT = 'Object' + STRING = 'String' + end + + module StepConfigTypes + DISCOVERY_CONFIG = [StepNames::DISCOVER_ACTIVE_DIRECTORY, + StepNames::DISCOVER_AWS_ASSETS, + StepNames::DISCOVER_AZURE_ASSETS, + StepNames::DISCOVER_EPO, + StepNames::DISCOVER_KNOWN, + StepNames::DISCOVER_NEW, + StepNames::FILE_REPUTATION_TRIGGER, + StepNames::PUBLISH_VULN_INT_TYPE, + StepNames::PUSH_RISK_SCORE, + StepNames::VULN_DETAILS_REQUEST] + EMPTY = [StepNames::NEW_ASSET_VULN, + StepNames::NEW_VULN, + StepNames::RISK_SCORE_UPDATED, + StepNames::VULN_DETAILS] + SITE = [StepNames::ADD_TO_SITE, + StepNames::ADD_VULN_AND_SCAN, + StepNames::IMPORT_EXTERNAL, + StepNames::SCAN, + StepNames::SCAN_IN_SITE, + StepNames::SYNC_EXTERNAL] + TAG = [StepNames::TAG] + end + + module Filters + CVSS_SCORE = 'CVSS_SCORE' + DHCP_HOST_NAME = 'DHCP_HOST_NAME' + HOURS_SINCE_LAST_SCAN= 'HOURS_SINCE_LAST_SCAN' + HOURS_SINCE_LAST_SCAN_ITEM = 'HOURS_SINCE_LAST_SCAN_ITEM' + IP_ADDRESS = 'IP_ADDRESS' + IP_RANGE = 'IP_RANGE' + MAC_ADDRESS = 'MAC_ADDRESS' + OPEN_PORT = 'OPEN_PORT' + RISK_SCORE = 'RISK_SCORE' + SERVICE_NAME = 'SERVICE_NAME' + end + + module Nexpose + def self.create_discovery_workflow(conductor:, name:, step1_type:, step1_param: nil, step2_type:, step2_param:) + step1 = self.send("create_#{step1_type.to_s.gsub(/-/, "_")}_step", id: step1_param) + step2 = self.send("create_#{step2_type.to_s.gsub(/-/, "_")}_step", id: step2_param) + step2.previous_type_name = step1.type_name + conductor.create_workflow(name: name, steps: [step1, step2]) + end + + def self.create_scan_new_vuln_workflow(conductor:, name:, filters:, site_id:) + step1 = self.create_new_vuln_step(workflow: nil, filters: filters, previous_type_name: StepNames::EMPTY) + step2 = self.create_add_vuln_and_scan_step(id: site_id) + step2.previous_type_name = step1.type_name + conductor.create_workflow(name: name, steps: [step1, step2]) + end + + def self.create_file_trigger_workflow(conductor:, name:, step1_param:, step2_param:) + step1 = self.create_file_reputation_step(workflow: nil, id: step1_param) + step2 = self.create_tag_step(workflow: nil, id: step2_param) + step2.previous_type_name = step1.type_name + conductor.create_workflow(name: name, steps: [step1, step2]) + end + + def self.create_scan_in_site_step(workflow: nil, id:, previous_type_name: StepNames::EMPTY) + Step.new(workflow: workflow, + service_name: ServiceNames::NEXPOSE, + type_name: StepNames::SCAN_IN_SITE, + previous_type_name: previous_type_name) + .add_property(StepConfiguration::ConfigParamProperties::SITE_ID, id) + end + + def self.create_file_reputation_step(workflow: nil, id:) + Step.new(workflow: workflow, + service_name: ServiceNames::DXL, + type_name: StepNames::FILE_REPUTATION_TRIGGER, + previous_type_name: nil) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, id) + end + + def self.create_discover_new_assets_step(workflow: nil, id:, previous_type_name: StepNames::EMPTY) + Step.new(workflow: workflow, + service_name: ServiceNames::NEXPOSE, + type_name: StepNames::DISCOVER_NEW, + previous_type_name: previous_type_name) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, id) + end + + def self.create_discover_known_assets_step(workflow: nil, id:, previous_type_name: StepNames::EMPTY) + step = Step.new(workflow: workflow, + service_name: ServiceNames::NEXPOSE, + type_name: StepNames::DISCOVER_KNOWN, + previous_type_name: previous_type_name) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, id) + config_params = step.configuration_params + config_params[:HOURS_SINCE_LAST_SCAN] = { + :valueClass => Values::ARRAY, + :items => [ + { + :valueClass => Values::OBJECT, + :objectType => Filters::HOURS_SINCE_LAST_SCAN_ITEM, + :properties => { + :operator => { + :valueClass => Values::STRING, + :value => ::Nexpose::Search::Operator::GREATER_THAN + }, + :operand1 => { + :valueClass => Values::STRING, + :value => '1' + } + } + } + ] + } + step.configuration_params = config_params + step + end + + def self.create_new_vuln_step(workflow: nil, filters:, previous_type_name: StepNames::EMPTY) + # The filter definitions on the server are not standard at this point so that is why it is necessary to hard code this + # Opening a defect to fix the consistency on these on the backend so we can use the add_filter function in the automation + step = Step.new(workflow: workflow, + service_name: ServiceNames::NEXPOSE, + type_name: StepNames::NEW_VULN, + previous_type_name: previous_type_name) + + filters.each { |filter| step.add_filter(filter) } + step + end + + def self.create_add_vuln_and_scan_step(workflow: nil, id:, previous_type_name: StepNames::EMPTY) + Step.new(workflow: workflow, + service_name: ServiceNames::NEXPOSE, + type_name: StepNames::ADD_VULN_AND_SCAN, + previous_type_name: previous_type_name) + .add_property(StepConfiguration::ConfigParamProperties::SITE_ID, id) + end + + def self.create_add_to_site_step(workflow: nil, id:, previous_type_name: StepNames::EMPTY) + Step.new(workflow: workflow, + service_name: ServiceNames::NEXPOSE, + type_name: StepNames::ADD_TO_SITE, + previous_type_name: previous_type_name) + .add_property(StepConfiguration::ConfigParamProperties::SITE_ID, id) + end + + def self.create_scan_step(workflow: nil, id:, previous_type_name: StepNames::EMPTY) + Step.new(workflow: workflow, + service_name: ServiceNames::NEXPOSE, + type_name: StepNames::SCAN, + previous_type_name: previous_type_name) + .add_property(StepConfiguration::ConfigParamProperties::SITE_ID, id) + end + + def self.create_tag_step(workflow: nil, id:, previous_type_name: StepNames::EMPTY) + Step.new(workflow: workflow, + service_name: ServiceNames::NEXPOSE, + type_name: StepNames::TAG, + previous_type_name: previous_type_name) + .add_property(StepConfiguration::ConfigParamProperties::TAG_ID, id) + end + + def self.get_discover_step(workflow: ) + workflow.get_step(StepNames::DISCOVER_NEW) || workflow.get_step(StepNames::DISCOVER_KNOWN) + end + end +end + diff --git a/lib/eso/service.rb b/lib/eso/service.rb new file mode 100644 index 00000000..ae9d36ce --- /dev/null +++ b/lib/eso/service.rb @@ -0,0 +1,82 @@ +module Eso + class Service + attr_accessor :host + + attr_accessor :port + + attr_accessor :url + + CONTENT_TYPE_JSON = 'application/json; charset-utf-8' + + def initialize(host:, port: 3780, nsc:) + @host = host + @port = port + @nexpose_console = nsc + end + + def get(url:, content_type: CONTENT_TYPE_JSON) + get = Net::HTTP::Get.new(url) + get.set_content_type(content_type) + request(request: get) + end + + def put(url:, payload:, content_type: CONTENT_TYPE_JSON) + put = Net::HTTP::Put.new(url) + put.set_content_type(content_type) + put.body = payload.to_s if payload + request(request: put) + end + + def post(url:, payload: nil, content_type: CONTENT_TYPE_JSON) + post = Net::HTTP::Post.new(url) + post.set_content_type(content_type) + post.body = payload.to_s if payload + request(request: post) + end + + def delete(url:, content_type: CONTENT_TYPE_JSON) + delete = Net::HTTP::Delete.new(url) + delete.set_content_type(content_type) + request(request: delete) + end + + def http(timeout:) + http = Net::HTTP.new(@host, @port) + http.read_timeout = timeout if timeout + http.use_ssl = false + http + end + + def https(timeout:) + http = Net::HTTP.new(@host, @port) + http.read_timeout = timeout if timeout + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + http + end + + def add_nexpose_session(request:) + request.add_field('nexposeCCSessionID', @nexpose_console.session_id) + request.add_field('Cookie', "nexposeCCSessionID=#{@nexpose_console.session_id}") + end + + def request(request:, timeout: nil) + http = https(timeout: timeout) + add_nexpose_session(request: request) + response = http.request(request) + case response + when Net::HTTPOK, Net::HTTPCreated + rv = nil + if response.content_type == "application/json" && !response.body.empty? + json_data = JSON.parse(response.body, symbolize_names: true) + json_data[:data].nil? ? rv = json_data : rv = json_data[:data] + end + rv + when Net::HTTPForbidden + raise "Access denied. Response was #{response.body}" + else + raise "There was an error sending the request. Response was #{response.body}" + end + end + end +end \ No newline at end of file diff --git a/lib/eso/step.rb b/lib/eso/step.rb new file mode 100644 index 00000000..8e2929c9 --- /dev/null +++ b/lib/eso/step.rb @@ -0,0 +1,166 @@ +module Eso + # Object representation of a step, which are attributes of Workflows and Integration Options + # + class Step + # UUID of this step. This is generated on creation on the server. + attr_accessor :uuid + + # Type of this step. Should be one of Eso::ServiceNames + attr_accessor :serviceName + + # The configuration for this step. + attr_accessor :stepConfiguration + + # Constructor for Step. + # + # @param [String] uuid UUID of this Step. This is created on the server side upon creation through the API. + # @param [String] service_name The name of step this is. + # @param [Workflow] workflow The workflow this step belongs to. + # @param [Hash] configuration_params Hash of the parameters for this step. + # + def initialize(uuid: nil, service_name:, workflow: nil, type_name:, previous_type_name: StepNames::EMPTY, configuration_params: nil) + @uuid = uuid if uuid + @serviceName = service_name + @stepConfiguration = StepConfiguration.new(type_name, previous_type_name) + @stepConfiguration.configurationParams = configuration_params if configuration_params + @stepConfiguration.workflowID = workflow.id if workflow + end + + # Return the configuration parameters for this step. + # + # @return [Hash] Hash of the configuration parameters for this step. + # + def configuration_params + @stepConfiguration.configurationParams + end + + # Set the the configuration parameters for this step. + # + # @param [Hash] config_params of the new configuration parameters you would like to set. + # @return [Hash] Hash of the updated configuration parameters for this step. + # + def configuration_params=(config_params) + @stepConfiguration.configurationParams = config_params + end + + # Return the type name for this step. + # + # @return [String] The currently configured type name. + # + def type_name + @stepConfiguration.typeName + end + + # Set the type name for this step. + # + # @param [String] The new type_name that you would like to set this to. See Eso::StepNames for valid names. + # @return [String] The newly set type name. + # + def type_name=(wf_action_name) + @stepConfiguration.typeName = wf_action_name + end + + # Return the previous type name for this step. + # + # @return [String] The previous type name for this step. + # + def previous_type_name + @stepConfiguration.previousTypeName + end + + # Set the previous type name for this step. + # + # @param [String] The new previous type name that you would like to set. See Eso::StepNames for valid names. + # @return [String] Hash of the configuration parameters for this step. + # + def previous_type_name=(action_name) + @stepConfiguration.previousTypeName = action_name + end + + # Return the properties of this step. + # + # @return [Hash{}] Hash of the properties for this step. + # + def properties + @stepConfiguration.configurationParams[:properties] + end + + # Set the properties of this step. + # + # @param [Hash] The new properties to set for this step. + # @return [Hash] Hash of the newly configured properties for this step. + # + def properties=(new_properties) + @stepConfiguration.configurationParams[:properties] = new_properties + end + + # Determine the siteID of this step, if it exists + # + # @return [String|nil] The String siteID value or nil if no siteID + def site_id + if @stepConfiguration.configurationParams[:properties][:siteID] + @stepConfiguration.configurationParams[:properties][:siteID][:value] + end + end + + # Returns all configured filters for this step. + # + # @return [Array] An array of the currently configured filters for this step, each represented as a hash. + # + def filters + rv = {} + self.properties.each_pair do |key, value| + if value[:properties] + rv[key] = value if value[:properties].has_key?(:operators) + end + end + rv + end + + # Convenience method which calls the #add_property method of the @stepConfiguration, but returns the Step + # + # @return [Step] Returns this Step for chaining + def add_property(name, value) + @stepConfiguration.add_property(name, value) + self + end + + # Convenience method which calls the #add_property method of the @stepConfiguration, but returns the Step + # + # @return [Step] Returns this Step for chaining + def update_property(name, value) + @stepConfiguration.add_property(name, value) + self + end + + # Add the specified filter to this step. The filter is converted to a hash and saved as such instead of being saved as a ESO::Filter object. + # + # @param [Filter] filter The filter to add to this step. + # + def add_filter(filter) + @stepConfiguration.configurationParams[:properties].merge! filter.to_hash + end + + # Return this step in a JSON digestible format. + # + # @return [String] JSON interpretation of this step. + # + def to_json + self.to_hash.to_json + end + + # Return this step as a hash. + # + # @return [Hash] Hash interpretation of this step. + # + def to_hash + hash = {} + instance_variables.each do |var| + value = instance_variable_get(var) + value = value.to_h if value.respond_to?('to_h') + hash[var.to_s.delete('@')] = value + end + hash + end + end +end diff --git a/lib/eso/step_configuration.rb b/lib/eso/step_configuration.rb new file mode 100644 index 00000000..038ba4ff --- /dev/null +++ b/lib/eso/step_configuration.rb @@ -0,0 +1,73 @@ +module Eso + class StepConfiguration + attr_accessor :typeName, :previousTypeName, :configurationParams, :workflowID + + module ConfigParamProperties + DISCOVERY_CONFIG_ID = 'discoveryConfigID' + EXCLUDE_ASSETS_WITH_TAGS= 'excludeAssetsWithTags' + IMPORT_TAGS = 'importTags' + ONLY_IMPORT_THESE_TAGS = 'onlyImportTheseTags' + SITE_ID = 'siteID' + TAG_ID = 'tagID' + end + + module ConfigParamPropertyTypes + BOOLEAN = [ConfigParamProperties::IMPORT_TAGS] + INTEGER = [ConfigParamProperties::DISCOVERY_CONFIG_ID, + ConfigParamProperties::SITE_ID, + ConfigParamProperties::TAG_ID] + STRING = [ConfigParamProperties::EXCLUDE_ASSETS_WITH_TAGS, + ConfigParamProperties::ONLY_IMPORT_THESE_TAGS] + end + + def initialize (typeName, previousTypeName, configurationParams=nil, workflowID=nil) + @typeName = typeName + @previousTypeName = previousTypeName + @configurationParams = configurationParams ? configurationParams : { + :valueClass => Values::OBJECT, + :objectType => typeName, + :properties => {}} + @workflowID = workflowID if workflowID + end + + # This adds the specified property to this StepConfiguration.configurationParams.properties Hash + # + # @param [String] name The name of the property to add, which should be one of ConfigParamProperties + # @param [Object] value The value of the property to add, which should already be in the appropriate format (Eso::Values) + # @return [StepConfiguration] Returns this object for chaining. + def add_property(name, value) + @configurationParams[:properties][name] = + case name + when *ConfigParamPropertyTypes::BOOLEAN + { + valueClass: Values::BOOLEAN, + value: value + } + when *ConfigParamPropertyTypes::INTEGER + { + valueClass: Values::INTEGER, + value: value + } + when *ConfigParamPropertyTypes::STRING + { + valueClass: Values::STRING, + value: value + } + else + raise ArgumentError, "Invalid StepConfiguration ConfigurationParameter Property name: #{name}. " + + 'Should be one of StepConfiguration::ConfigParamProperties' + end + self + end + + def to_h + hash = { + :typeName => @typeName, + :previousTypeName => @previousTypeName, + :configurationParams => @configurationParams + } + hash['workflowID'] = @workflowID if @workflowID + hash + end + end +end diff --git a/lib/eso/version.rb b/lib/eso/version.rb new file mode 100644 index 00000000..edbef1dd --- /dev/null +++ b/lib/eso/version.rb @@ -0,0 +1,3 @@ +module Eso + VERSION = '2.2.1' +end diff --git a/lib/eso/workflow.rb b/lib/eso/workflow.rb new file mode 100644 index 00000000..bdcd1b40 --- /dev/null +++ b/lib/eso/workflow.rb @@ -0,0 +1,151 @@ +module Eso + + # The following classes have mixed casing (snake and camel) to accommodate for the API. + # I guess a TODO would be to write a helper to automatically convert them. + class Workflow + # The id of the workflow. This will be created upon saving to the server upon creation. + attr_accessor :id + + # The name of the workflow. This is required. + attr_accessor :name + + # An array of the steps this workflow takes action on. + attr_accessor :steps + + # The time the workflow was created in milliseconds since epoch + attr_accessor :timeCreated + + + # Constructor for the workflow + # + # @param [String] id ID of the workflow. + # @param [String] name Name of the workflow. + # @param [Array] steps Array of the steps that this workflow takes. + # @param [Fixnum] timeCreated The time the workflow was created in millis since epoch + # + def initialize(id: nil, name:, steps: [], timeCreated: (Time.now.strftime('%s').to_i * 1000) ) + @id = id + @name = name + @steps = steps + @timeCreated = timeCreated + end + + # Load an existing workflow from the API. + # + # @param [Conductor] conductor The Conductor object governing the workflows + # @param [String] id ID of the workflow to load + # @return [Workflow] Workflow object that was loaded. + # + def self.load(conductor, id) + uri = "#{conductor.url}workflows/#{id}" + resp = conductor.get(url: uri) + workflow = self.new(id: resp[:id], name: resp[:name]) + steps = resp[:steps] + steps.each do |step| + workflow_step = Step.new(uuid: step[:uuid], + service_name: step[:serviceName], + workflow: workflow, + type_name: step[:stepConfiguration][:typeName], + previous_type_name: step[:stepConfiguration][:previousTypeName], + configuration_params: step[:stepConfiguration][:configurationParams]) + workflow.steps << workflow_step + end + workflow + end + + # Return the relevant step based on the given service name. + # For example, if you want the step related to the scan service you would pass 'nexpose-scan-service'. + # + # @param [String] service_name Service name to be returned. + # @return [Step] Step object corresponding to the given service. + # + def get_step(type_name) + @steps.find do |step| + step.type_name == type_name + end + end + + # Return the trigger step of a workflow. The trigger step is defined as a step that monitors for events + # that will cause the action to fire. + # + # Currently triggers do not have a previous-action so that is what this is returning. This behavior could change in ESO's future. + # + # @return [Step] Step object representation of the trigger step. + # + def trigger + @steps.find do |step| + step.stepConfiguration.previousTypeName.nil? + end + end + + # Return this object and the associated steps in a digestible JSON format. + # + # @return [String] JSON interpretation of this workflow. + # + def to_json + hash = self.to_hash + steps = hash["steps"] + hashified_steps = [] + steps.each {|step| hashified_steps << step.to_hash} + hash["steps"] = hashified_steps + hash.to_json + end + + # Return this object as a hash. + # The corresponding steps will still be objects. + # + # @return [Hash{}] Hash interpretation of this workflow. + def to_hash + hash = {} + instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var)} + hash + end + + # Representation of state of a workflow or integration option. Taken from service-orchestration State.java + class State + # Workflow or an integration option is configured and ready to accept events + READY = 'ready' + + # Workflow or an integration option is processing or has processed events + RUNNING = 'running' + + # The workflow or an integration option is running, but is temporarily unsuccessful processing events + RETRY = 'retry' + + # Workflow or an integration option is stopped by the user + STOPPED = 'stopped' + + # Workflow or an integration option has experienced an error that caused it to stop + ERROR = 'error' + end + + class StateHistory < Struct.new(:message, :state, :startTime) + end + + class History < Workflow + # The current state of the workflow + attr_accessor :state + + # The most recent message + attr_accessor :message + + # An array of Eso::Workflow::StateHistory + attr_accessor :state_histories + + # Constructor for the WorkflowHistory + # + # @param [String] id ID of the workflow. + # @param [String] name Name of the workflow. + # @param [Array] steps Array of the steps that this workflow takes. + # @param [Fixnum] timeCreated The time the workflow was created in millis since epoch + # @param [Eso::Workflow::State] state The current state of the workflow + # @param [String] message The most recent message + def initialize (id:, name:, timeCreated:, steps:, state:, message:, history:) + super(id: id, name: name, timeCreated: timeCreated, steps: steps) + @state = state + @message = message + @state_histories = history + end + end + end +end From e87cb24a6b9c12be738630b1383dc14b06cb30b4 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Wed, 6 Dec 2017 16:34:00 -0600 Subject: [PATCH 02/14] NEX-50812: Add X-Requested-With header to eso and nexpose --- lib/eso/service.rb | 3 ++- lib/nexpose/ajax.rb | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/eso/service.rb b/lib/eso/service.rb index ae9d36ce..59b25254 100644 --- a/lib/eso/service.rb +++ b/lib/eso/service.rb @@ -58,6 +58,7 @@ def https(timeout:) def add_nexpose_session(request:) request.add_field('nexposeCCSessionID', @nexpose_console.session_id) request.add_field('Cookie', "nexposeCCSessionID=#{@nexpose_console.session_id}") + request.add_field('X-Requested-With', 'XMLHttpRequest') end def request(request:, timeout: nil) @@ -79,4 +80,4 @@ def request(request:, timeout: nil) end end end -end \ No newline at end of file +end diff --git a/lib/nexpose/ajax.rb b/lib/nexpose/ajax.rb index 9660a869..493fbc93 100644 --- a/lib/nexpose/ajax.rb +++ b/lib/nexpose/ajax.rb @@ -149,6 +149,7 @@ def https(nsc, timeout = nil) def headers(nsc, request) request.add_field('nexposeCCSessionID', nsc.session_id) request.add_field('Cookie', "nexposeCCSessionID=#{nsc.session_id}") + request.add_field('X-Requested-With', 'XMLHttpRequest') end def request(nsc, request, timeout = nil) From ae66880b7888f3d87999b18cbf6b4b1e6d69a8ed Mon Sep 17 00:00:00 2001 From: Sam Morris Date: Thu, 14 Dec 2017 16:06:25 -0600 Subject: [PATCH 03/14] store Array properties differently --- lib/eso/configuration/configuration.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/eso/configuration/configuration.rb b/lib/eso/configuration/configuration.rb index ccdd82f3..889afcb2 100644 --- a/lib/eso/configuration/configuration.rb +++ b/lib/eso/configuration/configuration.rb @@ -97,7 +97,12 @@ def to_hash def self.load(array) property = array.first value_class = array.last['valueClass'] - value = array.last['value'] + value = + if value_class == 'Array' + array.last['items'].map{|item| item['value']} + else + array.last['value'] + end self.new(property, value_class, value) end end From 534d2bd6e6f51a5b83abf21996666dac08b6d9b0 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Thu, 14 Dec 2017 21:57:42 -0600 Subject: [PATCH 04/14] Appease most of the houndci-bot complaints --- lib/eso/workflow.rb | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/eso/workflow.rb b/lib/eso/workflow.rb index bdcd1b40..012835af 100644 --- a/lib/eso/workflow.rb +++ b/lib/eso/workflow.rb @@ -15,19 +15,18 @@ class Workflow # The time the workflow was created in milliseconds since epoch attr_accessor :timeCreated - # Constructor for the workflow # # @param [String] id ID of the workflow. # @param [String] name Name of the workflow. # @param [Array] steps Array of the steps that this workflow takes. - # @param [Fixnum] timeCreated The time the workflow was created in millis since epoch + # @param [Fixnum] time_created The time the workflow was created in millis since epoch # - def initialize(id: nil, name:, steps: [], timeCreated: (Time.now.strftime('%s').to_i * 1000) ) + def initialize(id: nil, name:, steps: [], time_created: (Time.now.strftime('%s').to_i * 1000)) @id = id @name = name @steps = steps - @timeCreated = timeCreated + @timeCreated = time_created end # Load an existing workflow from the API. @@ -84,10 +83,10 @@ def trigger # def to_json hash = self.to_hash - steps = hash["steps"] + steps = hash['steps'] hashified_steps = [] - steps.each {|step| hashified_steps << step.to_hash} - hash["steps"] = hashified_steps + steps.each { |step| hashified_steps << step.to_hash } + hash['steps'] = hashified_steps hash.to_json end @@ -97,12 +96,12 @@ def to_json # @return [Hash{}] Hash interpretation of this workflow. def to_hash hash = {} - instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var)} + instance_variables.each { |var| hash[var.to_s.delete('@')] = instance_variable_get(var) } hash end # Representation of state of a workflow or integration option. Taken from service-orchestration State.java - class State + module State # Workflow or an integration option is configured and ready to accept events READY = 'ready' @@ -119,8 +118,7 @@ class State ERROR = 'error' end - class StateHistory < Struct.new(:message, :state, :startTime) - end + StateHistory = Struct.new(:message, :state, :startTime) class History < Workflow # The current state of the workflow @@ -137,11 +135,11 @@ class History < Workflow # @param [String] id ID of the workflow. # @param [String] name Name of the workflow. # @param [Array] steps Array of the steps that this workflow takes. - # @param [Fixnum] timeCreated The time the workflow was created in millis since epoch + # @param [Fixnum] time_created The time the workflow was created in millis since epoch # @param [Eso::Workflow::State] state The current state of the workflow # @param [String] message The most recent message - def initialize (id:, name:, timeCreated:, steps:, state:, message:, history:) - super(id: id, name: name, timeCreated: timeCreated, steps: steps) + def initialize(id:, name:, time_created:, steps:, state:, message:, history:) + super(id: id, name: name, timeCreated: time_created, steps: steps) @state = state @message = message @state_histories = history From 8ca5878a7e64e75eb2903a293ea130f195f88696 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Fri, 15 Dec 2017 19:01:27 -0600 Subject: [PATCH 05/14] Remove eso version file --- lib/eso.rb | 1 - lib/eso/version.rb | 3 --- 2 files changed, 4 deletions(-) delete mode 100644 lib/eso/version.rb diff --git a/lib/eso.rb b/lib/eso.rb index dd1933e0..4d1d5120 100644 --- a/lib/eso.rb +++ b/lib/eso.rb @@ -16,7 +16,6 @@ require 'eso/service' require 'eso/step' require 'eso/step_configuration' -require 'eso/version' require 'eso/workflow' module Eso diff --git a/lib/eso/version.rb b/lib/eso/version.rb deleted file mode 100644 index edbef1dd..00000000 --- a/lib/eso/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Eso - VERSION = '2.2.1' -end From bf59a32dc4398a296a8f83e734349ac777b91b69 Mon Sep 17 00:00:00 2001 From: Sam Morris Date: Mon, 18 Dec 2017 13:16:07 -0600 Subject: [PATCH 06/14] add nil guard in property method --- lib/eso/configuration/configuration.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/eso/configuration/configuration.rb b/lib/eso/configuration/configuration.rb index 889afcb2..c841c4c9 100644 --- a/lib/eso/configuration/configuration.rb +++ b/lib/eso/configuration/configuration.rb @@ -36,7 +36,8 @@ def to_hash # @param [String] name The name of the property to retrieve # @return [String] The value of the property def property(name) - properties.find{|attr| attr.property == name}.value + prop = properties.find{|attr| attr.property == name} + prop.value unless prop.nil? end # Update a Configuration attribute property value given the name of the property From 2bdc3fef9ae998a957d0ea4bf77eed2d779290e2 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Thu, 18 Jan 2018 16:45:16 -0600 Subject: [PATCH 07/14] Enhanced AWSv2 ARN messages --- lib/eso/configuration/configuration_manager.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/eso/configuration/configuration_manager.rb b/lib/eso/configuration/configuration_manager.rb index 190cf36c..fc1d87fd 100644 --- a/lib/eso/configuration/configuration_manager.rb +++ b/lib/eso/configuration/configuration_manager.rb @@ -125,6 +125,11 @@ def preview_assets(configuration) module ConfigManagerMessages module TestConfig + AUTH_FAILED_AWS = 'Could not authenticate to Amazon Web Services.' + + # Actual message will list out the bad ARNs + AUTH_FAILED_AWS_ARN = /Could not authenticate to Amazon Web Services with the following ARNs/ + CONNECTION_SUCCESSFUL = 'The connection to the external service was successful.' # Applies to invalid user, password, wrong protocol, can't reach server, bad base or search query From 116a77f1ae42341e4052e9d6e634b8800e53c777 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Tue, 30 Jan 2018 15:12:29 -0600 Subject: [PATCH 08/14] Document more Error messages returned by the ConfigManager --- lib/eso/configuration/configuration_manager.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/eso/configuration/configuration_manager.rb b/lib/eso/configuration/configuration_manager.rb index fc1d87fd..dcfdd3d0 100644 --- a/lib/eso/configuration/configuration_manager.rb +++ b/lib/eso/configuration/configuration_manager.rb @@ -125,17 +125,21 @@ def preview_assets(configuration) module ConfigManagerMessages module TestConfig - AUTH_FAILED_AWS = 'Could not authenticate to Amazon Web Services.' - + AUTH_FAILED_AWS = 'Could not authenticate to Amazon Web Services.' # Actual message will list out the bad ARNs - AUTH_FAILED_AWS_ARN = /Could not authenticate to Amazon Web Services with the following ARNs/ + AUTH_FAILED_AWS_ARN = /Could not authenticate to Amazon Web Services with the following ARNs/ CONNECTION_SUCCESSFUL = 'The connection to the external service was successful.' - # Applies to invalid user, password, wrong protocol, can't reach server, bad base or search query - CONNECTION_FAILED = 'The connection to the external service failed.' + CONNECTION_FAILED = 'The connection to the external service failed.' + + INVALID_FIELDS = 'The configuration had invalid fields.' - INVALID_FIELDS = 'The configuration had invalid fields.' + RETRY_AD = 'Failed to reach out to the Active Directory service, will try again.' + RETRY_AWS = 'Failed to reach out to Amazon Web Services, will try again.' + RETRY_AZURE = 'Failed to reach out to the Azure service, will try again.' + RETRY_DXL = 'The DXL connection is currently down and the connection is in retry status.' + RETRY_EPO = 'Failed to reach out to the ePO service, will try again.' end end end From e044c54a9cd212dbcdb7f3f9b3445f05d60332f7 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Fri, 13 Jul 2018 02:47:24 -0500 Subject: [PATCH 09/14] QE-XXXX: Required fixes for AWS configs w/ array props --- lib/eso/configuration/configuration.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/eso/configuration/configuration.rb b/lib/eso/configuration/configuration.rb index c841c4c9..9896430b 100644 --- a/lib/eso/configuration/configuration.rb +++ b/lib/eso/configuration/configuration.rb @@ -25,7 +25,7 @@ def to_hash hash = {:configId => @config_id, :serviceName => @service_name, :configName => @config_name, - :configurationAttributes => {:valueClass => 'Object', + :configurationAttributes => {:valueClass => Eso::Values::OBJECT, :objectType => 'service_configuration', :properties => []}} properties.each {|prop| hash[:configurationAttributes][:properties] << prop.to_hash} @@ -88,7 +88,14 @@ def to_hash prop = @property.to_sym hash = {prop => {}} hash[prop]['valueClass'] = @value_class - hash[prop]['value'] = @value + if @value_class == Eso::Values::ARRAY + items = [] + @value.each{|v| items<< {'value' => v, 'valueClass' => Eso::Values::STRING}} + hash[prop]['items'] = items + else + hash[prop]['value'] = @value + end + hash end # Load a ConfigurationAttribute object from an Array @@ -99,7 +106,7 @@ def self.load(array) property = array.first value_class = array.last['valueClass'] value = - if value_class == 'Array' + if value_class == Eso::Values::ARRAY array.last['items'].map{|item| item['value']} else array.last['value'] From ef65ef083d31d101db653d2416a738e7094a1717 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Wed, 18 Jul 2018 12:07:05 -0500 Subject: [PATCH 10/14] QE-XXXX: Add ability to delete a property on a Configuration --- lib/eso/configuration/configuration.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/eso/configuration/configuration.rb b/lib/eso/configuration/configuration.rb index 9896430b..f3e57b76 100644 --- a/lib/eso/configuration/configuration.rb +++ b/lib/eso/configuration/configuration.rb @@ -49,6 +49,13 @@ def update_property(name, value) properties.find{|attr| attr.property == name}.value = value end + # Delete a Configuration attribute property value given the name of the property + # + # @param [String] name The name of the property to update + def delete_property(name) + properties.delete_if{|attr| attr.property == name} + end + # Load a Configuration object from a Hash # # @param [Hash] hash The Hash containing the Configuration object From 381d054a7c09911e718dee145bbf3ba86576d1b3 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Fri, 27 Jul 2018 23:04:46 -0500 Subject: [PATCH 11/14] Adds support for AWS asset+tag sync --- lib/eso/integration_options_manager.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/eso/integration_options_manager.rb b/lib/eso/integration_options_manager.rb index eb0a3f37..ee6067b2 100644 --- a/lib/eso/integration_options_manager.rb +++ b/lib/eso/integration_options_manager.rb @@ -114,6 +114,19 @@ def self.build_sync_azure_assets_option(name:, discovery_conn_id:, site_id: nil) IntegrationOption.new(name: name, steps: [step1, step2]) end + def self.build_sync_aws_assets_with_tags_option(name:, discovery_conn_id:, site_id: nil, tags: '') + step1 = Step.new(service_name: ServiceNames::AWS, type_name: StepNames::DISCOVER_AWS_ASSETS) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + .add_property(StepConfiguration::ConfigParamProperties::IMPORT_TAGS, true) + .add_property(StepConfiguration::ConfigParamProperties::EXCLUDE_ASSETS_WITH_TAGS, "") + .add_property(StepConfiguration::ConfigParamProperties::ONLY_IMPORT_THESE_TAGS, tags) + step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::SYNC_EXTERNAL, previous_type_name: step1.type_name) + + #This isn't always known immediately, which is why we have IntegrationOption.site_id= + step2.add_property(StepConfiguration::ConfigParamProperties::SITE_ID, site_id) if site_id + IntegrationOption.new(name: name, steps: [step1, step2]) + end + def self.build_sync_azure_assets_with_tags_option(name:, discovery_conn_id:, site_id: nil, tags: '') step1 = Step.new(service_name: ServiceNames::AZURE, type_name: StepNames::DISCOVER_AZURE_ASSETS) .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) From cc1e8a7f880a5e21c2c8b696ddd08d85a6d3466d Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Wed, 8 Aug 2018 16:13:54 -0500 Subject: [PATCH 12/14] QE-587: Add verify-aws-target Int Option support ...and continue to feel the pain of creating more build_* methods when I really want to refactor this whole thing --- lib/eso/integration_options_manager.rb | 16 +++++++++++++--- lib/eso/nexpose.rb | 3 +++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/eso/integration_options_manager.rb b/lib/eso/integration_options_manager.rb index ee6067b2..524ea8c8 100644 --- a/lib/eso/integration_options_manager.rb +++ b/lib/eso/integration_options_manager.rb @@ -104,6 +104,16 @@ def self.build_sync_aws_assets_option(name:, discovery_conn_id:, site_id: nil) IntegrationOption.new(name: name, steps: [step1, step2]) end + def self.build_verify_aws_targets_option(name:, discovery_conn_id:) + step1 = Step.new(service_name: ServiceNames::AWS, type_name: StepNames::VERIFY_AWS_ASSETS) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::VERIFY_EXTERNAL_TARGETS, previous_type_name: step1.type_name) + step3 = Step.new(service_name: ServiceNames::AWS, type_name: StepNames::VERIFY_AWS_ASSETS) + .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) + + IntegrationOption.new(name: name, steps: [step1, step2, step3]) + end + def self.build_sync_azure_assets_option(name:, discovery_conn_id:, site_id: nil) step1 = Step.new(service_name: ServiceNames::AZURE, type_name: StepNames::DISCOVER_AZURE_ASSETS) .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) @@ -127,12 +137,12 @@ def self.build_sync_aws_assets_with_tags_option(name:, discovery_conn_id:, site_ IntegrationOption.new(name: name, steps: [step1, step2]) end - def self.build_sync_azure_assets_with_tags_option(name:, discovery_conn_id:, site_id: nil, tags: '') + def self.build_sync_azure_assets_with_tags_option(name:, discovery_conn_id:, site_id: nil, only_tags: '', exclude_tags: '') step1 = Step.new(service_name: ServiceNames::AZURE, type_name: StepNames::DISCOVER_AZURE_ASSETS) .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) .add_property(StepConfiguration::ConfigParamProperties::IMPORT_TAGS, true) - .add_property(StepConfiguration::ConfigParamProperties::EXCLUDE_ASSETS_WITH_TAGS, "") - .add_property(StepConfiguration::ConfigParamProperties::ONLY_IMPORT_THESE_TAGS, tags) + .add_property(StepConfiguration::ConfigParamProperties::EXCLUDE_ASSETS_WITH_TAGS, exclude_tags) + .add_property(StepConfiguration::ConfigParamProperties::ONLY_IMPORT_THESE_TAGS, only_tags) step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::SYNC_EXTERNAL, previous_type_name: step1.type_name) #This isn't always known immediately, which is why we have IntegrationOption.site_id= diff --git a/lib/eso/nexpose.rb b/lib/eso/nexpose.rb index e151790c..37d37819 100644 --- a/lib/eso/nexpose.rb +++ b/lib/eso/nexpose.rb @@ -32,6 +32,8 @@ module StepNames SCAN_IN_SITE = 'scan-in-site' SYNC_EXTERNAL = 'sync-external-assets' TAG = 'tag' + VERIFY_AWS_ASSETS = 'verify-aws-targets' + VERIFY_EXTERNAL_TARGETS = 'verify-external-targets' VULN_DETAILS = 'vulnerability-details' VULN_DETAILS_REQUEST = 'vulnerability-details-request' end @@ -66,6 +68,7 @@ module StepConfigTypes StepNames::SCAN_IN_SITE, StepNames::SYNC_EXTERNAL] TAG = [StepNames::TAG] + VERIFY = [StepNames::DISCOVER_AWS_ASSETS] end module Filters From b67818715492c9dad9980bda096c6dcf4b49e982 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Thu, 9 Aug 2018 12:15:37 -0500 Subject: [PATCH 13/14] QE-587: Missed a previous_type_name for verify_aws_targets --- lib/eso/integration_options_manager.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/eso/integration_options_manager.rb b/lib/eso/integration_options_manager.rb index 524ea8c8..83f8b1fd 100644 --- a/lib/eso/integration_options_manager.rb +++ b/lib/eso/integration_options_manager.rb @@ -107,8 +107,10 @@ def self.build_sync_aws_assets_option(name:, discovery_conn_id:, site_id: nil) def self.build_verify_aws_targets_option(name:, discovery_conn_id:) step1 = Step.new(service_name: ServiceNames::AWS, type_name: StepNames::VERIFY_AWS_ASSETS) .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) - step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::VERIFY_EXTERNAL_TARGETS, previous_type_name: step1.type_name) - step3 = Step.new(service_name: ServiceNames::AWS, type_name: StepNames::VERIFY_AWS_ASSETS) + step2 = Step.new(service_name: ServiceNames::NEXPOSE, type_name: StepNames::VERIFY_EXTERNAL_TARGETS, + previous_type_name: step1.type_name) + step3 = Step.new(service_name: ServiceNames::AWS, type_name: StepNames::VERIFY_AWS_ASSETS, + previous_type_name: step2.type_name) .add_property(StepConfiguration::ConfigParamProperties::DISCOVERY_CONFIG_ID, discovery_conn_id) IntegrationOption.new(name: name, steps: [step1, step2, step3]) From 4425afff2cd65436e5bb45bc9a0a2760923070f9 Mon Sep 17 00:00:00 2001 From: Heather Wilson Date: Mon, 27 Aug 2018 18:23:38 -0500 Subject: [PATCH 14/14] Non-required change which makes the JSON for configs consistent --- lib/eso/step_configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/eso/step_configuration.rb b/lib/eso/step_configuration.rb index 038ba4ff..6c51fb48 100644 --- a/lib/eso/step_configuration.rb +++ b/lib/eso/step_configuration.rb @@ -25,7 +25,7 @@ def initialize (typeName, previousTypeName, configurationParams=nil, workflowID= @previousTypeName = previousTypeName @configurationParams = configurationParams ? configurationParams : { :valueClass => Values::OBJECT, - :objectType => typeName, + :objectType => 'params', :properties => {}} @workflowID = workflowID if workflowID end