Module: ALGOSEC_SDK::BusinessFlowHelper

Included in:
Client
Defined in:
lib/algosec-sdk/helpers/business_flow_helper.rb

Overview

Contains helper methods for BusinessFlow

Instance Method Summary collapse

Instance Method Details

#apply_application_draft(app_revision_id) ⇒ Object

Apply application draft

Parameters:

  • app_revision_id (String)

Returns:

  • true

Raises:

  • (RuntimeError)

    if the request failed



155
156
157
158
159
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 155

def apply_application_draft(app_revision_id)
  response = rest_post("/BusinessFlow/rest/v1/applications/#{app_revision_id}/apply")
  response_handler(response)
  true
end

#create_application_flow(app_revision_id, flow_name, sources, destinations, network_services, network_users, network_apps, comment, type = 'APPLICATION', custom_fields = []) ⇒ Object

Create a flow rubocop:disable Metrics/ParameterLists

Parameters:

  • app_revision_id (String)

    The application revision id to create the flow in

  • flow_name (Object)
  • sources (Array<String>)
  • destinations (Array<String>)
  • network_users (Array<String>)
  • network_apps (Array<String>)
  • network_services (Array<String>)
  • comment (String)
  • type (String) (defaults to: 'APPLICATION')

Returns:

  • Newly created application flow

Raises:

  • (RuntimeError)

    if the request failed



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 85

def create_application_flow(
  app_revision_id,
  flow_name,
  sources,
  destinations,
  network_services,
  network_users,
  network_apps,
  comment,
  type = 'APPLICATION',
  custom_fields = []
)
  # rubocop:enable Metrics/ParameterLists

  # Create the missing network objects from the sources and destinations
  create_missing_network_objects(sources + destinations)
  create_missing_services(network_services)

  get_named_objects = ->(name_list) { name_list.map { |name| { name: name } } }

  new_flow = {
    name: flow_name,
    sources: get_named_objects.call(sources),
    destinations: get_named_objects.call(destinations),
    users: network_users,
    network_applications: get_named_objects.call(network_apps),
    services: get_named_objects.call(network_services),
    comment: comment,
    type: type,
    custom_fields: custom_fields
  }
  response = rest_post("/BusinessFlow/rest/v1/applications/#{app_revision_id}/flows/new", body: [new_flow])
  flows = response_handler(response)
  # AlgoSec return a list of created flows, we created only one
  flows[0]
end

#create_missing_network_objects(network_object_names) ⇒ Object

Create all the missing network objects which are simple IPv4 ip or subnet

Parameters:

  • network_object_names (Array<String>)

    List of the network object names

Returns:

  • Newly created objects

Raises:

  • (RuntimeError)

    if the request failed



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 305

def create_missing_network_objects(network_object_names)
  # TODO: Add unitests that objects are being create only once (if the same object is twice in the incoming list)
  network_object_names = Set.new(network_object_names)
  ipv4_or_subnet_objects = network_object_names.map do |object_name|
    begin
      IPAddress.parse object_name
      search_result = search_network_object(object_name, NetworkObjectSearchType::EXACT)
      # If no object was found in search, we'll count this object for creation
      search_result.empty? ? object_name : nil
    rescue ArgumentError
      # The parsed object name was not IP Address or IP Subnet, ignore it
      nil
    end
  end.compact

  # Create all the objects. If the error from the server tells us that the object already exists, ignore the error
  ipv4_or_subnet_objects.map do |ipv4_or_subnet|
    create_network_object(NetworkObjectType::HOST, ipv4_or_subnet, ipv4_or_subnet)
  end.compact
end

#create_missing_services(service_names) ⇒ Object

Create all the missing network services which are of simple protocol/port pattern

Parameters:

  • service_names (Array<String>)

    List of the network service names

Returns:

  • Newly created objects

Raises:

  • (RuntimeError)

    if the request failed



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 330

def create_missing_services(service_names)
  parsed_services = service_names.map do |service_name|
    protocol, port = service_name.scan(%r{(TCP|UDP)/(\d+)}i).last
    [service_name, [protocol, port]] if !protocol.nil? && !port.nil?
  end.compact
  # Create all the objects. If the error from the server tells us that the object already exists, ignore the error
  parsed_services.map do |parsed_service|
    service_name, service_content = parsed_service
    begin
      create_network_service(service_name, [service_content])
    rescue StandardError => e
      # If the error is different from "service already exists", the exception will be re-raised
      raise e if e.to_s.index('Service name already exists').nil?
    end
  end.compact
end

#create_network_object(type, content, name) ⇒ Object

Create a new network object

Parameters:

  • type (NetworkObjectType)

    type of the object to be created

  • content (String)

    Define the newly created network object. Content depend upon the selected type

  • name (String)

    Name of the new network object

Returns:

  • Newly created object

Raises:

  • (RuntimeError)

    if the request failed



180
181
182
183
184
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 180

def create_network_object(type, content, name)
  new_object = { type: type, name: name, content: content }
  response = rest_post('/BusinessFlow/rest/v1/network_objects/new', body: new_object)
  response_handler(response)
end

#create_network_service(service_name, content) ⇒ Object

Create a new network service

Parameters:

  • service_name (String)
  • content

    List of lists in the form of (protocol, port)

Returns:

  • true if service created or already exists

Raises:

  • (RuntimeError)

    if the request failed



166
167
168
169
170
171
172
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 166

def create_network_service(service_name, content)
  content = content.map { |service| { protocol: service[0], port: service[1] } }
  new_service = { name: service_name, content: content }
  response = rest_post('/BusinessFlow/rest/v1/network_services/new', body: new_service)
  response_handler(response)
  true
end

#define_application_flows(app_name, new_app_flows) ⇒ Object

Update application flows of an application to match a requested flows configuration.

Parameters:

  • app_name (Integer)

    The app to create the flows for

  • new_app_flows (Object)

    Hash of new app flows, pointing from the flow name to the flow definition

Returns:

  • The updated list of flow objects from the server, including their new flowID

Raises:

  • (RuntimeError)

    if the request failed



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 282

def define_application_flows(app_name, new_app_flows)
  flows_from_server = get_application_flows_hash(get_app_revision_id_by_name(app_name))
  flows_to_delete, flows_to_create, flows_to_modify = plan_application_flows(flows_from_server, new_app_flows)
  implement_app_flows_plan(
    app_name,
    new_app_flows,
    flows_from_server,
    flows_to_delete,
    flows_to_create,
    flows_to_modify
  )

  # Stage 2: Run connectivity check for all the unchanged flows. Check with Chef is this non-deterministic approach
  # is OK with them for the cookbook.
  #
  # Return the current list of created flows if successful
  get_application_flows(get_app_revision_id_by_name(app_name))
end

#delete_flow_by_id(app_revision_id, flow_id) ⇒ Object

Delete a specific flow

Parameters:

  • app_revision_id (String)
  • flow_id (String)

Returns:

  • true

Raises:

  • (RuntimeError)

    if the request failed



56
57
58
59
60
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 56

def delete_flow_by_id(app_revision_id, flow_id)
  response = rest_delete("/BusinessFlow/rest/v1/applications/#{app_revision_id}/flows/#{flow_id}")
  response_handler(response)
  true
end

#get_app_revision_id_by_name(app_name) ⇒ Boolean

Get latest application revision id by application name

Parameters:

  • app_name (String, Symbol)

Returns:

  • (Boolean)

    application revision id

Raises:

  • (RuntimeError)

    if the request failed



145
146
147
148
149
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 145

def get_app_revision_id_by_name(app_name)
  response = rest_get("/BusinessFlow/rest/v1/applications/name/#{app_name}")
  app = response_handler(response)
  app['revisionID']
end

#get_application_flow_by_name(app_revision_id, flow_name) ⇒ Object

Fetch an application flow by it’s name

Parameters:

  • app_revision_id (String)

    The application revision id to fetch the flow from

  • flow_name (Object)

Returns:

  • The requested flow

Raises:

  • (RuntimeError)

    if the request failed



127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 127

def get_application_flow_by_name(app_revision_id, flow_name)
  flows = get_application_flows(app_revision_id)
  requested_flow = flows.find do |flow|
    break flow if flow['name'] == flow_name
  end

  if requested_flow.nil?
    raise(
      "Unable to find flow by name. Application revision id: #{app_revision_id}, flow_name: #{flow_name}."
    )
  end
  requested_flow
end

#get_application_flows(app_revision_id) ⇒ Array<Hash>

Get list of application flows for an application revision id

Parameters:

  • app_revision_id (String, Symbol)

Returns:

  • (Array<Hash>)

    flows

Raises:

  • (RuntimeError)

    if the request failed



37
38
39
40
41
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 37

def get_application_flows(app_revision_id)
  response = rest_get("/BusinessFlow/rest/v1/applications/#{app_revision_id}/flows")
  flows = response_handler(response)
  flows.map { |flow| flow['flowType'] == 'APPLICATION_FLOW' ? flow : nil }.compact
end

#get_application_flows_hash(app_revision_id) ⇒ Hash

Get application flows from the server as a hash from flow name it it’s content

Parameters:

  • app_revision_id (String, Symbol)

Returns:

  • (Hash)

    flows as a hash from name to flow

Raises:

  • (RuntimeError)

    if the request failed



47
48
49
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 47

def get_application_flows_hash(app_revision_id)
  Hash[get_application_flows(app_revision_id).map { |flow| [flow['name'], flow] }]
end

#get_flow_connectivity(app_revision_id, flow_id) ⇒ String

Get connectivity status for a flow

Parameters:

  • app_revision_id (String)
  • flow_id (String)

Returns:

  • (String)

    Connectivity Status dict that contain flowId, queryLink and status keys

Raises:

  • (RuntimeError)

    if the request failed



67
68
69
70
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 67

def get_flow_connectivity(app_revision_id, flow_id)
  response = rest_post("/BusinessFlow/rest/v1/applications/#{app_revision_id}/flows/#{flow_id}/check_connectivity")
  response_handler(response)
end

#implement_app_flows_plan(app_name, new_app_flows, flows_from_server, flows_to_delete, flows_to_create, flows_to_modify) ⇒ Object

Create/modify/delete application2 flows to match a given flow plan returned by ‘plan_application_flows’ param [Array<String>] flows_to_modify List of network flow names to delete and re-create with the new definition

Parameters:

  • app_name (Integer)

    The app to create the flows for

  • new_app_flows (Array<Hash>)

    List of network flows hash definitions

  • flows_from_server (Array<Hash>)

    List of network flows objects fetched from the server

  • flows_to_delete (Array<String>)

    List of network flow names for deletion

  • flows_to_create (Array<String>)

    List of network flow names to create

Returns:

  • True

Raises:

  • (RuntimeError)

    if any of the requests failed



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 228

def implement_app_flows_plan(
  app_name,
  new_app_flows,
  flows_from_server,
  flows_to_delete,
  flows_to_create,
  flows_to_modify
)
  # Get the app revision id
  app_revision_id = get_app_revision_id_by_name(app_name)

  # This param is used to determine if it is necessary to update the app_revision_id
  is_draft_revision = false

  # Delete all the flows for deletion and modification
  (flows_to_delete | flows_to_modify).each do |flow_name_to_delete|
    delete_flow_by_id(app_revision_id, flows_from_server[flow_name_to_delete]['flowID'])
    next if is_draft_revision
    app_revision_id = get_app_revision_id_by_name(app_name)
    # Refetch the fresh flows from the server, as a new application revision has been created
    # and it's flow IDs have been change. Only that way we can make sure that the following flow deletions
    # by name will work as expected
    flows_from_server = get_application_flows(app_revision_id)
    is_draft_revision = true
  end
  # Create all the new + modified flows
  (flows_to_create | flows_to_modify).each do |flow_name_to_create|
    new_flow_data = new_app_flows[flow_name_to_create]
    create_application_flow(
      app_revision_id,
      flow_name_to_create,
      # Document those key fields somewhere so users know how what is the format of app_flows object
      # that is provided to this function
      new_flow_data['sources'],
      new_flow_data['destinations'],
      new_flow_data['services'],
      new_flow_data.fetch('users', []),
      new_flow_data.fetch('applications', []),
      new_flow_data.fetch('comment', '')
    )
    unless is_draft_revision
      app_revision_id = get_app_revision_id_by_name(app_name)
      is_draft_revision = true
    end
  end

  apply_application_draft(app_revision_id) if is_draft_revision
end

#loginArray<Hash>

Request login to get session cookie credentials

Returns:

  • (Array<Hash>)

    flows

Raises:

  • (RuntimeError)

    if the request failed



29
30
31
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 29

def 
  response_handler(rest_post('/BusinessFlow/rest/v1/login'))
end

#plan_application_flows(server_app_flows, new_app_flows) ⇒ Object

Return a plan for modifying application flows based on current and newly proposed application flows definition

Parameters:

  • server_app_flows (Array<Hash>)

    List of app flows currently defined on the server

  • new_app_flows (Array<Hash>)

    List of network flows hash definitions

Returns:

  • 3 lists of flow names: flows_to_delete, flows_to_create, flows_to_modify

Raises:

  • (RuntimeError)

    if the request failed



204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 204

def plan_application_flows(server_app_flows, new_app_flows)
  current_flow_names = Set.new(server_app_flows.keys)
  new_flow_names = Set.new(new_app_flows.keys)
  # Calculate the flows_to_delete, flows_to_create and flows_to_modify and unchanging_flows
  flows_to_delete = current_flow_names - new_flow_names
  flows_to_create = new_flow_names - current_flow_names
  flows_to_modify = Set.new((new_flow_names & current_flow_names).map do |flow_name|
    flow_on_server = server_app_flows[flow_name]
    new_flow_definition = new_app_flows[flow_name]
    ALGOSEC_SDK::AreFlowsEqual.flows_equal?(new_flow_definition, flow_on_server) ? nil : flow_name
  end.compact)

  [flows_to_delete, flows_to_create, flows_to_modify]
end

#search_network_object(ip_or_subnet, search_type) ⇒ Object

Search a network object

Parameters:

  • ip_or_subnet (String)

    The ip or subnet to search the object with

  • search_type (NetworkObjectSearchType)

    type of the object search method

Returns:

  • List of objects from the search result

Raises:

  • (RuntimeError)

    if theh request failed



191
192
193
194
195
196
197
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 191

def search_network_object(ip_or_subnet, search_type)
  response = rest_get(
    '/BusinessFlow/rest/v1/network_objects/find',
    query: { address: ip_or_subnet, type: search_type }
  )
  response_handler(response)
end