gNOI Certificate Management Service
Use the gNOI CertificateManagement
service to manage certificates on
the target network element.
Overview
The gNOI CertificateManagement
service in the
gnoi.certificate
package handles certificate management on the
target network element. The proto definition file is located at https://github.com/openconfig/gnoi/blob/master/cert/cert.proto.
A public key infrastructure (PKI) supports the distribution and identification of
public encryption keys, enabling users to both securely exchange data over networks
such as the Internet and verify the identity of the other party. The Junos PKI
enables you to manage public key certificates on Junos devices, including
downloading, generating, and verifying certificates. The gNOI
CertificateManagement
service defines operations for
certificate management, which is through the Junos PKI. The two main operations
are:
-
Install—Install a new certificate using a new certificate ID on the target network device. If the certificate ID already exists, the operation returns an error.
-
Rotate—Replace an existing certificate, which already has an existing certificate ID, on the target network device. If the stream is broken or any steps fail during the process, then the device rolls back to the original certificate.
Figure 1 outlines
the workflow for the Install()
and Rotate()
operations. For both operations, the client can generate the certificate signing
request (CSR) itself or request the target to generate the CSR. In either case, the
client forwards the CSR to a certificate authority (CA) to request a digital
certificate. The client then loads the certificate on the target, either with a new
certificate ID for Install()
operations or with an existing
certificate ID for Rotate()
operations. For
Rotate()
operations, the client should also validate any
replacement certificates and finalize or cancel the Rotate()
operation based on the success or failure of the validation. If the client cancels
the operation, the server rolls back the certificate, key pair, and any CA bundle,
if it is present in the request.
Starting in Junos OS Evolved Release 23.1R1, during an Install()
,
Rotate()
, or LoadCertificate()
operation, the
gNOI server verifies the new end entity certificate using the corresponding CA
certificate. Thus, the gNOI server's PKI must include the root CA certificate that
verifies the new certificate. You can load the required CA certificate as part of
the gNOI CA bundle, or you can load it separately. If the verification fails, the
device does not install the new certificate.
The gNOI server supports only one global CA certificate bundle for gNOI services.
When you use the gNOI CertificateManagement
service to load the CA
bundle, the following statements are applicable:
-
The
CertificateManagagment
service always loads the CA certificate bundle using theca-profile-group
reserved identifiergnoi-ca-bundle
. -
If you use the
CertificateManagagment
service to load the CA certificate bundle, the device implicitly uses mutual authentication and assumes the following configuration, even though it is not explicitly configured on the device.[edit system services extension-service request-response grpc ssl] mutual-authentication { certificate-authority gnoi-ca-bundle; client-certificate-request require-certificate-and-verify; }
-
If the
CertificateManagagment
service sends a request to load a new CA certificate bundle, the server clears the certificates for the previous CA bundle from the device and loads the new ones. -
If you use the
CertificateManagagment
service to load a CA certificate bundle and you also configure the[edit system services extension-service request-response grpc ssl mutual-authentication]
statement hierarchy, then the configured statements take precedence.
Thus you can initially set up server-only authentication on the gNOI server and then
use the Install()
RPC to load the CA certificates. When you use
gNOI to load the initial CA certificate bundle, the device performs the following
steps:
- Adds the CA certificates in the Junos PKI.
- Automatically configures the gNOI CA certificate bundle at the
[edit security pki]
hierarchy level using theca-profile-group
identifiergnoi-ca-bundle
. - Switches from server-only authentication to mutual authentication.
[edit] security { pki { ca-profile gnoi-ca-bundle_1 { ca-identity gnoi-ca-bundle_1; } ca-profile gnoi-ca-bundle_2 { ca-identity gnoi-ca-bundle_2; } ca-profile-group gnoi-ca-bundle { cert-base-count 2; } } }
The Rotate()
RPC does not support switching between authentication
modes during the rotate operation. Thus, Rotate()
does not support
loading the CA certificate bundle on the gNOI server for the first time because it
causes the device to switch from server-only authentication to mutual authentication
during the operation. When the authentication mode changes, the network device must
restart the gRPC stack and the connection is lost. If the stream is broken, the
client cannot finalize the rotate request, and the device would roll back to the
certificates that were in place before the Rotate()
request was
initiated.
The hot-reloading
statement at the [edit system services
extension-service request-response grpc ssl]
hierarchy level only
maintains the gRPC session during a certificate update when the authentication
mode remains unchanged during the operation. If the authentication mode
switches, for example, from server-only to mutual authentication or vice versa,
the client disconnects.
Supported RPCs
Table 1 outlines
the CertificateManagement
service RPCs supported on Junos
devices.
RPC | Description | Introduced in Release |
---|---|---|
CanGenerateCSR() |
Query the target device to determine if it can generate a certificate signing request (CSR) with the specified key type, key size, and certificate type. Supported values:
Returns |
Junos OS Evolved 23.1R1 |
GenerateCSR() |
Generate and return a certificate signing request (CSR). |
Junos OS Evolved 22.2R1 |
GetCertificates() |
Return the local certificates loaded on the target device. |
Junos OS Evolved 22.2R1 |
Install() |
Load a new certificate on the target device by creating a CSR request, generating a certificate based on the CSR, and loading the certificate using a new certificate ID. |
Junos OS Evolved 22.2R1 |
LoadCertificate() |
Load a certificate signed by a Certificate Authority (CA) on the target device. |
Junos OS Evolved 22.2R1 |
LoadCertificateAuthorityBundle() |
Load a CA certificate bundle on the target device. |
Junos OS Evolved 22.2R1 |
RevokeCertificates() |
Revoke the certificates with the specified certificate IDs on the target device. |
Junos OS Evolved 23.1R1 |
Rotate() |
Replace an existing certificate on the target device by creating a CSR request, generating a certificate based on the CSR, and loading the certificate using an existing certificate ID. |
Junos OS Evolved 22.2R1 |
Network Device Configuration
To use gNOI certificate management services on Junos devices, you must configure
the use-pki
and hot-reloading
statements for
gRPC extensions services. In most cases, you configure the
use-pki
statement when you configure gRPC services on the
network device. The hot-reloading
statement is required to
maintain the gRPC session when updating certificates that affect the session.
Before you begin:
- Configure gRPC services on the network device as described in Configure gRPC Services.
- Configure the network management system to support gNOI operations as described in Configure gNOI Services.
To configure the network device for CertificateManagement
service operations:
Install a Certificate
You can use the CertificateManagement
service
Install()
RPC to load a new certificate on the target device.
When you install a new certificate using the Install()
operation,
you must specify a new certificate ID that does not already exist on the target
device. You can also optionally load a CA certificate bundle as part of the
Install()
operation.
As part of the Install()
operation, the device verifies the new
certificate. Therefore, the Junos PKI must have the root CA certificate that
verifies the new certificate. You can load the required CA certificate as part of
the Install()
operation, or you can load it separately, prior to
the operation, if it is not already in the PKI.
If you install a new local certificate that will be used for gRPC session authentication, you must also update the gRPC server configuration on the device to use the new certificate ID.
Example: Install a Certificate
In this example, the gNOI server has been initially configured with a local
certificate only and has not been configured to use mutual authentication. The
gNOI client uses the Install()
RPC to load a new local
certificate and a CA certificate bundle on the device. After the CA bundle is
loaded on the gNOI server, the server uses mutual authentication by default. The
CA bundle includes the root CA certificate for the client certificate as well as
the root CA certificate for the new server certificate.
The client executes the gnoi_cert_install_certificate_csr.py
Python application, which performs the following operations:
- Requests the target to generate a CSR.
- Gets a signed certificate based on the CSR.
- Loads the new server certificate, the server's new root CA certificate, and the client's root CA certificate on the target network device.
The application uses the InstallCertificateRequest
message with
the appropriate parameters to define the requests for generating the CSR and
loading the certificates. For each request, the application uses the
Install()
RPC to send the requests to the network
device.
The gnoi_cert_install_certificate_csr.py
application imports the
grpc_channel
module to establish the channel. The
grpc_channel
module is described in Configure gNOI Services. The
application's arguments are stored in the
args_cert_install_csr.txt
file. The application and
argument files are presented here.
gnoi_cert_install_certificate_csr.py
"""gNOI Install Certificate utility.""" from __future__ import print_function from __future__ import unicode_literals import argparse import logging import re from getpass import getpass from subprocess import call import cert_pb2 import cert_pb2_grpc from grpc_channel import grpc_authenticate_channel_server_only def get_args(parser): parser.add_argument('--server', dest='server', type=str, default='localhost', help='Server IP or name. Default is localhost.') parser.add_argument('--port', dest='port', nargs='?', type=int, default=32767, help='Server port. Default is 32767') parser.add_argument('--client_key', dest='client_key', type=str, default='', help='Full path of the client private key. Default is "".') parser.add_argument('--client_cert', dest='client_cert', type=str, default='', help='Full path of the client certificate. Default is "".') parser.add_argument('--root_ca_cert', dest='root_ca_cert', required=True, type=str, help='Full path of the Root CA certificate.') parser.add_argument('--user_id', dest='user_id', required=True, type=str, help='User ID for RPC call credentials.') parser.add_argument('--type', dest='type', type=int, default='1', help='Certificate Type. Default is 1. Valid value is 1 (1 is CT_X509); Invalid value is 0 (0 is CT_UNKNOWN).') parser.add_argument('--min_key_size', dest='min_key_size', type=int, default='2048', help='Minimum key size. Default is 2048.') parser.add_argument('--key_type', dest='key_type', type=int, default='1', help='Key Type. Default is 1 (KT_RSA); 0 is KT_UNKNOWN.') parser.add_argument('--common_name', dest='common_name', type=str, default='', help='CN of the certificate') parser.add_argument('--country', dest='country', type=str, default='US', help='Country name') parser.add_argument('--state', dest='state', type=str, default='CA', help='State name') parser.add_argument('--city', dest='city', type=str, default='Sunnyvale', help='City name') parser.add_argument('--organization', dest='organization', type=str, default='Acme', help='Organization name') parser.add_argument('--organizational_unit', dest='organizational_unit', type=str, default='Test', help='Organization unit name') parser.add_argument('--ip_address', dest='ip_address', type=str, default='', help='IP address on the certificate') parser.add_argument('--email_id', dest='email_id', type=str, default='', help='Email id') parser.add_argument('--certificate_id', dest='certificate_id', required=True, type=str, help='Certificate id.') parser.add_argument('--server_cert_private_key', dest='server_cert_private_key', type=str, default='', help='Server certificate private key') parser.add_argument('--server_cert_public_key', dest='server_cert_public_key', type=str, default='', help='Server certificate public key') parser.add_argument('--server_cert', dest='server_cert', type=str, default='server_cert', help='Server certificate') parser.add_argument('--server_root_ca1', dest='server_root_ca1', type=str, default='server_root_ca1', help='Server Root CA') parser.add_argument('--server_root_ca2', dest='server_root_ca2', type=str, default='server_root_ca2', help='Server Root CA') parser.add_argument('--client_root_ca1', dest='client_root_ca1', type=str, default='client_root_ca1', help='Client Root CA') parser.add_argument('--client_root_ca2', dest='client_root_ca2', type=str, default='client_root_ca2', help='Client Root CA') parser.add_argument('--client_root_ca3', dest='client_root_ca3', type=str, default='client_root_ca3', help='Client Root CA') parser.add_argument('--client_root_ca4', dest='client_root_ca4', type=str, default='client_root_ca4', help='Client Root CA') args = parser.parse_args() return args def install_cert(channel, metadata, args): try: stub = cert_pb2_grpc.CertificateManagementStub(channel) print("Executing GNOI::CertificateManagement::Install") # Create request to generate certificate signing request (CSR) it = [] req = cert_pb2.InstallCertificateRequest() req.generate_csr.csr_params.type = args.type req.generate_csr.csr_params.min_key_size = args.min_key_size req.generate_csr.csr_params.key_type = args.key_type req.generate_csr.csr_params.common_name = args.common_name req.generate_csr.csr_params.country = args.country req.generate_csr.csr_params.state = args.state req.generate_csr.csr_params.city = args.city req.generate_csr.csr_params.organization = args.organization req.generate_csr.csr_params.organizational_unit = args.organizational_unit req.generate_csr.csr_params.ip_address = args.ip_address req.generate_csr.csr_params.email_id = args.email_id req.generate_csr.certificate_id = args.certificate_id it.append(req) # Send request to generate CSR for csr_rsp in stub.Install(iter(it), metadata=metadata, timeout=180): logging.info(csr_rsp) # Write CSR to a file with open('/home/lab/certs/server_temp.csr', "wb") as file: file.write(csr_rsp.generated_csr.csr.csr) # If client connects to server IP address # update openssl.cnf template to include subjectAltName IP extension with open('/etc/pki/certs/openssl.cnf', 'r') as fd: data = fd.read() data1 = re.sub(r'(subjectAltName=IP:).*', r'\g<1>'+args.ip_address, data) with open('/home/lab/certs/openssl_temp.cnf', 'w') as fd: fd.write(data1) # Generate certificate with v3 extensions cmd = "openssl x509 -req -days 365 -in /home/lab/certs/server_temp.csr -CA /etc/pki/certs/serverRootCA.crt -CAkey /etc/pki/certs/serverRootCA.key -CAcreateserial -out /home/lab/certs/server_temp.crt -extensions v3_sign -extfile /home/lab/certs/openssl_temp.cnf -sha384" decrypted = call(cmd, shell=True) # Create request to install node certificate and CA certificates print("\nExecuting GNOI::CertificateManagement::Install") it = [] req = cert_pb2.InstallCertificateRequest() # Import certificate and add to request cert_data = bytearray(b'') with open("/home/lab/certs/server_temp.crt", "rb") as file: cert_data = file.read() req.load_certificate.certificate.type = args.type req.load_certificate.certificate_id = args.certificate_id req.load_certificate.certificate.certificate = cert_data # Add client and server CA certificates to request ca1 = req.load_certificate.ca_certificates.add() ca1.type = args.type ca1.certificate = open(args.client_root_ca1, 'rb').read() ca2 = req.load_certificate.ca_certificates.add() ca2.type = args.type ca2.certificate = open(args.server_root_ca1, 'rb').read() it.append(req) # Send request to install node certificate and CA bundle for rsp in stub.Install(iter(it), metadata=metadata, timeout=180): logging.info("Installing certificates: %s", rsp) print("Install complete.") except Exception as e: logging.error('Certificate install error: %s', e) print(e) def main(): parser = argparse.ArgumentParser(fromfile_prefix_chars='@') args = get_args(parser) grpc_server_password = getpass("gRPC server password for executing RPCs: ") metadata = [('username', args.user_id), ('password', grpc_server_password)] try: # Establish grpc channel to network device channel = grpc_authenticate_channel_server_only( args.server, args.port, args.root_ca_cert) install_cert(channel, metadata, args) except Exception as e: logging.error('Received error: %s', e) print(e) if __name__ == '__main__': logging.basicConfig(filename='gnoi-testing.log', format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') main()
args_cert_install_csr.txt
--server=10.53.52.169 --port=32767 --root_ca_cert=/etc/pki/certs/serverRootCA.crt --user_id=gnoi-user --type=1 --min_key_size=2048 --key_type=1 --common_name=gnoi-server.example.com --country=US --state=CA --city=Sunnyvale --organization=Acme --organizational_unit=testing --ip_address=10.53.52.169 --email_id=test@example.com --certificate_id=gnoi-server1 --client_root_ca1=/etc/pki/certs/clientRootCA.crt --server_root_ca1=/etc/pki/certs/serverRootCA1.crt
Execute the Application
When the client executes the application, the application requests the CSR, gets the signed certificate, and loads the new server certificate and CA certificates on the target network device.
lab@gnoi-client:~/src/gnoi/proto$ python3 gnoi_cert_install_certificate_csr.py @args_cert_install_csr.txt gRPC server password for executing RPCs: Creating channel Executing GNOI::CertificateManagement::Install Signature ok subject=CN = gnoi-server.example.com, C = US, ST = CA, O = Acme, OU = testing Getting CA Private Key Executing GNOI::CertificateManagement::Install Install complete.
After you install the new server certificate, you must configure the server
to use that certificate ID for gRPC session authentication, as shown here.
In addition, because the Install()
operation loaded new CA
certificates, the device implicitly uses mutual authentication. As a result,
all subsequent gRPC sessions must include the client's certificate and key
when establishing the channel.
user@gnoi-server> show configuration system services extension-service request-response grpc ssl port 32767; local-certificate gnoi-server1; hot-reloading; use-pki;
If you execute the application and provide a certificate ID that already
exists on the server, the application returns an
ALREADY_EXISTS
error, because the
Install()
operation requires a new certificate ID.
lab@gnoi-client:~/src/gnoi/proto$ python3 gnoi_cert_install_certificate_csr.py @args_cert_install_csr.txt gRPC server password for executing RPCs: Creating channel Executing GNOI::CertificateManagement::Install Signature ok subject=CN = gnoi-server.example.com, C = US, ST = CA, O = Acme, OU = testing Getting CA Private Key Executing GNOI::CertificateManagement::Install <_MultiThreadedRendezvous of RPC that terminated with: status = StatusCode.ALREADY_EXISTS details = "" debug_error_string = "{"created":"@1652241881.676147097","description":"Error received from peer ipv4:10.53.52.169:32767","file":"src/core/lib/surface/call.cc","file_line":903,"grpc_message":"","grpc_status":6}"
Rotate a Certificate
You can use the CertificateManagement
service
Rotate()
RPC to replace an existing certificate on the target
device. When you replace an existing certificate using the Rotate()
operation, you must load the certificate using the certificate ID that already
exists on the target device. You can also optionally replace the existing gNOI CA
certificate bundle as part of the Rotate()
operation.
The Rotate()
operation is similar to the Install()
operation except that the Rotate()
operation replaces an existing
certificate instead of installing a new certificate. In addition, the client must
validate that the updated certificate works and then finalize or cancel the
Rotate()
request based on the success or failure of the
certificate validation.
As part of the Rotate()
operation, the device verifies the new
certificate. Therefore, the Junos PKI must have the root CA certificate that
verifies the new certificate. You can load the required CA certificate as part of
the Rotate()
operation, or you can load it separately, prior to the
operation, if it is not already in the PKI.
Example: Rotate a Certificate
In this example, the client executes the
gnoi_cert_rotate_certificate_csr.py
Python application,
which performs the following operations:
- Requests the target to generate a CSR.
- Gets a signed certificate based on the CSR
- Replaces the node certificate and the gNOI CA bundle on the target network device.
- Validates the new certificate.
- Finalizes the
Rotate
operation.
The application uses the RotateCertificateRequest
message with
the appropriate parameters to define the requests for generating the CSR and
loading the certificate and CA bundle. For each request, the application uses
the Rotate()
RPC to send the request to the network device. To
enable the target device to verify the new node certificate, the application
replaces the existing CA bundle with a new CA bundle. The bundle includes both
the client CA certificate and the CA certificate required to verify the node
certificate.
The application validates that the new certificate works by creating a new gRPC
session with the network device and executing a simple Time()
RPC, although you can test the session authentication with any RPC. The
application finalizes the rotate request if the session is successfully
established and cancels the rotate request if the session authentication
fails.
The gnoi_cert_rotate_certificate_csr.py
application imports the
grpc_channel
module to establish the channel. The
grpc_channel
module is described in Configure gNOI Services. The
application's arguments are stored in the
args_cert_rotate_csr.txt
file. The application and argument
files are presented here.
gnoi_cert_rotate_certificate_csr.py
"""gNOI Rotate Certificate utility.""" from __future__ import print_function from __future__ import unicode_literals import argparse import logging import time import re import grpc from getpass import getpass from subprocess import call import cert_pb2 import cert_pb2_grpc import system_pb2 import system_pb2_grpc from grpc_channel import grpc_authenticate_channel_mutual def get_args(parser): parser.add_argument('--server', dest='server', type=str, default='localhost', help='Server IP or name. Default is localhost.') parser.add_argument('--port', dest='port', nargs='?', type=int, default=32767, help='Server port. Default is 32767') parser.add_argument('--client_key', dest='client_key', type=str, default='', help='Full path of the client private key. Default is "".') parser.add_argument('--client_cert', dest='client_cert', type=str, default='', help='Full path of the client certificate. Default is "".') parser.add_argument('--root_ca_cert', dest='root_ca_cert', required=True, type=str, help='Full path of the Root CA certificate.') parser.add_argument('--user_id', dest='user_id', required=True, type=str, help='User ID for RPC call credentials.') parser.add_argument('--type', dest='type', type=int, default='1', help='Certificate Type. Default is 1. Valid value is 1 (1 is CT_X509); Invalid value is 0 (0 is CT_UNKNOWN).') parser.add_argument('--min_key_size', dest='min_key_size', type=int, default='2048', help='Minimum key size. Default is 2048.') parser.add_argument('--key_type', dest='key_type', type=int, default='1', help='Key Type. Default is 1 (KT_RSA); 0 is KT_UNKNOWN.') parser.add_argument('--common_name', dest='common_name', type=str, default='', help='CN of the certificate') parser.add_argument('--country', dest='country', type=str, default='US', help='Country name') parser.add_argument('--state', dest='state', type=str, default='CA', help='State name') parser.add_argument('--city', dest='city', type=str, default='Sunnyvale', help='City name') parser.add_argument('--organization', dest='organization', type=str, default='Acme', help='Organization name') parser.add_argument('--organizational_unit', dest='organizational_unit', type=str, default='Test', help='Organization unit name') parser.add_argument('--ip_address', dest='ip_address', type=str, default='', help='IP address on the certificate') parser.add_argument('--email_id', dest='email_id', type=str, default='', help='Email id') parser.add_argument('--certificate_id', dest='certificate_id', required=True, type=str, help='Certificate id.') parser.add_argument('--server_cert_private_key', dest='server_cert_private_key', type=str, default='', help='Server certificate private key') parser.add_argument('--server_cert_public_key', dest='server_cert_public_key', type=str, default='', help='Server certificate public key') parser.add_argument('--server_cert', dest='server_cert', type=str, default='server_cert', help='Server certificate') parser.add_argument('--server_root_ca1', dest='server_root_ca1', type=str, default='server_root_ca1', help='Server Root CA') parser.add_argument('--server_root_ca2', dest='server_root_ca2', type=str, default='server_root_ca2', help='Server Root CA') parser.add_argument('--client_root_ca1', dest='client_root_ca1', type=str, default='client_root_ca1', help='Client Root CA') parser.add_argument('--client_root_ca2', dest='client_root_ca2', type=str, default='client_root_ca2', help='Client Root CA') parser.add_argument('--client_root_ca3', dest='client_root_ca3', type=str, default='client_root_ca3', help='Client Root CA') parser.add_argument('--client_root_ca4', dest='client_root_ca4', type=str, default='client_root_ca4', help='Client Root CA') parser.add_argument("--client_key_test", dest='client_key_test', type=str, default='', help='Full path of the test client private key. Default ""') parser.add_argument("--client_cert_test", dest='client_cert_test', type=str, default='', help='Full path of the test client certificate. Default ""') args = parser.parse_args() return args def rotate_cert(channel, metadata, args): try: result = '' stub = cert_pb2_grpc.CertificateManagementStub(channel) print("Executing GNOI::CertificateManagement::Rotate") # Create request to generate certificate signing request (CSR) it = [] req = cert_pb2.RotateCertificateRequest() req.generate_csr.csr_params.type = args.type req.generate_csr.csr_params.min_key_size = args.min_key_size req.generate_csr.csr_params.key_type = args.key_type req.generate_csr.csr_params.common_name = args.common_name req.generate_csr.csr_params.country = args.country req.generate_csr.csr_params.state = args.state req.generate_csr.csr_params.city = args.city req.generate_csr.csr_params.organization = args.organization req.generate_csr.csr_params.organizational_unit = args.organizational_unit req.generate_csr.csr_params.ip_address = args.ip_address req.generate_csr.csr_params.email_id = args.email_id req.generate_csr.certificate_id = args.certificate_id it.append(req) # Send request to generate CSR print('Sending request for CSR') for csr_rsp in stub.Rotate(iter(it), metadata=metadata, timeout=30): logging.info(csr_rsp) # Write CSR to a file with open('/home/lab/certs/server_temp.csr', "wb") as file: file.write(csr_rsp.generated_csr.csr.csr) # If client connects to server IP address # update openssl.cnf template to include subjectAltName IP extension with open('/etc/pki/certs/openssl.cnf', 'r') as fd: data = fd.read() data1 = re.sub(r'(subjectAltName=IP:).*', r'\g<1>'+args.ip_address, data) with open('/home/lab/certs/openssl_temp.cnf', 'w') as fd: fd.write(data1) # Generate certificate with v3 extensions cmd = "openssl x509 -req -days 365 -in /home/lab/certs/server_temp.csr -CA /etc/pki/certs/serverRootCA.crt -CAkey /etc/pki/certs/serverRootCA.key -CAcreateserial -out /home/lab/certs/server_temp.crt -extensions v3_sign -extfile /home/lab/certs/openssl_temp.cnf -sha384" decrypted = call(cmd, shell=True) # Create request to rotate node certificate and CA certificates print("\nExecuting GNOI::CertificateManagement::Rotate") it = [] req = cert_pb2.RotateCertificateRequest() # Import certificate and add to request with open("/home/lab/certs/server_temp.crt", "rb") as file: cert_data = file.read() req.load_certificate.certificate.type = args.type req.load_certificate.certificate.certificate = cert_data req.load_certificate.certificate_id = args.certificate_id # Add client and server CA certificates to request ca1 = req.load_certificate.ca_certificates.add() ca1.type = args.type ca1.certificate = open(args.client_root_ca1, 'rb').read() ca2 = req.load_certificate.ca_certificates.add() ca2.type = args.type ca2.certificate = open(args.server_root_ca1, 'rb').read() it.append(req) # Send request to replace node certificate and CA bundle for rsp in stub.Rotate(iter(it), metadata=metadata, timeout=60): logging.info("Rotating certificates. %s", rsp) # Validate certificates print("Validating certificates") time.sleep(5) validate_rc = True try: validate_channel = grpc_authenticate_channel_mutual( args.server, args.port, args.server_root_ca1, args.client_key_test, args.client_cert_test) validate_stub = system_pb2_grpc.SystemStub(validate_channel) validate_rsp = validate_stub.Time( request=system_pb2.TimeRequest(), metadata=metadata, timeout=60) except grpc.RpcError as e: print("Validation failed with error:", e) validate_rc = False pass if validate_rc: print("Finalizing certificate rotation.") it = [] req = cert_pb2.RotateCertificateRequest() req.finalize_rotation.SetInParent() it.append(req) for rsp in stub.Rotate(iter(it), metadata=metadata, timeout=30): logging.info("Finalizing rotate. %s", rsp) logging.info( "Certificate validation succeeded. Certificate rotation finalized.") result = "Certificate rotation finalized." else: print("Rolling back certificates.") logging.info( "Certificate validation failed. Rolling back to original certificates.") rsp.cancel() except Exception as e: logging.error('Certificate rotate error: %s', e) print(e) else: return result def main(): parser = argparse.ArgumentParser(fromfile_prefix_chars='@') args = get_args(parser) grpc_server_password = getpass("gRPC server password for executing RPCs: ") metadata = [('username', args.user_id), ('password', grpc_server_password)] try: # Establish grpc channel to network device channel = grpc_authenticate_channel_mutual( args.server, args.port, args.root_ca_cert, args.client_key, args.client_cert) response = rotate_cert(channel, metadata, args) print(response) except Exception as e: logging.error('Error: %s', e) print(e) if __name__ == '__main__': logging.basicConfig(filename='gnoi-testing.log', format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') main()
args_cert_rotate_csr.txt
--server=10.53.52.169 --port=32767 --root_ca_cert=/etc/pki/certs/serverRootCA.crt --client_key=/home/lab/certs/client.key --client_cert=/home/lab/certs/client.crt --user_id=gnoi-user --type=1 --min_key_size=2048 --key_type=1 --common_name=gnoi-server.example.com --country=US --state=CA --city=Sunnyvale --organization=Acme --organizational_unit=testing --ip_address=10.53.52.169 --email_id=test@example.com --certificate_id=gnoi-server --client_root_ca1=/etc/pki/certs/clientRootCA.crt --server_root_ca1=/etc/pki/certs/serverRootCA.crt --client_key_test=/home/lab/certs/client.key --client_cert_test=/home/lab/certs/client.crt
It's important to note that the root_ca_cert
argument is the
server's root CA certificate required for the initial channel credentials.
The server_root_ca1
argument is the root CA certificate
corresponding to the server's new certificate. The Junos PKI must have the
new root CA certificate in order to verify the new local certificate during
the Rotate()
operation. In addition, the channel
credentials for the gRPC session that validates the new certificate use this
root CA certificate. Although this example uses the same root CA certificate
for the new and old server certificates, these might differ for another
case.
Execute the Application
When the client executes the application, the application requests the CSR,
gets the signed certificate, and loads the replacement certificate and CA
bundle on the target network device. The application then validates the
replacement certificate with a new gRPC session that executes a simple
Time()
RPC. Upon successful validation, the client
finalizes the rotate request.
lab@gnoi-client:~/src/gnoi/proto$ python3 gnoi_cert_rotate_certificate_csr.py @args_cert_rotate_csr.txt gRPC server password for executing RPCs: Creating channel Executing GNOI::CertificateManagement::Rotate Sending request for CSR Signature ok subject=CN = gnoi-server.example.com, C = US, ST = CA, O = Acme, OU = testing Getting CA Private Key Executing GNOI::CertificateManagement::Rotate Validating certificates Creating channel Finalizing certificate rotation. Certificate rotation finalized.
Revoke a Certificate
A gNOI client can use the RevokeCertificates()
RPC to remove one or
more certificates from the target device. The client includes a
RevokeCertificatesRequest
message with the list of certificate
IDs to revoke.
When the gNOI server receives the RevokeCertificates()
request, it
processes each certificate ID in the list as follows:
-
If the certificate is present and the revocation is successful, the device removes the certificate from the file system and the Junos PKI and adds the certificate ID to the list of successfully revoked certificates.
-
If the certificate is present and the revocation fails, the device includes the certificate ID and the reason for the failure in the certificate revocation error list.
-
If the certificate is not present, the device considers the revocation operation successful and adds the certificate ID to the list of successfully revoked certificates.
If the request revokes the certificate used for the current session, the session is not affected.
After processing the request, the gNOI server returns a
RevokeCertificatesResponse
message that includes:
-
A list of successfully revoked certificate IDs.
-
A list of revocation errors containing the certificate ID and the reason for the failure.
Example: Revoke a Certificate
In this example, the client executes the
gnoi_cert_revoke_certificates.py
Python application, which
revokes two certificates on the server. The first certificate ID is a valid
identifier on the device. The second certificate ID is an identifier that does
not exist on the device.
The application uses the RevokeCertificatesRequest
message with
the appropriate parameters to define the request. The application sends the
RevokeCertificates()
RPC to the network device to perform
the operation.
The gnoi_cert_revoke_certificates.py
application imports the
grpc_channel
module to establish the channel. The
grpc_channel
module is described in Configure gNOI Services. The
application's arguments are stored in the
args_cert_revoke_certificates.txt
file. The application and
argument files are presented here.
gnoi_cert_revoke_certificates.py
"""gNOI Revoke Certificates utility.""" from __future__ import print_function from __future__ import unicode_literals import argparse import logging from getpass import getpass import cert_pb2 import cert_pb2_grpc from grpc_channel import grpc_authenticate_channel_mutual def get_args(parser): parser.add_argument('--server', dest='server', type=str, default='localhost', help='Server IP or name. Default is localhost.') parser.add_argument('--port', dest='port', nargs='?', type=int, default=32767, help='Server port. Default is 32767') parser.add_argument('--client_key', dest='client_key', type=str, default='', help='Full path of the client private key. Default is "".') parser.add_argument('--client_cert', dest='client_cert', type=str, default='', help='Full path of the client certificate. Default is "".') parser.add_argument('--root_ca_cert', dest='root_ca_cert', required=True, type=str, help='Full path of the Root CA certificate.') parser.add_argument('--user_id', dest='user_id', required=True, type=str, help='User ID for RPC call credentials.') parser.add_argument('--certificate_id1', dest='certificate_id1', required=True, type=str, help='Certificate id.') parser.add_argument('--certificate_id2', dest='certificate_id2', type=str, help='Certificate id.') args = parser.parse_args() return args def revoke_cert(channel, metadata, args): try: stub = cert_pb2_grpc.CertificateManagementStub(channel) print("Executing GNOI::CertificateManagement::RevokeCertificates") # Create request to revoke certificates req = cert_pb2.RevokeCertificatesRequest() req.certificate_id.append(args.certificate_id1) req.certificate_id.append(args.certificate_id2) # Send request to revoke certificates logging.info("Sending RevokeCertificates request.") rsp = stub.RevokeCertificates(req, metadata=metadata, timeout=60) logging.info(rsp) print("rsp:\n%s" %rsp) except Exception as e: logging.error('Error: %s', e) print(e) def main(): parser = argparse.ArgumentParser(fromfile_prefix_chars='@') args = get_args(parser) grpc_server_password = getpass("gRPC server password for executing RPCs: ") metadata = [('username', args.user_id), ('password', grpc_server_password)] try: # Establish grpc channel to network device channel = grpc_authenticate_channel_mutual( args.server, args.port, args.root_ca_cert, args.client_key, args.client_cert) revoke_cert(channel, metadata, args) except Exception as e: logging.error('Received error: %s', e) print(e) if __name__ == '__main__': logging.basicConfig(filename='gnoi-testing.log', format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') main()
args_cert_revoke_certificates.txt
--server=10.53.52.169 --port=32767 --root_ca_cert=/etc/pki/certs/serverRootCA.crt --client_key=/home/lab/certs/client.key --client_cert=/home/lab/certs/client.crt --user_id=gnoi-user --certificate_id1=gnoi-server --certificate_id2=id-does-not-exist
Execute the Application
When the client executes the application, the application instructs the target device to revoke the specified certificates. The device returns a list of successfully revoked certificates and any errors. The device deems the operation successful for both the valid certificate ID as well as for the certificate ID that does not currently exist on the device.
lab@gnoi-client:~/src/gnoi/proto$ python3 gnoi_cert_revoke_certificates.py @args_cert_revoke_certificates.txt gRPC server password for executing RPCs: Creating channel Executing GNOI::CertificateManagement::RevokeCertificates rsp: revoked_certificate_id: "gnoi-server" revoked_certificate_id: "id-does-not-exist"
Change History Table
Feature support is determined by the platform and release you are using. Use Feature Explorer to determine if a feature is supported on your platform.
Install()
, Rotate()
, and
LoadCertificate()
operations verify the new certificate as
part of the operation.