Calmery.me

みっかぼうずにならないようがんばる

VirtualBox上のUbuntuでDockerを動かしてみる

セキュリティさくら 分科会(仮称) 第2回 : ATND のまとめ.

事前準備

VirtualBox 上で Ubuntu を動かせる状態にしておく.自分は Ubuntu 17.04 を使用した.

コンテナと VM の違い

VM はハードウェアシュミュレーションであってコンテナはあるプロセスに専用の実行空間を用意すること.注目を浴びる「Dockerコンテナ」、従来の仮想化と何が違うのか? | コラム | 東京エレクトロン デバイス株式会社 とかを読んだら雰囲気はわかると思う.

Docker がしてくれるもの

  • コンテナ管理
  • 環境構築
  • イメージ管理

Docker の使い方

  • Image を作成する
    • docker build
  • コンテナを実行する
    • docker run

インストー

Get Docker for Ubuntu - Docker Documentation を見る.インストールする対象ごとにドキュメントが用意されているので参考にする.

$ sudo apt-get remove docker docker-engine
$ sudo apt-get update
$ sudo apt-get install \
    linux-image-extra-$(uname -r) \
    linux-image-extra-virtual
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce
$ apt-cache madison docker-ce
$ sudo docker run hello-world
$ sudo usermod -aG docker $(whoami)

usermod を実行後,ログインし直す必要がある.

Ubuntu の 17.04 では add-apt-repository の $(lsb_release -cs) を xenial に変更する必要がある.ここを変更していないと apt-get install docker-ce したときにそんなものないよって言われる.

$ sudo apt-get install docker-ce
  Readning package lists... Done
  Building dependency tree
  Reading state information... Done
  E: Unable to locate package docker-ce

Note: The lsb_release -cs sub-command below returns the name of your Ubuntu distribution, such as xenial.

Sometimes, in a distribution like Linux Mint, you might have to change $(lsb_release -cs) to your parent Ubuntu distribution. For example: If you are using Linux Mint Rafaela, you could use trusty.
Get Docker for Ubuntu - Docker Documentation

$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   xenial \
   stable"

Hello World

試しに動かしてみる.

$ sudo docker run hello-world
  Unable to find image 'hello-world:latest' locally
  latest: Pulling from library/hello-world
  78445dd45222: Pull complete 
  Digest: sha256:c5515758d4c5e1e838e9cd307f6c6a0d620b5e07e6f927b07d05f6d12a1ac8d7
  Status: Downloaded newer image for hello-world:latest

  Hello from Docker!
  This message shows that your installation appears to be working correctly.

  To generate this message, Docker took the following steps:
   1. The Docker client contacted the Docker daemon.
   2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
   3. The Docker daemon created a new container from that image which runs the
      executable that produces the output you are currently reading.
   4. The Docker daemon streamed that output to the Docker client, which sent it
      to your terminal.

  To try something more ambitious, you can run an Ubuntu container with:
   $ docker run -it ubuntu bash

  Share images, automate workflows, and more with a free Docker ID:
   https://cloud.docker.com/

  For more examples and ideas, visit:
   https://docs.docker.com/engine/userguide/

Struts2 を動かしてみる

何かと話題となった Struts2 を Docker で動かしてみる.

$ mkdir workspace
$ cd workspace
$ wget https://dist.apache.org/repos/dist/release/struts/2.5.10.1/struts-2.5.10.1-apps.zip
$ unzip struts-25.10.1-apps.zip
$ vim Dockerfile
$ cat Dockerfile
  FROM tomcat:7.0-jre8
  ADD struts-2.5.10.1/apps/struts2-rest-showcase.war /usr/local/tomcat/webapps/
  CMD ["catalina.sh", "run"]
$ ls
  struts-25.10.1 Dockerfile
$ docker build -t struts/st .
$ docker run -it --rm -p 8080:8080 struts/st
  ...
  May 13, 2017 5:10:29 AM org.apache.catalina.startup.Catalina start
  INFO: Server startup in 8858 ms

http://localhost:8080 を開き,以下のような画面が表示されたら成功.docker run の -p 8080:8080 ではコンテナ側の 8080 番ポートを実行環境の 8080 番ポートに割り当てているのだとか.コンテナに外部からアクセス(ポートフォワード) - Qiita あたりを参考に.
f:id:calmery:20170514204146p:plain

共有フォルダを作成する

分科会では Ubuntu 16.04 LTS : Samba : フルアクセスの共有フォルダ作成 : Server World を見ながら進めたが,ここでは sambaサーバによるファイル共有環境の構築手順 を見ながら進めることにする.

sambaサーバの構築手順
上図のファイル共有環境の構築のためには以下の手順が必要になります。
1. sambaのインストー
2. sambaユーザの作成
3. sambaユーザのグループへの追加
4. グループ共有用ディレクトリの作成とアクセス権の設定
5. sambaの設定ファイルの編集
sambaサーバによるファイル共有環境の構築手順

まずは samba を apt-get を使ってインストールする.ついでに ifconfig を使うための net-tools と vim もインストールした.

$ sudo apt-get install samba
$ sudo apt-get install vim net-tools

samba で使用するユーザを追加する.

sambaを使用するユーザを作成する場合、sambaが動作するLinux上にsambaを使用するユーザと同名のLinuxユーザがすでに存在している必要があります。
sambaサーバによるファイル共有環境の構築手順

ということなのでユーザを作成してから pdbedit で samba にユーザを追加する.既にあるユーザを追加する場合は pdbedit だけ実行すればいい.

$ sudo useradd -m user1
$ sudo pdbedit -a user1
 new password:
 retype new password:

グループを作成し,グループごとにアクセス権を与える.

sambaでは、sambaが動作するLinux上に存在するグループ毎に、共有するファイルやディレクトリのアクセス権を変更できます。今回はubuntu上にgroup1という名前のグループを作成し、group1に属するユーザだけがアクセスできるディレクトリを作成します。
sambaサーバによるファイル共有環境の構築手順

$ sudo groupadd group1
$ sudo gpasswd -a user1 group1
 Adding user user1 to group group1

ディレクトリの作成を行い,先ほど追加したグループへアクセス権限の追加を行う.

$ sudo mkdir /home/public
$ sudo chgrp group1 /home/public
$ sudo chmod -R 774 /home/public

samba の設定を変更する.

$ sudo vim /etc/samba/smb.conf
$ cat /etc/samba/smb.conf
  ...
  unix charset = UTF-8
  dos charset = CP932
  ...
  map to guest = never
  ...
  [public]
     path = /home/public
     browseable = yes
     writable = yes
     valid users = @group1

ここでは valid users にグループ名を指定しているが pdbedit で追加したユーザを直接指定することもできる.

valid users = calmery, user1, user2

ホスト側で開く.ここで指定するアドレスは ifconfig で調べられる.
f:id:calmery:20170514205819p:plain
先ほど登録したユーザ名とパスワードを入力する.
f:id:calmery:20170514205319p:plain
マウントするボリュームを選択する.
f:id:calmery:20170514205748p:plain
開けた.ここで追加したファイルは Ubuntu 上で確認できる.
f:id:calmery:20170514205537p:plain
f:id:calmery:20170514210032p:plain
右クリックで取り出せる.
f:id:calmery:20170514210110p:plain

ハマったところ

自分は分科会で共有フォルダを開くことができなかった.調べてみると

また、NATの使用時に、SSH等でホストOSからゲストOSへの接続を行いたい場合、ネットワークのアダプターにホストオンリーアダプターを別途使用することで、ゲストOSへホストOSから接続可能とすることができます。
VirtualBox CentOS6.7 64bitでNAT、ホストオンリーアダプターを使用 | kakiro-web カキローウェブ

ということらしく,VirtualBoxVM の設定のネットワークから「ホストオンリーアダプター」を追加すると共有フォルダを開くことができるようになった.
f:id:calmery:20170514204231p:plain

まとめ

なるほど(?)
ほとんど環境を整えただけで終わってしまったが,簡単な使い方はわかったので色々と調べてみようと思う.

Dockerを使って、Apache Struts2の脆弱性S2-037のやられ環境を手軽に作る - DARK MATTER
Dockerで作る開発環境 - Qiita
Docker入門-基礎編 いまから始めるDocker管理【2nd Edition】

ちなみに共有フォルダでは会話も楽しむことができる.

WebAssemblyを試してみた

メモ.WebAssembly については WebAssembly のコンセプト - WebAssembly | MDN を参考にするとよさそう.

さらに追記

$ nvm install 8.0.0
$ nvm use 8.0.0
$ node -v
v8.0.0
$ node
> WebAssembly
{}

新しくリリースされた v8.0.0 の Node.js ではデフォルトで使えるようになってるみたい.
追記のところで main 関数がとか言っていたけど Emscripten を使えばその辺りもいい感じしてもらえるっぽい.

見直してみると,色々と勘違いしているところもあるけれど記録としてそのままに.

追記

main 関数があっても動かせた.と言うか足りない関数は補ってねという感じなのかな.
楓 software: wasm に clang を使った場合に C 内で関数呼び出しする場合に出るエラー

(index):25 Uncaught (in promise) LinkError: WebAssembly.Instance(): Import #0 module="env" function="printf" error: function import requires a callable
    at fetch.then.then.then.module (http://localhost/:25:24)
    at <anonymous>
$ s2wasm main.s -o main.wast --allocate-stack 1024
const loadWebAssembly = ( filename, imports ) => {
    return fetch( filename )
        .then( response => response.arrayBuffer() )
        .then( buffer => WebAssembly.compile( buffer ) )
        .then( module => {
            imports = imports || {}
            imports.env = imports.env || {}

            imports.env.printf = arg => console.log( arg )

            if( !imports.env.memory )
                imports.env.memory = new WebAssembly.Memory( {
                    initial: 256
                } )

            if( !imports.env.table )
                imports.env.table = new WebAssembly.Table( {
                    initial: 18,
                    element: 'anyfunc'
                } )

            return new WebAssembly.Instance( module, imports )
        } )
}

loadWebAssembly( 'main.wasm' ).then( instance => {
    const exports = instance.exports
    exports.main()
} )

Google Chrome Canary や Node.js で WebAssembly を読み込むための関数を作った(よくわかっていないのでものによっては動かないかも)
WebAssembly/loadWebAssembly.js at master · calmery/WebAssembly · GitHub

環境構築

Mac OS X 10.11.6
Google Chrome Canary 59.0.3030.0 canary (64-bit)
Firefox Nightly 54.0a1 (2017-03-05) (64 bit)
Node.js v8.0.0-pre

おまじない.

$ pyenv global 2.7.12

The LLVM Compiler Infrastructure Project

$ cd
$ mkdir workspace
$ cd workspace
$ git clone http://llvm.org/git/llvm.git
$ cd llvm/tools
$ git clone http://llvm.org/git/clang.git
$ cd ../projects
$ git clone http://llvm.org/git/compiler-rt
$ cd ~/workspace
$ mkdir llvm_build
$ cd llvm_build
$ cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr/local -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly ../llvm
$ make -j 8
$ sudo make install

GitHub - WebAssembly/binaryen: Compiler infrastructure and toolchain library for WebAssembly, in C++

$ cd ~/workspace
$ git clone https://github.com/WebAssembly/binaryen.git
$ cd binaryen
$ cmake . && make
$ sudo make install

GitHub - WebAssembly/wabt: The WebAssembly Binary Toolkit

$ cd /usr/local/src
$ sudo git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ sudo make gcc-debug-no-tests
$ cd
$ echo "export PATH=$PATH:/usr/local/src/wabt/out/gcc/Debug/no-tests/" >> .bash_profile

Node.js は v8.0.0-pre でないと Google Chrome Canary や Firefox Nightly で動く WebAssembly が動かない.そのため v8/nodenodejs/node を使う.Nightly builds の v8.0.0-nightly20170307efaab8fccf も試したが動かなかった.
GitHub - nodejs/node: Node.js JavaScript runtime

$ git clone https://github.com/nodejs/node.git
$ cd node
$ git checkout -b canary origin/canary
$ ./configure
$ make -j4
 if [ ! -r node -o ! -L node ]; then ln -fs out/Release/node node; fi
$ ./out/Release/node -v
 v8.0.0-pre
$ ./out/Release/node --expose-wasm
> WebAssembly
{}

GitHub - v8/node: Node.js JavaScript runtime

$ git clone https://github.com/v8/node.git
$ cd node
$ git checkout -b vee-eight-lkgr origin/vee-eight-lkgr
$ ./configure
$ make -j4
 if [ ! -r node -o ! -L node ]; then ln -fs out/Release/node node; fi
$ ./out/Release/node -v
 v8.0.0-pre
$ ./out/Release/node --expose-wasm
> WebAssembly
{}

WebAssembly に変換する

C や C++ で書かれたコードを WebAssembly に変換する.

extern "C" {
    int fib( int n ){
        switch( n ){
            case 0: 
            	return 0;
            case 1: 
            	return 1;
            default: 
            	return fib( n - 2 ) + fib( n - 1 );
        }
    }
}

C や C++ のコードをまず LLVM bitecode に変換し,次にアセンブリに変換する.その後 S 式に変換しバイナリに変換する.

$ clang++ -v
clang version 5.0.0 (http://llvm.org/git/clang.git fc2d9054c86c9b8acbc98b06f1b9d8aa0e5d40f9) (http://llvm.org/git/llvm.git 58580c59aefa59fb793a72dd5abeb6a60340a317)
$ clang++ main.cpp -emit-llvm --target=wasm32 -Oz -c -o main.bc
$ llc -version
LLVM (http://llvm.org/):
  LLVM version 5.0.0svn
...
$ llc main.bc -march=wasm32 -filetype=asm -o main.s
$ s2wasm main.s > main.wast
$ sexpr-wasm main.wast -o main.wasm

追記
sexpr-wasm ではなく wast2wasm を使うようにする.

$ wast2wasm main.wast -o main.wasm

動かしてみる

GitHub - 1984weed/webassembly-sample を参考にした.

const loadWebAssembly = ( filename, imports ) => {
    return fetch(filename)
        .then( response => response.arrayBuffer() )
        .then( buffer => WebAssembly.compile( buffer ) )
        .then( module => {
            imports = imports || {}
            imports.env = imports.env || {}
            imports.env.memoryBase = imports.env.memoryBase || 0
            imports.env.tableBase = imports.env.tableBase || 0
            
            if( !imports.env.memory )
                imports.env.memory = new WebAssembly.Memory( {
                    initial: 256
                } )

            if( !imports.env.table )
                imports.env.table = new WebAssembly.Table( {
                    initial: 0,
                    element: 'anyfunc'
                } )

            return new WebAssembly.Instance( module, imports )
        } )
}

loadWebAssembly( 'main.wasm' ).then( instance => {
    const exports = instance.exports
    console.log( exports ) // => Object {memory: W…y.Memory, fib: function}
} )

Node.js はオプションで --expose-wasm をつける必要がある.

// Node.js v8.0.0-pre
// node --expose-wasm
const fs = require( 'fs' )
const buffer = fs.readFileSync( './main.wasm' )

const arrayBuffer = new Uint8Array( buffer ).buffer

WebAssembly.compile( arrayBuffer ).then( module => {
    let imports = {}
    imports.env = {}
    imports.env.memoryBase = 0
    imports.env.tableBase = 0

    if( !imports.env.memory )
        imports.env.memory = new WebAssembly.Memory( {
            initial: 256
        } )

    if( !imports.env.table )
        imports.env.table = new WebAssembly.Table( {
            initial: 0,
            element: 'anyfunc'
        } )

    const instance = new WebAssembly.Instance( module, imports )
    console.log( instance.exports )
} )
// Node.js v7.7.1
Promise {
  <rejected> Error: WebAssembly.compile(): Wasm decoding failedResult = expected version 0c 00 00 00, found 01 00 00 00 @+4

ちなみに C++ では名前に修飾がついてしまうので extern "C" で囲う必要がある.

console.log( instance.exports ) // => Object {memory: W…y.Memory, _Z3fibi: function}
> WebAssembly.Instance( module ).exports
{ _Z3fibi: [Function: 0], memory: Memory {} }

サンプルなどを見ていると WebAssembly ではなく Wasm を使っているものがある.だが Google Chrome Canary や Firefox Nightly では Wasm がない.

// Replace
Wasm.instantiateModule() // => ReferenceError: WASM is not defined
WebAssembly.Instance()

Node.js v7.2.1

Getting Started With WebAssembly in Node.js | www.thecodebarbarian.com
この方法は v.7.7.1 では動かなかった.sexpr-wasm demo も s2wasm で作られた wast がそのままでは使えない.書き換えれば使えはしたが v7.7.1 で動かなかった.

Emscripten で変換する

Developer’s Guide - WebAssembly を参考にして導入する.
Main — Emscripten 1.37.22 documentation
latest ではなく incoming を使う.

$ git clone https://github.com/juj/emsdk.git
$ cd emsdk
$ ./emsdk install sdk-incoming-64bit binaryen-master-64bit
$ ./emsdk activate sdk-incoming-64bit binaryen-master-64bit
$ source ./emsdk_env.sh
$ emcc
INFO:root:(Emscripten: Running sanity checks)
WARNING:root:no input files
$ mkdir hello
$ cd hello
$ echo '#include <stdio.h>' > hello.c
$ echo 'int main(int argc, char ** argv) {' >> hello.c
$ echo 'printf("Hello, world!\n");' >> hello.c
$ echo '}' >> hello.c
$ emcc hello.c -s WASM=1 -o hello.html
$ ls
hello.c		hello.html	hello.js	hello.wasm
$ node -v
 v8.0.0-pre
$ node --expose-wasm hello.js
trying binaryen method: native-wasm
asynchronously preparing wasm
binaryen method succeeded.
run() called, but dependencies remain, so not running
Hello, world!

ここで生成された hello.wasm は動かしてみるのように読み込むことはできなかった.

> WebAssembly.compile( arrayBuffer ).then( ...
Promise { <pending> }
(node:92075) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): LinkError: WebAssembly.Instance(): Import #0 module="env" function="DYNAMICTOP_PTR" error: global import must be a number
(node:92075) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

どうやら main 関数があると動かないみたい.出力されたファイルをそのまま実行すると動きはする.Google Chrome Canary や Firefox Nightly も出力された hello.html では動くのだが動かしてみるのように読み込むと動かない.以下のようにオプションを指定すると動いた.だがこちらも main 関数があると動かない.この辺りはもうちょっと調べたい.(追記 未確認だけど main 関数というよりは存在しない関数を呼び出しているからっぽい.ここでは main 関数内で呼び出している printf 関数のせいかも)

$ emcc hello.c -Os -s WASM=1 -s SIDE_MODULE=1 -s 'BINARYEN_METHOD="native-wasm"' -o hello.wasm
$ ls
main.cpp main.wasm
$ node --expose-wasm
> const fs = require( 'fs' )
> const buffer = fs.readFileSync( 'hello.wasm' )
> const arrayBuffer = new Uint8Array( buffer ).buffer
...

動かしてみる のコードを使用して動かした結果はこうなった.

emcc hello.c -s WASM=1 -o hello.html

main 関数があってもなくても読み込めない.

emcc hello.c -Os -s WASM=1 -s SIDE_MODULE=1 -s 'BINARYEN_METHOD="native-wasm"' -o hello.wasm

main 関数がなければ Google Chrome Canary,Firefox Nightly,Node.js v8.0.0-pre で動かせた.
最適化の -Os を抜くと読み込めなかったりとよくわからない.

Rust から変換する

Compiling Rust to WebAssembly Guide – Hacker Noon
結局は Emscripten と同じみたい.

fn main(){
    println!( "Hello World" );
}
$ rustc --target=wasm32-unknown-emscripten hello.rs -o hello.html
$ node -v
 v8.0.0-pre
$ node --expose-wasm hello.js
trying binaryen method: native-wasm
asynchronously preparing wasm
binaryen method succeeded.
run() called, but dependencies remain, so not running
Hello World

まとめ

Emscripten を使えば良さそうだけどよくわからなかった.調べる.

稲荷山登山チャレンジ(成功)

友達と京都旅行,伏見稲荷大社に行ってきた.以前来たときは時間がなくて本殿で引き返したのだけど今回は登ることができた.満足.夕方と翌日のお昼,合わせて二回登ったのだけど雰囲気が違っていて面白い.夜は本当に何か出そう.夕方から夜にかけては人も少ないし(暗くて危ないけど)いいかも.

きれいだった😆 #theta360 - Spherical Image - RICOH THETA
京都駅から稲荷駅までは JR の奈良行きに乗って二駅.近い.快速は停まらないみたい.初めは日も暮れかけていたので寄り道せずに登った.翌日は記録をとってみた.多少抜けはあるけどこんな感じ.これで歩いた距離は 7 キロらしい.

思い出というか,一度目は飲み物を買い忘れていて途中の自販機で買った.当然といえば当然なのだけど高い.飲み物は登る前に買った方がよさそう(当たり前)

翌日は参拝できるところは全て行こうと五円玉を三十枚ほど持っていったのだけど足りなかった.倍はいる.この日は休日で五円玉を作るのに京都駅近くのゆうちょでひたすら五円玉を引き出した.今思うともはや不審者だった.

手荷物は稲荷駅から本殿に向う途中,右手の参集殿にあるロッカーに預けた.駅のロッカーは全く空いてなかったけど,こっちはかなり空いていた.意外と知られてないのかも.

案内には山頂まで一時間と書いてあるけど,休まず歩いて 30 分くらい.思ったより近い.二回目はお参りしつつ,寄り道しつつだったので時間がかかった.登り始めたのが 11 時半,山頂に着いたのが 13 時半,本殿に戻ってきたのが 15 時で三時間半くらい.途中でご飯を食べたりお参りしたりで歩いたのは二時間くらいだと思う.

ところどころ寄り道するのが楽しい.力松社近くからいける清瀧はすごい.行ってよかった.ジブリに出てきそう.ただ帰りが登りできつい.
f:id:calmery:20170225034614j:plain
f:id:calmery:20170225012858j:plain
四ツ辻からは道が二つに分かれていた.どちらに行っても山頂に行くことはできる.だけどここは登ってきた方から見て右側の道に進んだ方が良さそう.まっすぐ行くと緩やかで楽かと思いきや最後にトドメを刺しにくる.鬼畜.ここを登るのは相当きつそう.

Post from RICOH THETA. - Spherical Image - RICOH THETA
楽しかった.また秋くらいに行きたい.春は花粉症で夏は暑い,冬は寒いし.秋くらいがちょうどいい.まあ人が多そうだけど.

伏見稲荷大社とは全く関係がないのだけど嵐山の トップページ|京都嵐山 鯛茶漬け専門店-鯛匠HANANA- で食べることができる鯛茶漬けがおいしい.本当においしい.お店が開く 30 分前に行ったからか待たずに食べることができた.あんまり遅いと待つことになるので早めに.というか予約しておけばいい.
f:id:calmery:20170225021237j:plain
友達と.わーい!すごーい!おいしー!

Post from RICOH THETA. - Spherical Image - RICOH THETA