/

問題文

社内のラボ環境でVyOSにサーバ、クライアントを接続し、相互にIPv6で通信できる環境を構築していた。
ある日VyOSの再起動を行ったところ、設定ファイルの保存を行っていなかったためVyOSの設定が消えてしまった。
 
VyOSを以下の要件に従って設定を行いクライアント-サーバ間で通信ができるように復旧し、管理者用アカウントを追加して欲しい。
また、今後同様の事象が発生しないように対策を講じること。

条件

  • eth0は踏み台接続用のインタフェースなので設定を変更してはならない
  • タイムゾーンはJSTに設定すること
  • VyOSに以下のアカウントを新しく追加し、デフォルトのアカウント (vyos ユーザ) は削除すること
    • ユーザー名: ictsc2019
    • パスワード: ictsc2019

ゴール

  • 上記の要件に従って設定を行いクライアント-サーバ間で通信ができる
  • 再起動をしても設定が消えないようにする

情報

VyOS

  • IPアドレス: 192.168.0.1
  • ユーザー名: vyos
  • パスワード: vyos

クライアント

  • IPアドレス: 192.168.0.2
  • ユーザー名: admin
  • パスワード: admin

補足

  • クライアントは、RAにより 2001:db8:2000::/64 のプレフィックスのアドレスが割り振られる
  • サーバは、2001:db8:1000::2/64 がインターフェースに割り振られており、ゲートウェイとして 2001:db8:1000::1 が指定されている

解説・解答例

条件・ゴール通りに一つずつ設定を行っていきましょう。

設定の投入方法

VyOSにアクセスした後、設定を変更可能なモードに移行するにはconfigureコマンドを実行しましょう。設定変更に関するコマンドはコンフィグレーションモードで行います。

$ configure
[edit]
#

eth1とeth2に適切な設定を投入

クライアントとサーバ側のインターフェースに適切な設定を投入しましょう。
トポロジ図を参照すると、クライアント側のインターフェースはeth1、サーバ側はeth2だという事が分かります。

eth0の設定

補足情報からクライアントには2001:db8:2000::/64のアドレスがRAで配布される事が分かります。VyOSのeth1にRAの設定を投入します。なお、インタフェースに割り当てるアドレスは2001:db8:2000::/64内のアドレスであれば、どんなアドレスにしても問題ありません。ここではEUI-64で自動生成しています。
以下のコマンドで設定します。

# set interfaces ethernet eth1 ipv6 address eui64 2001:db8:2000::/64
# set interfaces ethernet eth1 ipv6 router-advert send-advert true
# set interfaces ethernet eth1 ipv6 router-advert prefix 2001:db8:2000::/64
# commit

eth1の設定

補足情報からサーバのインタフェースは2001:db8:1000::2/64が静的に割り振られており、ゲートウェイとして2001:db8:1000::1が設定されていると分かります。
VyOSのeth2に2001:db8:1000::1/64を静的に設定しましょう。

# set interfaces ethernet eth2 address 2001:db8:1000::1/64
# commit

アカウントの追加と削除

デフォルトのvyos:vyosを削除し、管理用にictsc2019:ictsc2019のアカウントを追加します。
ログイン状態にあるアカウントを削除する事はできないので、先にアカウントの追加を行います。

# set system login user ictsc2019 authentication plaintext-password ictsc2019
# set system login user ictsc2019 level admin
# commit

一旦ログアウトして、ictsc2019ユーザでSSH接続します。
ログインできたらvyosユーザを削除して完了です。

# delete system login user vyos
# commit

タイムゾーンの設定

これはコマンド一発です。

# set system time-zone Asia/Tokyo
# commit

設定の保存

ここまで設定を終えて、クライアントからサーバへの疎通が取れる事を確認したら、設定を保存しておきましょう。
これもコマンド一発です。

# save
 /

問題名  

監視できない!

問題文  

あなたは新しくkubernetesのクラスタを構築しました。そして、クラスタの情報を取得するためにnotifierというアプリケーションを作りました。このアプリケーションは自身がデプロイされたクラスタのPodに関するイベントを外部のサーバにwebhookする機能を持っています。

さて、そのアプリケーションをデプロイしてRunningと表示されるのですが、なぜかイベントを見ることが出来ません。なんとかしてイベントを見れるようにしてください。

条件  

  1. notifierのイメージを変更してはいけない。
  2. notifierはkubernetesの内部に設置する必要がある。
  3. default namespaceに別のPodをデプロイする際に、デフォルトで権限を与えすぎないようにする必要がある。

ゴール  

miscサーバでlocalhostにcurlすると、最新のイベントが確認できる。

各マシンの説明  

k8s  

kubernetesが動作しているマシン。kubernetesの上ではnotifierが動作している。また、/manifestsにはnotifierをデプロイする際に使用したマニフェストが保存されている。

  • IPアドレス: 192.168.0.1
  • ユーザ名: admin
  • パスワード: kkkkkkkks

misc  

Docker Registryやwebappなどが動作しているマシン。

  • IPアドレス: 192.168.0.201
  • ユーザ名: admin
  • パスワード: kkkkkkkks

各アプリケーションの説明  

notifier  

kubernetes上で動作しているアプリケーション。kubernetesのPodリソースを監視し、作成, 変更, 削除があった場合には http://misc/webhook にイベント情報を飛ばす。

webapp  

misc上で動作しているWebhookを受け取るアプリケーション。notifierから受け取った最新10件のイベント情報を http://misc で公開している。

解説  

踏み台サーバを経由してmiscサーバに入りcurl http://miscを実行してみると、確かにログが1件も見えていないことが分かります。

その次に、k8sサーバに入りkubectl get podskubectl logs ${POD_NAME}等のコマンドでnotifierのログを見てみます。

条件にもある通り、default namespaceにデプロイされるコンテナに不必要な権限が与えられてしまってはいけないので、まず初めにnotifier用のService Accountを作成します。

notifierが新しく作成したService Accountを使用するようにDeploymentにも変更を加えます。

次に、このService Accountに対し、notifierで必要とされる権限を付与していきます。何度かlogを見ながら試行錯誤していくと、podsに対するlist権限とwatch権限が必要だと分かるので、その2つをnotifierに付与します

最後に想定回答を示します。

 /

問題文

WEBサーバを運用する部署に所属することになったあなたは、前任者が構築したサーバを引き継ぐことになりました。現在の状態、発生した問題などについて、前任者の報告がありました。

LXCを使用してWEBサーバを構築しました。
コンテナではapacheが1234/tcpで起動しています。
LXCホストのiptablesでホスト80/tcpへの通信をコンテナ1234/tcpへ転送するDNATの設定をしました。
VNCサーバからLXCホストの80/tcpへアクセスすると無事にWEBサーバの起動を確認できました。
しかし、システムのアップデートをしようと思ったらコンテナからaptコマンドを使用できなくなりました。

どうやら、担当者は少々無理をしてコンテナのWEBサーバを外部に公開したようです。原因の究明と適切な設定を施してほしいです。設定は永続化し、再起動などが行えるようにしてください。

ゴール  

  • 外部(VNCサーバ)からLXCホスト80番でWEBページが確認できる(開始時点で閲覧可能だが、トラブルシューティング後も見られるようにすること)
  • コンテナ内で apt update ができる
  • 設定の永続化が行われている

問題サーバー  

  • IPアドレス: 192.168.0.1
  • ユーザー名: admin
  • パスワード: 3tRC7llZ
  • サービス名:dnat.service
  • コンテナ名:ubuntu-dnat

原因究明

iptablesのPREROUTINGチェインでDNATしているため、コンテナから外に出るときにもこのルールが適用されてしまう。

解説

現状の確認

初期状態だと、VNCサーバからLXCホスト80番でWEBページが確認できるのでまず確認してみる。

wget -O - 192.168.0.1

問題なくWEBページが公開できている。次に、問題サーバへ接続しスクリプトファイルを確認する。

#!/bin/bash
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.176.76.106:1234

どうやらsystemdを使ってiptablesを永続化させている。最後に発生している問題を確認する。

[email protected]:~$ sudo lxc exec ubuntu-dnat /bin/bash
[sudo] password for admin: 
[email protected]:~# apt update
Err:1 http://archive.ubuntu.com/ubuntu bionic InRelease                                                                    
  Cannot initiate the connection to archive.ubuntu.com:80 (2001:67c:1360:8001::21). - connect (101: Network is unreachable) Cannot initiate the connection to archive.ubuntu.com:80 (2001:67c:1560:8001::11). - connect (101: Network is unreachable) Cannot initiate the connection to archive.ubuntu.com:80 (2001:67c:1560:8001::14). - connect (101: Network is unreachable) Cannot initiate the connection to archive.ubuntu.com:80 (2001:67c:1360:8001::17). - connect (101: Network is unreachable) Could not connect to archive.ubuntu.com:80 (91.189.88.24), connection timed out Could not connect to archive.ubuntu.com:80 (91.189.88.31), connection timed out Could not connect to archive.ubuntu.com:80 (91.189.88.149), connection timed out Could not connect to archive.ubuntu.com:80 (91.189.88.162), connection timed out
Err:2 http://archive.ubuntu.com/ubuntu bionic-updates InRelease        

名前解決はできるが、HTTP接続に問題があるようだ。

解決方法

下記の2つ、どちらかのルールに変更すれば、コンテナから外に出る80/tcpがルールが掛からなくなります。これでapt updateが正常に実行できます。

  • 公開しているネットワークのインターフェイス(th0)を指定する
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.176.76.106:1234
  • destinationを指定してLXCホスト宛にのみDNATを行う
iptables -t nat -A PREROUTING -p tcp -d 192.168.0.1/24 --dport 80 -j DNAT --to-destination 10.176.76.106:1234

ここまでで、9割の解答になります。

適切な方法

上記の方法で問題は解決できますが、適切であるとは言えません。問題文にある無理やりを解決する方法は以下の2つがあげれます。

  • lxc configを使用する
  • iptables-persistentを使用する
  • nginxを使用する(想定外)

lxc configを使用した方法

そもそもLXCにはコンテナを外部に公開するコマンドが用意されています。この問題のタイトル郷に入っては郷に従えはこの解法から決めました。

サービスの停止

sudo systemctl stop dnat.service
sudo systemctl disable dnat.service

lxc config でproxyを設定

sudo lxc config device add ubuntu-dnat http proxy listen=tcp:0.0.0.0:80 connect=tcp:10.176.76.106:1234 bind=host

iptables-persistentを使用した方法

本問題環境(ubuntu18)ではiptablesを永続化することはデフォルトではできません。そこでsystemdを使用して起動するたびにコマンドを実行していました。この点を無理やりと考えるとiptables-persistentを使用するのが適切です。

サービスを停止させます。

sudo systemctl stop dnat.service
sudo systemctl disable dnat.service

解決方法のルールを適応し、問題が解決している状態でiptables-persistentをインストールするとその状態で永続化されます。

sudo apt-get install iptables-persistent

nginxを使用した方法

あるチームが回答してくれた方法にnginxのリバースプロキシを使用する解法がありました。WEBを公開する手法として適切であると判断し、模範解答に追加しました。

[email protected]:~$ cat /etc/nginx/conf.d/ictsc.conf 
server{
    proxy_set_header    Host    $host;
    proxy_set_header    X-Real-IP    $remote_addr;
    proxy_set_header    X-Forwarded-Host       $host;
    proxy_set_header    X-Forwarded-Server    $host;
    proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;

    location / {
        proxy_pass    http://10.176.76.106:1234;

    }
}

採点基準

  • apt get が使用できる状態になっており、回答にコマンドと説明が記述されている :140点
  • 原因を究明し、回答に記述されている :40点
  • 適切な手法で解決し、回答に記述されている :20点
 /

問題文

あなたはICTSC専門学校の3学生です。 新入生向けに学内向けCTFを開催するにあたってアプリ開発チームとサーバ構築チームに分かれてCTFサーバの構築を行うことになりました。サーバ構築チームはあなたの後輩のA君が担当、あなたは両方を担当しています。当初はサークルで余っていたラックサーバ上のVMで展開する予定でしたがA君が突然「VMじゃなくてDockerでやりたいです」と言い出しました。 A君は最近Dockerを触り始めたばかりなのですが、サーバの構成自体は簡単なので大丈夫だろうと判断し任せることになりました。 アプリ開発が終わり、A君に展開に必要なファイルを渡してDocker上で展開ししてもらったところコンパイルがうまくいかないと言われました。 これ以上時間をかけてしまうと後のスケジュールに影響してしまうため、A君に代わりあなたがトラブルシュートを行うことにします。 原因を特定しスコアボードが正常に表示されるようにDockerfileを修正してください。

条件

  • スコアボードアプリケーションのソースコード及びdocker-compose.yamlを変更してはいけない
  • MySQL及びRedisの構成ファイルを変更してはいけない
  • 必ずアプリケーションのコンパイル・実行はコンテナ上で行い、原則ホストOSでパッケージをインストールしてはいけない
  • コンテナで使用しているイメージは変更してはいけない
  • 3つのDockerfileから構成されるようにすること

ゴール

問題の原因を特定して http://192.168.0.1/index にアクセスしたときスコアボードが正常に表示されるように修正する。

情報

問題サーバー

  • IPアドレス: 192.168.0.1
  • ユーザー: admin
  • パスワード: 248b1fa8f258

解説

踏み台サーバを経由し問題サーバ上でdocker-compose buildしてみると以下のようなエラーで終了することがわかります。

... Step 4/8 : RUN go build -tags netgo -o webapp main.go ---> Running in 566031a5ac14 main.go:9:2: cannot find package "github.com/garyburd/redigo/redis" in any of: /usr/local/go/src/github.com/garyburd/redigo/redis (from $GOROOT) /go/src/github.com/garyburd/redigo/redis (from $GOPATH) main.go:4:2: cannot find package "github.com/gin-gonic/gin" in any of: /usr/local/go/src/github.com/gin-gonic/gin (from $GOROOT) /go/src/github.com/gin-gonic/gin (from $GOPATH) main.go:8:2: cannot find package "github.com/go-gorp/gorp" in any of: /usr/local/go/src/github.com/go-gorp/gorp (from $GOROOT) /go/src/github.com/go-gorp/gorp (from $GOPATH) main.go:10:2: cannot find package "github.com/go-sql-driver/mysql" in any of: /usr/local/go/src/github.com/go-sql-driver/mysql (from $GOROOT) /go/src/github.com/go-sql-driver/mysql (from $GOPATH) ERROR: Service 'webapp' failed to build: The command '/bin/sh -c go build -tags netgo -o webapp main.go' returned a non-zero code: 1

このことから、アプリケーションのビルドでエラーが発生しその原因は必要なパッケージが存在していないということがわかります。 この問題の解決法はいくつかあるためあくまで一例ですが以下のようにwebapp/Dockerfileを修正することで解決できます。

FROM golang:alpine ADD ./Score /work WORKDIR /work RUN apk -U add git RUN go get -u github.com/go-sql-driver/mysql && \ go get -u github.com/go-gorp/gorp && \ go get -u github.com/gin-gonic/gin && \ go get -u github.com/garyburd/redigo/redis && \ go build -tags netgo -o webapp main.go FROM busybox ENV GOPATH=/usr/local/bin/ COPY --from=builder /work/exec-webapp.sh /usr/local/bin/ ENTRYPOINT ["/usr/local/bin/exec-webapp.sh", "/usr/local/bin/webapp"]

これでアプリケーションのビルドは成功しましたが、
... Removing intermediate container c07108921a48 ---> ac82f0d3b17a Step 8/9 : COPY --from=builder /work/exec-webapp.sh /usr/local/bin/ ERROR: Service 'webapp' failed to build: invalid from flag value builder: pull access denied for builder, repository does not exist or may require 'docker login`
とあるように、COPY命令でビルドしたアプリケーションをbuilderというコンテナからコピーしようとしてもコンテナが見つからず失敗してしまいます。
このbuilderというコンテナは本来先ほどアプリケーションを指しているべきなのですが、その設定が抜けていたために起きていました。
このような構成になっているのはDocker MultiStage Buildという技術を利用しているためで、こうすることで一つのDockerfileでビルドと実行を行うコンテナを同時に展開することができ、実際に実行するコンテナサイズを最小限に抑えることができます。
よって、このエラーを解決するためにはAS builderをアプリケーションをビルドするコンテナに設定します。
また、COPY命令の引数を見てみるとコピーしているのは/work/exec-webapp.shというシェルスクリプトのみで肝心のアプリケーションやその他の必要なファイルが一切含まれていないこと、シェルスクリプトに実行権限が与えられていないことがわかります。
以上を踏まえて修正を行うと以下のようになります。

FROM golang:alpine AS builder ADD ./Score /work WORKDIR /work RUN apk -U add git RUN go get -u github.com/go-sql-driver/mysql && \ go get -u github.com/go-gorp/gorp && \ go get -u github.com/gin-gonic/gin && \ go get -u github.com/garyburd/redigo/redis && \ go build -tags netgo -o webapp main.go FROM busybox ENV GOPATH=/usr/local/bin/ COPY --from=builder /work /usr/local/bin/ RUN chmod +x /usr/local/bin/exec-webapp.sh ENTRYPOINT ["/usr/local/bin/exec-webapp.sh", "/usr/local/bin/webapp"]

コンテナが起動しhttp://[問題サーバIP]:8080/indexでWebAppが起動していれば成功です。

解答Dockerfile一例

FROM golang:alpine AS builder ADD ./Score /work WORKDIR /work RUN apk -U add git RUN go get -u github.com/go-sql-driver/mysql && \ go get -u github.com/go-gorp/gorp && \ go get -u github.com/gin-gonic/gin && \ go get -u github.com/garyburd/redigo/redis && \ go build -tags netgo -o webapp main.go FROM busybox ENV GOPATH=/usr/local/bin/ COPY --from=builder /work /usr/local/bin/ RUN chmod +x /usr/local/bin/exec-webapp.sh ENTRYPOINT ["/usr/local/bin/exec-webapp.sh", "/usr/local/bin/webapp"]

採点基準

Golangの依存関係の解消 -> 50
中間イメージへの名前指定 -> 50
COPYのsource側ディレクトリ修正にてWebAppが起動 -> 100

合計200点