ようへいの日々精進XP

おっさんの日記です。

Python logger の Slack Handler を試す(Incoming Webhook 編)

tl;dr

Python スクリプトで logger を使って吐いているログを Slack に通知したいと思ったのでメモ。


memo

色々ある

Slack にメッセージを飛ばすモジュールは色々あるけど、Slack API を生で叩くモジュールが多い印象。logger のハンドラとして利用出来て、且つ、Incoming Webhook で利用出来そうなのが今回試した slack_log_handler だった。

参考

pypi.python.org

インストール

requirements.txt に以下のように書いてから...

slack_log_handler

インストールを実行。

sudo pip install -r requirements.txt

サンプル

実行例

コンソール上で。ログは標準出力に。

$ export WEBHOOK_URL=https://hooks.slack.com/services/xxxxxx/xxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxx
$ python test.py debug
2016-02-21 02:15:50,466 [DEBUG] Debug です
$ python test.py info
2016-02-21 02:15:55,799 [INFO] Info です
$ python test.py warn
2016-02-21 02:16:15,875 [WARNING] Warn です
$ python test.py error
2016-02-21 02:16:20,877 [ERROR] Error です
$ python test.py crit
2016-02-21 02:16:26,205 [CRITICAL] Critical です

Slack 同じようにログが飛んでいる。

f:id:inokara:20160221021813p:plain

各ログレベル毎にアイコンが違うのがシャレオツ。

ユーザー名とかアイコンとかどんな風に設定するのか

以下のように Slack Handler を初期化する時に指定するようだ。

slack = SlackLogHandler(
    os.getenv('WEBHOOK_URL'),
    username = 'foobar',
    emojis = {
        logging.INFO: ':grinning:',
        logging.WARNING: ':white_frowning_face:',
        logging.ERROR: ':persevere:',
        logging.CRITICAL: ':confounded:',
    }
)

以下のように。

f:id:inokara:20160221082720p:plain


さいごに

Handler であること

ログの吐き出し先を標準出力、ファイルや Slack 等統一したインターフェースで放り込めるのがイイ(と思う。)

以上

メモでした。

手作業で構築した AWS リソースの管理には awspec が良いと思ったのでメモ

tl;dr

手作業で構築した AWS リソースの管理には以前から気になっていた awspec が良いと思ったのでメモ。

二台、三台のインスタンスなら...とうっかりと手作業で構築したインスタンスや、どんな設定で作ったか判らないけど、なんとなく利用されている S3 Bucket の管理をどうしようかなと思っていたら awspec の generate コマンドがリソース情報を生成してくれるので試してみた。


参考

github.com

qiita.com


memo

インストール

$ cat Gemfile
source "https://rubygems.org"

gem 'awspec'
gem 'rake'

$ bundle

初期化

$ bundle exec awspec init
 + spec/
 + spec/spec_helper.rb
 + Rakefile
 + spec/.gitignore
 + .rspec

awspec generate

以下のようにサポートされているリソースであれば各リソースの情報を利用してテストを生成してくれるという有り難い機能。

$ bundle exec awspec generate
Commands:
  awspec generate cloudwatch_alarm                    # Generate cloudwatch_alarm spec
  awspec generate directconnect                       # Generate directconnect spec
  awspec generate ebs                                 # Generate attached ebs spec
  awspec generate ec2 [vpc_id]                        # Generate ec2 spec from VPC ID (or VPC "Name" tag)
  awspec generate elb [vpc_id]                        # Generate elb spec from VPC ID (or VPC "Name" tag)
  awspec generate help [COMMAND]                      # Describe subcommands or one specific subcommand
  awspec generate iam_policy                          # Generate attached iam_policy spec
  awspec generate nat_gateway [vpc_id]                # Generate nat_gateway spec from VPC ID (or VPC "Name" tag)
  awspec generate network_acl [vpc_id]                # Generate network_acl spec from VPC ID (or VPC "Name" tag)
  awspec generate rds [vpc_id]                        # Generate rds spec from VPC ID (or VPC "Name" tag)
  awspec generate route53_hosted_zone [example.com.]  # Generate route53_hosted_zone spec from Domain name
  awspec generate route_table [vpc_id]                # Generate route_table spec from VPC ID (or VPC "Name" tag)
  awspec generate s3_bucket [backet_name]             # Generate s3_bucket spec from S3 bucket name. if NO args, Generate all.
  awspec generate security_group [vpc_id]             # Generate security_group spec from VPC ID (or VPC "Name" tag)
  awspec generate subnet [vpc_id]                     # Generate subnet spec from VPC ID (or VPC "Name" tag)
  awspec generate vpc [vpc_id]                        # Generate vpc spec from VPC ID (or VPC "Name" tag)

Options:
  [--profile=PROFILE] 

EC2 の情報を生成する

以下のように generate コマンドを利用して vpc-xxxxxxxx 内の EC2 情報を生成してみる。

$ echo "require 'spec_helper'" > spec/ec2_spec.rb
$ bundle exec awspec generate ec2 vpc-xxxxxxxx >> spec/ec2_spec.rb

以下のような内容が出力される。

require 'spec_helper'
describe ec2('oreno-instance01') do
  it { should exist }
  it { should be_stopped }
  its(:instance_id) { should eq 'i-xxxxxxxxx' }
  its(:image_id) { should eq 'ami-xxxxxxxx' }
  its(:private_dns_name) { should eq 'ip-10-0-x-xxx.ap-northeast-1.compute.internal' }
  its(:public_dns_name) { should eq '' }
  its(:instance_type) { should eq 't1.micro' }
  its(:private_ip_address) { should eq '10.0.x.xxx' }
  it { should have_security_group('test') }
  it { should belong_to_vpc('vpc-xxxxxxxx') }
  it { should belong_to_subnet('subnet-xxxxxxxxxxx') }
  it { should have_ebs('vol-xxxxxxxxxx') }
end

生成された情報を元にテストしてみる。

$ bundle exec rake spec

以下のように出力される。

f:id:inokara:20160221112941p:plain

おお。

このバケットってどんな設定だっけ

S3 バケットの設定も生成しておく。

$ echo "require 'spec_helper'" > spec/s3_bucket_spec.rb
$ bundle exec awspec generate s3 inokara-sandbox >> spec/s3_bucket_spec.rb

以下のように生成される。

$ cat spec/s3_bucket_spec.rb 
require 'spec_helper'
describe s3_bucket('inokara-sandbox') do
  it { should exist }
  its(:acl_owner) { should eq 'xxxxxxxxx' }
  its(:acl_grants_count) { should eq 1 }
  it { should have_acl_grant(grantee: 'xxxxxxxx', permission: 'FULL_CONTROL') }
end

テストしてみる。

f:id:inokara:20160221120110p:plain

VPC の設定も見てみよう

VPC の設定も生成しておく。

$ echo "require 'spec_helper'" > spec/vpc_spec.rb
$ bundle exec awspec generate vpc vpc-xxxxxxxx >> spec/vpc_spec.rb

以下のように生成される。

$ cat spec/vpc_spec.rb 
require 'spec_helper'
describe vpc('vpc-xxxxxxxxx') do
  it { should exist }
  it { should be_available }
  its(:vpc_id) { should eq 'vpc-xxxxxxxxx' }
  its(:cidr_block) { should eq '10.0.0.0/16' }
  it { should have_route_table('rtb-xxxxxxxxx') }
  it { should have_route_table('rtb-xxxxxxxxx') }
  it { should have_network_acl('acl-xxxxxxxxx') }
end

テストしてみる。

f:id:inokara:20160221120122p:plain


最後に

generate がとても有り難い

手作業で構築した AWS リソースは generate コマンドを利用してリソースをコードに吐き出すことでテキストベースで管理出来るのが嬉しい。手で修正を加えたら generate で吐き出すような運用をすれば良さそう。(generate で吐き出すのを忘れる可能性が...)

ということで

構築前にテストを書いて、構築後のチェックで利用という側面もあると思うが、手作業で構築したリソースの資産管理として awspec を使っていきたいと思った。

以上。

PowerShell 関連〜PowerShell から Windows Firewall を操作するメモ

tl;dr

PowerShell から Windows Firewall を操作するメモ。


参考


memo

試している環境

f:id:inokara:20160221185231p:plain

PowerShell のバージョン。

PS C:\Users\Administrator> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      3.0
WSManStackVersion              3.0
SerializationVersion           1.1.0.1
CLRVersion                     4.0.30319.34209
BuildVersion                   6.2.9200.17065
PSCompatibleVersions           {1.0, 2.0, 3.0}
PSRemotingProtocolVersion      2.2

既存の Firewall ルールを確認する

コマンドレットの Get-NetFirewallRule を利用する。

PS C:\Users\Administrator> Get-NetFirewallRule

実行すると以下のようにダラダラーとルールが出力される。

f:id:inokara:20160221185543p:plain

特定のルールだけ絞り込みたい場合には Where-Object を利用して絞り込む。(以下は RemoteDesktop 関連のルールを確認する場合)

PS C:\Users\Administrator> Get-NetFirewallRule | Where-Object Name -Like 'RemoteDesktop*'

以下のように出力される。

Name                  : RemoteDesktop-UserMode-In-TCP
DisplayName           : Remote Desktop - User Mode (TCP-In)
Description           : Inbound rule for the Remote Desktop service to allow RDP traffic. [TCP 3389]
DisplayGroup          : Remote Desktop
Group                 : @FirewallAPI.dll,-28752
Enabled               : True
Profile               : Any
Platform              : {}
Direction             : Inbound
Action                : Allow
EdgeTraversalPolicy   : Block
LooseSourceMapping    : False
LocalOnlyMapping      : False
Owner                 :
PrimaryStatus         : OK
Status                : The rule was parsed successfully from the store. (65536)
EnforcementStatus     : NotApplicable
PolicyStoreSource     : PersistentStore
PolicyStoreSourceType : Local

Name                  : RemoteDesktop-UserMode-In-UDP
DisplayName           : Remote Desktop - User Mode (UDP-In)
Description           : Inbound rule for the Remote Desktop service to allow RDP traffic. [UDP 3389]
DisplayGroup          : Remote Desktop
Group                 : @FirewallAPI.dll,-28752
Enabled               : True
Profile               : Any
Platform              : {}
Direction             : Inbound
Action                : Allow
EdgeTraversalPolicy   : Block
LooseSourceMapping    : False
LocalOnlyMapping      : False
Owner                 :
PrimaryStatus         : OK
Status                : The rule was parsed successfully from the store. (65536)
EnforcementStatus     : NotApplicable
PolicyStoreSource     : PersistentStore
PolicyStoreSourceType : Local

ルールの追加

例えば、特定の IP アドレスから全てのポートに対する接続を Block したい場合には以下のように実行する。

PS C:\Users\Administrator>New-NetFirewallRule `
  -Name 'xxx.xxx.xxx.xxx-block' `
  -DisplayName 'xxx.xxx.xxx.xxx-block' `
  -Description 'xxx.xxx.xxx.xxx-block' `
  -Enabled False `
  -Profile Any `
  -Direction Inbound `
  -Action Block `
  -Program Any `
  -LocalAddress Any `
  -RemoteAddress 192.168.100.100 `
  -Protocol Any `
  -LocalPort Any `
  -RemotePort Any `
  -LocalUser Any `
  -RemoteUser Any

上記の例では 192.168.100.100 からの接続を Block する。実行すると以下のように出力される。

PS C:\Users\Administrator> New-NetFirewallRule `
>>   -Name 'xxx.xxx.xxx.xxx-block' `
>>   -DisplayName 'xxx.xxx.xxx.xxx-block' `
>>   -Description 'xxx.xxx.xxx.xxx-block' `
>>   -Enabled False `
>>   -Profile Any `
>>   -Direction Inbound `
>>   -Action Block `
>>   -Program Any `
>>   -LocalAddress Any `
>>   -RemoteAddress 192.168.100.100 `
>>   -Protocol Any `
>>   -LocalPort Any `
>>   -RemotePort Any `
>>   -LocalUser Any `
>>   -RemoteUser Any
>>


Name                  : xxx.xxx.xxx.xxx-block
DisplayName           : xxx.xxx.xxx.xxx-block
Description           : xxx.xxx.xxx.xxx-block
DisplayGroup          :
Group                 :
Enabled               : False
Profile               : Any
Platform              : {}
Direction             : Inbound
Action                : Block
EdgeTraversalPolicy   : Block
LooseSourceMapping    : False
LocalOnlyMapping      : False
Owner                 :
PrimaryStatus         : OK
Status                : The rule was parsed successfully from the store. (65536)
EnforcementStatus     : NotApplicable
PolicyStoreSource     : PersistentStore
PolicyStoreSourceType : Local

今回はいきなり設定が反映されないように Enablefalse の状態で設定している。

念の為に Windows Firewall の GUI でも確認してみる。

f:id:inokara:20160221193630p:plain

Block the connection でルールが登録されている。

f:id:inokara:20160221193647p:plain

全てのプロトコルが対象となっている。

f:id:inokara:20160221193653p:plain

リモートの IP アドレスも登録されている。

ルールの修正

例えば リモートの IP を追加したい場合には以下のように実行する。

PS C:\Users\Administrator> Set-NetFirewallRule –DisplayName “xxx.xxx.xxx.xxx-block” -RemoteAddress 192.168.100.101,192.168.100.101

実行すると以下のように 192.168.100.101 が登録されている。

f:id:inokara:20160221194624p:plain

-RemoteAddress の追記ってどうするんだろうと...。上記の例だと既存の IP を確認した上で、新しい IP をカンマ区切りで書く必要があるのが辛い。

ルールを有効にする

登録の際にはルールは無効にしている為、以下のように実行してルールを有効にする。

PS C:\Users\Administrator> Set-NetFirewallRule –DisplayName “xxx.xxx.xxx.xxx-block” -Enabled True

以下のように有効になっている。

f:id:inokara:20160221195052p:plain

ちなみに無効にする場合には以下のように。

PS C:\Users\Administrator> Set-NetFirewallRule –DisplayName “xxx.xxx.xxx.xxx-block” -Enabled False

f:id:inokara:20160221195226p:plain

おけ。

ルールの削除

最後にルールの削除は以下のように。

PS C:\Users\Administrator> Remove-NetFirewallRule –DisplayName “xxx.xxx.xxx.xxx-block”

簡単。


ということで

PowerShell から思ったよりも簡単に Windows Firewall を操作することが出来た。不正なアクセスしてくる IP を拒否したりするような仕組みは PowerShell だけで作れるような気がしてきた。

PowerShell 関連〜Slack の Incoming Webhook を叩いて PowerShell からメッセージを投稿する

tl;dr

Slack の Incoming Webhook を叩いて PowerShell からメッセージを投稿するメモ(参考にさせて頂いたページを写経な感じになっているが...)


参考

ありがとうございます。


メモ

事前に PowerShell が実行出来るようにしておく

f:id:inokara:20160221202749p:plain

ザクっと以下のような PowerShell スクリプトを...

slack.ps1 というファイル名で保存。

function slack_notification($message) {

  $payload = @{ 
     text = $message;
     username = "PowerShell User"; 
     icon_emoji = ":frog:"
  }

  ConvertTo-Json $notificationPayload
  Invoke-RestMethod `
    -Uri "https://hooks.slack.com/services/xxxxxxxxxxx/xxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx" `
    -Method Post `
    -Body (ConvertTo-Json $payload)
}

slack_notification("Hello World")

curlPowerShellInvoke-RestMethod コマンドレットを利用する。また、ポストする JSON メッセージは以下のようにハッシュを生成して ConverTo-Json コマンドレットを利用して JSON 化する。

実行してみる

PS C:\Users\Administrator\Documents\powershell> powershell .\slack.ps1
{
    "username":  "PowerShell User",
    "icon_emoji":  ":frog:",
    "text":  "Hello World"
}
ok
PS C:\Users\Administrator\Documents\powershell> powershell .\slack.ps1
{
    "username":  "PowerShell User",
    "icon_emoji":  ":frog:",
    "text":  "Hello World"
}
ok
PS C:\Users\Administrator\Documents\powershell> powershell .\slack.ps1
{
    "username":  "PowerShell User",
    "icon_emoji":  ":frog:",
    "text":  "Hello World"
}
ok

以下のように Slack にメッセージが届いている。

f:id:inokara:20160221205518p:plain

おけおけ。


以上

日本語のメッセージを飛ばすのはもちょっと工夫する必要がありそうなので追々。

おやつの curl メモ(リダイレクト、cookie とか)

ども、かっぱです。

tl;dr

リダイレクトがあったり Cookie をセットしたりする Web サイトを調査する際に利用する curl のオプションをいくつか試してみることにした。


メモ

アプリケーション

以下のような sinatra アプリケーションを用意して確認する。

require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/reloader'
                                           
get '/' do 
  cookies[:fortune] = 'cookie'
  redirect to('/fortune')                         
  p 'set cookie'
end
                                 
get '/fortune' do
  if request.cookies['fortune'] == 'cookie'
    p 'has cookie'                         
  else
    p 'no cookie'
  end                                      
end

以下のように起動する。

$ ruby app.rb &
[1] 25386
t$ == Sinatra (v1.4.7) has taken the stage on 4567 for development with backup from Thin
Thin web server (v1.6.4 codename Gob Bluth)
Maximum connections set to 1024
Listening on localhost:4567, CTRL+C to stop

普通にアクセス

普通に / にアクセスすると以下のように。

$ curl -v localhost:4567/
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 4567 (#0)
>127.0.0.1 - - [21/Feb/2016:21:35:51 +0900] "GET / HTTP/1.1" 302 - 0.0006
 GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:4567
> Accept: */*
>
< HTTP/1.1 302 Moved Temporarily
< Content-Type: text/html;charset=utf-8
< Set-Cookie: fortune=cookie; path=/; HttpOnly
< Location: http://localhost:4567/fortune
< Content-Length: 0
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Connection: keep-alive
* Server thin is not blacklisted                                                                                                                                                                                                               < Server: thin                                                                                                                                                                                                                                 <
* Connection #0 to host localhost left intact

リダイレクト先がレスポンスヘッダの Location: にセットされていることが判る。また、Set-Cookie によって fortune=cookie がセットされていることも判る。

リダイレクト先までアクセスする

--location オプション(-L オプション)を利用することでリダイレクト先までアクセスすることが出来る。

$ curl -i --location localhost:4567/
127.0.0.1 - - [21/Feb/2016:21:41:32 +0900] "GET / HTTP/1.1" 302 - 0.0007
HTTP/1.1 302 Moved Temporarily
Content-Type: text/html;charset=utf-8
Set-Cookie: fortune=cookie; path=/; HttpOnly
Location: http://localhost:4567/fortune
Content-Length: 0
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Connection: keep-alive
Server: thin

"no cookie"
127.0.0.1 - - [21/Feb/2016:21:41:32 +0900] "GET /fortune HTTP/1.1" 200 9 0.0004                                                                                                                                                                tHTTP/1.1 200 OK                                                                                                                                                                                                                                tContent-Type: text/html;charset=utf-8
Content-Length: 9
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Connection: keep-alive
Server: thin

cookie を保持したままリダイレクト先までアクセスする

cookie を保存する場合には --cookie オプション(-c オプション)を利用する。

$ curl -i -c cookie_saved --location localhost:4567/
127.0.0.1 - - [21/Feb/2016:21:44:12 +0900] "GET / HTTP/1.1" 302 - 0.0006
HTTP/1.1 302 Moved Temporarily
Content-Type: text/html;charset=utf-8
Set-Cookie: fortune=cookie; path=/; HttpOnly
Location: http://localhost:4567/fortune
Content-Length: 0
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Connection: keep-alive
Server: thin

"has cookie"
127.0.0.1 - - [21/Feb/2016:21:44:12 +0900] "GET /fortune HTTP/1.1" 200 10 0.0006
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 10
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Connection: keep-alive
Server: thin

cookie_saved は以下のような内容になっている。

$ cat cookie_saved
# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

#HttpOnly_localhost     FALSE   /       FALSE   0       fortune cookie

リダイレクト先を増やしてみて cookie が保持されているかを確認する

以下のようにリダイレクト追加。

require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/reloader'

get '/' do
  cookies[:fortune] = 'cookie'
  redirect to('/fortune')
  p 'set cookie'
end

get '/fortune' do
  if request.cookies['fortune'] == 'cookie'
    p 'has cookie'
    p request.cookies['fortune']
    redirect to('/koisuru')
  else
    p 'no cookie'
  end
end

get '/koisuru' do
  if request.cookies['fortune'] == 'cookie'
    p 'has cookie'
    p request.cookies['fortune']
  else
    p 'no cookie'
  end
end

以下のように出力されて cookie が保持されていることが判る。

$ curl -i --cookie cookie_saved --location localhost:4567/
127.0.0.1 - - [21/Feb/2016:21:50:59 +0900] "GET / HTTP/1.1" 302 - 0.0006
HTTP/1.1 302 Moved Temporarily
Content-Type: text/html;charset=utf-8
Set-Cookie: fortune=cookie; path=/; HttpOnly
Location: http://localhost:4567/fortune
Content-Length: 0
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Connection: keep-alive
Server: thin

"has cookie"
"cookie"
127.0.0.1 - - [21/Feb/2016:21:50:59 +0900] "GET /fortune HTTP/1.1" 302 - 0.0005
HTTP/1.1 302 Moved Temporarily
Content-Type: text/html;charset=utf-8
Location: http://localhost:4567/koisuru
Content-Length: 0
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Connection: keep-alive
Server: thin

"has cookie"
"cookie"
127.0.0.1 - - [21/Feb/2016:21:50:59 +0900] "GET /koisuru HTTP/1.1" 200 6 0.0006
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 6
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Connection: keep-alive
Server: thin

以上

curl にはたくさんオプションがあって驚いた。