Crate jet

source ·
Expand description

Client bindings for the Juniper JET gRPC API.

Building

The gRPC service bindings are generated by tonic-build at build-time.

The protoc command line tool (included in the protobuf package on most Linux distributions) must be available at compile time.

Features

The JET gRPC service definitions vary be Junos version.

Support for each major.minor Junos version is enabled by a corresponding junos-{major}-{minor} cargo feature.

Only the latest supported Junos version is enabled by default, via the latest cargo feature.

Multiple versions can be enabled simultaneously, but will impact compile times.

Examples

To run the examples, a device running the appropriate version of Junos should be available, and configured to enable the JET gRPC server endpoint.

For convenience a set of RSA keys and associated X.509 certificates are provided in examples/pki/. These are for testing purposes only and should never be used for production deployments.

Router configuration

Minimally, the device should be configured with the appropriate server TLS certificate.

First, copy the PEM encoded server certificate and key bundle onto the device:

$ scp examples/pki/server.bundle.pem $JUNOS_DEVICE_ADDR:/var/tmp/

And load the file contents into the configuration:

[edit]
support@router# set security certificates local jet-example load-key-file /var/tmp/server.bundle.pem

The server X.509 certificate contains a Subject Alternative Name extension containing router.example.net.

Then configure the JET service to use the certificate:

[edit system services extension-service]
support@router# show
request-response {
    grpc {
        ssl {
            local-certificate jet-example;
            mutual-authentication {
                certificate-authority ca;
                client-certificate-request request-certificate-and-verify;
            }
        }
        max-connections 8;
        routing-instance mgmt_junos;
    }
}

To avoid having verification fail without hacking at your local DNS infrastructure, the example application provides a CLI option --server-tls-domain-name that will override the name used during server certificate verification.

Mutual TLS authentication

Optionally, to enable mutual TLS authentication, the Junos device should be configured to verify the presented client certificate using the appropriate CA certificate.

Transfer the CA X.509 certificate to the device:

$ scp examples/pki/ca.crt $JUNOS_DEVICE_ADDR:/var/tmp/

Configure a ca-profile for the CA:

[edit security pki]
support@router# show
ca-profile ca {
    ca-identity ca;
}

And load the file contents into configured ca-profile:

support@router> request security pki ca-certificate load ca-profile ca filename /var/tmp/ca.crt

Then configure the JET service to require client certificates and to verify using the correct CA:

[edit system services extension-service request-response grpc ssl]
support@router# show
local-certificate jet-example;
mutual-authentication {
    certificate-authority ca;
    client-certificate-request require-certificate-and-verify;
}

The example application can then be run using --client-cert-path examples/pki/client.crt and --client-key-path examples/pki/client.key options.

use std::error::Error;
use std::fs;
use std::path::PathBuf;

use clap::Parser;

use jet::junos_20_4::jnx::jet::{
    authentication::{authentication_client::AuthenticationClient, LoginRequest},
    common::StatusCode,
    management::{
        management_client::ManagementClient, op_command_get_request::Command, OpCommandGetRequest,
        OpCommandOutputFormat,
    },
};

use rpassword::prompt_password;

use simple_logger::SimpleLogger;

use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};

/// JET demo application.
#[derive(Debug, Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
    /// Junos operational mode command to execute
    command: String,
    /// JET server hostname.
    #[arg(short = 'H', long)]
    host: String,
    /// JET service TCP port.
    #[arg(short = 'P', long, default_value_t = 32767)]
    port: u16,
    /// Authentication username.
    #[arg(short = 'u', long, default_value = "support")]
    username: String,
    /// Client certificate path.
    #[arg(long, requires("client_key_path"))]
    client_cert_path: Option<PathBuf>,
    /// Client key path.
    #[arg(long, requires("client_cert_path"))]
    client_key_path: Option<PathBuf>,
    /// CA certificate path.
    #[arg(long, default_value = "./examples/pki/ca.crt")]
    ca_cert_path: PathBuf,
    /// Application client ID.
    #[arg(long, default_value = "jet-demo-rs")]
    client_id: String,
    /// Override the domain name against which the server's TLS certificate is verified.
    #[arg(long)]
    server_tls_domain_name: Option<String>,

    #[command(flatten)]
    verbosity: clap_verbosity_flag::Verbosity,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let args = Cli::parse();

    SimpleLogger::new()
        .with_level(args.verbosity.log_level_filter())
        .init()?;
    log::debug!("logger initialized");

    log::debug!("setting up TLS config");
    let tls_config = {
        let mut config = ClientTlsConfig::new().ca_certificate(Certificate::from_pem(
            fs::read_to_string(args.ca_cert_path)?,
        ));
        if let Some(domain_name) = args.server_tls_domain_name {
            log::info!("overriding server TLS domain name: {domain_name}");
            config = config.domain_name(domain_name);
        }
        match (args.client_cert_path, args.client_key_path) {
            (Some(cert_path), Some(key_path)) => {
                log::info!("enabling mutual TLS authentication");
                config.identity(Identity::from_pem(
                    fs::read_to_string(cert_path)?,
                    fs::read_to_string(key_path)?,
                ))
            }
            (None, None) => config,
            _ => unreachable!("cli argument parsing rules should prevent this being reached"),
        }
    };

    log::info!("connecting gRPC channel");
    let mut channel = Channel::from_shared(format!("https://{}:{}", args.host, args.port))?
        .tls_config(tls_config)?
        .connect()
        .await?;

    log::info!("attempting to authenticate to JET server");
    let login_resp = AuthenticationClient::new(&mut channel)
        .login(LoginRequest {
            client_id: args.client_id,
            username: args.username,
            password: prompt_password("Password: ")?,
        })
        .await?
        .into_inner();
    if let Some(status) = login_resp.status {
        match status.code() {
            StatusCode::Success => log::info!("authentication successful"),
            StatusCode::Failure => {
                return Err(format!("authentication failed: {}", status.message).into());
            }
        };
    } else {
        return Err(format!("no status in login response message: {:?}", login_resp).into());
    };

    let mut op_command_resp_stream = ManagementClient::new(&mut channel)
        .op_command_get(OpCommandGetRequest {
            out_format: OpCommandOutputFormat::OpCommandOutputCli.into(),
            command: Some(Command::CliCommand(args.command)),
        })
        .await?
        .into_inner();
    while let Some(op_command_resp) = op_command_resp_stream.message().await? {
        if let Some(status) = op_command_resp.status {
            match status.code() {
                StatusCode::Success => println!("{}", op_command_resp.data),
                StatusCode::Failure => {
                    return Err(format!("op command failed: {}", status.message).into());
                }
            }
        } else {
            return Err(format!(
                "no status in op_command response message: {:?}",
                op_command_resp
            )
            .into());
        }
    }

    Ok(())
}

Modules

  • junos_19_1junos-19-1
    Generated JET gRPC service definitions for Junos 19.1.
  • junos_19_2junos-19-2
    Generated JET gRPC service definitions for Junos 19.2.
  • junos_19_3junos-19-3
    Generated JET gRPC service definitions for Junos 19.3.
  • junos_19_4junos-19-4
    Generated JET gRPC service definitions for Junos 19.4.
  • junos_20_1junos-20-1
    Generated JET gRPC service definitions for Junos 20.1.
  • junos_20_2junos-20-2
    Generated JET gRPC service definitions for Junos 20.2.
  • junos_20_3junos-20-3
    Generated JET gRPC service definitions for Junos 20.3.
  • junos_20_4junos-20-4
    Generated JET gRPC service definitions for Junos 20.4.
  • junos_21_1junos-21-1
    Generated JET gRPC service definitions for Junos 21.1.
  • junos_21_2junos-21-2
    Generated JET gRPC service definitions for Junos 21.2.
  • junos_21_3junos-21-3
    Generated JET gRPC service definitions for Junos 21.3.
  • junos_21_4junos-21-4
    Generated JET gRPC service definitions for Junos 21.4.
  • junos_22_2junos-22-2
    Generated JET gRPC service definitions for Junos 22.2.
  • junos_22_3junos-22-3
    Generated JET gRPC service definitions for Junos 22.3.
  • junos_22_4junos-22-4
    Generated JET gRPC service definitions for Junos 22.4.
  • junos_23_1junos-23-1
    Generated JET gRPC service definitions for Junos 23.1.
  • latestlatest
    Generated JET gRPC service definitions for the latest Junos version.