はじめに
iControl RESTの勉強の目的で、iControl RESTで次のことを行うPythonスクリプトを作成してみました。
- SSL証明書(サーバ証明書・中間証明書)、秘密鍵をBIG-IPにアップロードして
- BIG-IPのFile オブジェクトとしてインポートして
- 既存のClient SSL Profile内のcert, key, chainを更新する
動作イメージはこのような感じです。
要するに、SSL証明書の更新の自動化です。(コマンドを手動実行する必要があるので、オペレーションの簡略化、というのが正確でしょうか。)
注意
実際に利用することを前提としたものではなく「個人がiControl RESTの学習のために作ってみた」ものに過ぎません。動くことは確認しましたが、例えば、どこかの処理でエラーが発生した場合の対処など、実用上必要になるだろう処理が多く抜けていると思います。
もし同様の機能を真面目に作成されたい方は、下記の参考情報のURLを参照するのが良いかと思います。
参考情報・引用元
-
DevCentral: Demystifying iControl REST Part 5: Transferring Files
- "Demystifying iControl REST"は、F5のスタッフによって作成されたiControl RESTの入門用シリーズです。このPart5では、iControlでファイルを転送する方法が説明されていて、具体例として、SSL証明書、秘密鍵のアップロードが扱われてます。今回、ファイルのアップロードは、DevCentralに記載のコード(_upload メソッド)をそのまま拝借してます。
環境
以下の環境で作成・動作を確認しました。
- クライアント:Linux(Python 3.6.3)
- サーバ(BIG-IP):v12.1.2
概要(スクリプトの使い方)
完成したスクリプトを「bigip_ssl_importer.py」という名前とした場合、次のように実行します。
(bash) $ bigip_ssl_importer.py -b "接続するBIG-IPのホスト名 or IPアドレス" \
-u "ユーザ名" \
-p "パスワード" \
-s "SSL Profile名" \
-c "サーバ証明書ファイル"(任意) \
-k "サーバ秘密鍵ファイル"(任意) \
-i "中間証明書ファイル"(任意)
証明書や秘密鍵ファイルの指定は任意です。ただし、サーバ秘密鍵ファイルを指定するときは、サーバ証明書ファイルも指定しないと動作しないようにしています。
証明書や秘密鍵のフォーマットはPEM形式、パスワードおよびパスフレーズなしを前提としています。
コード説明
ここから下はソースコードです。コードは、ある程度の固まりごとに区切っていますが、基本的に上から全部マージすれば、動作するものとなっています。(一部はDevCentralの記事を参照する必要があります)
ヘッダ
# !/usr/bin/env python
# coding: utf-8
import sys
import os
import requests
import json
import urllib3
import argparse
今回使ったライブラリ。特に説明すべき事項はありません。
ファイルアップロード用メソッド
def _upload(host, creds, fp):
・・・
(DevCentralの記事を参照)
・・・
これはDevCentralに記載のコードをそのまま使わせてもらいました。そのため、コード詳細は参考情報のリンク先を参照ください。
iControl RESTでアップロードするファイルは、アップロード時に指定するURLに応じて、2種類の異なるディレクトリに保存されます。(OSまたはHotfixのイメージファイルをアップロードする場合と、それ以外の2種類。)
今回のアップロードファイルは、**/var/config/rest/downloads/**に保存されます。この時点ではまだファイルがBIG-IPホスト内に転送されただけであり、設定内で扱えるファイルオブジェクトになっていません。
BIG-IPにアップロードされたファイルを、設定にインポートする処理
アップロードしたファイルを、SSLの証明書や秘密鍵のファイルオブジェクトとして登録します。iControl RESTリクエストのためのURLやJSONデータを準備して、リクエストを送信します。
class ssl_file_importer:
def __init__(self, session, bigip_host):
self.session = session
self.payload = {}
self.payload['name'] = ''
self.payload['command'] = 'install'
self.payload['from-local-file'] = ''
self.url = 'https://%s/mgmt/tm/sys/crypto' % bigip_host
def import_files(self, files):
for file_type, file_name in files.items():
self.payload['name'] = file_name
self.payload['from-local-file'] = '/var/config/rest/downloads/%s' % file_name
if (file_type == 'key'):
self._api_exec('/key')
else:
self._api_exec('/cert')
def _api_exec(self, api_url):
api_url = self.url + api_url
res = b.post(api_url, data=json.dumps(self.payload))
return res
ここでBIG-IPに行なっている処理をtmshコマンドで行なった場合、以下のようなコマンドになります。
(tmos) $ install sys crypto cert (ファイルオブジェクト名) from-local-file /var/config/rest/downloads/(証明書ファイル名)
(tmos) $ install sys crypto key (ファイルオブジェクト名) from-local-file /var/config/rest/downloads/(秘密鍵ファイル名)
上記が、iControl RESTでは次のようなリクエストになります。
メソッド | URL(Endpoint) | Body(JSON) |
---|---|---|
POST | /mgmt/tm/sys/crypto/cert | (下に記載) |
POST | /mgmt/tm/sys/crypto/key | (下に記載) |
(cert)
{"name": "ファイルオブジェクト名",
"command": "install",
"from-local-file": "/var/config/rest/downloads/証明書ファイル名"}
(key)
{"name": "ファイルオブジェクト名",
"command": "install",
"from-local-file": "/var/config/rest/downloads/秘密鍵ファイル名"}
Client SSL Profileの更新処理
インポートした証明書や秘密鍵を、指定のClient SSL Profileに紐づけます。
class ssl_profile_modifier:
def __init__(self, session, bigip_host, profile_name):
self.session = session
self.profile_name = profile_name
self.payload = {}
self.url = 'https://%s/mgmt/tm/ltm/profile/client-ssl' % bigip_host
res = self._get_cKC()
self.payload = json.loads(res.text)
def apply(self, files):
if 'key' in files:
self.payload['certKeyChain'][0]['key'] = files['key']
if 'cert' in files:
self.payload['certKeyChain'][0]['cert'] = files['cert']
if 'chain' in files:
self.payload['certKeyChain'][0]['chain'] = files['chain']
res = self._api_exec()
return res
def _get_cKC(self):
api_url = '%s/~Common~%s?$select=certKeyChain' % (self.url, self.profile_name)
res = b.get(api_url)
return res
def _api_exec(self):
api_url = '%s/~Common~%s' % (self.url, self.profile_name)
res = b.patch(api_url, data=json.dumps(self.payload))
return res
具体的には、Client SSL Profile内の「certKeyChain」というパラメータ内に、ファイルオブジェクトとしての証明書や秘密鍵を紐づけます。「certKeyChain」とパラメータ名が長いので、以下「cKC」と略します。
ここでBIG-IPに行なっている処理をtmshコマンドであらわすと、以下のようなコマンドになります。
(tmos) $ modify ltm profile client-ssl (profile名) { cert-key-chain replace-all-with { (cKC設定名) { cert (サーバ証明書ファイルオブジェクト) key (サーバ秘密鍵ファイルオブジェクト) chain (中間証明書ファイルオブジェクト) } }
certKeyChainパラメータについての補足事項
BIG-IPの仕様として、一つのclientssl profileに複数のcKC設定を入れることができます(つまり一つのclientssl profileに複数のSSL証明書・秘密鍵設定が紐づけられます)。
今回のスクリプトでは、複数のcKC設定には対応しません。cKC設定は1つのclient ssl profileに一つしか入っていないものを前提としてます。
一つのcKCの中には、cert, key, chain, passphrase 等の設定値があります。部分的な変更(例えば、certとkeyは変更してもchainは変更しないとか)に対応するには、一旦、現在のcKC内のパラメータを取得し、その後、コマンド実行時の引数の有無に従って、必要な箇所のみを更新します。
補足事項、ここまで。
上記の通り、cKC設定自体は複数登録可能な項目なので、iControl RESTでは、cKCの設定は配列で表されます。1つしかない前提なので、配列の[0]を対象に変更を行います。
iControl RESTでは次のようなリクエストになります。(サーバ証明書、サーバ秘密鍵、中間証明書を全て指定した場合)
メソッド | URL(Endpoint) | Body(JSON) |
---|---|---|
PACTCH | /mgmt/tm/ltm/profile/client-ssl/~Common~(profile名) | (下に記載) |
{"certKeyChain": [
{
"name": "cKC設定名",
"cert": "サーバ証明書のファイルオブジェクト名",
"certReference": {
"link": "(略)"
},
"chain": "中間証明書のファイルオブジェクト名",
"chainReference": {
"link": "(略)"
},
"key": "サーバ秘密鍵のファイルオブジェクト名",
"keyReference": {
"link": "(略)"
}
}
]}
メイン関数(残りの処理)
iControl RESTの動作はほぼ確認できたので、後の必要な処理は全部mainにしてしまいました。
引数の処理
parser = argparse.ArgumentParser()
parser.add_argument('-b', '--bigip', help='set connect big-ip hostname or ip address', required=True)
parser.add_argument('-u', '--user', help='set icontrol user name', required=True)
parser.add_argument('-p', '--password', help='set icontrol user password', required=True)
parser.add_argument('-s', '--sslprofile', help='set ssl profile name', required=True)
parser.add_argument('-c', '--cert', help='set cert file, if required.')
parser.add_argument('-k', '--key', help='set key file, if required.')
parser.add_argument('-i', '--chain', help='set intermediate(chain) file, if required.')
args = parser.parse_args()
cred = (args.user, args.password)
bigip_host = args.bigip
ssl_profile = args.sslprofile
files = {}
if (args.cert): files['cert'] = args.cert
if (args.key): files['key'] = args.key
if (args.chain): files['chain'] = args.chain
引数の処理にargparseを使い、上から4つ(接続先BIG-IPの指定、ユーザ名とパスワード、SSL Profile名)は指定が必須で、証明書、秘密鍵ファイルについては任意としました。
sessionオブジェクトの初期化
b = requests.session()
b.auth = cred
b.verify = False
b.headers.update({'Content-Type':'application/json'})
iControl RESTのセッションオブジェクトです。
処理の実行
# もしkeyファイルをimportする場合は、必ずcertも同時にimportすること
# certファイルについては、単体でimportしても良い
if 'key' in files and 'cert' not in files:
sys.stderr.write('Error: Key file should import with Cert file.')
sys.exit()
# cert file, key fileをiControlでBIG-IPにUploadする
for file in files.values():
_upload(bigip_host, cred, file)
# UploadしたCertとkeyをBIG-IP内でConfigにImportする
importer = ssl_file_importer(b, bigip_host)
importer.import_files(files)
# ssl profileの設定を変更する
ssl_prof_modifier = ssl_profile_modifier(b, bigip_host, ssl_profile)
ssl_prof_modifier.apply(files)
コードについては以上です。
補足事項(画像について)
今回、最初のイメージを作るのに、BIG-IPのステンシルを以下から利用しました。
askF5: K682: F5 logo, product images, and Visio stencils
今回、これをdraw.ioで読み込ませるのに一番苦労しました。最新機種のiシリーズの画像にしたかったのですが、うまく読み込んでくれず・・・
しかも、努力した割にあまり見た目がよくないのが残念です。
このページは以上です。