gNOI 証明書管理サービス
概要 gNOI CertificateManagement サービスを使用して、ターゲット ネットワーク要素の証明書を管理します。
概要
パッケージ内の gNOI CertificateManagement サービスが、 gnoi.certificate ターゲット ネットワーク要素の証明書管理を処理します。proto 定義ファイルは 、https://github.com/openconfig/gnoi/blob/master/cert/cert.proto にあります。
PKI(公開鍵基盤)は、公開鍵の配布と識別をサポートしており、ユーザーはインターネットなどのネットワークを介して安全にデータを交換し、相手の身元を確認することができます。Junos PKI を使用すると、証明書のダウンロード、生成、検証など、Junos デバイスで公開キー証明書を管理できます。gNOI CertificateManagement サービスは、Junos PKI を介した証明書管理の操作を定義します。2つの主な操作は次のとおりです。
-
インストール—ターゲットネットワークデバイスに新しい証明書IDを使用して新しい証明書をインストールします。証明書 ID が既に存在する場合、操作はエラーを返します。
-
回転 — ターゲット ネットワーク デバイス上に既存の証明書 ID を既に持っている既存の証明書を置き換えます。ストリームが破損している場合、またはプロセス中に何らかの手順が失敗した場合、デバイスは元の証明書にロールバックします。
図 1 は、 と Rotate() の操作のワークフローの概要をInstall()示しています。どちらの操作でも、クライアントは証明書署名要求(CSR)自体を生成するか、ターゲットに CSR を生成するよう要求できます。いずれの場合も、クライアントは CSR を認証機関(CA)に転送して、デジタル証明書を要求します。クライアントは、操作用の新しい証明書 ID を持つか、操作用Install()に既存の証明書 ID を持つ証明書をRotate()ターゲットに読み込みます。操作の場合Rotate()、クライアントは交換証明書も検証し、検証の成功または失敗にRotate()基づいて操作の最終確認または取り消しを行う必要があります。クライアントが操作をキャンセルした場合、サーバーは証明書、キーペア、および CA バンドル(要求に存在する場合)をロールバックします。
リリース 23.1R1 Junos OS Evolved以降、、 、Rotate()または LoadCertificate() の操作中Install()に、gNOI サーバーは、対応する CA 証明書を使用して新しいエンド エンティティ証明書を検証します。したがって、gNOI サーバーの PKI には、新しい証明書を検証するルート CA 証明書が含まれている必要があります。必要な CA 証明書を gNOI CA バンドルの一部として読み込むことも、個別にロードすることもできます。検証に失敗した場合、デバイスは新しい証明書をインストールしません。
gNOI サーバーは、gNOI サービスに対して 1 つのグローバル CA 証明書バンドルのみをサポートします。gNOI CertificateManagement サービスを使用して CA バンドルを読み込む場合、以下のステートメントが適用されます。
-
gNOI は、予約済み識別子
gnoi-ca-bundleを使用して、常に CA 証明書バンドルをca-profile-group読み込みます。 -
gNOIを使用してCA証明書バンドルを読み込む場合、デバイスは暗黙的に相互認証を使用し、デバイス上で明示的に設定されていないにもかかわらず、以下の設定を想定します。
[edit system services extension-service request-response grpc ssl] mutual-authentication { certificate-authority gnoi-ca-bundle; client-certificate-request require-certificate-and-verify; } -
gNOI サーバーが新しい CA 証明書バンドルの読み込み要求を受信した場合、以前の CA バンドルの証明書をデバイスからクリアし、新しい CA バンドルを読み込みます。
-
gNOI を使用して CA 証明書バンドルを読み込み、ステートメント階層も設定
[edit system services extension-service request-response grpc ssl mutual-authentication]した場合、設定されたステートメントが優先されます。
したがって、最初に gNOI サーバーでサーバー専用認証を設定してから、RPC を Install() 使用して CA 証明書を読み込むことができます。gNOI を使用して最初の CA 証明書バンドルを読み込む場合、デバイスは次の手順を実行します。
- Junos PKI に CA 証明書を追加します。
- 識別子
gnoi-ca-bundleを使用して、 階層レベルでgNOI CA証明書バンドルを[edit security pki]自動的に設定しますca-profile-group。 - サーバーのみの認証から相互認証に切り替えます。
[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;
}
}
}
RPC は Rotate() 、回転操作中の認証モード間の切り替えをサポートしていません。このため、操作中にデバイスがサーバーのみの認証から相互認証に切り替えられるため、 Rotate() 初めて gNOI サーバー上の CA 証明書バンドルの読み込みをサポートしません。認証モードが変更されると、ネットワークデバイスはgRPCスタックを再起動する必要があり、接続は失われます。ストリームが壊れている場合、クライアントは回転要求を最終化できず、デバイスは要求が開始される前に配置されていた証明書に Rotate() ロールバックします。
階層レベルの [edit system services extension-service request-response grpc ssl] ステートメントはhot-reloading、認証モードが操作中に変更されない場合、証明書の更新中にgRPCセッションのみを維持します。たとえば、認証モードがサーバーのみから相互認証に切り替えたり、その逆を切り替えたりすると、クライアントは切断します。
サポートされている RPC
表 1 は、Junos デバイスで CertificateManagement サポートされるサービス RPC の概要を示しています。
| リリースで導入された | RPC | の説明 |
|---|---|---|
CanGenerateCSR() |
ターゲットデバイスにクエリーを実行して、指定されたキータイプ、キーサイズ、証明書タイプを持つ証明書署名要求(CSR)を生成できるかどうかを確認します。サポートされている値:
gNOI サーバーが特定の |
Junos OS Evolved 23.1R1 |
GenerateCSR() |
証明書署名要求(CSR)を生成して返します。 |
Junos OS Evolved 22.2R1 |
GetCertificates() |
ターゲット デバイスに読み込まれたローカル証明書を返します。 |
Junos OS Evolved 22.2R1 |
Install() |
CSR要求を作成し、CSRに基づいて証明書を生成し、新しい証明書IDを使用して証明書を読み込むことで、ターゲットデバイスに新しい証明書を読み込みます。 |
Junos OS Evolved 22.2R1 |
LoadCertificate() |
対象のデバイスで認証機関(CA)によって署名された証明書を読み込みます。 |
Junos OS Evolved 22.2R1 |
LoadCertificateAuthorityBundle() |
ターゲット デバイス上の CA 証明書バンドルを読み込みます。 |
Junos OS Evolved 22.2R1 |
RevokeCertificates() |
ターゲットデバイスで指定された証明書 ID を持つ証明書を取り消します。 |
Junos OS Evolved 23.1R1 |
Rotate() |
CSR要求を作成し、CSRに基づいて証明書を生成し、既存の証明書IDを使用して証明書を読み込むことで、ターゲットデバイス上の既存の証明書を置き換えます。 |
Junos OS Evolved 22.2R1 |
ネットワーク デバイスの設定
JunosデバイスでgNOI証明書管理サービスを使用するには、gRPC拡張サービスの use-pki および hot-reloading ステートメントを設定する必要があります。ほとんどの場合、ネットワークデバイスで use-pki gRPCサービスを設定する場合、 ステートメントを設定します。ステートメントは hot-reloading 、セッションに影響を与える証明書を更新する際に、gRPCセッションを維持するために必要です。
開始する前に、以下を行います。
- gRPCサービスの設定に関する説明に従って、ネットワークデバイス上で gRPCサービスを設定します。
- gNOI サービスの構成に関する説明に従って、gNOI 操作をサポートするようにネットワーク管理システムを構成します。
サービス運用用にネットワーク デバイスを設定するには、次の手順に CertificateManagement 従います。
証明書のインストール
サービス Install() RPC をCertificateManagement使用して、ターゲット デバイスに新しい証明書を読み込むことができます。操作を使用して新しい証明書をインストールするInstall()場合、ターゲット デバイスにまだ存在しない新しい証明書 ID を指定する必要があります。また、オプションで、操作の一環として CA 証明書バンドルをInstall()読み込むことができます。
操作の Install() 一環として、デバイスが新しい証明書を検証します。そのため、Junos PKI には、新しい証明書を検証するルート CA 証明書が必要です。操作の一部として必要な CA 証明書を Install() 読み込むことも、PKI にまだ存在しない場合は、操作の前に別に読み込むことができます。
gRPC セッション認証に使用される新しいローカル証明書をインストールする場合は、新しい証明書 ID を使用するために、デバイス上の gRPC サーバー設定も更新する必要があります。
例:証明書のインストール
この例では、gNOI サーバーは当初ローカル証明書のみで構成されており、相互認証を使用するように構成されていません。gNOI クライアントは、RPC を Install() 使用して、デバイス上の新しいローカル証明書と CA 証明書バンドルを読み込みます。CA バンドルが gNOI サーバーに読み込まれた後、サーバーはデフォルトで相互認証を使用します。CAバンドルには、クライアント証明書のルートCA証明書と新しいサーバー証明書のルートCA証明書が含まれています。
クライアントは Python アプリケーションを gnoi_cert_install_certificate_csr.py 実行し、次の操作を実行します。
- ターゲットに CSR の生成を要求します。
- CSR に基づいて署名された証明書を取得します。
- ターゲット ネットワーク デバイス上の新しいサーバー証明書、サーバーの新しいルート CA 証明書、クライアントのルート CA 証明書を読み込みます。
アプリケーションは、 InstallCertificateRequest 適切なパラメーターを含むメッセージを使用して、CSR を生成して証明書を読み込む要求を定義します。要求ごとに、アプリケーションは RPC を Install() 使用してネットワーク デバイスにリクエストを送信します。
アプリケーションは gnoi_cert_install_certificate_csr.py 、チャネルを grpc_channel 確立するためにモジュールをインポートします。このモジュールについては grpc_channel 、「 gNOI サービスの設定」を参照してください。アプリケーションの引数は、ファイルに args_cert_install_csr.txt 格納されます。ここには、アプリケーションと引数のファイルが表示されます。
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=50051,
help='Server port. Default is 50051')
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=50051 --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
アプリケーションの実行
クライアントがアプリケーションを実行すると、アプリケーションは CSR を要求し、署名された証明書を取得して、ターゲット ネットワーク デバイス上の新しいサーバー証明書と CA 証明書を読み込みます。
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.
新しいサーバー証明書をインストールした後、ここに示すように、gRPCセッション認証にその証明書IDを使用するようにサーバーを設定する必要があります。さらに、操作で新しい CA 証明書が Install() 読み込まれるため、デバイスは暗黙的に相互認証を使用します。その結果、チャネルを確立する際、後続のすべての gRPC セッションにクライアントの証明書とキーを含める必要があります。
user@gnoi-server> show configuration system services extension-service request-response grpc ssl port 50051; local-certificate gnoi-server1; hot-reloading; use-pki;
アプリケーションを実行し、サーバーに既に存在する証明書 ID を指定すると、操作に新しい証明書 ID が必要になるため、アプリケーションはInstall()エラーを返ALREADY_EXISTSします。
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:50051","file":"src/core/lib/surface/call.cc","file_line":903,"grpc_message":"","grpc_status":6}"
証明書の回転
サービスRotate()RPCをCertificateManagement使用して、ターゲットデバイス上の既存の証明書を置き換えることができます。操作を使用して既存の証明書をRotate()置き換える場合は、ターゲット デバイスに既に存在する証明書 ID を使用して証明書を読み込む必要があります。また、オプションで、運用の一環として既存のgNOI CA証明書バンドルをRotate()交換することもできます。
操作は Rotate() 、新しい証明書を Install() インストールする代わりに既存の証明書を置き換える Rotate() という点を除き、操作と似ています。さらに、クライアントは更新された証明書が機能することを検証し、証明書検証の Rotate() 成功または失敗に基づいて要求を最終確認またはキャンセルする必要があります。
操作の Rotate() 一環として、デバイスが新しい証明書を検証します。そのため、Junos PKI には、新しい証明書を検証するルート CA 証明書が必要です。操作の一部として必要な CA 証明書を Rotate() 読み込むことも、PKI にまだ存在しない場合は、操作の前に別に読み込むことができます。
例:証明書の回転
この例では、クライアントは Python アプリケーションを gnoi_cert_rotate_certificate_csr.py 実行し、次の操作を実行します。
- ターゲットに CSR の生成を要求します。
- CSR に基づいて署名された証明書を取得します。
- ターゲット ネットワーク デバイス上のノード証明書と gNOI CA バンドルを置き換えます。
- 新しい証明書を検証します。
- 操作をファイナライズします
Rotate。
アプリケーションは、 RotateCertificateRequest 適切なパラメーターを含むメッセージを使用して、CSR を生成し、証明書と CA バンドルを読み込む要求を定義します。要求ごとに、アプリケーションは RPC を使用して Rotate() ネットワーク デバイスに要求を送信します。ターゲットデバイスが新しいノード証明書を検証できるように、アプリケーションは既存のCAバンドルを新しいCAバンドルに置き換えます。バンドルには、ノード証明書の検証に必要なクライアントCA証明書とCA証明書の両方が含まれています。
アプリケーションは、ネットワーク デバイスとの新しい gRPC セッションを作成し、シンプルな Time() RPC を実行することで、新しい証明書が機能することを検証します。ただし、任意の RPC でセッション認証をテストできます。セッションが正常に確立され、セッション認証に失敗した場合、回転要求がキャンセルされた場合、アプリケーションは回転要求を最終決定します。
アプリケーションは gnoi_cert_rotate_certificate_csr.py 、チャネルを grpc_channel 確立するためにモジュールをインポートします。このモジュールについては grpc_channel 、「 gNOI サービスの設定」を参照してください。アプリケーションの引数は、ファイルに args_cert_rotate_csr.txt 格納されます。ここには、アプリケーションと引数のファイルが表示されます。
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=50051,
help='Server port. Default is 50051')
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=50051 --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
引数は、最初のチャネル資格情報に必要なサーバーのルート CA 証明書であることに root_ca_cert 注意してください。引数は server_root_ca1 、サーバーの新しい証明書に対応するルート CA 証明書です。操作中に新しいローカル証明書を検証するには、Junos PKI に新しいルート CA 証明書が Rotate() 必要です。さらに、新しい証明書を検証するgRPCセッションのチャネル資格情報は、このルートCA証明書を使用します。この例では、新旧のサーバー証明書に同じルート CA 証明書を使用していますが、別のケースでは異なる場合があります。
アプリケーションの実行
クライアントがアプリケーションを実行すると、アプリケーションは CSR を要求し、署名された証明書を取得して、ターゲット ネットワーク デバイス上で交換証明書と CA バンドルを読み込みます。その後、アプリケーションは、シンプルな Time() RPC を実行する新しい gRPC セッションで置き換え証明書を検証します。検証に成功すると、クライアントは回転要求を最終決定します。
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.
証明書の取り消し
gNOI クライアントは RPC を RevokeCertificates() 使用して、ターゲット デバイスから 1 つ以上の証明書を削除できます。クライアントには、 RevokeCertificatesRequest 取り消す証明書 ID のリストを含むメッセージが含まれています。
gNOI サーバーは、要求を RevokeCertificates() 受信すると、リスト内の各証明書 ID を以下のように処理します。
-
証明書が存在し、取り消しが成功した場合、デバイスは証明書をファイルシステムと Junos PKI から削除し、正常に取り消された証明書のリストに証明書 ID を追加します。
-
証明書が存在し、失効に失敗した場合、デバイスには証明書 ID と障害の原因が証明書失効エラー リストに含まれます。
-
証明書が存在しない場合、デバイスは取り消し操作が成功したと見なし、正常に取り消された証明書のリストに証明書 ID を追加します。
要求が現在のセッションに使用される証明書を取り消す場合、セッションは影響を受けません。
要求を処理した後、gNOI サーバーは以下を含むメッセージを RevokeCertificatesResponse 返します。
-
取り消された証明書 ID のリスト。
-
証明書 ID を含む失効エラーのリストと失敗の原因。
例: 証明書の取り消し
この例では、クライアントはPythonアプリケーションを gnoi_cert_revoke_certificates.py 実行し、サーバー上の2つの証明書を取り消します。最初の証明書 ID は、デバイス上の有効な識別子です。2 番目の証明書 ID は、デバイスに存在しない識別子です。
アプリケーションは、 RevokeCertificatesRequest 適切なパラメーターとともにメッセージを使用して要求を定義します。アプリケーションは、RPC を RevokeCertificates() ネットワーク デバイスに送信して操作を実行します。
アプリケーションは gnoi_cert_revoke_certificates.py 、チャネルを grpc_channel 確立するためにモジュールをインポートします。このモジュールについては grpc_channel 、「 gNOI サービスの設定」を参照してください。アプリケーションの引数は、ファイルに args_cert_revoke_certificates.txt 格納されます。ここには、アプリケーションと引数のファイルが表示されます。
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=50051,
help='Server port. Default is 50051')
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=50051 --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
アプリケーションの実行
クライアントがアプリケーションを実行すると、アプリケーションは、指定された証明書を取り消すようにターゲット デバイスに指示します。デバイスは、正常に取り消された証明書とエラーのリストを返します。デバイスは、有効な証明書 ID と、現在デバイスに存在していない証明書 ID の両方について、操作が成功したと見なします。
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"
Rotate()、 、および
LoadCertificate() の操作では、
Install()新しい証明書が操作の一部として検証されます。