LoginSignup
3
4

More than 5 years have passed since last update.

(習作) 端末からBIG-IPにSSL証明書をインポートするコマンドを作ってみた

Last updated at Posted at 2018-01-07

はじめに

iControl RESTの勉強の目的で、iControl RESTで次のことを行うPythonスクリプトを作成してみました。

  1. SSL証明書(サーバ証明書・中間証明書)、秘密鍵をBIG-IPにアップロードして
  2. BIG-IPのFile オブジェクトとしてインポートして
  3. 既存のClient SSL Profile内のcert, key, chainを更新する

動作イメージはこのような感じです。

ssl_file_importer.png

要するに、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シリーズの画像にしたかったのですが、うまく読み込んでくれず・・・

しかも、努力した割にあまり見た目がよくないのが残念です。

このページは以上です。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4