Advanced Usage#

This document explains some of the illumio library and the Policy Compute Engine’s more complex features.

Proxy Settings#

If you need to use a proxy to communicate with the PCE, HTTP/S proxies can be configured using the set_proxies function:

>>> pce.set_proxies(
...     http_proxy='http://my.proxyserver.com:8080',
...     https_proxy='http://my.proxyserver.com:8080'
... )

If not set in the session, the requests library will pull proxy settings from environment variables, see the requests proxy documentation for details.

Note

Proxy values set with set_proxies will apply to the session, and will be overwritten by proxy values set in the executing shell environment. If you need to override environment proxy settings, you can specify the proxies parameter directly as a keyword argument:

>>> pce.ip_lists.get(proxies={'http': 'http://proxy.server:8080', 'https': 'http://proxy.server:8080'})

TLS Certificates#

If you’re using the illumio library with an on-prem PCE, you may be using self-signed or internal ceriticate chains for your instance.

Requests through the PolicyComputeEngine can leverage the requests library verify and cert parameters to specify CA certificates and cert/key pairs respectively.

See the requests documentation for details.

Disable TLS verification:

>>> pce.labels.get(verify=False)

Verify using custom CA bundle:

>>> pce.labels.get(verify='/path/to/ca/bundle')

Verify using a local client-side cert pair:

>>> pce.labels.get(verify=True, cert='/path/to/keypair.pem')
>>> pce.labels.get(verify=True, cert=('/path/to/client.crt', '/path/to/client.key'))

Asynchronous Collection Requests#

The PCE provides dedicated endpoints for reading large object collections through the API. These collection requests kick off asynchronous jobs on the PCE, and return the job status URL to be polled until the job is completed. See the REST API async overview for details.

The get_async and get_collection methods abstract the async job poll-wait loop from the caller in order to provide a simpler synchronous interface for collection requests. If your implementation requires control over job polling, you can set the poll/wait loop up manually:

>>> resp = pce.get(headers={'Prefer': 'respond-async'})
>>> job_poll_url = resp.headers['Location']
>>> poll_interval = resp.headers['Retry-After']
>>> while True:
>>>     resp = pce.get(job_poll_url)
>>>     poll_result = resp.json()
>>>     poll_status = poll_result['status']
>>>     if poll_status == 'done':
>>>         collection_href = poll_result['result']['href']
>>>         break
>>>     elif poll_status == 'failed':
>>>         raise IllumioException("Job failed: {}".format(poll_result))
>>>     time.sleep(poll_interval)
>>> resp = pce.get(collection_href)
>>> job_results = resp.json()

Container Clusters and Workloads#

The PCE can provide visibility and enforcement for Kubernetes and OpenShift container orchestration clusters, representing them as ContainerCluster objects. Container clusters in turn are made up of ContainerWorkload objects, which are governed by ContainerWorkloadProfile objects.

While Kubelink and CVEN deployments must be installed in your Kubernetes or OpenShift cluster separately, container cluster objects can be created using the illumio library and used to pair these deployments.

Note

When a container cluster is created through the API, the container_cluster_token is returned in the POST response. This token is only available after the object is created and cannot be retrieved via the API: make sure to store it in a secure, persistent form such as a Kubernetes or OpenShift Secret.

>>> container_cluster = ContainerCluster(
...     name='CC-GKE-Prod',
...     description='Production Kubernetes cluster on GCP'
... )
>>> container_cluster = pce.container_clusters.create(container_cluster)
>>> container_cluster
ContainerCluster(
    href='/orgs/1/container_clusters/bba0aa4d-9613-4cf0-b51c-eeb598f2b0f4',
    name='CC-GKE-Prod',
    description='Production Kubernetes cluster on GCP',
    container_cluster_token='1_b7abea42cdade009ab68df7d8e2422749ea38cdbb31d5230bb08258358e58647',
    ...
)

Virtual Services#

Sometimes workloads may run multiple services or processes, making it difficult to fit them into the typical labelling workflow. Container cluster nodes are a good example. For these cases, VirtualService objects provide an abstraction for a given service that can be labelled and bound to one or more workloads. The virtual service object can then be referenced in rule definitions through the resolve_labels_as block to apply policy for the service across all bound workloads.

See the virtual services section of the Illumio Policy Guide for details.

In the following example, we define two virtual services for a RabbitMQ cluster serving channels for multiple applications. We’ll explore how they can be used in the sections below.

>>> rabbitmq_svc = pce.services.create(Service(
...     name='S-RabbitMQ',
...     service_ports=[
...         ServicePort(port=5671, to_port=5672, proto='tcp'),   # AMQP/S listeners
...         ServicePort(port=15671, to_port=15672, proto='tcp'), # mgmt API/UI, rmqadmin
...     ]
... ))
>>> rmq_role_label = pce.labels.create(Label(key='role', value='R-RabbitMQ'))
>>> notifier_app_label = pce.labels.create(Label(key='app', value='A-Notifier'))
>>> newsfeed_app_label = pce.labels.create(Label(key='app', value='A-NewsFeed'))
>>> prod_env_label = pce.labels.create(Label(key='env', value='E-Prod'))
>>> aws_loc_label = pce.labels.create(Label(key='loc', value='L-AWS'))
>>> notifier_virtual_service = pce.virtual_services.create(
...     VirtualService(
...         name='VS-Notifier-RabbitMQ',
...         apply_to=ApplyTo.HOST_ONLY,
...         service=rabbitmq_svc,
...         labels=[rmq_role_label, notifier_app_label, prod_env_label, aws_loc_label]
...     )
... )
>>> newsfeed_virtual_service = pce.virtual_services.create(
...     VirtualService(
...         name='VS-NewsFeed-RabbitMQ',
...         apply_to=ApplyTo.HOST_ONLY,
...         service=rabbitmq_svc,
...         labels=[rmq_role_label, newsfeed_app_label, prod_env_label, aws_loc_label]
...     )
... )

Service Bindings#

To associate workloads with a virtual service, you will need to create a ServiceBinding object. Service bindings link one or more workloads to a virtual service so they can be referenced in rules, as we’ll show in the next section.

Note

Virtual services must be provisioned to active state before service bindings can be applied. The example below shows two ways of referencing the active HREF, first using a Reference object, and second by updating the virtual service object’s href field directly.

We’ll extend the example above by binding the RabbitMQ server workload to both virtual services:

>>> rmq_workload = pce.workloads.create(Workload(
...     name='RabbitMQ Prod', hostname='rmq0.company.com', public_ip='10.0.129.44'
... ))
>>> policy_version = pce.provision_policy_changes(
...     change_description="Create RabbitMQ virtual services",
...     hrefs=[rabbitmq_svc.href, notifier_virtual_service.href, newsfeed_virtual_service.href]
... )
>>> notifier_vs_active_ref = Reference(
...     href=convert_draft_href_to_active(notifier_virtual_service.href)
... )
>>> notifier_binding = ServiceBinding(
...     virtual_service=notifier_vs_active_ref,
...     workload=rmq_workload,
...     port_overrides=[
...         PortOverride(
...             port=5671, new_port=5673, new_to_port=5674, proto='tcp'
...         )
...     ]
... )
>>> newsfeed_virtual_service.href = convert_draft_href_to_active(newsfeed_virtual_service.href)
>>> newsfeed_binding = ServiceBinding(
...     virtual_service=newsfeed_virtual_service,
...     workload=rmq_workload,
...     port_overrides=[
...         PortOverride(
...             port=5671, new_port=5675, new_to_port=5676, proto='tcp'
...         )
...     ]
... )
>>> pce.service_bindings.create([notifier_binding, newsfeed_binding])

Writing Rules for Virtual Services#

When using virtual services, your security policy rules will need to be written slightly differently. Since the virtual service already contains the service definition, the rule’s ingress_services parameter is left blank. The rule must also explicitly be configured to resolve labels as virtual services.

See the virtual service rule writing guide for details.

To illustrate the concept, we’ll create a rule set for the NewsFeed application and add a rule allowing all workloads to reach the RabbitMQ server using the virtual service we created above:

>>> newsfeed_rule_set = pce.rule_sets.create(
...     RuleSet(
...         name='RS-NewsFeed-Prod',
...         scopes=[
...             LabelSet(labels=[newsfeed_app_label, prod_env_label, aws_loc_label])
...         ]
...     )
... )
>>> newsfeed_rmq_rule = Rule.build(
...     consumers=[AMS],
...     providers=[rmq_role_label],
...     ingress_services=[],
...     resolve_providers_as=[RESOLVE_AS_VIRTUAL_SERVICES]
... )
>>> pce.rules.create(newsfeed_rmq_rule, parent=newsfeed_rule_set)

Traffic Queries#

You can use the PCE API to search the traffic database for flows matching specific criteria. The traffic query endpoints provide the same interface as the Explorer feature in the PCE.

Note

The synchronous traffic query endpoint and get_traffic_flows function are deprecated - use the async endpoint and get_traffic_flows_async function instead. See the Explorer REST API docs for details.

>>> traffic_query = TrafficQuery.build(
...     start_date="2022-07-01T00:00:00Z",
...     end_date="2022-08-01T00:00:00Z",
...     include_services=[
...         {'port': 3389, 'proto': 'tcp'},
...         {'port': 3389, 'proto': 'udp'},
...     ],
...     policy_decisions=['blocked', 'potentially_blocked', 'unknown']
... )
>>> blocked_rdp_traffic = pce.get_traffic_flows_async(
...     query_name='blocked-rdp-traffic-july-22',
...     traffic_query=traffic_query
... )