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



215
216
217
218
219
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 215

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(name, custom_fields = [], contacts = [], labels = [], flows = []) ⇒ Object

Create an application

Parameters:

  • name (String)

    The application’s name.

  • custom_fields (Array<String>) (defaults to: [])

    Existing custom fields to assign to the application.

  • contacts (Array<String>) (defaults to: [])

    Existing contacts to assign to the application.

  • labels (Array<String>) (defaults to: [])

    Existing labels to assign to the application.

  • flows (Array<String>) (defaults to: [])

    The flows to add to the application upon creation.

Returns:

  • Newly created Application object

Raises:

  • (RuntimeError)

    if the request failed



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 43

def create_application(
  name,
  custom_fields = [],
  contacts = [],
  labels = [],
  flows = []
)
  new_application = {
    name: name,
    custom_fields: custom_fields,
    contacts: contacts,
    labels: labels,
    flows: flows
  }
  response = rest_post('/BusinessFlow/rest/v1/applications/new', body: new_application)
  response_handler(response)
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



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 122

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



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 365

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|
    next unless get_network_object_type(object_name)
    search_results = search_network_object(object_name, NetworkObjectSearchType::EXACT)
    if search_results.empty?
      # nothing was found, mark the object for creation
      object_name
    else
      # if no object named the same way, mark it for creation
      search_results.any? { |result| result['name'] == object_name } ? nil : object_name
    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(get_network_object_type(ipv4_or_subnet), 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



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 390

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



240
241
242
243
244
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 240

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



226
227
228
229
230
231
232
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 226

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

#decommission_application(app_revision_id) ⇒ Object

Decommission an application

Parameters:

  • app_revision_id (String)

Returns:

  • true

Raises:

  • (RuntimeError)

    if the request failed



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

def decommission_application(app_revision_id)
  response = rest_post("/BusinessFlow/rest/v1/applications/#{app_revision_id}/decommission")
  response_handler(response)
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



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 342

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



93
94
95
96
97
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 93

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_id_by_name(app_name) ⇒ Boolean

Get application id by it’s name

Parameters:

  • app_name (String, Symbol)

Returns:

  • (Boolean)

    application id

Raises:

  • (RuntimeError)

    if the request failed



207
208
209
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 207

def get_app_id_by_name(app_name)
  get_application_by_name(app_name)['applicationId']
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



199
200
201
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 199

def get_app_revision_id_by_name(app_name)
  get_application_by_name(app_name)['revisionID']
end

#get_application_by_name(app_name) ⇒ Hash

Get application by name

Parameters:

  • app_name (String, Symbol)

Returns:

  • (Hash)

    application object

Raises:

  • (RuntimeError)

    if the request failed



190
191
192
193
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 190

def get_application_by_name(app_name)
  response = rest_get("/BusinessFlow/rest/v1/applications/name/#{app_name}")
  response_handler(response)
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



164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 164

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



74
75
76
77
78
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 74

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



84
85
86
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 84

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

#get_applicationsArray<Hash>

Get all applications

Returns:

  • (Array<Hash>)

    application objects

Raises:

  • (RuntimeError)

    if the request failed



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

def get_applications
  response = rest_get('/BusinessFlow/rest/v1/applications/')
  response_handler(response)
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



104
105
106
107
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 104

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



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 288

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_hash(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



31
32
33
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 31

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



264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 264

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



251
252
253
254
255
256
257
# File 'lib/algosec-sdk/helpers/business_flow_helper.rb', line 251

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