Help us improve your experience.

Let us know what you think.

Do you have time for a two-minute survey?

 
 

Overview of Third-Party Applications on Junos OS Evolved

Introduction to Third-Party Applications on Junos OS Evolved

Junos OS Evolved runs natively on Linux, which means you can integrate third-party applications and tools developed for Linux into Junos OS Evolved. Linux development tools also give you the power to create and run your own applications on Junos OS Evolved. You can choose to run these applications inside a container, or natively on the device with signing keys.

Running Applications in Containers vs Using Signing Keys

There are two ways to run a third-party applications in Junos OS Evolved: running inside a container, or running natively using signing keys.

Applications in Containers

Junos OS Evolved supports running applications inside Docker containers. Containers run on Junos OS Evolved, and applications run inside the containers, keeping them isolated from the OS. You can use prebuilt Docker container images and install additional tools and libraries inside the container. Containers can be upgraded by using Linux workflow.

Containers are already a commonly used method for running Linux applications, so many existing third-party applications can be easily imported into Junos OS Evolved by deploying them inside containers. The isolated nature of containers makes them easy to deploy and remove without compromising the integrity of Junos OS Evolved. In addition, Junos OS Evolved places default limits on the resource usage of containers, to ensure that rogue containers cannot overwhelm your system.

The Docker container service is not automatically started at system initialization. To enable automatic startup for the Docker container service, enter the following command from the Linux shell:

For more information about running applications in containers, see Running Third-Party Applications in Containers

Using Signing Keys

The other method of running third-party applications on Junos OS Evolved is by using signing keys. You can generate signing keys and use them to sign executable files or shared objects. Signing an executable file gives it permission to run on the device, allowing you to approve trusted applications to run alongside authorized Juniper Networks software.

Signing keys are controlled by a Linux subsystem called Integrity Measurement Architecture (IMA). IMA policy consists of rules that define which actions needs to be taken before a file can be executed. IMA measurement policy will measure and store a file’s hash, and IMA appraisal policy will make sure that the file has a valid hash or digital signature. IMA will only allow a file to run if this validation succeeds.

Junos OS Evolved requires users to sign all files that will be mapped into memory for execution. IMA verification helps ensure that these files have not been accidentally or maliciously altered. Containers and files inside containers do not need to be signed.

For more information about using signing keys, see Signing Third-Party Applications to Run Natively on Junos OS Evolved

Security Caveats

Junos OS Evolved is designed from the ground up with security in mind. IMA and Linux containers help to control the security impact of third-party applications on Junos OS Evolved, but third-party applications still have the potential to introduce security vulnerabilities through malicious code.

Always consider the security implications of adding a third-party application to Junos OS Evolved. Make sure any applications you add to Junos OS Evolved are thoroughly vetted for potential security risks.

Application Pre-requisites

Third party application support was introduced with Junos OS Evolved release 20.1R1, so in order to install and use third party applications you must be running Junos OS Evolved release 20.1R1 or later.

Applications must support the Linux kernel version running on Junos OS Evolved to work properly. Use the show version command to view the currently running Linux kernel version.

Applications written for Junos OS Evolved typically require the ability to read and modify the networking state, to send and receive packets, and to read and modify the configuration. Junos OS Evolved supports a limited number of APIs, so applications must be configured with these APIs in mind.

Application APIs

There are two categories of APIs used by applications:

  • Linux APIs for reading and modifying the networking state, and sending and receiving packets.

  • Juniper APIs for interacting with the system.

Junos OS Evolved supports these two categories of APIs. Table 1 provides a high-level view of the set of APIs used by applications:

Table 1: Application APIs

API

Functionality

Packet IO and Linux socket APIs

Ability to send and receive packets over mgmt and/or data interfaces.

Standard libc – send, receive, listen, etc.

rtnetlink

Ability to use rtnetlink to query networking state like interfaces, routes, etc.

netdevice

Ability to configure network devices.

proc

Ability to query kernel data structures using standard interfaces provided by Linux kernel.

Junos APIs

Ability to access Juniper Northbound APIs - NetConf/JET/Telemetry.

How to Run Applications

Applications can be launched by using Linux syntax for execution:

Using Intercept Libraries

Junos OS Evolved is the same as Junos OS except that it runs on native Linux and, therefore, can accommodate running third-party applications. There are some differences between the way Linux displays requested network topology information such as interface and route data and the way Junos OS displays this information. The CLI is designed to overcome these differences. But typically, third-party applications running on native Linux obtain this information directly from the native Linux sources using shell commands.

Junos OS Evolved uses an intercept mechanism that redirects shell requests for network topology information to a space where the information can be obtained from Junos OS. This intercept mechanism is accomplished through intercept libraries, libsi.so and libnli.so, that you preload. After you preload the intercept library, certain types of requests are intercepted and show Junos OS information.

The intercept libraries are optional; they are needed only if the application requires the APIs mentioned in Table 2:

Table 2: APIs That Require Intercept Libraries

API

Description

Packet IO and Linux socket APIs

Ability to send and receive packets over management and/or data interfaces. Standard libc, such as send, receive, listen.

rtnetlink

Ability to use rtnetlink to query networking state like interfaces, routes.

netdevice

Ability to configure network devices.

proc

Ability to query kernel data structures using standard interfaces provided by Linux kernel.

Junos APIs

Ability to access Juniper North Bound APIs - NetConf/JET/Telemetry.

Note:

Junos OS Evolved Release 20.1R1 supports the following features:

  • Use the set system netlink-async-mode configuration to enable NETLINK_ROUTE asynchronous notifications. This feature is disabled by default. Use show nsld mode to show the current netlink asynchronous mode.

  • SIOCETHTOOL ioctl, which can be used by other applications.

  • Multipath next-hop route information through netlink route attributes.

Example of a Preloaded Linux Command

An example how the preload directive works follows using the command ifconfig, which displays interfaces.

If you preload the ifconfig command with the intercept library, Junos OS interface information is returned. Notice that the intercept library only translates logical interfaces. In this example, because there are logical interfaces only on lo0 and re0:mgmt-0.0, the output displays only these two interfaces for the preloaded ifconfig command.

You can get the same results by running jbash, which is a shell provided with Junos OS Evolved that preloads libnli.so and libsi.so by default.

CAUTION:

Only use jbash to get the network state information. Don’t use jbash as your default shell.

If you issue the command without preloading it with the intercept library, the output shown is from Linux. Notice that the following output is longer than that from Junos OS. Linux does not make the distinction between physical interfaces and logical interfaces that the Junos CLI does.

Interface Name Translation

One limiting factor to using this intercept mechanism is that Linux interface naming is incompatible with the Junos OS interface naming. Linux supports 15-byte interface names (15 + null-character); network interface names that exceed this limit are truncated in outputs. Junos OS logical interface names could be longer than 15 bytes, for example, et-0/0/10:2.32767.

To work around this difference, Junos OS Evolved uses a translation rule (see Table 3) to render logical interface names in a Linux-compliant format. The translation renders a format such as name-fpcSlot/picSlot/port:channelId.subUnit to nn-ffpttccssss. Using interface names translated according to this rule, third-party applications can effectively fetch the topology information from Junos OS.

Only translation of logical interface names is supported, and translation of both channelized and nonchannelized logical interface names is supported.

Table 3: Translation Rule for Interface Names

Value

Description

Allotted Space (in bytes)

Range

nn

mapped name bytes

2

ff

fpc in hex

2

0-255

p

pic in hex

1

0-15

tt

port number in hex

2

0-255

cc

channel in hex; use “xx” if not present

2

0-255

ssss

subunit in hex

4

0-65535

Except for management interfaces, if the logical interface name does not have a hyphen (-) in it, the dot (.) in the name is changed to an underscore (_), for example: ifdname.subunit gets translated to ifdname_subunit.

For management interfaces, reX:mgmt-Y.Z translates to mgmt-x-yy-zzzz, where x, yy, zzzz are in hex-padded with 0 for a fixed length. And the reverse translation happens on the same lines.

See Table 4 for examples of Junos logical interface names and their Linux-compliant forms.

Table 4: Examples of Translated Logical Interface Names

Junos Logical Interface Name

Translated Linux-Compliant Interface Name

et-1/2/3.4

et-01203xx0004

ge-1/2/3.32

ge-01203xx0020

et-1/15/3.4

et-01f03xx0004

et-1/2/255:6.7

et-012ff060007

et-1/2/4:5.32767

et-01204057fff

re0:mgmt-1.2

mgmt-0-01-0002

ae0.1

ae0_1

irb0.11

irb0_11

When accessing Junos OS states by preloading libnli.so, the interface name in the output is shown as a translated Linux-compliant interface name. You must also use the translated Linux-compliant interface name when using it as an argument in a command. The translated et-01000000000 interface name is used as an argument in the following example:

Other Caveats for the Intercept Feature

This intercept feature supports read-only requests. Any write request returns an error.

Representation of certain Junos network state may not be mappable to Linux equivalents. In these cases, the data is either be omitted or re-mapped to a comparable Linux model. For example, Junos OS Evolved supports a rich suite of nexthop types such as composite or unilist that do not have comparable implementations in native Linux.

Third-party applications that are linked statically cannot be intercepted and, therefore, are not supported by this feature.

Running Third-Party Applications in Containers

To run your own applications on Junos OS Evolved, you have the option to deploy them inside a Docker container. The container runs on Junos OS Evolved, and the agents run inside the container, keeping them isolated from the OS. Containers are installed in a separate partition mounted at /var/extensions.

Note:

Docker containers are not integrated into Junos OS Evolved, they are created and managed entirely through Linux by using Docker commands. For more information on Docker containers and commands, see the official Docker documentation: https://docs.docker.com/get-started/

Containers have default limits for the resources that they can use from the system:

  • Storage – The size of the /var/extensions partition is platform driven: 8GB or 30% of the total size of /var, whichever is smaller.

  • Memory – Containers have a default limit of 2GB or 10% of total physical memory, whichever is smaller.

  • CPU – Containers have a default limit of 20% max CPU use across all cores.

Note:

You can modify the resource limits on containers if necessary. See Modifying Resource Limits for Containers.

Deploying a Docker Container

To deploy a docker container:

  1. Start the docker service using the vrf0 socket:
  2. Set the following setenv variable:
  3. Import the image.
    Note:

    The URL for the import command needs to be changed for different containers.

  4. Make sure the image is downloaded, and get the image ID.
  5. Create a container using the image ID and enter a bash session in that container.
    Note:

    Docker containers are daemonized by default unless you use the -it argument.

Managing a Docker Container

Docker containers are managed through Linux workflow. Use the ps or top Linux commands to show which Docker containers are running, and use Docker commands to manage the containers. For more information on Docker commands, see: https://docs.docker.com/engine/reference/commandline/cli/

Note:

Junos OS Evolved high availability features are not supported for custom applications in Docker containers, If an application has high availability functionality then you should run the application on each RE to ensure it can sync itself.

Enabling Netlink or Packet IO in a Container

You need to provide additional arguments to Docker commands if your container requires extra capabilities like Netlink or Packet IO. The following example shows how to activate Netlink or Packet IO capabilities for a container by adding arguments to a Docker command:

  1. Create a read-only name persistent volume upon starting Docker services:

  2. Share the host’s network namespace with the container process:

  3. Automatically start the container upon system reboot:

  4. Enable net admin capability, which is required by Netlink and Packet IO libraries:

  5. Enable the environmental variables required for Netlink and Packet IO:

Selecting a VRF for a Docker Container

Containers inherit virtual routing and forwarding (VRF) from the Docker daemon. In order to run containers in a distinct VRF, a Docker daemon instance needs to be started in the corresponding VRF. The docker@vrf.service instance allows for starting a daemon in the corresponding VRF. If the VRF is unspecified, the VRF defaults to vrf0.

The docker.service runs in vrf:none by default.

The docker daemon for a specific VRF listens on corresponding socket located at /run/docker-vrf.sock.

The Docker client gets associated with the VRF specific docker daemon by use the following arguments:

For example, to run a container in vrf0 enter the following Docker command and arguments:

Note:

A container can only be associated to a single VRF.

Modifying Resource Limits for Containers

The default resource limits for containers are controlled through a file located at /etc/extensions/platform_attributes. You will see the following text upon opening this file:

To change the resource limits for containers, add values to the EXTENSIONS entries at the bottom of the file:

  • EXTENSIONS_FS_DEVICE_SIZE_MIB= controls the maximum storage space that containers can use. Enter the value in bytes. The default value is 8GB or 30% of the total size of /var, whichever is smaller.

  • EXTENSIONS_CPU_QUOTA_PERCENTAGE= controls the maximum CPU usage that containers can use. Enter a value as a percentage of CPU usage. The default value is 20% max CPU use across all cores

  • EXTENSIONS_MEMORY_MAX_MIB= controls the maximum amount of physical memory that containers can use. Enter the value in bytes. The default value is 2GB or 10% of total physical memory, whichever is smaller.

CAUTION:

Before modifying the resource limits for containers, be aware of the CPU and memory requirements for the scale you have to support in your configuration. Exercise caution when increasing resource limits for containers to prevent them from causing a strain on your system.

Protecting the Integrity of Junos OS Evolved with IMA

Network devices that run Junos OS Evolved are protected by an integrity solution called Integrity Measurement Architecture (IMA).

Integrity is a fundamental security property that represents trust, completeness, and freedom from alteration. In computer security, common targets for integrity protections are operating system files. A common method of ensuring integrity is to compare a file against a known good file.

In the context of Junos OS Evolved, the security goal is to ensure that the software running on a device has not been accidentally or maliciously altered. The software running on a device is either authentic Junos software from Juniper Networks or authorized software deployed by a customer.

The threat model for network devices includes attempts by malicious actors to deploy malware that violates either the implicit or explicit policies of device owners. Such malware could include back doors, Trojan horses, or implants that could adversely the safe and secure operation of devices or networks. Malicious actors use a variety of tools, techniques, and procedures to breach integrity including physical attacks, local attacks, and remote attacks.

Many regulatory schemes levy file integrity requirements, including PCI-DSS - Payment Card Industry Data Security Standard (Requirement 11.5), SOX - Sarbanes-Oxley Act (Section 404), NERC CIP - NERC CIP Standard (CIP-010-2), FISMA - Federal Information Security Management Act (NIST SP800-53 Rev3), HIPAA - Health Insurance Portability and Accountability Act of 1996 (NIST Publication 800-66) and the SANS Critical Security Controls (Control 3).

In order to ensure file integrity and to mitigate the malware risk, Junos OS Evolved runs IMA, and a companion mechanism: the Extended Verification Module (EVM). These open source protections are part of a set of Linux Security Modules that are industry-standard and consistent with the trust mechanisms specified by the Trusted Computing Group.

Juniper Networks applies digital signatures to Junos OS Evolved files, and allows customers to apply digital signatures as well. Digital signatures are created using protected private keys, and then verified using public keys embedded into one or more keyrings.

The IMA/EVM subsystem protects the system by performing run-time checks. If a file fails verification, it is not opened or executed.

That means that unverified software is blocked on a device running Junos OS Evolved.

Signing Third-Party Applications to Run Natively on Junos OS Evolved

Signing Keys Overview

Starting in Junos OS Evolved Release 20.1R1, you can generate signing keys and use them to sign executable files or shared objects. Signing an executable file gives it permission to run on the device, allowing you to approve trusted applications to run alongside authorized Juniper Networks software.

Junos OS Evolved requires users to sign all files that will be mapped into memory for execution. This includes the following file types:

  • Executable and Linkable Format (ELF) files

  • Shared Objects (.so) files

The following types of files do not need to be signed:

  • Docker containers

  • Applications inside containers

  • Scripts

    Note:

    Although scripts don’t need to be signed, they do need to be passed through a signed interpreter for execution. Junos OS Evolved comes installed with signed Python 2 and Python 3 interpreters that can be used through the python script-name shell command.

Signing keys are controlled by a Linux subsystem called Integrity Measurement Architecture (IMA). IMA policy consists of rules that define which actions needs to be taken before a file can be executed. IMA measurement policy will measure and store a file’s hash, and IMA appraisal policy will make sure that the file has a valid hash or digital signature. IMA will only allow a file to run if this validation succeeds. For more information about IMA, see Protecting the Integrity of Junos OS Evolved with IMA.

Signing keys are stored in the system keystore, and the certificates used the verify signing keys are stored in the IMA extended keyring. Keep reading to learn how to generate, import, view, and use signing keys.

Generating Signing Keys

Keys can be generated through the OpenSSL command-line or a OpenSSL configuration file.

Generating Signing Keys Using the OpenSSL Command-Line

The following example OpenSSL command can be used to generate signing keys:

This command will generate 2 files:

  1. privkey.pem - The PEM encoded private key that can be used to sign executable files.

  2. ima-cert.x509 - The DER encoded certificate to be loaded into the IMA extended keyring.

Note:

The OpenSSL command-line is limited in its functionality. It does not allow you to set values for the X509v3 extensions. All keys generated using the command above can be used as Certificate Authorities (CAs), and therefore can be used to sign other certificates. To prevent this, we can use an OpenSSL Configuration File.

Generating Signing Keys Using an OpenSSL Configuration File

Create a file named ima-x509.cnf and paste the following contents:

After the configuration file is created, use the following OpenSSL command to create the ima-privkey.pem and ima-cert.x509 files:

The private key file ima-privkey.pem is used to generate signing keys, and the certificate file ima-cert.x509 is used to verify the signature. Both files are used during the process of importing signing keys into the system keystore and IMA extended keyring.

Importing Signing Keys into the System Keystore and IMA Extended Keyring

Signing keys need to be imported into the system keystore prior to use. Keys that are imported into the system keystore are automatically imported into the IMA extended keyring.

To import a signing key into the system keystore, use the request security system-keystore import command with the following 3 mandatory arguments:

  1. key-name - A unique name for the key

  2. private-key - Path to the private key file

  3. x509-cert - Path to the DER encoded certificate file

The following example command will create a key named ima-test-key by using the private key file ima-privkey.pem and the certificate file ima-cert.x509:

When the key is successfully imported into the system-keystore you will see the above output displaying the name of the key, the paths to the private key and certificate on disk, and the Subject Key Identifier (SKI) for the key. You can check if this SKI matches with the key loaded into the IMA Extended keyring with the following command:

Viewing the System Keystore and IMA Extended Keyring

You can view the contents of the system keystore and the IMA extended keyring through Junos OS Evolved CLI show commands.

Use the show security integrity system-keystore command to view the available signing keys in the system keystore:

The information in the Key SKI field can be used to map these keys to the IMA extended keyring.

Use the show security integrity extended-keyring command to view the contents of the IMA extended keyring:

How to Sign Applications

After a signing key has been imported into the system keystore, it can be used to sign executable binaries.

Use the request security integrity measure file filename key key-name command to sign a file.

The following example command shows a file named ima-test being signed by a key named ima-test-key:

You can verify that your file was successfully signed by using the request security integrity appraise file filename key key-name command, as follows:

If the file was not signed properly, the following message will display:

After a file has been signed, it can be run natively on your Junos OS Evolved device.

How to Run Signed Applications

On attempting to execute a file that has not been signed, you may get a Permission Denied error:

Once the file has been successfully signed, it can then be executed from a shell prompt by adding the ./ prefix in front of the filename:

Removing Third-Party Applications

There are several methods for removing third-party applications. The method you should use is based on how you installed the application.

  1. If a third-party application was installed with the request system software add command, then you can remove the same application by using the request system software delete command.

  2. If a third-party application was installed by copying binaries, then you need to know the location of the installed binaries and the key used to sign them.

    The first step in removing these applications is to unlink the key with the request security system-keystore unlink key command.

    Next, remove any binaries that you installed for the application with the rm -f /path/to/binary1 /path/to/binary2 shell command.

  3. If a third-party application was installed through a Docker container, then use the following Docker command to remove the container: