20210516のGoに関する記事は5件です。

Golangはじめて物語(第8話:API Gateway+LambdaでJWTを実装する)

はじめに Golang での JWT の実装はライブラリが充実しているので、比較的簡単に実装できる。 しかし、ググって見つかるのは Gin や Echo と組み合わせたものが多く、AWS で API Gateway を使った実例というのが少なかったので、組み込んでみる。 本記事は、以下の知識があることを前提とする。 JWT に関する概要が分かっている(と言っても、あまり難しいことは知らなくてよくてこの記事くらいの概要が分かっていれば充分) Golang で API Gateway + Lambda の WebAPI を実装したことがある(手前味噌ではあるが、この記事を読んで理解ができていれば充分) なお、今回は IdP には頼らずに自分で秘密鍵を使ってトークンを払い出して検証するという方式を検証する。 トークン払い出しの実装 まずは、JWT のトークンを払い出す部分を実装する。 ライブラリは form3tech-oss/jwt-go を使用する。 import ( "context" "encoding/json" "time" jwt "github.com/form3tech-oss/jwt-go" "github.com/google/uuid" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) func main() { lambda.Start(handler) } func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { uuidObj, _ := uuid.NewUUID() tokenString, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.StandardClaims{ Id: uuidObj.String(), ExpiresAt: time.Now().Add(time.Minute * 1).Unix(), }).SignedString("[秘密鍵の文字列]") jsonBytes, _ := json.Marshal(struct { Token string `json:"token"` }{ Token: tokenString, }) return events.APIGatewayProxyResponse{ StatusCode: 200, IsBase64Encoded: false, Body: string(jsonBytes), }, nil } これだけ。とてもシンプル。 キモになるのは以下の部分で、それ以外は API Gateway のレスポンスを作っているだけである。 tokenString, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.StandardClaims{ Id: uuidObj.String(), ExpiresAt: time.Now().Add(time.Minute * 1).Unix(), }).SignedString("[秘密鍵の文字列]") 見ての通り、jwt.NewWithClaims() は、暗号化方式、Claim、秘密鍵 を渡しているだけである。 秘密鍵の文字列は、当然のことながら平文で書いて Git に登録すべきではない。本記事では AWS で API Gateway を使う前提としているため、KMS で管理しているデータキーを使うのが良いだろう。 ExpiresAt は本記事では検証のために短く設定しているが、ここは要件に合わせて設定をしよう。 Id については何でもよい。これはトークンの JTI にあたる部分になる。 リクエスト単位でランダムな文字列になり、改ざん検知ができれば良いので、UUID を生成して渡すことにした。 これを、API Gateway の Lambda 統合で呼び出すように設定をしよう(設定方法は冒頭の、第一話を参照)。 これでAPIを呼び出すと、以下のように JSON が返却される。 $ curl -i https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/auth {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjEyNjEzNDIsImp0aSI6IjI3ZTk3NzJiLWI3MWItMTFlYi1hYWQyLTYyM2E0YzVlMGY4MCJ9.Rf2f0_Iwm-uIXXOPYvbEjjkBk9xcc8IYNDWGoobqdE8"} これを、JWT.IO の Debugger に食わせてみると、以下のように、自分の作ったキーが復号されることが分かる。 ※右下の your-256-bit-secret と書かれているところに、上記で設定した秘密鍵の文字列を入力すると、正しい文字列になる。 トークン検証の実装 さて、続いてはトークンの検証だ。 本記事では、検証を行う必要がある API で、Authorization ヘッダに設定されたトークンを検証する。 ※実装を簡易にするために、Bearer の文字列は設定しない。 以下のような関数を作って検証をすることにしよう。 import ( "errors" "log" jwt "github.com/form3tech-oss/jwt-go" ) func checkToken(headers map[string]string) error { tokenString, headerIsNotNull := headers["Authorization"] if !headerIsNotNull { return errors.New("[Header]Authorization is not specified") } token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { b := []byte("[秘密鍵の文字列]") return b, nil }) if err != nil { log.Println(err) return errors.New("jwt.Parse() returned error") } return nil } キモになるのは、 token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { b := []byte("[秘密鍵の文字列]") return b, nil }) で、これでトークンを構造体に割り付けることができる。 ここも、払い出し同様に、秘密鍵の文字列はシークレット用の処理を施すこと。 トークンから Claim を参照にするには、token.Claims.(jwt.MapClaims) を見ればよい。 あとは、好きに中身を検証しよう。 なお、ExpiresAt(exp) は、jwt.Parse() 内で勝手に検証してくれるため、自分で実装する必要はない。便利! ドキュメントには VerifyExpiresAt という関数もあるが、おそらくこれは、検証なしの Parse 関数を使った場合等に自分で実装をするためのものと思われる。 ともあれ、これを実装した API に対して、以下のようにデータを投げてみよう。 $ curl -i -X PUT -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjEyNjEzNDIsImp0aSI6IjI3ZTk3NzJiLWI3MWItMTFlYi1hYWQyLTYyM2E0YzVlMGY4MCJ9.Rf2f0_Iwm-uIXXOPYvbEjjkBk9xcc8IYNDWGoobqdE8' https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/resource 払い出した正しいトークンを投げた場合は正常に処理が行われ、そうでない場合は jwt.Parse() がエラーになった旨のログが出るだろう。ためしに、トークンの文字列を変更してみたり、払い出しから1分経過後にトークンを使ってみると分かりやすい。 でも、色々な API でトークンの検証するの面倒臭くない? そんな物臭なあなたに Lambda Authorizer。 というか、AWS で API Gateway を使うのであればこちらを採用する方が、マネージドサービスの恩恵を享受できるだろう。 Authorize 設定をした API について、共通的に検証用の Lambda 関数を呼び出してくれるという便利機能だ。 Terraformでは、Authorize 設定をしたいリソース/メソッドについて、以下の設定を行う。 resource "aws_api_gateway_method" "data_put" { rest_api_id = aws_api_gateway_rest_api.jwt_example.id resource_id = aws_api_gateway_resource.resource.id http_method = "PUT" authorization = "CUSTOM" authorizer_id = aws_api_gateway_authorizer.jwt.id } resource "aws_api_gateway_authorizer" "jwt" { name = "jwt_authorizer" type = "REQUEST" rest_api_id = aws_api_gateway_rest_api.jwt_example.id authorizer_uri = aws_lambda_function.authorizer.invoke_arn } aws_api_gateway_method の authorization は、 IAM や COGNITO_USER_POOLS による認証も可能だが、今回の Lambda の方式の場合は CUSTOM を設定する。 また、認証用の Lambda を別途 Terraform で定義して、aws_api_gateway_authorizer の authorizer_uri に ARN を設定する。 上記の設定をした Lambda Authorizer では、API Gateway が Lambda に渡してくるイベントが APIGatewayProxyRequest ではなくて APIGatewayCustomAuthorizerRequestTypeRequest になるので注意が必要だ。また、ハンドラの戻り値についても、APIGatewayCustomAuthorizerResponse を返す必要がある。 APIGatewayCustomAuthorizerResponse の PolicyDocument では、IAM のポリシードキュメント形式で返す必要があるが、結局は、APIGatewayCustomAuthorizerRequestTypeRequest の MethodArn のプロパティで、アクセスさせたいリソースとメソッドは簡単に抽出できるため、たいして難しくはない。捻ったことをしようとしない限りは、お決まりの形で Allow/Deny を返してあげれば良いだろう。 上記を踏まえて、以下のような Lambda のハンドラを用意する。 import ( "context" "errors" "log" jwt "github.com/form3tech-oss/jwt-go" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) func main() { lambda.Start(handler) } func checkToken(headers map[string]string) error { tokenString, headerIsNotNull := headers["Authorization"] if !headerIsNotNull { return errors.New("[Header]Authorization is not specified") } token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { b := secretKey return b, nil }) if err != nil { log.Println(err) return errors.New("jwt.Parse() returned error") } return nil } func handler(ctx context.Context, request events.APIGatewayCustomAuthorizerRequestTypeRequest) (events.APIGatewayCustomAuthorizerResponse, error) { if err := checkToken(request.Headers); err != nil { log.Println(err) log.Println("Invalid Token.") return events.APIGatewayCustomAuthorizerResponse{ PrincipalID: "user", PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ Version: "2012-10-17", Statement: []events.IAMPolicyStatement{ { Action: []string{"execute-api:Invoke"}, Effect: "Deny", Resource: []string{request.MethodArn}, }, }, }, }, nil } return events.APIGatewayCustomAuthorizerResponse{ PrincipalID: "user", PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ Version: "2012-10-17", Statement: []events.IAMPolicyStatement{ { Action: []string{"execute-api:Invoke"}, Effect: "Allow", Resource: []string{request.MethodArn}, }, }, }, }, nil } これで、先ほどと同様に $ curl -i -X PUT -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjExMzUyODYsInN1YiI6IjAwMDAxIn0.QcJWTzH1wBe6QdbWJIECO3QMn9ZiE_bK9-Paft1YBy4' https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/resource を投げ込むと、resource の PUT の API でチェックを実装しなくても、API Gateway がオーソライザーの Lambda を実行してチェックしてくれるぞ!なお、このケースでは、デフォルトの HTTP レスポンスコードは 403 で、 {"Message":"User is not authorized to access this resource with an explicit deny"} というワーディングが返される。 変更したい場合は、ゲートウェイのレスポンスから変更を行おう。 また、デフォルトではAPI Gatewayのキャッシュ機能がONであるため、トークン期限切れでも認証が通ってしまうことがある。嫌なのであれば、Terraform の aws_api_gateway_authorizer のリソースで authorizer_result_ttl_in_seconds の設定を変更しよう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Notion APIライブラリまとめ

Ruby Laravel Swift JS go python vue PHP React
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

napalm の 6 倍速いリモートコマンド実行ツールを書きました

ネットワーク機器のコマンドをリモート実行するツール「telee」を書きました。 このツールは、機器へのログイン処理とコマンド実行を代理します。 従来の expect スクリプト, NAPALM や Ansible の役割を部分的に担えます。 以下は動作例です。 Cisco 社製 OS はもちろん、Juniper, YAMAHA 等、他社製 OS へのログインにも対応しています。 接続プロトコルは telnet もしくは ssh が選択出来ます。その他のプロトコルには対応していません。 実行速度を NAPALM の同一機能と比較すると、一般的な用途で 6 - 72 倍高速です。 ※ 測定の詳細は Performance Comparison: telee and napalm をご確認ください。 最新版のバイナリは、リポジトリの Releases page からダウンロード出来ます。 プラットフォームは、Linux, Mac の amd64, arm64 にそれぞれ対応しています。 記事更新時点の最新バージョンは v1.7.2 です。 Download v1.7.2 for Linux 64-bit Download v1.7.2 for Linux 64-bit ARM Download v1.7.2 for Mac OS 64-bit Download v1.7.2 for Mac OS 64-bit ARM (M1) ネットワークエンジニアの皆様、ぜひお試しください。フィードバックも大歓迎です。 以下、詳細です。 背景 ネットワーク機器の操作には、telnet を利用する場合があります。 しかし telnet は、公開鍵のようなシームレスな認証に対応していません。 機器にログインするには、大半のケースでユーザ・パスワード認証が必要です。 この入力に手間を感じていました。 ネットワーク業界で最も高いシェアを持つ Cisco 社製のスイッチを例に取ります。 このスイッチで設定の一覧を表示するには、以下の 7 ステップが必要です。 (1) telnet コマンドを実行 (2) ユーザ名を入力 (3) パスワードを入力 (4) enable コマンドを実行 (5) 特権 EXEC パスワードを入力 (6) terminal length 0 コマンドを実行 (7) show running-config コマンドを実行 多いです。数台であれば耐えられますが、台数が増えるにつれて辛みも増していきます。 解決策 この課題は、一般的に以下のような手段で解決します。 (1) TeraTerm マクロ で TTL ファイルを書く (2) expect コマンド を用いてスクリプトを書く (3) Ansible の network_cli module 等を用いて task を書く (4) NAPALM を用いてコードまたは YAML を書く (5) NAPALM: Command Line Tool を利用する (1) - (4) は、いずれも 1 行のコマンドを実行するために複数行の構文が必要です。 また、運用継続に伴ってスクリプトやコードが焼き増された場合、多重メンテが必要になります。 (5) は解決策たりえますが、telnet や SSH と比較すると処理が緩慢で、普段使いには難があります。 豊富な機能を搭載している反面、ワンラインが伸びやすく、簡潔に書きづらい点も課題です。 これらを回避しつつ、より手軽にコマンドを実行したいという思いで、今回「telee」を書きました。 利用方法 以下は、記事作成時点の README.md (日本語版) です。最新の情報はリポジトリをご参照ください。 基本書式 telee -H HOSTNAME -C COMMAND [options...] コマンドオプション オプション 概要 デフォルト値 環境変数 --hostname(-H) ホスト名または IP アドレスを指定します。 - $TELEE_HOSTNAME --port(-P) ポート番号を指定します。 23 - --command(-C) 実行するコマンドを指定します。 - $TELEE_COMMAND --exec-platform(-x) 接続先の環境名を指定します。※後述参照 ios - --username(-u) ユーザ名を指定します。 admin $TELEE_USERNAME --password(-p) パスワードを指定します。 cisco $TELEE_PASSWORD --priv-password(--pp) 特権モードのパスワードを指定します。 enable $TELEE_PRIVPASSWORD --enable-mode(-e, --ena, --enable) 特権モードの利用時に設定します。 false - --redundant-mode(-r, --redundant) HA 専用のプロンプトを持つ機器への接続時に設定します。 false - --secure-mode(-s, --sec, --secure) SSH での接続時に設定します。--port は 22 に設定されます。 false - --timeout(-t) タイムアウト秒を指定します。 5 - Exec Platform --exec-platform はログインプロンプトの識別に利用しています。 指定を誤った場合、正常にログイン出来ず、コマンドがタイムアウトします。 指定可能なプラットフォームと、動作確認バージョンはそれぞれ以下の通りです。 定義名 (-x 指定) プラットフォーム名 Telnet SSH aireos Cisco AireOS ✅ 8.5.120.0 ✅ 8.5.120.0 allied AlliedTelesis AlliedWare ✅ 1.6.14B02 Not Supported asa Cisco ASA Software ✅ 9.0(4) Not Supported asa (redundant) Cisco ASA Software (HA) ✅ 9.10(1) Not Supported foundry Brocade IronWare ✅ 07.2.02aT7e1 Not Supported ios Cisco IOS, IOS-XE ✅ 15.2(5c)E ✅ 15.2(5c)E nxos Cisco NX-OS ✅ 6.2(14) Not Supported srx JuniperNetworks JunOS Not Supported ✅ 15.1X49-D90.7 ssg JuniperNetworks ScreenOS ✅ 6.3.0r21.0 Not Supported ssg (redundant) JuniperNetworks ScreenOS (HA) ✅ 6.3.0r22.0 Not Supported yamaha YAMAHA Router OS ✅ Rev.8.03.94 Not Supported 実行手順 ログインに利用するユーザ名とパスワードを、環境変数に設定します。 export TELEE_USERNAME=telee export TELEE_PASSWORD=Teleedev! export TELEE_PRIVPASSWORD=Teleedev!! ホスト名を指定してコマンドを実行します。 $ telee --hostname lab-cat29l-02f99-01 --command "show int descr" show int descr Load for five secs: 2%/0%; one minute: 1%; five minutes: 1% Time source is NTP, 23:16:54.302 JST Sat May 8 2021 Interface Status Protocol Description Vl1 admin down down Vl800 up up *** LAB-MGMT *** Gi0/1 up up CLIENT_DEVICE_LONG_DESCR Gi0/2 up up CLIENT_DEVICE Gi0/3 up up CLIENT_DEVICE Gi0/4 up up CLIENT_DEVICE Gi0/5 up up CLIENT_DEVICE Gi0/6 down down CLIENT_DEVICE Gi0/7 down down CLIENT_DEVICE Gi0/8 up up GATEWAY_ROUTER Gi0/9 admin down down Gi0/10 admin down down lab-cat29l-02f99-01> 完了です。 応用例 パイプやリダイレクトにかける 出力結果を別のコマンドで処理出来ます。以下のように実行します。 実行例 $ telee --hostname lab-cat29l-02f99-01 --command "show int descr" | grep "Interface\|down" Interface Status Protocol Description Vl1 admin down down Gi0/1 down down CLIENT_DEVICE_LONG_DESCR Gi0/6 down down CLIENT_DEVICE Gi0/7 down down CLIENT_DEVICE Gi0/9 admin down down Gi0/10 admin down down $ telee --hostname lab-cat29l-02f99-01 --command "show run" --enable > telee.log $ head -n 10 telee.log show run Load for five secs: 1%/0%; one minute: 1%; five minutes: 1% Time source is NTP, 23:21:34.501 JST Sat May 8 2021 Building configuration... Current configuration : 18687 bytes ! ! Last configuration change at 01:30:16 JST Sun Feb 14 2021 ! Cisco IOS 以外を操作する --exec-platform オプションの指定が必要です。 AireOS を操作する場合は、以下のように実行します。 実行例 $ telee -H 192.168.0.250 -C "show sysinfo" -x aireos show sysinfo Manufacturer's Name.............................. Cisco Systems Inc. Product Name..................................... Cisco Controller Product Version.................................. 8.5.120.0 Bootloader Version............................... 1.0.20 Field Recovery Image Version..................... 7.6.101.1 Firmware Version................................. PIC 19.0 OUI File Last Update Time........................ Sun Sep 07 10:44:07 IST 2014 Build Type....................................... DATA + WPS System Name...................................... lab-wlc-01f01-01a System Location.................................. System Contact................................... System ObjectID.................................. 1.3.6.1.4.1.9.1.1279 IP Address....................................... 192.168.0.250 <snip> Cisco ASA を操作する --enable-mode オプションの指定が必要です。 Cisco ASA は、ユーザ権限で terminal pager 0 が実行出来ません。 Cisco ASA を操作する場合は、以下のように実行します。 実行例 $ telee -H lab-asa5505-02f01-01 -C "show version" -x asa --enable-mode show version Cisco Adaptive Security Appliance Software Version 9.0(4) Device Manager Version 7.1(5)100 Compiled on Wed 04-Dec-13 08:33 by builders System image file is "disk0:/asa904-k8.bin" Config file at boot was "startup-config" lab-asa5505-02f01-01 up 70 days 2 hours Hardware: ASA5505, 512 MB RAM, CPU Geode 500 MHz, Internal ATA Compact Flash, 128MB BIOS Flash M50FW016 @ 0xfff00000, 2048KB Encryption hardware device : Cisco ASA-5505 on-board accelerator (revision 0x0) Boot microcode : CN1000-MC-BOOT-2.00 SSL/IKE microcode : CNLite-MC-SSLm-PLUS-2.03 <snip> SSH で接続する --secure-mode オプションの指定が必要です。以下のように実行します。 実行例 $ telee -H lab-cat29l-02f99-01 -C "show run" --enable --secure show run Load for five secs: 8%/0%; one minute: 2%; five minutes: 1% Time source is NTP, 02:25:22.496 JST Fri May 14 2021 Building configuration... Current configuration : 18716 bytes ! ! Last configuration change at 01:46:41 JST Fri May 14 2021 by raciadev ! version 15.2 no service pad service tcp-keepalives-in service timestamps debug datetime msec localtime show-timezone service timestamps log datetime msec localtime show-timezone service password-encryption ! hostname lab-cat29l-02f99-01 <snip> 応答の遅い機器を操作する --timeout でタイムアウト値を調整出来ます。以下のように実行します。 実行例 $ telee -H lab-fs909-02f01-01 -C "show system" -x allied -u manager --timeout 10 show system Switch System Status Date 2021-05-09 Time 01:04:54 Board Bay Board Name ---------------------------------------------------------------------- Base - FS909M ---------------------------------------------------------------------- Memory - DRAM : 32768 kB FLASH : 8192 kB MAC : 00-1A-EB-93-1C-95 ---------------------------------------------------------------------- SysDescription : CentreCOM FS909M Ver 1.6.14 B02 SysContact : SysLocation : LAB SysName : lab-fs909-02f01-01 SysUpTime : 1267989237(146days, 18:11:32) Release Version : 1.6.14 Release built : B02 (Nov 23 2010 at 14:29:56) Flash PROM : Good RAM : Good SW chip : Good <snip> RADIUS Attribute を用いて特権を付与した環境で利用する --default-privilege-mode オプションの指定が必要です。以下のように実行します。 実行例 $ telee -H lab-nx70-02f01-01 -C "show version" -x nxos --default-privilege-mode show version Cisco Nexus Operating System (NX-OS) Software TAC support: http://www.cisco.com/tac Documents: http://www.cisco.com/en/US/products/ps9372/tsd_products_support_series_home.html Copyright (c) 2002-2015, Cisco Systems, Inc. All rights reserved. The copyrights to certain works contained in this software are owned by other third parties and used and distributed under license. Certain components of this software are licensed under the GNU General Public License (GPL) version 2.0 or the GNU Lesser General Public License (LGPL) Version 2.1. A copy of each such license is available at http://www.opensource.org/licenses/gpl-2.0.php and http://www.opensource.org/licenses/lgpl-2.1.php Software BIOS: version N/A kickstart: version 6.2(14) system: version 6.2(14) BIOS compile time: kickstart image file is: bootflash:///n7000-s1-kickstart.6.2.14.bin <snip> おわりに 今回は、ネットワーク機器の運用を効率化するツールを紹介しました。 ネットワーク機器のコマンド実行結果をシェル上で直接扱えることには、高い汎用性があります。 例えば、旧来の Rancid や Ansible による情報取得を、より単純なシェルスクリプトで代替出来ます。 コマンドとシェルの知識だけ持っていれば良いため、学習コスト・運用コストとも安く済みます。 Syslog サーバを有している場合は、EEM のように、ログに基づいたコマンドの実行が出来ます。 サーバ主導となるため、A のログをトリガーに B でコマンドを実行、といった構成が実現出来ます。 各々のユースケースで、採用できそうなポイントがありましたら是非お試しください。 最後までお読み頂きありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

napalm の 6 倍速いネットワーク機器用リモートコマンド実行ツールを書きました

ネットワーク機器のコマンドをリモート実行するツール「telee」を書きました。 このツールは、機器へのログイン処理とコマンド実行を代理します。 従来の expect スクリプト, NAPALM や Ansible の役割を部分的に担えます。 以下は動作例です。 Cisco 社製 OS はもちろん、Juniper, YAMAHA 等、他社製 OS へのログインにも対応しています。 接続プロトコルは telnet もしくは ssh が選択出来ます。その他のプロトコルには対応していません。 実行速度を NAPALM の同一機能と比較すると、一般的な用途で 6 - 72 倍高速です。 ※ 測定の詳細は Performance Comparison: telee and napalm をご確認ください。 最新版のバイナリは、リポジトリの Releases page からダウンロード出来ます。 プラットフォームは、Linux, Mac の amd64, arm64 にそれぞれ対応しています。 記事更新時点の最新バージョンは v1.7.2 です。 Download v1.7.2 for Linux 64-bit Download v1.7.2 for Linux 64-bit ARM Download v1.7.2 for Mac OS 64-bit Download v1.7.2 for Mac OS 64-bit ARM (M1) ネットワークエンジニアの皆様、ぜひお試しください。フィードバックも大歓迎です。 以下、詳細です。 背景 ネットワーク機器の操作には、telnet を利用する場合があります。 しかし telnet は、公開鍵のようなシームレスな認証に対応していません。 機器にログインするには、大半のケースでユーザ・パスワード認証が必要です。 この入力に手間を感じていました。 ネットワーク業界で最も高いシェアを持つ Cisco 社製のスイッチを例に取ります。 このスイッチで設定の一覧を表示するには、以下の 7 ステップが必要です。 (1) telnet コマンドを実行 (2) ユーザ名を入力 (3) パスワードを入力 (4) enable コマンドを実行 (5) 特権 EXEC パスワードを入力 (6) terminal length 0 コマンドを実行 (7) show running-config コマンドを実行 多いです。数台であれば耐えられますが、台数が増えるにつれて辛みも増していきます。 解決策 この課題は、一般的に以下のような手段で解決します。 (1) TeraTerm マクロ で TTL ファイルを書く (2) expect コマンド を用いてスクリプトを書く (3) Ansible の network_cli module 等を用いて task を書く (4) NAPALM を用いてコードまたは YAML を書く (5) NAPALM: Command Line Tool を利用する (1) - (4) は、いずれも 1 行のコマンドを実行するために複数行の構文が必要です。 また、運用継続に伴ってスクリプトやコードが焼き増された場合、多重メンテが必要になります。 (5) は解決策たりえますが、telnet や SSH と比較すると処理が緩慢で、普段使いには難があります。 豊富な機能を搭載している反面、ワンラインが伸びやすく、簡潔に書きづらい点も課題です。 これらを回避しつつ、より手軽にコマンドを実行したいという思いで、今回「telee」を書きました。 利用方法 以下は、記事作成時点の README.md (日本語版) です。最新の情報はリポジトリをご参照ください。 基本書式 telee -H HOSTNAME -C COMMAND [options...] コマンドオプション オプション 概要 デフォルト値 環境変数 --hostname(-H) ホスト名または IP アドレスを指定します。 - $TELEE_HOSTNAME --port(-P) ポート番号を指定します。 23 - --command(-C) 実行するコマンドを指定します。 - $TELEE_COMMAND --exec-platform(-x) 接続先の環境名を指定します。※後述参照 ios - --username(-u) ユーザ名を指定します。 admin $TELEE_USERNAME --password(-p) パスワードを指定します。 cisco $TELEE_PASSWORD --priv-password(--pp) 特権モードのパスワードを指定します。 enable $TELEE_PRIVPASSWORD --enable-mode(-e, --ena, --enable) 特権モードの利用時に設定します。 false - --redundant-mode(-r, --redundant) HA 専用のプロンプトを持つ機器への接続時に設定します。 false - --secure-mode(-s, --sec, --secure) SSH での接続時に設定します。--port は 22 に設定されます。 false - --timeout(-t) タイムアウト秒を指定します。 5 - Exec Platform --exec-platform はログインプロンプトの識別に利用しています。 指定を誤った場合、正常にログイン出来ず、コマンドがタイムアウトします。 指定可能なプラットフォームと、動作確認バージョンはそれぞれ以下の通りです。 定義名 (-x 指定) プラットフォーム名 Telnet SSH aireos Cisco AireOS ✅ 8.5.120.0 ✅ 8.5.120.0 allied AlliedTelesis AlliedWare ✅ 1.6.14B02 Not Supported asa Cisco ASA Software ✅ 9.0(4) Not Supported asa (redundant) Cisco ASA Software (HA) ✅ 9.10(1) Not Supported foundry Brocade IronWare ✅ 07.2.02aT7e1 Not Supported ios Cisco IOS, IOS-XE ✅ 15.2(5c)E ✅ 15.2(5c)E nxos Cisco NX-OS ✅ 6.2(14) Not Supported srx JuniperNetworks JunOS Not Supported ✅ 15.1X49-D90.7 ssg JuniperNetworks ScreenOS ✅ 6.3.0r21.0 Not Supported ssg (redundant) JuniperNetworks ScreenOS (HA) ✅ 6.3.0r22.0 Not Supported yamaha YAMAHA Router OS ✅ Rev.8.03.94 Not Supported 実行手順 ログインに利用するユーザ名とパスワードを、環境変数に設定します。 export TELEE_USERNAME=telee export TELEE_PASSWORD=Teleedev! export TELEE_PRIVPASSWORD=Teleedev!! ホスト名を指定してコマンドを実行します。 $ telee --hostname lab-cat29l-02f99-01 --command "show int descr" show int descr Load for five secs: 2%/0%; one minute: 1%; five minutes: 1% Time source is NTP, 23:16:54.302 JST Sat May 8 2021 Interface Status Protocol Description Vl1 admin down down Vl800 up up *** LAB-MGMT *** Gi0/1 up up CLIENT_DEVICE_LONG_DESCR Gi0/2 up up CLIENT_DEVICE Gi0/3 up up CLIENT_DEVICE Gi0/4 up up CLIENT_DEVICE Gi0/5 up up CLIENT_DEVICE Gi0/6 down down CLIENT_DEVICE Gi0/7 down down CLIENT_DEVICE Gi0/8 up up GATEWAY_ROUTER Gi0/9 admin down down Gi0/10 admin down down lab-cat29l-02f99-01> 完了です。 応用例 パイプやリダイレクトにかける 出力結果を別のコマンドで処理出来ます。以下のように実行します。 実行例 $ telee --hostname lab-cat29l-02f99-01 --command "show int descr" | grep "Interface\|down" Interface Status Protocol Description Vl1 admin down down Gi0/1 down down CLIENT_DEVICE_LONG_DESCR Gi0/6 down down CLIENT_DEVICE Gi0/7 down down CLIENT_DEVICE Gi0/9 admin down down Gi0/10 admin down down $ telee --hostname lab-cat29l-02f99-01 --command "show run" --enable > telee.log $ head -n 10 telee.log show run Load for five secs: 1%/0%; one minute: 1%; five minutes: 1% Time source is NTP, 23:21:34.501 JST Sat May 8 2021 Building configuration... Current configuration : 18687 bytes ! ! Last configuration change at 01:30:16 JST Sun Feb 14 2021 ! Cisco IOS 以外を操作する --exec-platform オプションの指定が必要です。 AireOS を操作する場合は、以下のように実行します。 実行例 $ telee -H 192.168.0.250 -C "show sysinfo" -x aireos show sysinfo Manufacturer's Name.............................. Cisco Systems Inc. Product Name..................................... Cisco Controller Product Version.................................. 8.5.120.0 Bootloader Version............................... 1.0.20 Field Recovery Image Version..................... 7.6.101.1 Firmware Version................................. PIC 19.0 OUI File Last Update Time........................ Sun Sep 07 10:44:07 IST 2014 Build Type....................................... DATA + WPS System Name...................................... lab-wlc-01f01-01a System Location.................................. System Contact................................... System ObjectID.................................. 1.3.6.1.4.1.9.1.1279 IP Address....................................... 192.168.0.250 <snip> Cisco ASA を操作する --enable-mode オプションの指定が必要です。 Cisco ASA は、ユーザ権限で terminal pager 0 が実行出来ません。 Cisco ASA を操作する場合は、以下のように実行します。 実行例 $ telee -H lab-asa5505-02f01-01 -C "show version" -x asa --enable-mode show version Cisco Adaptive Security Appliance Software Version 9.0(4) Device Manager Version 7.1(5)100 Compiled on Wed 04-Dec-13 08:33 by builders System image file is "disk0:/asa904-k8.bin" Config file at boot was "startup-config" lab-asa5505-02f01-01 up 70 days 2 hours Hardware: ASA5505, 512 MB RAM, CPU Geode 500 MHz, Internal ATA Compact Flash, 128MB BIOS Flash M50FW016 @ 0xfff00000, 2048KB Encryption hardware device : Cisco ASA-5505 on-board accelerator (revision 0x0) Boot microcode : CN1000-MC-BOOT-2.00 SSL/IKE microcode : CNLite-MC-SSLm-PLUS-2.03 <snip> SSH で接続する --secure-mode オプションの指定が必要です。以下のように実行します。 実行例 $ telee -H lab-cat29l-02f99-01 -C "show run" --enable --secure show run Load for five secs: 8%/0%; one minute: 2%; five minutes: 1% Time source is NTP, 02:25:22.496 JST Fri May 14 2021 Building configuration... Current configuration : 18716 bytes ! ! Last configuration change at 01:46:41 JST Fri May 14 2021 by raciadev ! version 15.2 no service pad service tcp-keepalives-in service timestamps debug datetime msec localtime show-timezone service timestamps log datetime msec localtime show-timezone service password-encryption ! hostname lab-cat29l-02f99-01 <snip> 応答の遅い機器を操作する --timeout でタイムアウト値を調整出来ます。以下のように実行します。 実行例 $ telee -H lab-fs909-02f01-01 -C "show system" -x allied -u manager --timeout 10 show system Switch System Status Date 2021-05-09 Time 01:04:54 Board Bay Board Name ---------------------------------------------------------------------- Base - FS909M ---------------------------------------------------------------------- Memory - DRAM : 32768 kB FLASH : 8192 kB MAC : 00-1A-EB-93-1C-95 ---------------------------------------------------------------------- SysDescription : CentreCOM FS909M Ver 1.6.14 B02 SysContact : SysLocation : LAB SysName : lab-fs909-02f01-01 SysUpTime : 1267989237(146days, 18:11:32) Release Version : 1.6.14 Release built : B02 (Nov 23 2010 at 14:29:56) Flash PROM : Good RAM : Good SW chip : Good <snip> RADIUS Attribute を用いて特権を付与した環境で利用する --default-privilege-mode オプションの指定が必要です。以下のように実行します。 実行例 $ telee -H lab-nx70-02f01-01 -C "show version" -x nxos --default-privilege-mode show version Cisco Nexus Operating System (NX-OS) Software TAC support: http://www.cisco.com/tac Documents: http://www.cisco.com/en/US/products/ps9372/tsd_products_support_series_home.html Copyright (c) 2002-2015, Cisco Systems, Inc. All rights reserved. The copyrights to certain works contained in this software are owned by other third parties and used and distributed under license. Certain components of this software are licensed under the GNU General Public License (GPL) version 2.0 or the GNU Lesser General Public License (LGPL) Version 2.1. A copy of each such license is available at http://www.opensource.org/licenses/gpl-2.0.php and http://www.opensource.org/licenses/lgpl-2.1.php Software BIOS: version N/A kickstart: version 6.2(14) system: version 6.2(14) BIOS compile time: kickstart image file is: bootflash:///n7000-s1-kickstart.6.2.14.bin <snip> おわりに 今回は、ネットワーク機器の運用を効率化するツールを紹介しました。 ネットワーク機器のコマンド実行結果をシェル上で直接扱うことには、汎用性があります。 例えば、旧来の Rancid や Ansible による情報取得を、より単純なシェルスクリプトで代替出来ます。 コマンドとシェルの知識だけ持っていれば良いため、学習コスト・運用コストとも安く済みます。 Syslog サーバを有している場合は、EEM のように、ログに基づいたコマンドの実行が出来ます。 サーバ主導となるため、A のログをトリガーに B でコマンドを実行、といった構成が実現出来ます。 有効なユースケースがありましたら是非お試しください。 最後までお読み頂きありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goでinterfaceのポインタを使わない理由と使う場面

はじめに Goで「interfaceのポインタは使うことがない」のが定石だけど、errors.Asのテストケースをみていて有用な場合があるのではと思った。 interfaceのポインタ https://golang.org/doc/faq#pointer_to_interface をみると、 Almost never. Pointers to interface values arise only in rare, tricky situations involving disguising an interface value's type for delayed evaluation. にあるように、interfaceのポインタを使う場面はほぼないと言っていいようだ。 続いて以下のように続く。 It is a common mistake to pass a pointer to an interface value to a function expecting an interface. The compiler will complain about this error but the situation can still be confusing, because sometimes a pointer is necessary to satisfy an interface. The insight is that although a pointer to a concrete type can satisfy an interface, with one exception a pointer to an interface can never satisfy an interface. 型TはTのみ、型*TはT/*Tの両方のレシーバのメソッドを含む。 つまり、型Tと型*Tでメソッドセットが異なり、 型*Tがレシーバであるメソッドを含むinterfaceを満たすには型Tのポインタが必ず必要ということである。 ただし、その唯一の例外が型Tがinterfaceの場合であり、したがって基本的にinterfaceのポインタが必要になることはない。 以下がその様子の確認である。 // Write defined as // func (b *Buffer) Write(p []byte) (n int, err error) {.. var buf bytes.Buffer // io.Copy(buf, os.Stdin) <- compile error io.Copy(&buf, os.Stdin) var writer io.Writer = &buf // io.Copy(&writer, os.Stdin) <- compile error io.Copy(writer, os.Stdin) errors.Asで使う 唐突であるが、Goでのエラー処理をどう実装すべきか悩むことは多いが、個人的には https://dave.cheney.net/paste/gocon-spring-2016.pdf がわかりやすく、気に入っている。主題とずれるので詳しくは書かないがエッセンスとしては エラーの分岐処理にてどのエラーかを判断する方法として、悪い方法 -> 良い方法の順に以下 if strings.Contains(err.Error(), "not found")のように文字列比較 if err == io.EOFのようにio.EOF等のSentinel errorと比較 独自エラー型を定義して、switch err := err.(type)のように比較 interfaceにのみ依存し、if te, ok := err.(temporary); ok && te.Temporary()のように比較する エラーに付加情報を与えてエラーを返す。 記事は古いが、現在では、Go1.13以降のerrorsのWrap/UnWrapを想定すればよい である。 ここで最も好ましいとされる方法であるinterfaceとしてエラーを公開し、クライアント側でwrapされたエラーをハンドリングすることを想定する。 Go1.13以前では、https://github.com/pkg/errors を使って、 if te,ok := errors.Cause(err).(temporary); ok && te.Temporary(){ のようにするようにしていたが、厳密には「Causeは最も元となるエラーを返すので、複数回wrapしているときに意図せず取得できない」1問題がある(とおもっている2)。 Go1.13以降のerrorsではそもそも似たようなことはできないと勘違いしていたのだが、たまたまerrorsのテストコードを眺めて、interfaceのポインタを利用するとうまくできるということに気づいた。 テストコードを参考に実際の利用想定されやすいような例を作ると以下のようになる。 package main import ( "errors" "fmt" "os" ) func main() { err := connectDatabase() if err != nil { var timeout interface{ Timeout() bool } // targetはnon-nilなポインタで、かつその先はinterface型かerrorをimplementしてる型である必要がある。 if errors.As(err, &timeout) { fmt.Printf("Got error: %v\nTimeout return %v\n", err, timeout.Timeout()) // 以下は確認用であり、普通のアプリケーションコードでは書かない if pathErr, ok := timeout.(*os.PathError); ok { fmt.Printf("%#v", *pathErr) } } } } func connectDatabase() error { err := loadConfig() if err != nil { return fmt.Errorf("failed to load database config :%w", err) } //.. return nil } func loadConfig() error { _, err := os.Open("non-existing") return err } // OUTPUT IS BELOW // Got error: failed to load database config :open non-existing: no such file or directory // Timeout return false // os.PathError{Op:"open", Path:"non-existing", Err:0x2} また、ソースをみればわかるように先述のCauseの問題は発生しない。 interfaceのポインタを使うべき数少ない正しい場面だと思う。 [おまけ]型TはTのみ、型*TはT/*Tの両方のレシーバのメソッドを含むのはなぜか https://golang.org/doc/faq#different_method_sets をみると納得である。 As the Go specification says, the method set of a type T consists of all methods with receiver type T, while that of the corresponding pointer type *T consists of all methods with receiver *T or T. That means the method set of *T includes that of T, but not the reverse. This distinction arises because if an interface value contains a pointer *T, a method call can obtain a value by dereferencing the pointer, but if an interface value contains a value T, there is no safe way for a method call to obtain a pointer. (Doing so would allow a method to modify the contents of the value inside the interface, which is not permitted by the language specification.) interfaceの構造はRuss Coxさんが https://research.swtch.com/interfaces に素晴らしい記事を書いてくれている3。 interfaceにポインタ以外を代入した場合、コピーを作成し、dataがポインタとしてそこを参照する。記事でいうと、一番最初の図の200がそのデータに該当する。 このメモリ領域の上書きは言語仕様上禁止されているようである。さらに仮に上書きできたとしても、*Tをレシーバとするメソッドをcallしたクライアントから見ると(修正されるのはあくまでcopy先だから)修正が反映されていないので混乱を生み、好ましくない。 こういった理由から型Tは*Tがレシーバのメソッドを含まないようである。 言葉で書くと何言っているかわからないので、ソース見た方が早い。 ↩ この問題に触れているのを見かけたことがないのが個人的には不思議で間違っていないか少し不安ではある。 ↩ 記事は2009年のものでだいぶ古いので不安になるが、2021年現在の実装を軽く確認するかぎり、変わっていないよう。例えばinterfaceの定義はここで、method tableにinterface定義のメソッドのみ含めるのはこのあたり。型がword以下の場合にポインタではなく直接dataに該当データをいれる最適化のコードは小1時間探したが見つけることはできなかった。https://github.com/teh-cmc/go-internals/tree/master/chapter2_interfaces とか読めばわかるかもしれない。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む