Quantcast
Channel: 俺的備忘録 〜なんかいろいろ〜
Viewing all 1028 articles
Browse latest View live

Linuxサーバのローカルメールの内容を(無料枠の)SlackにPOSTさせるPythonスクリプト

$
0
0

Linuxでなにかしら処理を動かしていると、処理が終了した際にローカルのユーザにメールを送ってくることが多い(デフォルトでは/var/spool/mail/[user]にファイルとして吐かれてる場合が多い(mbox形式))。
ちゃんとしたサーバだったら監視ツールとか入れたり、別途Slackにポストさせたり、ちゃんとメーリングリスト宛にメールを転送させるように設定したりしているけど、適当にCUIでキックした処理の終了連絡とか、cronで定期実行してる処理だとそこまでしてなかったりする。

で、そもそも最近はメールもあまり見ないし、Slackにはメールを受け付けてチャンネルにPOSTしてくれる機能もあるので、これを使おう…と思ったのだが、有料契約のワークスペースじゃないと使えないらしい。
今回の場合は個人用に作ってあるワークスペースで、単に監視情報を受け付けるのが目的なので、正直有料プランに契約するのはちょっとなぁ…と思ったので、定期的にmboxのメールを確認し、新規で発生したメールについてはWebhookからSlackにPostさせるようなスクリプトを作成することにした。
Pythonであればライブラリもあるので、Python(Python3)で作成する。
以下、コード。Webhookのトコのurlは事前に自分のワークスペースで取得しておくこと。

● mbox2slack.py

#!/usr/bin/env python3
import mailbox
import os
import slackweb


# slackへのpost関数
def to_slack(messages):
    slack_url = "https://hooks.slack.com/services/hoge/hoge/hogehoge"
    slack = slackweb.Slack(url=slack_url)
    slack.notify(username=os.environ.get("HOSTNAME"), text=messages)


# メールをチェックしてslackへPOSTする関数
def check_mail(file):
    # mboxからkeysを取得する
    mbox = mailbox.mbox(file)
    keys = mbox.keys()

    # mboxをlockし、1メールづつSlackにPOSTする(読んだメールは削除する)
    mbox.lock()
    for k in keys:
        to_slack(mbox.get_string(k))
        mbox.remove(k)

    mbox.flush()
    mbox.close()
    mbox.unlock()


# main関数
def main():
    mbox_dir = "/var/spool/mail/"
    user = os.environ.get("USER")

    check_mail(mbox_dir + user)

    # main関数の実行
if __name__ == "__main__":
    main()

 

いろいろと面倒くさがって書いてるので、ある程度環境に依存しそう(一般ユーザの場合は権限の有無でmailの削除ができない可能性があるなど)だし、Attachment使ってないから読みにくいんだけど、ローカルのメールをPOSTするだけならこんなのでもいいかな。

Node.jsとPythonを使ったSlack API 活用術 - チャットボットの活用で仕事効率化 Node.jsとPythonを使ったSlack API 活用術 - チャットボットの活用で仕事効率化

MacOSで「dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib」なるエラーが出るようになる

$
0
0

MacOSで作業中、brewの操作実行後に以下のようなエラーが表示される。

dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib
  Referenced from: /usr/local/bin/awk
  Reason: image not found

なんだこりゃ?と思って調べてみたところ、どうやらlibreadlineのバージョンが8に上がった際、旧バージョン(7)のファイルがなくなったために参照先が無いとエラーになっている様子。
とりあえず、バージョン付きのファイルとしてシンボリックリンクを作成することで対応。

ln -s /usr/local/opt/readline/lib/libreadline.dylib /usr/local/opt/readline/lib/libreadline.7.dylib
ln -s /usr/local/opt/readline/lib/libhistory.dylib /usr/local/opt/readline/lib/libhistory.7.dylib

 

エラーは出なくなったので、これでしばらくは大丈夫だろう。

 

Apple MacBook Pro (13インチ, 2.3GHzデュアルコアi5プロセッサ, 256GB) - スペースグレイ Apple MacBook Pro (13インチ, 2.3GHzデュアルコアi5プロセッサ, 256GB) - スペースグレイ

Yubikey 4に秘密鍵を入れてssh接続に利用する

$
0
0

個人的に、sshの接続は基本的には公開鍵認証を使うようにしている。
で、秘密鍵はローカルマシンで保持させてるのだけど、Yubikeyに秘密鍵を入れて、証明書認証の設定をすることでトークン認証にすることもできるようだ。
確かに、これなら複数のマシンで共通の秘密鍵を利用できるし、PINコードでの認証も必要になるので、2要素認証的な要素もあってセキュリティも固くなる。

触ってみないとなんともいえないので、ひとまずは実際に試してみることにした。なお、Yubikeyから鍵を取得するため、OpenSCが必要になるらしい。
ssh接続先にはCentOS 7、Ubuntu Server 18.04 LTSを利用する。また、クライアント側についてはMac OSを利用している。

1.OpenSCのインストール

まず、ssh接続時にYubikeyから鍵を読み込む時にOpenSCが必要になるので、それをインストールする。
Macであればbrewからインストールできる。

brew install opensc

 

なお、Debian/Ubuntuでもaptからインストールできる。

sudo apt install opensc

 

2.Yubico PIV Managerを利用して秘密鍵を生成する

次に、Yubicoに秘密鍵を生成して書き込むために『Yubico PIV Manager』を利用する。
『Yubico PIV Manager』は以下からダウンロードできるので、使用しているOSに応じたインストーラーを入れる。


Releases - developers.yubico.com

 

インストール後、『Yubico PIV Manager』を起動したらPINを設定するように言われるので、6-8桁のPINを設定する。
※忘れると秘密鍵を利用できなくなるので、忘れないように注意すること。

 

PINを設定したら、「Certificates」を開く。
「Certificates」は「Authentication(9a)」「Digital Signature(9c)」「Key Management(9d)」「Card Authentication(9e)」の4種類ある。
以下、それぞれの概要(詳細についてはこちらを参照)。

  • Authentication … カードとカード所有者を認証するために使用される。秘密鍵の操作にPINの入力は必須。
  • Digital Signature … 文章、またはファイル・実行可能ファイルへの署名を目的としたデジタル署名に使用される。秘密鍵の操作にPINの入力は必須。
  • Key Management … 機密保持のための暗号化に使用される。秘密鍵の操作にPINの入力は必須。
  • Card Authentication … PIV対応ドアロックなどの物理認証のサポートに使用される。秘密鍵の操作にPIN入力が必須ではない。

それぞれの用途はあるようだけど、ssh認証で使う分にはどれでもそんなに変わらないようだ。
自分のYubikeyでは「Authentication」「Key Management」が設定済なので、「Digital Signature」でsshの鍵を生成…しようとしたところ、どうもうまくいかない。どうやらOpenSC側に9aに証明書、9cに鍵があるとエラーになるバグが存在するようだ。しょうがないので、9eにssh鍵を登録することにする(ちなみに、9aに鍵が入ってる場合はこの問題は起きないらしい。面倒臭いな…)。
「Certificates」を開いたら、「Digital Signature」を開き、「Generate new key …」をクリックする。

 

あとは、ここから秘密鍵の生成をするだけだ。
Expiration dateについては、1年だとちょっと短いので適当に2030年とかにしておく(画像は9cスロットのもの)。

 

これで、秘密鍵がYubikeyに登録されたはずだ。

 

3.サーバ側に公開鍵を登録する

秘密鍵をYubikeyに登録したら、サーバ側に公開鍵を登録する必要がある。
以下のコマンドを実行し、Yubikeyから公開鍵を取得する。

ssh-keygen -D /usr/local/opt/opensc/lib/opensc-pkcs11.so

 

Yubikeyに秘密鍵が登録されていれば公開鍵が出力されるはずなので、これをサーバ側に登録してやる。

 

4.ssh接続する

最後に、実際にssh接続してみる。
ssh接続の際にYubikeyの秘密鍵を参照する場合は、秘密鍵のPATHとしてOpenSCのライブラリを指定してやればいい。

ssh -I /usr/local/opt/opensc/lib/opensc-pkcs11.so user@host
[blacknon@BlacknonMacBook-Pro2018][~/Work/201901/20190127]                                          [BATTERY:100%]
(`・ω・´)  < ssh -I /usr/lib64/opensc-pkcs11.so blacknon@172.20.100.122            [2019/01/28 00:54:32 (月) JST]
C_GetAttributeValue failed: 18
C_GetAttributeValue failed: 18
Enter PIN for 'Yubico PIV Authentication':
Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-29-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Jan 27 15:54:36 UTC 2019

  System load:  0.0               Processes:            116
  Usage of /:   6.9% of 97.93GB   Users logged in:      1
  Memory usage: 9%                IP address for ens18: 172.28.0.122
  Swap usage:   0%                IP address for ens19: 172.20.100.122

 * MicroK8s is Kubernetes in a snap. Made by devs for devs.
   One quick install on a workstation, VM, or appliance.

   - https://bit.ly/microk8s

 * Full K8s GPU support is now available!

   - https://blog.ubuntu.com/2018/12/10/using-gpgpus-with-kubernetes


 * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:
     https://ubuntu.com/livepatch

94 個のパッケージがアップデート可能です。
0 個のアップデートはセキュリティアップデートです。


*** System restart required ***
Last login: Sun Jan 27 15:53:30 2019 from 172.28.10.222
blacknon@bs-pub-ubuntu-03:~$

9cスロットを使う場合に出るバグに苦しめられたけど、ひとまずこれでうまくいった。
使いこなせれば結構面白いんじゃないだろうか。

 

[正規販売代理店品]YubiKey 4 [正規販売代理店品]YubiKey 4

curlにかわるコマンドラインのhttpクライアントコマンド『HTTPie』

$
0
0

ちょっと調べ物をしていたところ、Pythonで書かれたモダンなcurlというようなCUIツール『HTTPie』なるものを知った。

As easy as httpie /aitch-tee-tee-pie/ 🥧 Modern command line HTTP client – user-friendly curl alternative with intuitive UI, JSON support, syntax highlighting, wget-like downloads, extensions, etc. ...
jakubroztocil/httpie - GitHub

 

curlだと色分け表示されずにちょっと見にくいところを色付きで表示してくれたりするらしい。
インストールは簡単で、以下のコマンドで行える。

● macOS

brew install httpie

 

●Debian/Ubuntu

sudo apt install httpie

 

●RHEL/CentOS

apt install httpie

実行ファイルは「http」になっているようだ。使い方はcurlと同じらしい。
細かいオプションの使い方もほとんど変わってないようだ。

http<option>... url


htmやJsonもSyntax Highlightしてくれるので大変見やすい。

 

Web Scraping with Python: Collecting More Data from the Modern Web Web Scraping with Python: Collecting More Data from the Modern Web

Golangでssh port forwardingをする

$
0
0

自前で作ってるGolang製のsshクライアントにssh port forwardingを追加したくなったので、まずはサンプルコードを書いてみることにした。
StackOverflowにちょうどいいサンプルがあったので参考にさせてもらったところ、ターミナルセッションとは別にsshの接続をする必要がありそうだ。ポートフォワード自体はio.Copyで対応できるようだ。

以下、サンプル(こちらにもおいてある)。

● ssh_term_portforward.go

package main

import (
	"fmt"
	"io"
	"net"
	"os"
	"os/signal"
	"syscall"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

var (
	host       = "targethost"
	port       = "22"
	user       = "user"
	pass       = "password"
	localPort  = "localhost:10022"
	remotePort = "localhost:22"
)

func forward(localConn net.Conn, config *ssh.ClientConfig) {
	// Setup sshClientConn (type *ssh.ClientConn)
	sshClientConn, err := ssh.Dial("tcp", host+":"+port, config)
	if err != nil {
		fmt.Println("ssh.Dial failed: %s", err)
	}

	// Setup sshConn (type net.Conn)
	sshConn, err := sshClientConn.Dial("tcp", remotePort)

	// Copy localConn.Reader to sshConn.Writer
	go func() {
		_, err = io.Copy(sshConn, localConn)
		if err != nil {
			fmt.Println("io.Copy failed: %v", err)
		}
	}()

	// Copy sshConn.Reader to localConn.Writer
	go func() {
		_, err = io.Copy(localConn, sshConn)
		if err != nil {
			fmt.Println("io.Copy failed: %v", err)
		}
	}()
}

func main() {
	// Create sshClientConfig
	sshConfig := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{
			ssh.Password(pass),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	// Setup localListener (type net.Listener)
	localListener, err := net.Listen("tcp", localPort)
	if err != nil {
		fmt.Println("net.Listen failed: %v", err)
	} else {
		go func() {
			for {
				// Setup localConn (type net.Conn)
				localConn, err := localListener.Accept()
				if err != nil {
					fmt.Println("listen.Accept failed: %v", err)
				}
				go forward(localConn, sshConfig)
			}
		}()
	}

	// SSH connect.
	client, err := ssh.Dial("tcp", host+":"+port, sshConfig)

	// Create Session
	session, err := client.NewSession()
	defer session.Close()

	// キー入力を接続先が認識できる形式に変換する(ここがキモ)
	fd := int(os.Stdin.Fd())
	state, err := terminal.MakeRaw(fd)
	if err != nil {
		fmt.Println(err)
	}
	defer terminal.Restore(fd, state)

	// ターミナルサイズの取得
	w, h, err := terminal.GetSize(fd)
	if err != nil {
		fmt.Println(err)
	}

	modes := ssh.TerminalModes{
		ssh.ECHO:          1,
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}

	err = session.RequestPty("xterm", h, w, modes)
	if err != nil {
		fmt.Println(err)
	}

	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	session.Stdin = os.Stdin

	err = session.Shell()
	if err != nil {
		fmt.Println(err)
	}

	// ターミナルサイズの変更検知・処理
	signal_chan := make(chan os.Signal, 1)
	signal.Notify(signal_chan, syscall.SIGWINCH)
	go func() {
		for {
			s := <-signal_chan
			switch s {
			case syscall.SIGWINCH:
				fd := int(os.Stdout.Fd())
				w, h, _ = terminal.GetSize(fd)
				session.WindowChange(h, w)
			}
		}
	}()

	err = session.Wait()
	if err != nil {
		fmt.Println(err)
	}
}

 

ローカルのポートが空いてない場合は、ポートフォワードは行わずにそのまま接続するようにしている。
そのへんは、用途に合わせて書き換えてやればいいだろう。

 

【参考】

I'm trying to create (and later close) a simple TCP port forward over SSH with Go. I'm new to Golang and statically typed languages. (Coming from Ruby.)In a terminal I would simply run ssh -L 9000:
Simple SSH port forward in Golang - Stack Overflow

 

Go言語でつくるインタプリタ Go言語でつくるインタプリタ

コンソール上でワイルドカード指定した各ファイルの特定列をpasteで横並びに結合する

$
0
0

Twitterを見ていたら、そういう処理について見かけたので備忘で残しておく。
あるディレクトリ配下のファイルに対して、特定の列(3列目など)をpasteで横並びにして比較するというもの。

ファイル数が決まってて、かつ少ないのであれば、pasteに対してプロセス置換を使ってコマンドの実行結果を渡すことで対応ができるけど、ファイル数が多いとちょっとツライ。

paste <(cut -f1 hoge.txt) <(cut -f1 fuga.txt)

とはいえ基本形はこれで対応できるので、うまいことプロセス置換をする箇所について自動生成させてやればいい。
例えば、以下のような形でglobでのワイルドカードからブレース展開でのプロセス置換を生成して、それをevalで実行させてやればいい。

eval eval paste "'<(cut -d\" \" -f2 '{"$(echo /tmp/*.txt|tr \  ,)"}')'"
[root@BS-PUB-CENT7-01 ~]# grep . /tmp/test*
/tmp/test1.txt:a01 a02 a03 a04 a05
/tmp/test1.txt:b01 b02 b03 b04 b05
/tmp/test1.txt:c01 c02 c03 c04 c05
/tmp/test2.txt:e01 e02 e03 e04 e05
/tmp/test2.txt:f01 f02 f03 f04 f05
/tmp/test2.txt:g01 g02 g03 g04 g05
/tmp/test3.txt:i01 i02 i03 i04 i05
/tmp/test3.txt:j01 j02 j03 j04 j05
/tmp/test3.txt:k01 k02 k03 k04 k05
[root@BS-PUB-CENT7-01 ~]# eval eval paste "'<(cut -d\" \" -f2 '{"$(echo /tmp/*.txt|tr \  ,)"}')'"
a02	e02	i02
b02	f02	j02
c02	g02	k02

 

列数がものすごく多いとツライけど、ひとまずはこんな感じで対処できる。
evalでやる以外には、globのワイルドカード指定からpasteで実行するプロセス置換のコマンドを生成して、それをパイプでbashに渡すといった方法も考えられる。

 

[改訂第3版]シェルスクリプト基本リファレンス ──#!/bin/shで、ここまでできる (WEB+DB PRESS plus) [改訂第3版]シェルスクリプト基本リファレンス ──#!/bin/shで、ここまでできる (WEB+DB PRESS plus)

Golangでssh-agentを利用できるsshクライアントを作ってみる

$
0
0

自前で作ってるGolangで書いたsshクライアントに、ssh-agentの機能を追加できないかなと思い調べてみたところ、ライブラリがすでに用意されているようで結構簡単に実装できそうだということがわかった。
というわけで、ssh-agentで鍵を転送できるsshクライアント(ターミナル接続可能)のサンプルコードを書いてみる。

以下、そのコード。ソースはこちらにもあげている。

● ssh_term_agent.go

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"syscall"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
	"golang.org/x/crypto/ssh/terminal"
)

var (
	host          = "target_host"
	port          = "22"
	user          = "user"
	pass          = "password"
	ssh_agent_key = "/path/to/key/id_rsa" // フルPATHの必要あり
)

func main() {
	// ssh agentに読み込ませる鍵ファイルを読み込む
	buf, err := ioutil.ReadFile(ssh_agent_key)
	if err != nil {
		panic(err)
	}

	// ssh agentに読み込ませる鍵ファイルをパースする(interface形式)
	key, err := ssh.ParseRawPrivateKey(buf)
	if err != nil {
		panic(err)
	}

	// ssh agentで転送するkeyringを作成する
	keyring := agent.NewKeyring()

	// keyringに鍵追加前のリストを出力
	fmt.Println(keyring.List())

	// keyringに鍵を追加する
	err = keyring.Add(agent.AddedKey{
		PrivateKey:       key,
		ConfirmBeforeUse: true,
		LifetimeSecs:     3600,
	})
	if err != nil {
		fmt.Println("add keyring error: %s", err)
	}

	// keyringに鍵追加後のリストを出力
	fmt.Println(keyring.List())

	// Create sshClientConfig
	sshConfig := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{
			ssh.Password(pass),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	// SSH connect.
	client, err := ssh.Dial("tcp", host+":"+port, sshConfig)

	// Create Session
	session, err := client.NewSession()
	defer session.Close()

	// ssh agentに鍵を転送する
	agent.ForwardToAgent(client, keyring)
	agent.RequestAgentForwarding(session)

	// キー入力を接続先が認識できる形式に変換する(ここがキモ)
	fd := int(os.Stdin.Fd())
	state, err := terminal.MakeRaw(fd)
	if err != nil {
		fmt.Println(err)
	}
	defer terminal.Restore(fd, state)

	// ターミナルサイズの取得
	w, h, err := terminal.GetSize(fd)
	if err != nil {
		fmt.Println(err)
	}

	modes := ssh.TerminalModes{
		ssh.ECHO:          1,
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}

	err = session.RequestPty("xterm", h, w, modes)
	if err != nil {
		fmt.Println(err)
	}

	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	session.Stdin = os.Stdin

	err = session.Shell()
	if err != nil {
		fmt.Println(err)
	}

	// ターミナルサイズの変更検知・処理
	signal_chan := make(chan os.Signal, 1)
	signal.Notify(signal_chan, syscall.SIGWINCH)
	go func() {
		for {
			s := <-signal_chan
			switch s {
			case syscall.SIGWINCH:
				fd := int(os.Stdout.Fd())
				w, h, _ = terminal.GetSize(fd)
				session.WindowChange(h, w)
			}
		}
	}()

	err = session.Wait()
	if err != nil {
		fmt.Println(err)
	}
}

 

実行後、ssh接続されたターミナルで「ssh-add -l」を実行すると鍵が転送されているのが確認できる。
個人的にはあまりssh-agentを使わず、そのままProxyでつなげることのほうが多いのだけど、使ってみると便利だし、今後は積極的に使っていこうと思う(lsshにも実装するし)。

 

Go言語による並行処理 Go言語による並行処理

OSCエスケープシーケンスを使ってGnomeターミナルで現在使っているセッションだけ文字色、背景色を変更する

$
0
0

前々から、Linuxデスクトップで使えるターミナルエミュレーターで、 iTerm2のようにOSCエスケープのprintfからプロファイルを変更させる方法について調べていたのだけど、どうやら文字色・背景色であれば変更できるようだということがわかった。

OSCエスケープで文字色、背景色を変える場合、10、11を使うといいらしい。
RGBを00-ffで指定してやればいいようだ。

echo -ne '\e]10;#000000\a' # 文字色を変更
echo -ne '\e]11;#ffffff\a' # 背景色を変更

以下は実行例のコマンド。
こんな感じで背景色、文字色を指定できる。

echo -ne '\e]10;#ffffff\a\e]11;#000000\a'文字色を白、背景色を黒に
echo -ne '\e]10;#ffffff\a\e]11;#300000\a'文字色を白、背景色を赤に
echo -ne '\e]10;#ffffff\a\e]11;#003000\a'文字色を白、背景色を緑に
echo -ne '\e]10;#ffffff\a\e]11;#000030\a'文字色を白、背景色を青に
echo -ne '\e]10;#000000\a\e]11;#ffffff\a'文字色を黒、背景色を白に

 

以下は、Ubuntu 18.04 LTSでのGnome Terminalでの実行例。
OSCエスケープをprintしているターミナルだけ背景色、文字色が変わっているのがわかる。

 

以下はTerminatorでの実行例。
ちゃんと背景色、文字色を変更できている。

 

これなら、ssh接続時に自動で背景色を変えたりといったことも簡単にできそうだ。

 

[改訂第3版]シェルスクリプト基本リファレンス ──#!/bin/shで、ここまでできる (WEB+DB PRESS plus) [改訂第3版]シェルスクリプト基本リファレンス ──#!/bin/shで、ここまでできる (WEB+DB PRESS plus)

WSLからssh接続先の出力結果をクリップボードにコピーする

$
0
0

Twitterをぼけーっと見てたとき、こういう処理についてどうやって実現するかって内容を見かけたので、ちょっと調べてみた。

ローカルのWSLで処理を完結するならclip.exeとかを使えば良さそうなのだけど、sshでリモートに接続してたりするとこの方法は使えない。
じゃどうするかというと、ローカルでX Window Serverを立ち上げて、ssh接続先でxclipからクリップボードにコピーすればいいようだ。
リモートマシンにパッケージ入れないといけないのがちょっと抵抗ありそうだけど、調べた限りこれが一番シンプルそう。

以下、ざっくりとした流れ

1.クライアント側でX Window Serverの立ち上げ

まず、クライアントであるWindows側でX Window Serverを立ち上げする。
とりあえずよく使われてるXmingあたりを入れておけばいいだろう。

X Window Serverを立ち上げたら、WSL側でX Windowに接続できるよう以下のコマンドを実行する。
(もしよく利用するのであれば、.bashrcなどにも記述しておくといいかもしれない)

export DISPLAY=localhost:0.0

2.ssh接続、xclipでのクリップボードへのコピー

クライアント側でX Window Serverを立ち上げたら、X11 forwardingしてssh接続する。

ssh -X hoge@fuga ...

 

ssh接続後、xclipを使ってやる。

echo hogehoge | xclip

 

これで、Windows側のクリップボードにhogehogeがコピーされているはずだ。

 

ちなみに今まで知らなかったのだけど、こういうときにMacやLinuxだったらOSCエスケープの52でクリップボードにアクセスしてコピーができるらしい。
(おそらくWSLでもできているのだろうけど、WSLとWindowsでクリップボードが共有できてないので機能していない様子)

Macとかだと、(iTerm2でやった場合、クリップボードにアクセスできるよう設定にチェックが必要だけど)以下のようにすれば出力をクリップボードにコピーできる。
iTerm2でのOSC Escape 52についてはこちらが詳しい。

printf hogehoge123 | base64 | echo -ne "\033]52;c;$(cat)\a" # クリップボードにコピー

 

OSCエスケープって便利なのね…(´・ω・`)。
ターミナルの背景色や文字色も指定できるし、今回の件で調べるまで知らなかったよ。

 

Terminal Emulator Terminal Emulator

iTerm2のようなデスクトップLinux用のターミナルエミュレータ『Tilix』を使ってみる

$
0
0

前に自分の開発環境用のPCを買う際、デスクトップLinux用でiTerm2に代わるようなターミナルエミュレーターがなかったのもあって結局MacBookを買ったのだけど、またちょっと調べてみたところかなりいかしたターミナルエミュレータを見つけた。

A tiling terminal emulator for Linux using GTK+ 3. Contribute to gnunn1/tilix development by creating an account on GitHub.
gnunn1/tilix - GitHub

 

GTK3を使ってて、言語はD言語で書かれているようだ。
以下のような特徴を持っているらしい。

  • 背景に画像を設定可能(ただしプロファイル別には設定できない)
  • プロファイルの自動切り替え機能
  • ウィンドウの分割可能
  • ターミナルのロギング機能
  • ショートカットのカスタム機能
  • 結構細かいプロファイルの設定

こんなターミナルエミュレータがあったとは知らなんだ(´・ω・`)。
上記は2019年現在の機能なのだけど、こちらを見るとiTerm2ライクな機能(OSCエスケープでのProfileの切り替えなど)をサポートすることも視野に入れてるらしい。現時点ではOSCエスケープでの文字色、背景色の切り替えはできないようだけど、ロードマップを見る限りは将来的には対応してくれそう?な気はする。

とりあえず、まずは実際に触ってみよう。
今回は手元にあったUbuntu Desktop 18.04 LTSにインストールする。
Ubuntuの場合はパッケージが用意されているようで、 以下のコマンドでインストールできる。
(その他のディストリビューションの場合は、こちらのページからパッケージが用意されているか確認できる。Archは用意されてるっぽい。)

sudo add-apt-repository ppa:webupd8team/terminix
sudo apt-get update
sudo apt install tilix

 

これでインストールは完了。
あとはtilixを起動するだけだ。起動後の画面はこんな感じ。

 

プロファイル別ではないけれど背景画像も設定できるし、結構良さげ。
感想としては、現状はまだiTerm2から移行したいとは思えないというのが本音ではあるけれど、今後も改修していくようなので先が楽しみな感じだ。

 

GNOMEプログラミング―GNOMEアプリケーション開発の基礎 GNOMEプログラミング―GNOMEアプリケーション開発の基礎

xfceでPlankを使うときに出てくる透明な枠を消す

$
0
0

最近、Linuxの開発環境として使っていたThinkpadを新調したので、Manjaro Linux + Xfceで利用し始めている。
で、DockにPlankを使っているのだけど、どうも変な影のようなものが表示されるようになった。(下の画面でいうと、Burpのウィンドウのとこにある溝のようなのがそれ)

 

で、なんだろこれと思って調べたところ、Xfceの設定で消せる内容だったようだ。

Xfceの設定マネージャーから「ウィンドウマネージャー(詳細)」を開き、「コンポジット処理」タブから「ドックウィンドウに影を落とす」のチェックを外すことで非表示にできる。

 

これでOK。

 

Arch Linux Environment Setup How-To (English Edition) Arch Linux Environment Setup How-To (English Edition)

MacOSでssh-agentにYubikeyの鍵を登録する(OpenSC経由)

$
0
0

Mac OSで、ssh-agentにYubikeyの鍵を登録したいということがあったので、備忘で残しておく(すでにOpenSCとかはインストール済で、ssh -Iでログインができる状態と仮定)。
ssh-agentでYubikeyの鍵を利用する場合、通常であればssh-agent起動後、以下のようなコマンドを実行すればいい。

ssh-add -s /usr/local/lib/opensc-pkcs11.so

 

が、以下のようなエラーが出て追加できないパターンが多い。

$ ssh-add -s /usr/local/lib/opensc-pkcs11.so
Enter passphrase for PKCS#11:
Could not add card "/usr/local/lib/opensc-pkcs11.so": agent refused operation

これは、ssh-agentを起動する際にpkcs11のwhitelistにopenscのPATHが入ってないことが原因。
なので、以下のようにしてssh-agentを起動する必要がある。なお、PATHはopensc-pkcs11.soの実体があるPATHにする必要があるので注意(インストール方法にもよるけど、大体はシンボリックリンクになっているはず)。
複数のディレクトリをwhitelistに入れる場合、カンマ区切りで指定することができる。

ssh-agent -P "/path/to/pkcs11_lib_dir/*,/path/to/sub_dir/*"

 

これで、ssh-agentにOpenSCから読み込んだ鍵を登録することができる。

 

[正規販売代理店品]YubiKey 4 [正規販売代理店品]YubiKey 4

Golangでssh-agentから鍵を取得してssh接続する

$
0
0

前回、Golangでssh-agentをForwardingする処理について書いたことがあったけど、認証自体は普通にパスワードで処理するように書いていた。
で、ssh-agentで認証させることもできるようなので、サンプルコードを書いてみた。

以下、サンプル(一応こちらにもあげている)。

package main

import (
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/signal"
	"syscall"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
	"golang.org/x/crypto/ssh/terminal"
)

var (
	host          = "target.host"
	port          = "22"
	user          = "user"
	ssh_agent_key = "/path/to/.ssh/id_rsa" // SshAgentに新たに追加する鍵のPATH(フルパス)
)

func main() {
	// SSH_AUTH_SOCKのSocketへ接続し、SshAgentとしてセッションを開始する
	sock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
	if err != nil {
		panic(err)
	}
	agentsock := agent.NewClient(sock)

	// ssh agentに読み込ませる鍵ファイルを読み込む
	buf, err := ioutil.ReadFile(ssh_agent_key)
	if err != nil {
		panic(err)
	}

	// ssh agentに読み込ませる鍵ファイルをパースする(interface形式)
	key, err := ssh.ParseRawPrivateKey(buf)
	if err != nil {
		panic(err)
	}

	// SshAgentに鍵を追加する前のリストを出力
	fmt.Println(agentsock.List())

	// 鍵を追加する
	err = agentsock.Add(agent.AddedKey{
		PrivateKey:       key,
		ConfirmBeforeUse: true,
		LifetimeSecs:     300, // SshAgentに追加する鍵の利用可能時間(秒)
	})

	if err != nil {
		fmt.Println("add keyring error: %s", err)
	}

	// SshAgentに鍵追加後のリストを出力
	fmt.Println(agentsock.List())

	// Create sshClientConfig
	signers, err := agentsock.Signers()
	if err != nil {
		fmt.Println("create signers error: %s", err)
	}

	sshConfig := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(signers...),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	// SSH connect.
	client, err := ssh.Dial("tcp", host+":"+port, sshConfig)

	// Create Session
	session, err := client.NewSession()
	defer session.Close()

	// ssh agentに鍵を転送する
	agent.ForwardToAgent(client, agentsock)
	agent.RequestAgentForwarding(session)

	// キー入力を接続先が認識できる形式に変換する(ここがキモ)
	fd := int(os.Stdin.Fd())
	state, err := terminal.MakeRaw(fd)
	if err != nil {
		fmt.Println(err)
	}
	defer terminal.Restore(fd, state)

	// ターミナルサイズの取得
	w, h, err := terminal.GetSize(fd)
	if err != nil {
		fmt.Println(err)
	}

	modes := ssh.TerminalModes{
		ssh.ECHO:          1,
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}

	err = session.RequestPty("xterm", h, w, modes)
	if err != nil {
		fmt.Println(err)
	}

	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	session.Stdin = os.Stdin

	err = session.Shell()
	if err != nil {
		fmt.Println(err)
	}

	// ターミナルサイズの変更検知・処理
	signal_chan := make(chan os.Signal, 1)
	signal.Notify(signal_chan, syscall.SIGWINCH)
	go func() {
		for {
			s := <-signal_chan
			switch s {
			case syscall.SIGWINCH:
				fd := int(os.Stdout.Fd())
				w, h, _ = terminal.GetSize(fd)
				session.WindowChange(h, w)
			}
		}
	}()

	err = session.Wait()
	if err != nil {
		fmt.Println(err)
	}
}

 

Golangはssh周りの機能が多いので楽でいいなあ。
できれば、x11ForwardingとPKCS#11の接続も実装してくれると嬉しい(´・ω・`)。

 

Goならわかるシステムプログラミング Goならわかるシステムプログラミング

bash/zshで入力中の内容を利用するカスタムキーバインドを作成する

$
0
0

bash/zshで、入力中の内容を利用したカスタムキーバインドを作りたいということがあったので、備忘で残しておく。
(当たり前の話として、bash/zshどちらもキーバインドでfunctionを呼び出すことになるので、適当なfunctionを作ることになる)

以下、実際に動かしてるときの状態。
最初はzsh、次にbashに切り替えている。

ここでは、サンプルとしてカーソル位置から右側の文字列をダブルクオーテーションでくくるfunctionを作成する。

1.Bashの場合

Bashでは、bind -xでfunctionを呼び出す際に以下の変数が利用できる。

  • READLINE_LINE … 入力中のテキスト
  • READLINE_POINT … 入力中のカーソル位置

 

つまり、function内で上記変数から入力中のテキストと位置を取得して、加工後にREADLINE_LINEに代入してやればいいということになる。
以下、bash版のサンプル。入力中の内容からカーソル位置より右を取得して、それをダブルクオーテーションで囲むように加工して置き換えている。

function __toggle_command_doublequote_format() {
    # 入力中の内容を変数に代入
    local BUF=${READLINE_LINE:${READLINE_POINT}}

    # すでにダブルクオーテーションで囲まれている場合は、前後のダブルクオーテーションを削除してエスケープ済のダブルクオーテーションを解除
    if [[ ${BUF:0:1} == '"' ]] && [[ ${BUF: -1} == '"' ]];then
        local BUF="${BUF:1}" # 最初の1文字目を削除
        local BUF="${BUF:0:-1}" # 最後の1文字目を削除
        local BUF=${BUF//\\\"/\"} # エスケープ済の"が含まれている場合、解除するよう置換する
    else
        local BUF=${BUF//\"/\\\"} # "が含まれている場合、エスケープするよう置換する
        local BUF='"'"${BUF}"'"'
    fi

    # 入力中の内容を置き換える
    READLINE_LINE="${READLINE_LINE::${READLINE_POINT}}${BUF}"
}

# Alt + D でカーソル位置から行末までをダブルクオーテーションで囲む
bind -x '"\ed": __toggle_command_doublequote_format'

2.Zshの場合

Zshの場合は、bindでfunctionを呼び出す際に以下の様な変数が使えるようだ。

  • BUFFER … 入力中のテキスト
  • CURSOR … 入力中のカーソル位置
  • LBUFFER … 入力中のテキスト(カーソルより左側)
  • RBUFFER … 入力中のテキスト(カーソルより右側)

 

上記を踏まえ、zsh版は作ればいい。
以下、zsh版のサンプル。

function __toggle_command_doublequote_format() {
    # 入力中の内容を変数に代入
    # shellに応じて処理を変える
    local BUF=${RBUFFER}

    # すでにダブルクオーテーションで囲まれている場合は、前後のダブルクオーテーションを削除してエスケープ済のダブルクオーテーションを解除
    if [[ ${BUF:0:1} == '"' ]] && [[ ${BUF: -1} == '"' ]];then
        local BUF="${BUF:1}"
        local BUF="${BUF:0:-1}"
        local BUF=${BUF//\\\"/\"} # エスケープ済の"が含まれている場合、解除するよう置換する
    else
        local BUF=${BUF//\"/\\\"} # "が含まれている場合、エスケープするよう置換する
        local BUF='"'"${BUF}"'"'
    fi

    # 入力中の内容を置き換える
    RBUFFER=${BUF}
}

# Alt + D で、現在入力中の内容をダブルクオーテーションで囲む
zle -N __toggle_command_doublequote_format
bindkey '^[d' __toggle_command_doublequote_format

 

ちなみに、functionがどっちでも動くように記述する場合、以下のようにする。
キーバインドでのfunctionの呼び出しは各シェルによって異なるので、自分の使う方のを書けばいいだろう。

function __toggle_command_doublequote_format() {
    # 入力中の内容を変数に代入
    # shellに応じて処理を変える
    case $(basename ${SHELL}) in
        zsh*)
            local BUF=${RBUFFER}
            ;;
        bash*)
            local BUF=${READLINE_LINE:${READLINE_POINT}}
            ;;
    esac

    # すでにダブルクオーテーションで囲まれている場合は、前後のダブルクオーテーションを削除してエスケープ済のダブルクオーテーションを解除
    if [[ ${BUF:0:1} == '"' ]] && [[ ${BUF: -1} == '"' ]];then
        local BUF="${BUF:1}" # 最初の1文字目を削除
        local BUF="${BUF:0:-1}" # 最後の1文字目を削除
        local BUF=${BUF//\\\"/\"} # エスケープ済の"が含まれている場合、解除するよう置換する
    else
        local BUF=${BUF//\"/\\\"} # "が含まれている場合、エスケープするよう置換する
        local BUF='"'"${BUF}"'"'
    fi

    # shellに応じて処理を変える
    case $(basename ${SHELL}) in
        zsh*)
            RBUFFER=${BUF}
            ;;
        bash*)
            READLINE_LINE="${READLINE_LINE::${READLINE_POINT}}${BUF}"
            ;;
    esac
}

 

ダブルクオーテーションで囲ったりするのは結構頻度高いわりに書くのが楽だったのでサンプルにしたけど、入力中の値を加工できるなら色々とできそうだ。
それこそ、置換処理とかあると面白いのでは無いだろうか?

 

[基礎知識+リファレンス]macOSコマンド入門 ――ターミナルとコマンドライン、基本の力 [基礎知識+リファレンス]macOSコマンド入門 ――ターミナルとコマンドライン、基本の力

ターミナル上で長い行の折返しをさせないようにする

$
0
0

普段利用する際には問題ないのだが、本当にごくたまに、ターミナル上で長い行がいる場合、折り返さずに切り落として表示していほしいことがある。
(今回の場合、自分でも何をトチ狂ったのかbashファンクションでインクリメンタルサーチを作ってたので、そこで困ったという背景)

表示される内容がASCIIだけであれば、cutなどでターミナル幅を超える文字数を削除するようにすればいいのだが、UTF-8とかが含まれているとそれも難しくなってくる。
さて、どうすればいいのだろう…?と調べていたところ、どうやらDECエスケープシーケンスを利用してターミナル側の行末オートワープ機能を無効化してやればいいようだ(こんなオプションがあったのか…)。

printf '\033[?7l' # 行末オートワープ機能(折返し機能)の無効化
command ...
printf '\033[?7h' # 行末オートワープ機能(折返し機能)の有効化

 

これで、ターミナルの幅を超える内容は折り返されずに非表示になる。
(あまり役に立つ機会は多くないだろうけど)

 

【参考】

 

Terminal Emulator Terminal Emulator

tarでアーカイブ展開時にファイル名にだけ頭に文字列を追加する

$
0
0

Twitterでそんな感じの処理について書かれてたので、備忘で残しておく。
内容としては、tarで展開した後にファイルだけ頭に特定の文字列を追加するという内容。

GNU tarであればtransformオプションがあるから楽…ではあるのだが、Mac OSだとデフォルトではGNU tarは入ってないし、特定の条件下でないとうまく動作しない。

 

1.GNU tarの場合(特定の条件でのみ動作)

先程も書いたのだけど、GNU tarであれば–transformオプションというのがあり、これを使うことでsedのような形式で置換処理を書くことができる。

tar xzvf ./file.tar.gz --show-transformed-names --transform 's/foo/bar/g'
[REMOTE][blacknon@BS-PUB-UBUNTU-01][/tmp]
(`・ω・´) < tar tvf test_d.tar.gz
drwxrwxr-x blacknon/blacknon 0 2019-03-12 22:13 test_d/
-rw-rw-r-- blacknon/blacknon 0 2018-07-01 22:42 test_d/test0003.jpg
-rw-rw-r-- blacknon/blacknon 0 2018-07-01 22:42 test_d/test0002.jpg
-rw-rw-r-- blacknon/blacknon 0 2018-07-01 22:42 test_d/test0005.jpg
-rw-rw-r-- blacknon/blacknon 0 2018-07-01 22:42 test_d/test0001.jpg
-rw-rw-r-- blacknon/blacknon 0 2018-07-01 22:42 test_d/test0004.jpg
drwxrwxr-x blacknon/blacknon 0 2019-03-12 22:13 test_d/test_d2/
-rw-rw-r-- blacknon/blacknon 0 2018-07-01 22:42 test_d/test_d2/test0008.jpg
-rw-rw-r-- blacknon/blacknon 0 2018-07-01 22:42 test_d/test_d2/test0007.jpg
-rw-rw-r-- blacknon/blacknon 0 2018-07-01 22:42 test_d/test_d2/test0009.jpg
-rw-rw-r-- blacknon/blacknon 0 2018-07-01 22:42 test_d/test_d2/test0006.jpg
-rw-rw-r-- blacknon/blacknon 0 2018-07-01 22:42 test_d/test_d2/test0010.jpg

[REMOTE][blacknon@BS-PUB-UBUNTU-01][/tmp]
(`・ω・´) < tar xzvf test_d.tar.gz --show-transformed-names --transform 's/test/TEST/g'
TEST_d/
TEST_d/TEST0003.jpg
TEST_d/TEST0002.jpg
TEST_d/TEST0005.jpg
TEST_d/TEST0001.jpg
TEST_d/TEST0004.jpg
TEST_d/TEST_d2/
TEST_d/TEST_d2/TEST0008.jpg
TEST_d/TEST_d2/TEST0007.jpg
TEST_d/TEST_d2/TEST0009.jpg
TEST_d/TEST_d2/TEST0006.jpg
TEST_d/TEST_d2/TEST0010.jpg

 

なので、これを正規表現を使ってファイル名の頭に文字列が追加されるように書いてやればいい。

tar xzvf ./file.tar.gz --show-transformed-names --transform 's:.*/:&hoge_:'
[REMOTE][blacknon@BS-PUB-UBUNTU-01][/tmp]
(`・ω・´) < tar xzvf test_d.tar.gz --show-transformed-names --transform 's:.*/:&ABC_:'
test_d/
test_d/ABC_test0003.jpg
test_d/ABC_test0002.jpg
test_d/ABC_test0005.jpg
test_d/ABC_test0001.jpg
test_d/ABC_test0004.jpg
test_d/ABC_test_d2/
test_d/test_d2/ABC_test0008.jpg
test_d/test_d2/ABC_test0007.jpg
test_d/test_d2/ABC_test0009.jpg
test_d/test_d2/ABC_test0006.jpg
test_d/test_d2/ABC_test0010.jpg

 

ただ、実はこれだとちょっと失敗で、余計なディレクトリ(上の例だとABC_test_d2)が生成されてしまうことになる。
tarのtransformではsedのように条件指定ができないので、こういったディレクトリが作られてしまうことになる。
また、置換条件に “.*/” を利用しているので、ディレクトリでアーカイブされていないファイルを展開する場合にもうまく動かないだろう。

なので、特定の条件にあるファイルであればこれで動作するが、そうでない場合は次に記述する方法を取るといいだろう。

2. -vオプションの出力を利用してmvする

tar単体ではうまくファイル名だけを置換するのが難しかったり、そもそもGNU tarが無かったりといったことがある。
じゃどうするというと、tarではvで展開する内容を出力できるので、それを利用してmvコマンドを生成し、そのままbashに食わせることで対応ができる。

MacOSとLinuxだとGNUコマンドの有無で動作が違うので、別々に記述する。

■ Linuxの場合

tar xzvf ./test_d.tar.gz | sed -r 's/.+/mv & \$(test -f & \&\& echo &|sed -r "\/.\/s:\(.*\/\):\\1ABC_:")/' | bash

 

■MacOSの場合

tar xzvf ./test_d.tar.gz 2>&1 | \sed -E 's/^x //g' | \sed -E 's/.+/mv & \$(test -f & \&\& echo &|sed -E "\/.\/s:\(.*\/\):\\1ABC_:")/g' | bash
[REMOTE][blacknon@BS-PUB-UBUNTU-01][/tmp]
(`・ω・´) < tar xzvf ./test_d.tar.gz | sed -r 's/.+/mv & \$(test -f & \&\& echo &|sed -r "\/.\/s:\(.*\/\):\\1ABC_:")/' | bash
mv: 'test_d/' の後に宛先のファイルオペランドがありません
Try 'mv --help' for more information.
mv: 'test_d/test_d2/' の後に宛先のファイルオペランドがありません
Try 'mv --help' for more information.

[REMOTE][blacknon@BS-PUB-UBUNTU-01][/tmp]
(`・ω・´) < find ./test_d
./test_d
./test_d/ABC_test0003.jpg
./test_d/ABC_test0005.jpg
./test_d/ABC_test0001.jpg
./test_d/ABC_test0002.jpg
./test_d/ABC_test0004.jpg
./test_d/test_d2
./test_d/test_d2/ABC_test0006.jpg
./test_d/test_d2/ABC_test0007.jpg
./test_d/test_d2/ABC_test0008.jpg
./test_d/test_d2/ABC_test0009.jpg
./test_d/test_d2/ABC_test0010.jpg

面倒くさかったのでディレクトリ時のエラーは消してないが、必要であれば/dev/nullにでも出しておけばいいだろう。
ちゃちゃっと処理をすることを目標にしているのでディレクトリ名にスペースが入ってる場合等は考慮していないので、その場合は環境に合わせて書き加えたりするといいだろう。


 

[改訂第3版]シェルスクリプト基本リファレンス ──#!/bin/shで、ここまでできる (WEB+DB PRESS plus) [改訂第3版]シェルスクリプト基本リファレンス ──#!/bin/shで、ここまでできる (WEB+DB PRESS plus)

vimrcでAltキーとの組み合わせを定義する(Linux or Mac)

$
0
0

bashやzshではAlt + 左右キーで単語間を移動することができて、個人的にはそれを良く使っているのだけど、vimではデフォルトだとその操作ができなかったりする。
で、ちょっと不便だな、使えるようにならないかなと思い調べてみた。

vimrcではAltを使う際、「<A-Key>」といった形式で指定ができるようだ。
なので、以下のように指定してやればAltキーと組み合わせた操作を指定できる。

noremap <A-Left>  b # Alt + Left で1単語戻る
noremap <A-Right> w # Alt + Right で1単語進む

ただ、これはLinuxのコンソールでしか動作しないようで、MacのiTerm2だとうまく動作しなかった。
iTerm2で動作させる場合は、プロファイルでOptionキーをESC+に設定した上で、以下のように定義してやればいいようだ。

noremap <ESC><ESC>[D b # Alt + left で1単語戻る
noremap <ESC><ESC>[C w # Alt + right で1単語進む

 

Alt + BackSpaceとかもこれで動作するので、vimでAltキーを利用する場合はこんな感じの設定をすれば良さそうだ。

 

実践Vim 思考のスピードで編集しよう! (アスキー書籍) 実践Vim 思考のスピードで編集しよう! (アスキー書籍)

Golangでターミナル接続sshクライアントを作成する(PKCS11での物理トークン(Yubikey)を用いた接続)

$
0
0

ここ最近、仕事が変わったのとRustでこさえてたツールの改修に手間取っててあまり触ってたなかったのだけど、ある程度一段落したのでGoのsshクライアントの改修に着手した。
で、sshでの接続方式というといくつかあると思うが、最近はYubikeyをPIVカードとして利用して、Yubikey内の秘密鍵を使ってssh接続するというやり方もあるので、それをGolangで実装しようと思いやってみた。

これ、実はかなり手間取ってしまい、結構時間がかかってしまった…。
Yubikey等のPIVカードに入ってる秘密鍵を使ってssh接続する場合、OpenSCなどのPKCS11を扱えるライブラリが必要になるのだが、それがいまいちよくわからなかった(実装できた今もさっぱりわからない…)。

Golangは結構いろんな人がいろんなライブラリを提供してくれてて、その中にpkcs11を扱うための、OpenSCなどのラッパーとして扱えるライブラリもあるのだが、それらを利用する。
ssh接続をする場合はYubikeyのKeyPairを使って、Signerとして扱える必要があるので、今回はcrypto11というライブラリを主に使っている(ココいくまでも右往左往してしまった…)。

以下、サンプルコード。
一応こっちにも上げてある。

● ssh_term_pkcs11.go

package main

import (
	"crypto"
	"fmt"
	"os"
	"os/signal"
	"syscall"

	"github.com/ThalesIgnite/crypto11"
	"github.com/miekg/pkcs11"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

var (
	// ↓この2つは指定が必要
	pkcs11Path = "/usr/local/lib/opensc-pkcs11.so"
	tokenLabel = "tokenLabel"

	// 鍵のIDは決め打ち。(p11toolでの出力だと「01」に該当)
	// 自動取得にする場合は、"pkcs11.NewAttribute"で指定して取得することで対応すればいい。
	// ↓ サンプル
	// ------
	// findTemplate := []*pkcs11.Attribute{
	//     pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
	//     pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true),
	//     pkcs11.NewAttribute(pkcs11.CKA_ID, true),
	//     pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA),
	// }
	// p.FindObjectsInit(session, findTemplate)
	// ------
	keyID = []byte{'\x01'}

	// sshの接続情報
	host = "target.host"
	port = "22"
	user = "user"
)

func main() {
	// PINコードの入力を受け付け
	fmt.Printf("PIN: ")
	tokenPin, err := terminal.ReadPassword(int(syscall.Stdin))
	if err != nil {
		fmt.Println("err: {}", err)
		os.Exit(1)
	}

	// pkcs11のConfigを作成
	config := &crypto11.PKCS11Config{
		Path:       pkcs11Path,
		TokenLabel: tokenLabel,
		Pin:        string(tokenPin),
	}

	// pkcs11のConfigを使いctxを作成
	ctx, err := crypto11.Configure(config)
	if err != nil {
		fmt.Println("err: {}", err)
		os.Exit(1)
	}

	// pkcs11のセッションを作成
	keysession, err := ctx.OpenSession(0, pkcs11.CKF_SERIAL_SESSION)
	if err != nil {
		fmt.Println("err: {}", err)
		os.Exit(1)
	}
	pkcs11Session := &crypto11.PKCS11Session{ctx, keysession}

	// PIVのSignerを作成する(slot,idは決め打ち)
	prv, err := crypto11.FindKeyPairOnSession(pkcs11Session, 0, keyID, nil)
	if err != nil {
		fmt.Println("err: {}", err)
		os.Exit(1)
	}
	signer := prv.(crypto.Signer)

	// PIVのSignerからssh用のSignerを作成する
	sshSigner, err := ssh.NewSignerFromSigner(signer)
	if err != nil {
		fmt.Println("err: {}", err)
		os.Exit(1)
	}

	auth := []ssh.AuthMethod{}

	auth = append(auth, ssh.PublicKeys(sshSigner))

	// ---------------------
	//
	// こっから下はコピペのやつ
	//
	// ---------------------

	// Create sshClientConfig
	sshConfig := &ssh.ClientConfig{
		User:            user,
		Auth:            auth,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		BannerCallback:  ssh.BannerDisplayStderr(),
	}

	// SSH connect.
	client, err := ssh.Dial("tcp", host+":"+port, sshConfig)
	if err != nil {
		fmt.Println("client: {}", err)
		os.Exit(0)
	}

	// Create Session
	session, err := client.NewSession()
	defer session.Close()

	// キー入力を接続先が認識できる形式に変換する(ここがキモ)
	fd := int(os.Stdin.Fd())
	state, err := terminal.MakeRaw(fd)
	if err != nil {
		fmt.Println(err)
	}
	defer terminal.Restore(fd, state)

	// ターミナルサイズの取得
	w, h, err := terminal.GetSize(fd)
	if err != nil {
		fmt.Println(err)
	}

	modes := ssh.TerminalModes{
		ssh.ECHO:          1,
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}

	err = session.RequestPty("xterm", h, w, modes)
	if err != nil {
		fmt.Println(err)
	}

	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	session.Stdin = os.Stdin

	err = session.Shell()
	if err != nil {
		fmt.Println(err)
	}

	// ターミナルサイズの変更検知・処理
	signal_chan := make(chan os.Signal, 1)
	signal.Notify(signal_chan, syscall.SIGWINCH)
	go func() {
		for {
			s := <-signal_chan
			switch s {
			case syscall.SIGWINCH:
				fd := int(os.Stdout.Fd())
				w, h, _ = terminal.GetSize(fd)
				session.WindowChange(h, w)
			}
		}
	}()

	err = session.Wait()
	if err != nil {
		fmt.Println(err)
	}
}

 

ひとまず、これでPIVカードに入ってる秘密鍵を利用してssh接続できるはず…(自分の環境(Mac + OpenSC + Yubikey)ではできた)。
あまり一般的な接続方式ではないけど、今後は物理トークンを用いた接続も使われていくことがあるだろうし、うまいこと付き合っていきたいところだなぁと(´・ω・`)。

PKCS11、ちゃんと扱える様になりたいのだけど、自分が理解できるような情報がいまいち見つけられず、未だによくわかってない…。
誰か詳しい人いたら教えてください(´;ω;`)。

 

[正規販売代理店品]YubiKey 4 [正規販売代理店品]YubiKey 4

第41回シェル芸勉強会に参加してきました(復習)

$
0
0

先日実施された、第41回シェル芸勉強会に参加してきたので、その復習。(最近はツールの作成の方ばっかやってて、ブログ 書くの久しぶりだなぁ…(´・ω・`))
4月開催というのもあってか、いつものに比べたらマイルド?らしい。(確かにまぁ、そう言われるとそうかも…?)
今回はsortの処理に主眼を置いたとのこと。

問題はこちら。最初に、問題等に使用するファイルをgitからcloneしておくといい。
今回はsortが主題ということもあってか、結構ファイルを使うようだ。

git clone https://github.com/ryuichiueda/ShellGeiData

Q1

リポジトリに入っている「excel」というテキストデータの2列目がExcelの列形式(1列目から順にアルファベットがA,B,Cとならんで、Zまできたら桁上りする)になっているので、それをExcelの列の並びにしてsortするという問題。

最初、前にやってたExcelの列名対応一覧とかいうのを元にできないかと試してたのだが、ちょっと無理があったようだ。
とりあえず、時間内に終わらせるために、他の人もやってたけど桁数の列を追加して、そこを1つ目のキーにしてsortさせるという処理で対応した。

cat excel|awk '{print $0,length($2)}'|sort -k3n -k2|cut -d\  -f1-2
[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < cat excel # excelファイルの中身を見る
114514 B
593195 AA
1192296 CEZ
4120 TZ
999 QQQ

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < cat excel | awk '{print $0,length($2)}' # 出力結果の後ろに、2列目の桁数を追加する
114514 B 1
593195 AA 2
1192296 CEZ 3
4120 TZ 2
999 QQQ 3

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < cat excel | awk '{print $0,length($2)}' | sort -k3n -k2 # sortで、3列目→2列目の優先度で並べ替えをする
114514 B 1
593195 AA 2
4120 TZ 2
1192296 CEZ 3
999 QQQ 3

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < cat excel | awk '{print $0,length($2)}' | sort -k3n -k2 | cut -d\  -f1-2 # cutで1、2列目だけを表示させる
114514 B
593195 AA
4120 TZ
1192296 CEZ
999 QQQ

 

Q2

干支が書かれているファイル「eto_yomi」を、干支順に出力させるという内容。
補助のために「eto」というファイルも用意されているので、それらを利用する。

こういう場合、やはりjoinでファイル同士を結合してやるというのが王道のような気がする。

join -o 2.2 2.3 1.1 -j 2 &lt;(grep -o . eto|cat -n|sort -k2) &lt;(cat -n eto_yomi|sort -k2) | sort -k3n | cut -d \  -f1-2
[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < grep -o . eto|cat -n|sort -k2 # A. 行番号を付与して干支を辞書sortして出力
    12  亥
     4  卯
     2  丑
     7  午
     1  子
     9  申
     5  辰
     3  寅
    10  酉
     8  未
     6  巳
    11  戌

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < cat -n eto_yomi|sort -k2 # B. eto_yomiも行番号を付与して干支順に辞書sort
    10  亥 い
     4  卯 う
     7  丑 うし
    11  午 うま
     2  子 ね
     1  申 さる
     6  辰 たつ
     3  寅 とら
     8  酉 とり
    12  未 ひつじ
     5  巳 み
     9  戌 いぬ

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < join -o 2.2 2.3 1.1 -j 2 <(grep -o . eto|cat -n|sort -k2) <(cat -n eto_yomi|sort -k2) # AとBの出力をjoinで結合
亥 い 12
卯 う 4
丑 うし 2
午 うま 7
子 ね 1
申 さる 9
辰 たつ 5
寅 とら 3
酉 とり 10
未 ひつじ 8
巳 み 6
戌 いぬ 11

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < join -o 2.2 2.3 1.1 -j 2 <(grep -o . eto|cat -n|sort -k2) <(cat -n eto_yomi|sort -k2) | sort -k3n | cut -d \  -f1-2 # 3列目を基準にsortして列を削除
子 ね
丑 うし
寅 とら
卯 う
辰 たつ
巳 み
午 うま
未 ひつじ
申 さる
酉 とり
戌 いぬ
亥 い

 

その他、もうちょっと短いやり方として、grepとsed(からまたgrepを実行)というやり方がある。
GNU sedのeコマンドを利用することで、置換結果をそのままコマンドとして実行させられるので、使える環境だと結構便利だったりする。

grep -o . eto | sed 's/./grep &amp; eto_yomi/ge'
[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < grep -o . eto | sed 's/./grep & eto_yomi/ge' # grepでeto順にeto_yomiを読み込む
子 ね
丑 うし
寅 とら
卯 う
辰 たつ
巳 み
午 うま
未 ひつじ
申 さる
酉 とり
戌 いぬ
亥 い

 

Q3

ファイル「kim_calc」の内容を、計算式の数字が少ない順にsortするという問題。

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < cat kim_calc
1+2+4 金正日
4*3 金正男
3-1-5 金日成
495/3 金正恩
0x1F 金正哲

 

こういった計算式の場合、とりあえずbashの算術展開につっこめばよしなにしてくれるので、それで対応する。

sed -r 's/^([^ ]+)/echo $((\1)) \1/ge' kim_calc | sort -k1n | cut -d' ' -f2-
[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < sed -r 's/^([^ ]+)/echo $((\1)) \1/ge' kim_calc | sort -k1n
-3 3-1-5 金日成
7 1+2+4 金正日
12 4*3 金正男
31 0x1F 金正哲
165 495/3 金正恩

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < sed -r 's/^([^ ]+)/echo $((\1)) \1/ge' kim_calc | sort -k1n | cut -d' ' -f2-
3-1-5 金日成
1+2+4 金正日
4*3 金正男
0x1F 金正哲
495/3 金正恩

 

Q4

「sjis」というSJISのファイルがあるので、これを辞書順(要は文字順)・数字順にsortするという内容のようだ(最初、いまいちよくわかってなかった…)。

1. 辞書順

こちらについては単純に、sjisファイルをnkfでutf-8にしてsort、そしてそのままSJISに戻してやればいい。

nkf -w sjis | sort | nkf -s

2.数字順

こちらについては、一度全角文字になっているのを半角にしてやる必要がある。
nkfでは-Zオプションを付与することで全角→半角に変換することができるのだが、その逆についてはできない。このため、半角→全角についてはuconvを利用する。

nkf -wZ sjis | sort -n | uconv -x Halfwidth-Fullwidth | sed 's/ / /g' | nkf -s

Q5

「size」という、KBやらMBの数字が書かれているファイルがあるので、それをサイズ順にsortするという内容。
こういう場合はnumfmtで一度戻してやって、そのままsort -hで処理するのが楽そうだ。

paste <(sed 's/B//g;s/k/K/g' size|numfmt --from=si) size | sort -k1n | cut -f2-
[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < paste <(sed 's/B//g;s/k/K/g' size|numfmt --from=si) size | sort -k1n | cut -f2-
0.4GB
410MB
1.2GB
2GB
40000MB
1000000000kB

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < sed 's/B//g;s/k/K/g' size # numfmtに渡せるように整形する
2G
1.2G
40000M
1000000000K
0.4G
410M

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < sed 's/B//g;s/k/K/g' size|numfmt --from=si # numfmtでバイトに変換
2000000000
1200000000
40000000000
1000000000000
400000000
410000000

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < paste <(sed 's/B//g;s/k/K/g' size|numfmt --from=si) size # pasteで結合
2000000000  2GB
1200000000  1.2GB
40000000000 40000MB
1000000000000   1000000000kB
400000000   0.4GB
410000000   410MB

[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < paste <(sed 's/B//g;s/k/K/g' size|numfmt --from=si) size | sort -k1n | cut -f2- # sortして1列目削除
0.4GB
410MB
1.2GB
2GB
40000MB
1000000000kB

numfmtはCentOS7くらいの頃からcoreutilsに入ってきてるコマンドなので、大体のLinuxで使えると思う。

Q6

「nums」というファイルがあるので、それをbashの内部コマンドとsleepを使って処理しよう、という問題。
内部コマンドのみとなっているのでcatも使えない状態。なので、while等を使って無理やりファイルを読み込ませるといったことも必要になる。

とりあえず、以下のようにすれば対応できた。

while read a;do (sleep $a && echo $a ) & done < nums

 

Q7

「roman」という、ローマ数字の書かれたファイルがあるので、これをsortするという内容。
で、自前で処理するのはめんどくさいのでなんか無いかなと調べてみたところ、どうやらnumconvというローマ数字や漢数字等を変換するコマンドがあるらしい(知らなかった…)。

Ubuntuであれば、aptから以下のコマンドでインストールができる(yumやpacman、brewではインストールできない様子)。

sudo apt install numconv

 

numconvを使えば、以下で処理が完了する。

paste <(cat roman|numconv) roman | sort -k1n | cut -f2
[REMOTE][blacknon@bs-pub-ubuntu-03][~/ShellGeiData/vol.41]
(`・ω・´) < paste <(cat roman|numconv) roman | sort -k1n | cut -f2
IV
VIII
IX
XI
XX
XLIII
LXXXIX

 

Q8

「gagigugego」という濁点付きのファイルがあるので、これを濁点付きが先にくるようにsortさせるという内容。
通常は濁点付きは後になるので、これをどう処理するかという内容らしい。

で、自分は午前の部に出てなかったのだが、どうやら一度uconvで変換してやることでこの処理ができるらしい(知らなかった…)。

 

uconvにこんな使い方があるとは知らなかった…。

 

numfmtはよく使ってたけど、numconvは知らなかったな。
どうやら漢数字等も扱えるようなので、ちゃんと試してみたいところ。yumやpacmanで扱えないのはちょっと辛いので、なんかインストール方法調べたいかなと。

 

入門bash 第3版 入門bash 第3版

numconvコマンドでローマ数字や漢数字をラテン数字に変換する

$
0
0

先日行われたシェル芸勉強会に行ってたのだが、そこで漢数字やローマ数字をラテン数字に変換できるnumconvなるコマンドの存在を知った。

どうやらパッケージは用意されているようだが、管理システムでインストールできるのはaptだけのようだ。
brewやyum、pacmanでは入れられないので、用意されているパッケージファイルを利用するか、ソースからコンパイルする必要がある。

apt install numconv

使い方は簡単で、対象となるローマ数字や漢数字をそのままパイプ越しに渡してやればいいようだ。
残念ながら1行すべて変換対象として渡してやる必要があるので、文章中の数字を変換する場合はちょっと扱いづらいかも…。

[REMOTE][blacknon@bs-pub-ubuntu-03][~]
(`・ω・´) < echo 十五 | numconv
15

[REMOTE][blacknon@bs-pub-ubuntu-03][~]
(`・ω・´) < echo 三千 | numconv
3000

[REMOTE][blacknon@bs-pub-ubuntu-03][~]
(`・ω・´) < echo 二億 | numconv
200000000

[REMOTE][blacknon@bs-pub-ubuntu-03][~]
(`・ω・´) < echo Ⅴ | numconv
5

[REMOTE][blacknon@bs-pub-ubuntu-03][~]
(`・ω・´) < echo ⅩⅢ | numconv
13

 

あまり使う機会は無いだろうけど、結構便利そうだ。

たのしいローマ数字 たのしいローマ数字
Viewing all 1028 articles
Browse latest View live