これは何?

ICTSC2021 夏の陣のためにお茶大生5人で勉強した時の記録です。

注意事項

  • このサイトで紹介している問題は、ICTSC tech blog から参照しており、私たちや弊サイトに著作権はありません。
  • このサイトの内容を読んでバカにする/誹謗中傷等のことはやめてください。
  • このサイトの内容の正当性は保証していません。参考にし何か問題が起きたとしても自己責任でお願いします。
  • 一部未完成のページが存在します。ご了承ください。
  • 著作権等で問題がありましたら、HunachiのDMにまでご連絡していただけますと幸いです。

この記事の元になっているリポジトリ

https://github.com/Hunachi/ictsc-418

参加チーム名

Status code 418s

チームメンバー(アルファベット順)

  • Hunachi Twitter, GitHub
    • 主な担当: 雑多なやつ(Program,Linux,データベース)
  • maimai-y
    • 主な担当: Web系(Web,FTP)
  • momom-i
    • 主な担当: DNS系(DNS,IPv4,v6,メール)
  • nonnonno
    • 主な担当: ネットワーク系(ルーティング,Tunnel)
  • とり
    • 主な担当: Container系(LoadBalancer,k8s,Container)

何か問題があった場合の連絡先

Hunachi

本番の結果!!

順位

9位でした🎉 順位

感想ブログ

ICTSC2021に向けた勉強会スケジュール

書いた人:Hunachi

*第1回勉強会*

  • 開催日時:6/21 (月) 21:30~23:00
  • 内容:dockerについて調べて触ってみる:whale:(数回同じ内容の可能性あり)
  • 詳しい学習内容:Docker勉強会の第一回
  • その他:任意参加

*第2回勉強会*

  • 開催日時:7/5 (月) 21:30~23:00
  • 内容:予選の過去問を見ながら、ルーティング(ネットワーク)とコンテナ(Docker)について知識をつける。
  • 詳しい学習内容:過去問勉強会の第一回
  • その他:任意参加

*第3回以降の勉強会*

  • 開催日時:毎週月曜日 21:30~23:00
  • 内容:それぞれが解いてきた過去問についてしゃべってもらいます!
  • 宿題:自分の担当分野の過去問1問以上を解く。問題のサイトの解説より詳しい解説を書くようにする。
  • その他:任意参加

過去問勉強会

第一回

内容

第二回以降

  • 解いてきた過去問について解説する。

参照した問題・解説のサイト: https://blog.icttoracon.net/2020/03/01/

生き返れMariaDB

解いた人:とり

参照した問題・解説のサイト:生き返れMariaDB

使用環境・ツール

  • docker
  • MariaDB

問題文

  • MariaDBのコンテナがすぐに落ちる
  • VM上でdocker ps -aをするとmariaDBコンテナが1つ存在している
  • VM上でdocker start mariaDB→docker exec -it mariaDB bashをしても落ちて開けない

理想の終了状態

  • コンテナが継続して起動している状態である
  • docker exec -it mariaDB bashでコンテナに入ることができ、入った際にMariaDBのDBのictsc_incのusersテーブルを見たとき
+----+------------+-----------+------+-------+
| ID | First_Name | Last_Name | Age  | Sex   |
+----+------------+-----------+------+-------+
|  1 | Tarou      | Yamada    |   23 | MAN   |
|  2 | Emi        | Uchiyama  |   23 | WOMAN |
|  3 | Ryo        | Sato      |   25 | MAN   |
|  4 | Yuki       | Tayama    |   22 | WOMAN |
|  5 | Yuto       | Takahashi |   21 | MAN   |
+----+------------+-----------+------+-------+
  • 社内システムがDBを参照出来るよう、3306番ポートが開いている。

配点

  • このコンテナが不安定になっている原因を明確な証拠をもとに正しく特定できている(60%)
  • コンテナが継続動作するように復旧できている(25%)
  • 当該コンテナ上で問題文通りのレコードを参照できる(15%)

考えられる検証、修正手順

なんとなく、定期的に落ちるならメモリ不足かなと思った

バグの原因を特定する案

  • ログを確認する

    • docker logsでログ情報を確認する(確認できるのかな)
    • docker mysql exited with code 137 こんな感じで出ていたらmysqlが落ちていることがわかる
      • mysqlじゃないけど
  • $ docker stats してみる

こんな流れでメモリ不足が出る

  1. dockerでメモリを過剰に使用
  2. mysqlのdockerがメモリ不足で落ちる
  3. ホストのoom-killerも動き、何故かmysqlが狙われてkillされる

メモリの割り当て

docker-machine inspect

以下のコマンドでメモリの割り当てをする

docker run -m 1024m hoge /bin/bash

https://qiita.com/niisan-tokyo/items/2d7d21aeb4e25f7a7bbe

oom-killerのkillを阻止する

こんな感じでkillの確認ができる

$ sudo cat /var/log/messages | grep Killed
Oct  1 11:11:54 ip-xx-xx-xx-xx kernel: [1983378.957901] Killed process 5789 (ruby) total-vm:4957320kB, anon-rss:2717004kB, file-rss:0kB

危なそうなプロセスの確認

$ dstat --top-oom
--out-of-memory---
    kill score
mysqld        484
mysqld        484
mysqld        484

修正は/proc/PID/oom_adjに(優先度低)-16から+15(優先度高)の値を設定することができる


解説

原因の特定方法

  1. docker inspectコマンドを使用する
    docker inspectコマンドは、dockerコンテナの設定情報を参照することが出来るコマンドです。これを見ると、コンテナがどのような設定で動作しているのかが分かります。しかし、この設定データの量は膨大で、全てを見るには時間がかかってしまいます。 例えばですが、この問題の原因のメモリについて調べたい際、など、grepコマンドなどを使い検索すると、
$ docker inspect mariaDB | grep Memory
"Memory": 4194304,
           "KernelMemory": 0,
           "MemoryReservation": 0,
           "MemorySwap": -1,
           "MemorySwappiness": null,

といった値が出てきます。ここに出た"Memory": 4194304という値はバイト表記でして、MBに換算すると約4MBということが分かります。設定されていた値と同じですね。

  1. docker statsコマンドを使用する
    docker statsコマンドは、コンテナのリソース使用状況を表示するコマンドです。Linuxのtopコマンドに似たような機能を持っています。 ここで、コンテナのリソース使用状況を知ることが出来ます。
$ docker stats
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
eb99786557f7        mariaDB             8.44%               3.664MiB / 4MiB     91.60%              656B / 0B           1.89GB / 0B         3

こちらでもMEM USAGE / LIMITの欄で3.664MiB / 4MiBと見えており、メモリが4MB制限であること、そして使用中のメモリが逼迫している状態であることから不安定になる原因であることが推定出来ます。

解決手順

2パターン。

  1. docker updateコマンドを使用しリソース設定を更新する docker updateコマンドはコンテナの設定を更新するためのコマンドです。 コンテナの設定をコンテナを作り直すことなく変更することが可能です。 例として、問題のコンテナのメモリ制限を4MBから1GBへと緩和します。例えば、 docker update --memory 1G mariaDB このコマンドを実行する事により、メモリ上限を4MBから1GBに変更することが出来ます。
$ docker inspect mariaDB | grep Memory
"Memory": 1073741824,
"KernelMemory": 0,
"MemoryReservation": 0,
"MemorySwap": -1,
"MemorySwappiness": null,

となり、Memory": 1073741824(byte)は約1GBですので、1GBへと緩和されたことが分かります

  1. 同じ設定のコンテナを作り直す

解法の一つとして、同じ設定のMariaDBコンテナを再作成する方法があります。 docker run -v mariaVOL:/var/lib/mysql -d --name mariaDB -e MYSQL_ROOT_PASSWORD=MariaPass -p 3306:3306 -d mariadb:latest などで、メモリ制限を無くしたコンテナを作成します。 ただし、MariaDBのデータベースはボリュームmariaVOLにマウントされているという点に注意しなければなりません。mariaVOLへコンテナをマウントしないと、コンテナに入ってもデータベースを参照することが出来なくなってしまいます。

上記の2つの設定のどちらかを適用し、

$ docker exec -it mariaDB bash
$ mysql -u root -p ${MYSQL_ROOT_PASSWORD}

> use ictsc_inc;
> select * from users;
// selectの結果が表示される

コメント

$ docker stats使う時は、一秒ずつ表示が増えてウザいので、$ docker stats --no-streamとするといいらしい。

v4v6 移行が終わらない

解いた人:momom-i

参照した問題・解説のサイト:v4v6 移行が終わらない

問題

おお久しぶり!!

君が居ないあいだに社内ネットワークを IPv6 only にしておいたんだ。外向きの IP アドレスは v4 しかないけどルーターで NAT64 をしているからインターネットにはつながるようになっているよ。

ただ名前解決ができないのと社内ネットワークにある Web に繋がらなくなっちゃったんだ、これ以上は手が付かないから君がなんとかしてくれないかな?

初期状態

  • ubuntu-1 から curl https://blog.icttoracon.net/ をしてもつながらない
  • ubuntu-1 から curl http://[2403:bd80:c000:900::1]/ をしてもつながらない

終了状態

  • ubuntu-1 から curl https://blog.icttoracon.net/ をするとステータスコード 200 のレスポンスが返ってくる。
  • ubuntu-1 から curl http://[2403:bd80:c000:900::1]/ をするとステータスコード 200 のレスポンスが返ってくる。

配点

  • ubuntu-1 から curl https://blog.icttoracon.net/ をするとステータスコード 200 のレスポンスが返ってくる 80%
  • ubuntu-1 から curl http://[2403:bd80:c000:900::1]/ をするとステータスコード 200 のレスポンスが返ってくる。 20%

問題文でされた操作

  1. 社内ネットワークを IPv6 only にした
  2. 外向き IP アドレスは v4 しかないが、ルーターで NAT64 をしている

https://blog.icttoracon.net/が返らない原因

プリフィックスのある v6 アドレスを DNS で生成されていない

まずは、DNS で IPv6 アドレスを問い合わせてみる

dig -t AAAA blog.icttoracon.net +short

NAT64、DNS64 そして通信に介在するネットワーク機器は同じ IPv4-IPv6 変換アドレス用のプリフィクスを使用するよう設定されている必要があります。(参照)

今回は外向き IP アドレスは v4 のみなのでプリフィックスのある v6 アドレスが返されないといけない。

https://[2403:bd80:c000:900::1]/が返らない原因

ping6 2403:bd80:c000:900::1をしてみる。もし疎通性がなければ、パケットフィルタリングが問題。もし疎通性があれば、前回と同様で web サーバが IPv6 で受け付けてないとか nginx だったら config の不備が問題。

考えられる解決方法

DNS64 に対応させる

BIND の場合、BIND のバージョンが 9.8 以降か確認し、/etc/bind/named.conf に以下のような記述 DNS64 の設定があるか確認する

options {
    # IPv4は32bitなので96bit付け足してIPv6に対応させる
    dns64 64:ff9b::/96 {
    #     クライアントを指定できるが、anyで良い気がする
        clients { any; };
    };
};

service named restartをして再起動

UNBOUND の場合は、/etc/unbound/unbound.conf に

server:
    # dns64をつければ後の文字はなんでも良いっぽい
    module-config: "dns64 validator iterator"
    dns64-prefix: 64:ff9b::/96

を設定して、unbound -c unbound.confで設定を読み込み

※参照サイト:
1.オライリー DNS64 ページ
2.unbound 公式 DNS64 ドキュメント

Nginx の config を修正

/etc/nginx/nginx.conf のlisten 80;の下に以下のような記述があるか確認。

# IPv6のポート80でリッスンするよ〜という記述
listen [::]:80;

パケットフィルタリング

ip6tables という IPv6 の iptables コマンドで確認ができるらしい!(参照)

ip6tables -L

アプリケーションに必要なポートを web サーバ側で許可する

# TCP80番ポートのアクセス許可
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT

※参考※


解説

unbound と Apache2 の設定が適切になされていないことで起こる問題です。

unbound

dns64-prefixubuntu-routerJOOL の設定を参照する必要があります。これを確認すると dns64-prefix64:ff9b::/96 で有ることがわかります。(jool global displayコマンドのpool6で prefix が確認できそう(参照))

また、blog.icttoracon.net ではデュアルスタック方式を採用しているおり dns64-synthall が no に設定されていると、blog.icttoracon.net にもともと設定されている IPv6 アドレスが AAAA レコードとして名前解決されるため、 ubuntu-1 からアクセスすることができなくなってしまいます。(元から AAAA レコードに何かアドレスが入っていたっぽいな。dns64-synthall のオンオフによる挙動はここら辺に載っている)

さらに、この dns-1 のサーバーは NAT64 ネットワーク内に設置されているために forward-addr が 8.8.8.8 になっていると通信が行えません。forward-addr: 64:ff9b::808:808 に変更する必要があります。(そうだったのか^^;;)

問題が発生している原因は 2 つ存在している。1 つ目は、unbound に DNS64 の設定が正しくされていない点である。これを解決するために /etc/unbound/unbound.conf.d/dns.conf を以下のように変更する。(公式の example.conf 参照)

server:
# ログレベル
  verbosity: 2
#   pidfileの場所指定
  pidfile: "/var/run/unbound.pid"
#   ログを出すかどうか
  use-syslog: yes
#   さっき書いた通りでdns64を使用する場合はかく
  module-config: "dns64 iterator"
#   prefixを指定する
  dns64-prefix: 64:ff9b::/96
#   yesにするとDNS64やNAT64を通るようになる
  dns64-synthall: yes
  interface: ::0
  access-control: ::0/0 allow

forward-zone:
# `.`にすると全て転送される
  name: "."
#   8.8.8.8のプレフィックス付きIPv6アドレス。
  forward-addr: 64:ff9b::808:808

これにより https://blog.icttoracon.net/ にアクセスできるようになる。

2 つ目の原因は ubuntu-router の Web (Apache2) の Listen Address が適切に設定されていないことにある。これを解決するために以下の一行を config に追記する。

Listen [::0]:80

※参考※

適当に俳句投稿サービス作ったらXSRF脆弱性孕んでた件。

解いた人:maimai-y

参照した問題・解説のサイト:適当に俳句投稿サービス作ったらXSRF脆弱性孕んでた件。

問題文

俳句投稿サービスHikerを作成した。Hikerでは、ユーザー作成後ログインして俳句を詠むことができる。詠んだ俳句は公開される。また、他のユーザーが詠んだ俳句に対してmogamigawaする機能(所謂お気に入り機能)がある。

ユーザーからのフィードバックで、意図しない俳句がmogamigawaされており困っているという情報が複数あった。それらのユーザーは共通して特定のWebサイトを閲覧したようである。 以上のことからHikerはXSRF脆弱性を孕んでいることが予想される。これらのユーザーはこの脆弱性を利用して、意図しない俳句をmogamigawaさせられたと考えられる。

任意の方法でこの脆弱性に対して対策を施してMerge Requestを建ててほしい。

使用環境・ツール

  • サーバーサイド: Golang, Gin // GinはGo言語の人気webフレームワーク
  • フロントエンド: multitemplate + Bootstrap4
  • データベース: MySQL + GORM
  • docker-compose

初期状態

  • 各チームのVMはHikeのステージング環境である。remoteRepositoryは削除されているので、各自でfork先urlを設定すること。
    • url: https://gitlab.com/ictsc2019-teamチーム番号/ictsc2019-f21-xsrf
    • urlのチーム番号部分には1,2,3,…,15のような自分のチーム番号を代入すること
  • 既にictscというユーザーが俳句を投稿している
  • Hikeで各自アカウントを新規作成後、ログインし https://hackmd.io/tRks_vT-QjasH9hUsEJ-BA?view にアクセスするとictscというユーザーの俳句をmogamigawaしてしまう
  • ソースコードはGitLabで管理されており、問題解答開始時にチームリーダーにOWNER権限のinviteのメールが送信される
  • Hikerが動いているときは、http://192.168.15.1:8080 でサービスにアクセスできる

終了状態

  • 適切なXSRF対策がされている
  • 初期状態に示されているURL上の検証コードはあくまで一例であることに注意すること
  • 修正されたソースコードのMerge RequestをGitLab上で作成する // Merge RequestはGitHubでいうPull Request
  • スコアサーバに、Merge RequestのURLを提出する

解決方法

CSRFとは

CSRF ... Cross-Site Request Forgeries/クロスサイト・リクエスト・フォージェリ(偽サイトを使ってリクエストを偽造する)

https://medium-company.com/%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%AA/

他の参考サイト:

https://www.ipa.go.jp/security/vuln/websecurity-HTML-1_6.html

https://qiita.com/wanko5296/items/142b5b82485b0196a2da#csrf%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B

解決の流れ

CSRFへの代表的な対策

Formページ返却時のトークン付与 今回の例でいうと、はじめに掲示板への書き込み画面を表示する際にサーバがクライアントに対して特定の文字列(トークン)を設定します。実際に書き込みのリクエストがあった際にサーバーが**「この人に送ったトークンと同じトークンがリクエストに入ってる?」**と確認することで、攻撃者からの不正なリクエストを防ぐことができます。これは、攻撃者は利用者に送信したトークンの値を知らないためです。

  1. mogamigawaする画面を要求されたら、暗号論的擬似乱数生成器を用いて機密情報を作るようにする
  2. 機密情報も入れてmogamigawaする画面を返すようにする
  3. mogamigawaするときに、hiddenタグで機密情報も送るようにする
  4. セクションIDと機密情報があっているか確認するようにする

解説

ハッカソン的なイベントでよく適当にwebサービスを作ると思います。作りますね。そんなときのあるあるですが、割とWebのセキュリティを考えずにデプロイして成果発表みたいなノリです。良くないですね。そんな問題でした。

XSRF脆弱性の対策をします。様々な手法が考えられますが、今回はWeb Application Framework(WAF)にGinを採用しているので、utrack/gin-csrfを使って対策するケースで解説します。 utrack/gin-csrfで実装する理由は、問題環境はステージング環境でありdocker-composeで管理されていることから、様々な場所で実行されることを考慮して、utrack/gin-csrfのような環境に依存しにくい実装をしたいためです。

リモートリポジトリを追加する

ステージング環境にデプロイされているソースコードのディレクトリに移動してリモートリポジトリを追加します。

$ git remote add origin https://gitlab.com/ictsc2019-teamチーム番号/ictsc2019-f21-xsrf

この問題では解答にMerge Requestを建てなければいけないので、cloneしたときかこのタイミングでbranchを切ります。

$ git checkout -b xsrf-fix
$ git push --set-upstream origin xsrf-fix   // これを書くと毎回origin xsrf-fixの部分を書かなくて良くなるっぽい

あとはcommit~~と徳~~を積んでMerge Requestをします。

server.go の修正

utrack/gin-csrfのREADME.mdを参考に頑張ります。 // https://github.com/utrack/gin-csrf#readme 変更点は次の通りです。

import部分の追記

  • "net/http"を追加しました。csrfのErrorFuncでhttp.StatusBadRequestを使用するためです。
  • "github.com/utrack/gin-csrf"を追加しました。gin-csrfを使います。
  • "ictsc2019-f21-xsrf/util"を追加しました。csrfのSecretをランダムな文字列にする関数をapp/util/util.goに追記して、それを使用するためです。
import (
    "log"
    "net/http"
    "path/filepath"
 
    "github.com/gin-contrib/multitemplate"
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/gin"
    csrf "github.com/utrack/gin-csrf"
 
    "ictsc2019-f21-xsrf/domain"
    "ictsc2019-f21-xsrf/handler"
    "ictsc2019-f21-xsrf/util"
)

func main部分の変更

以下の記述を追加します。utrack/gin-csrfのREADME.mdの通りです。

// csrf
r.Use(csrf.Middleware(csrf.Options{
    Secret: util.RandString(10),
    ErrorFunc: func(c *gin.Context) {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "CSRF token mismatch",
        })
        c.Abort()
    },
}))

そして、mogamigawaするAPIをPOSTに変更します。

apiRouter.POST("/mogamigawa", handler.NewMogamigawa)

以上がserver.goの更新作業になります。

util.go に追記

server.goで呼び出されているutil.RandString(n int)を、util.goに作成します。 "math/rand"を追加でimportしてください。

const rs2Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
func RandString(n int) string {
    b := make([]byte, n)
    for i := range b {
        b[i] = rs2Letters[rand.Intn(len(rs2Letters))]
    }
    return string(b)
}

RandString(n int)を叩くことでn文字のランダムな文字列を返すことができます。これをcsrfのSecretに使います。

このSecretですが、文字列が固定されている解答がありました。これは第三者から推測が困難ではないかもしれないので適切ではありません。(減点はしてません)

RandString(n int)を叩くことでn文字のランダムな文字列を返すことができます。これをcsrfのSecretに使います。

このSecretですが、文字列が固定されている解答がありました。これは第三者から推測が困難ではないかもしれないので適切ではありません。(減点はしてません)

XSRF対策をする

今回は全てのFORM要素にhiddenなinputを用意して、そこにtokenを持たせ、送信させることにします。

hikeline.html の修正

17行目にhiddenなinputを追加します。

<form method="POST" action="api/newhike">
<input type="hidden" name="_csrf" value="{{ .csrfToken }}">

また、hikeline.htmlのmogamigawaのbutton部分は以下のように入れ替えます。この変更でmogamigawaのAPIをPOSTにした変更に対応し、XSRF対策ができます。

<!-- mogamigawa button -->
<div>
  <form action="/api/mogamigawa?hike_id={{ .Hike_id }}" method="POST">
    <input type="hidden" name="_csrf" value="{{ $.csrfToken }}">
    <button type="submit">
      <a href=""><i class="fas fa-water"></i></a>
    </button>
    <style>
      button {
        padding: 0, 0;
        border-style: none;
      }
 
    </style>
  </form>
</div>

signin.htmlsignup.htmlにもformがありますが、ここはXSRFから保護しなければならない場所ではありませんよね?

55%の採点を受けたチームは、この部分でapi/mogamigawaの対策はできてるんだけど、api/newhikeの対策がなされてない解答になっていました。

以上がhtmlの修正作業になります。

res.goの修正

htmlに{{ .csrfToken }}という新しいプレースホルダーを追加しました。これに対応して/app/handler/res.goを更新します。

"github.com/utrack/gin-csrf"を追加でimportしてください。gin-csrfを使います。 hikeline.htmlを返す部分のc.HTML(http.StatusOK, hikeline.html, gin.H{})に、以下のように記述を追加します。冗長なので、全箇所の記述は省略します。

c.HTML(http.StatusOK, hikeline.html, gin.H{
"csrfToken": csrf.GetToken(c),
})

Merge Request

以上の変更をcommitしたらpushして、GitLabでMerge Requestを建てます。

解説は以上です。

採点基準

  1. 適切なMerge Requestがなされている: +10%
    • あまりにも杜撰な解答は許されません。:angry:
  2. 任意の手法でXSRF対策をしている: +90%
    • mogamigawaのAPIとフロントエンドにのみ修正を加えた場合は半分の +45% にしています。
    • mogamigawaのAPIに加えてXSRF対策の必要なnewhikeのAPIにも対策をした場合に +90% としました。
    • 適切なXSRF対策がなされている場合にのみ点数を取れるようにしました。

講評

10チームが解答を提出してくれました。

参照した問題・解説のサイト: https://blog.icttoracon.net/2019/08/31/

接続が不安定になっちゃった!

解いた人:nonnonno

参照した問題・解説のサイト:接続が不安定になっちゃった!

使用環境・ツール

何台かのサーバ

問題文でされた操作・バグの内容

通常用セグメント 192.168.1.0/24 と、管理用セグメント 192.168.2.0/24 を持ったネットワーク上にいくつかのサーバがある。client1をこのネットワークに追加し設定したところ、client1とclient2間の通信が不安定になってしまった(通常用セグメントを使うとclient1とclient2の接続が不安定になるということっぽい)。 管理用セグメント (192.168.2.0/24) からは正常にアクセスできるため、こちらからsshすること。

理想の終了状態

192.168.1.0/24 のセグメントで正常に通信が行えるようにし、今後同じ状況にならないように設定を書き換えて、原因を報告してほしい。

考えられる原因とその検証・修正手順

解説

本問題は、動的割当のホストと静的割当のホストのIPアドレスが重複してしまい、通信が不安定になるトラブル。
この問題はVyOSの設定のservice dhcp-server global-parameters ‘ping-check false;’という項目でDHCPによるアドレス割り当ての前にアドレスの使用状況を確認する動作が無効化されていたため、静的割り当てのホストと同じアドレスがDHCPによって払い出されていた。

解答例

VyOS設定コマンド参考リンク
VyOSとclientの関係参考リンク(こちらにもコマンド説明あり) ルータに対して以下の操作を行う。

configure

で設定モードに入る。

delete service dhcp-server global-parameters ‘ping-check false;’

として、 commitsave をして完了。
次に、クライアント端末に対して動的割り当ての設定を行う。

sudo dhclient -r

sudo dhclient

dhclientコマンドは、DHCPプロトコルを利用し、NICにIPアドレスを設定したり、借り受けたIPアドレスの解放や、デフォルトルーターやネームサーバーの情報を確認する目的にも利用される。
今回は、rオプションを使うことで現在借り受けているIPアドレスを開放し、再度DHCPクライアントとして起動してアドレスの付与を受ける。

採点基準

  • 正常に通信を行えるかどうか
  • 原因を特定し、今後同じ状況にならないような設定にしているか

クライアントのどちらか片方のアドレスをnetplanで静的に書き換えても正常に通信はできますが、問題文に今後同じ状況にならないように設定してください。と記載されているため、DHCPの設定まで直して満点。

APIが飛ばないんですけど

解いた人:maimai-y

参照した問題・解説のサイト:APIが飛ばないんですけど…

問題文

Webアプリケーションからドメインが異なるAPIにリクエストを発行する際には、クロスオリジンについて注意する必要があります。 CORS (Cross-Origin Resource Sharing) に関する以下の問いについて、それぞれ適切な選択肢を選んでください。

問1

問題文

https://example.com と同じOriginを選んで下さい。

解答

オリジン Origin は、ウェブコンテンツにアクセスするために使われる URL のスキーム (プロトコル)、 ホスト (ドメイン)、 ポート によって定義されます。 スキーム、ホスト、ポートがすべて一致した場合のみ、二つのオブジェクトは同じオリジンであると言えます。

A

理由:

B サーバーは既定で80番ポートで HTTP コンテンツを配信するため https://example.comhttps://example.com:80 と同じ

C ホストが異なる

D プロトコルが異なる

解説

ポート番号、プロトコル(HTTP か HTTPS か)、ホストが一致するときのみ同一のOriginとなります。したがって https://example.com/hoge のみが正解です。

参考: https://developer.mozilla.org/ja/docs/Glossary/Origin

問2

問題文

app.ictsc で動いているアプリケーションから api.ictsc へ以下のような fetch() を実行したところ、CORSのエラーで正常に動きませんでした。 api.ictsc に設定する必要があるHTTP response headerをすべて選んでください。

fetch({
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  "body": JSON.stringify(data)
})
  • Access-Control-Allow-Origin
  • Access-Control-Allow-Headers
  • Access-Control-Allow-Methods

解答

全部

CORS (Cross-Origin Resource Sharing) とは...:

あるオリジンで動いている Web アプリケーションに対して、別のオリジンのサーバーへのアクセスをオリジン間 HTTP リクエストによって許可できる仕組みのこと

Access-Control-Allow-Origin:

指定されたオリジンからのリクエストを行うコードでレスポンスが共有できるかどうかを示します。

Access-Control-Allow-Headers:

実際のリクエストの間に使用できる HTTP ヘッダーを示すために使用されます。

Access-Control-Allow-Methods

リソースにアクセスするときに利用できる1つまたは複数のメソッドを指定します。

例:

Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Methods: *

解説

問題文中の fetch では https://api.ictschttps://app.ictsc から POST リクエストが実行されます。これはホストが異なるため、異なるOriginへのリクエストになるので、応答の HTTP ヘッダに Access-Control-Allow-Origin が必要です。

リクエストには Content-Type ヘッダが含まれており、その値が application/json になっています。Content-Type が以下の3つの値以外のときは、実際のリクエストの前に preflight request が発行されます。

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

preflight request は OPTIONSメソッドで行われ、サーバ側の許可するメソッドやヘッダが応答の HTTP ヘッダ内の情報として返されます。問題文のように Content-Type: application/json のリクエストを送る場合は、サーバ側で prefilght request への応答の HTTP ヘッダに Access-Control-Allow-Headers: Content-Type と設定しておく必要があります。

一方で、メソッドがGET, HEAD, POST の場合は preflight request への応答の HTTP ヘッダに Access-Control-Allow-Methods をつける必要はありません。

したがって、設定するべきヘッダは Access-Control-Allow-OriginAccess-Control-Allow-Headers となります。

参考: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Preflighted_requests

問3

問題文

選択肢に示すHTTPメソッドのうち、いかなる場合においてもpreflight requestが行われるものを選んでください。

  • GET
  • POST
  • HEAD
  • DELETE

解答

DELETE

CORS のプリフライトリクエストは CORS のリクエストの一つであり、サーバーが CORS プロトコルを理解していて準備がされていることを、特定のメソッドとヘッダーを使用してチェックします。

プリフライトリクエストはブラウザーが自動的に発行するものであり、通常は、フロントエンドの開発者が自分でそのようなリクエストを作成する必要はありません。これはリクエストが"to be preflighted"と修飾されている場合に現れ、単純リクエストの場合は省略されます。

「単純リクエスト」は、以下のすべての条件を満たすものです。

  • 許可されているメソッドのうちの一つであること。
  • ...

解説

preflight request の概要については問2の解説で説明したとおりです。メソッドが以下に挙げるものの場合は、必ず preflight request が発行されます。

  • PUT
  • DELETE
  • CONNECT
  • OPTIONS
  • TRACE
  • PATCH

したがって正解は DELETE となります。

問4

問題文

preflight requestについて示した文章のうち、正しいものを全て選んでください。

  • リクエスト元のドメインとリクエスト先のドメインが同じ場合は、いかなる場合においてもpreflight requestは行われない。
  • クロスオリジンで独自HTTPメソッド TEST を発行するためには、Access-Control-Allow-Methods* を追加することで必ず正しく動く。
  • preflight requestに対する応答は、Access-Control-Allow-* ヘッダの内容が正しいHTTP responseであれば他の内容はなんでもよい。
  • Access-Control-Allow-Origin* を設定しておけば、他のヘッダが適切である限りいかなる場合でも動作する。

解答

x x x o

  • Access-Control-Allow-Methods
    • * (ワイルドカード)
    • "*" の値は、資格情報のないリクエスト (HTTP Cookie や HTTP 認証情報のないリクエスト) の特殊なワイルドカードです。

解説

リクエスト元のドメインとリクエスト先のドメインが同じ場合は、いかなる場合においてもpreflight requestは行われない。

ドメインが同じであってもOriginが同じであるとは限りません。ポート番号やプロトコルが異なる場合は異なるOriginとなります。Originが異なる場合、特定の条件を満たせばpreflight requestが行われるため、この文章は間違っています。

クロスオリジンで独自HTTPメソッド TEST を発行するためには、Access-Control-Allow-Methods* を追加することで必ず正しく動く。

Access-Control-Allow-Methods: * と設定した場合の独自メソッドの動作は実装に依存しています。Ubuntu 18.0.4 上で Python3.6.8 の Bottle v0.12.17 によりHTTPサーバをhttp://localhost:8090, http://localhost:8080に立てて、前者から後者に JavaScript の fetch でリクエストを送って検証しました。Chromium 76.0.3809 で TESTリクエストを行ってみると成功しますが、FireFox 68.0.1 では失敗しました。 したがって、「必ず正しく動く」とするこの文章は間違っています。

参考: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS/Errors/CORSMethodNotFound

preflight requestに対する応答は、Access-Control-Allow-* ヘッダの内容が正しいHTTP responseであれば他の内容はなんでもよい。

preflight request に対する応答のステータスコードが200番台でない場合、リクエストを送ることができません。上記と同様の検証環境で、preflight requestに対するHTTP responseのステータスコードを404にするとPOSTリクエストが飛ばないことを確かめられました。したがってこの文章は間違っています。

参考: https://fetch.spec.whatwg.org/#cors-preflight-fetch

Access-Control-Allow-Origin* を設定しておけば、他のヘッダが適切である限りいかなる場合でも動作する。

リクエストにCookieなどのリクエスト情報が含まれている場合、Access-Control-Allow-Origin: *というワイルドカードの指定ではなく、具体的なOriginの指定が必要です。したがって、「いかなる場合でも動作する」とするこの文章は間違っています。

参考: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Requests_with_credentials

以上より、全ての文が間違っているので、何も選択しないのが正解です。

参照した問題・解説のサイト: https://blog.icttoracon.net/2019/12/10/

君k8s得意って言っていたよね?

解いた人:とり

参照した問題・解説のサイト:君k8s得意って言っていたよね?

使用環境・ツール

  • Kubernetes
  • Redmine
  • MariaDB

Redmine

プロジェクト管理ができるオープンソースソフトウェア。Dockerに公式イメージが存在する。 Redmine

MariaDB

MariaDBは、MySQL派生として開発されている、オープンソースの関係データベース管理システムである。RDBMS。

https://mariadb.org/

問題文でされた操作

  • Redmineは指定のManifest(Redmine_Manifest)でデプロイしてください。
  • Redmine_Manifestは変更出来ません。
  • Redmine_Manifest内のコンテナイメージはcontainer-registryから取得してください。
  • マニフェストの再適用, OSの再起動の操作は可能です。
  • 誤操作等で競技続行不可の場合は出題時環境への復元のみ承ります。 Kubernetes上にRedmineサービスを稼働させる問題です。 出題時にはRedmineを構成するRedmine-Pod, MariaDB-PodがPendingとなっており、利用不可の状態です。 コンテナが稼働しない原因を突き止め対処することでRedmineサービスを稼働させることができます。

問題解決のために以下の原因を解決する必要があります。

  1. masterへpodのデプロイに関するtaints(テインツ)の削除
  2. コンテナランタイムcri-oにinsecure-registryの設定を追加
  3. MariaDBのPersistentVolumeのディレクトリ権限(Permission)を修正

理想の終了状態

  • VNCクライアントのブラウザからRedmineが閲覧できること。http://192.168.0.100:30000
  • Redmineのデータがコンテナ再起動時にも保持されていること。

情報

  • Server:
  • k8smaster1:
    • ip: 192.168.0.100
    • userid: root
    • password: USerPw@19
  • container-registry:
    • ip: 192.168.0.101
    • 備考: 操作不可
  • Redmine_Manifest:
    • path: “/root/ictsc_problem_manifests/*.yaml”
  • Redmineログイン情報
    • userid: ictsc
    • password: USerPw@19

コメント

manifestファイルが欲しい〜〜〜〜〜〜


考えられる検証、修正手順

バグの原因を特定する案

  • kubectl describe pods Redmine-Pod
  • kubectl describe pods MariaDB-Pod で何が原因で動かないかをはじめに調べる
  • 問題文にこのような文章があったため、これに沿って設定を行ってみることにする
1. masterへpodのデプロイに関するtaintsの削除
2. コンテナランタイムcri-oにinsecure-registryの設定を追加
3. MariaDBのPersistentVolumeのディレクトリ権限(Permission)を修正

masterへpodのデプロイに関するtaintsの削除

Taintsとは

  • Tolerationsとセットで扱う

taintは"汚れ"という意味。tolerationは"容認"という意味。つまり"汚れ"を"容認"できるならscheduleできる仕組み toleration はPodに適用され、一致するtaintが付与されたNodeへpodが不適当なnodeにscheduleされないようにする。 Node Affinityなどの場合には、それらが未指定の場合はどこのNodeにでもscheduleされてしまうが、TaintsとTolerationsの場合は指定しない限りそのNodeにscheduleされることはない。 1つもしくは複数のTaintsをnodeに設定することができ、podには1つもしくは複数のtolerationsを設定することができる。

kubectl taint nodes node1 key1=value1:NoSchedule

node1にはこんな感じで適用させる。

kubectl taint nodes node1 key1=value1:NoSchedule-

外すには以上のコマンドを利用する。

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"

こんな感じでマニフェストファイルの書かれているため、削除自体はファイルを参考にできそう。

コンテナランタイムcri-oにinsecure-registryの設定を追加

CRI-Oとは

CRI-Oとは、コンテナ型仮想化で使われる技術の1つで、Kubernetesとコンテナランタイムが通信するための仕様として規定されているCRI(Container Runtime Interface)と、OCI Runtime Specificationに基づいて作られたKubernetesやDockerの高レベルなランタイム。CNCFで開発が行われ、オープンソースソフトウェア。コード見たらGoで書かれてた。

insecure-registryの設定を追加

insecure-registry ... Registryとの非セキュアな通信を許可するオプションとして--insecure-registryオプションが存在する。Registryが暗号化されていないhttp通信の場合に必要な設定。

一つ目のドキュメントをみると、cri-oはdaemon.jsonを使っているみたいなので、二つ目のドキュメントの参考をそのまま使うことができそう。

$ vi /etc/docker/daemon.json
{ "insecure-registries":["172.16.1.100:5000"] } # プライベートレジストリを指定 このリンクは任意
$ systemctl restart docker
$ systemctl restart crio # これもやっておいた方がいいかも?

# 起動確認例
$ docker pull 172.16.1.100:5000/ubuntu:16.04
$ docker run \
> -it \
> --rm \
> --name c1 \
> 172.16.1.100:5000/ubuntu:16.04 cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.2 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.2 LTS"
VERSION_ID="16.04"

参考:

  • https://kubernetes.io/ja/docs/setup/production-environment/container-runtimes/
  • https://www.itmedia.co.jp/enterprise/articles/1708/25/news014_2.html

MariaDBのPersistentVolumeのディレクトリ権限(Permission)を修正

これはよくわかんないけど、yamlを読んで修正をするのか、PersistentVolumeのhostPathの権限を見直すのかなのかな。よくわかんない。

kind: PersistentVolume
apiVersion: v1
metadata:
  name: my-pv-hostpath
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  hostPath:
    path: /data

解説

流れまで書いてあって丁寧だなと思った。

解決手順

masterへpodのデプロイに関するtaintsの削除

  • kubectl get podでコンテナの状態を見ます。
[root@k8smaster1 ~]# kubectl get pod
NAME                                  READY   STATUS    RESTARTS   AGE
my-release-mariadb-0                  0/1     Pending   0          9d
my-release-redmine-859cf77958-n95j5   0/1     Pending   0          9d

→ Pendingになっていることがわかる

  • kubectl describe pod <pod名>で各Podを確認する
    • あってた
Events:
  Type     Reason            Age                 From               Message
  ----     ------            ----                ----               -------
  Warning  FailedScheduling  9d (x5 over 9d)     default-scheduler  0/1 nodes are available: 1 node(s) had taints that the pod didn't tolerate.
  • nodeのtaintsをpodが許容できないということなので、nodeのtaintsをkubectl describe nodesで確認します。
[root@k8smaster1 ~]# kubectl describe nodes
Name:               k8smaster1
Roles:              master
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=k8smaster1
                    kubernetes.io/os=linux
                    node-role.kubernetes.io/master=
Annotations:        kubeadm.alpha.kubernetes.io/cri-socket: /var/run/crio/crio.sock
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Sat, 23 Nov 2019 19:58:55 +0900
Taints:             node-role.kubernetes.io/master:NoSchedule
  • 一番最後の行でnode-role.kubernetes.io/master:NoScheduleとあるため、Podがスケジューリングできない。
  • Taintsを削除する
[root@k8smaster1 ~]# kubectl taint nodes k8smaster1 node-role.kubernetes.io/master:NoSchedule-
node/k8smaster1 untainted

⏫ こんな感じで-つけるだけでも削除できるんだね。便利〜〜〜。

コンテナランタイムcri-oにinsecure-registryの設定を追加

  • kubectl get podで確認する
[root@k8smaster1 ~]# kubectl get pod
NAME                                  READY   STATUS             RESTARTS   AGE
my-release-mariadb-0                  0/1     ImagePullBackOff   0          9d
my-release-redmine-859cf77958-n95j5   0/1     ImagePullBackOff   0          9d
  • またdescribeする

Failed to pull image "private-registry.local/bitnami/mariadb:10.3.20-debian-9-r0": rpc error: code = Unknown desc = pinging docker registry returned: Get https://private-registry.local/v2/: dial tcp 192.168.0.101:443: connect: no route to host

  • ホストへのルートがない?のかな

MariaDBのPersistentVolumeのディレクトリ権限(Permission)を修正

  • kubectl get podをする
[root@k8smaster1 ~]# kubectl get pod
NAME                                  READY   STATUS    RESTARTS   AGE
my-release-mariadb-0                  0/1     Error     5          9d
my-release-redmine-859cf77958-n95j5   0/1     Running   1          9d
  • mariadbのエラー
  • ログを確認できるらしい
[root@k8smaster1 ~]# kubectl logs  my-release-mariadb-0
 16:43:21.98
 16:43:21.98 Welcome to the Bitnami mariadb container
 16:43:21.98 Subscribe to project updates by watching https://github.com/bitnami/bitnami-docker-mariadb
 16:43:21.98 Submit issues and feature requests at https://github.com/bitnami/bitnami-docker-mariadb/issues
 16:43:21.98 Send us your feedback at containers@bitnami.com
 16:43:21.99
 16:43:21.99 INFO  ==> ** Starting MariaDB setup **
 16:43:22.04 INFO  ==> Validating settings in MYSQL_*/MARIADB_* env vars
 16:43:22.04 INFO  ==> Initializing mariadb database
mkdir: cannot create directory '/bitnami/mariadb/data': Permission denied
  • createができない権限不足
    • これ見たらmkdirしようと思っちゃうけど解法は違う
  • /root/ictsc_problem_manifestsにあるk8sManifestを読み解くと、/var/opt/pv{1,2}にPersistentVolumeがある
    • そこに権限を渡せばいい。
  • kubectl get pvmariadbの対応するPathに権限を付与する
    • kubectl get pv
[root@k8smaster1 ictsc_problem_manifests]# kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
pv0001   20Gi       RWO            Recycle          Bound    default/data-my-release-mariadb-0                           9d
pv0002   20Gi       RWO            Recycle          Bound    default/my-release-redmine                                  9d
 
[root@k8smaster1 ]# chmod -R 777 /var/opt/pv1/

終わりに

kubectl describeは偉大。Taints理解した。

MySQLの復旧をお願いします!!

解いた人:Hunachi

参照した問題・解説のサイト:MySQLの復旧をお願いします!!

使用環境・ツール

環境

  • IPアドレス: 192.168.0.1
  • ユーザー: admin
  • パスワード: USerPw@19
  • DBユーザー: root
  • DBパスワード: root

状況

  • このMySQLは毎日定時にsysbench databaseのバックアップを取得していて(コンテスト問題の作成上truncate table文が実行された日まで)、偶然truncate文が実行される(数分)前にこの日のバックアップが完了していた
  • バックアップは以下のコマンドで取得されている
  • mysqldump --opt --single-transaction --master-data=2 --default-character-set=utf8mb4 --databases sysbench > /root/backup/backup.dump
  • mysql -u root -p < /root/backup/backup.dumpでバックアップが取得された時点に復旧できる
  • adminユーザからsudo suすることでrootユーザから操作してください

問題

問1

truncate table sbtest3;というクエリが実行された日時をyymmdd HH:MM:SSのフォーマットで報告してください。 また、どのようにこの日時を特定したかを説明してください。

問2

truncate tableが実行される直前の状態(truncate tableが実行される1つ前のクエリが実行された状態)にデータを復旧し、復旧後checksum table sysbench.sbtest3;の結果を報告してください。 また、データの復旧に必要な手順を説明してください。


技術調査

問1について

  • 実行されたmysqlコマンドの履歴を表示する方法 cat ~/.mysql_history で実行されたmysqlコマンドの履歴を表示することができるはず。参考

これで試した結果(dockerで環境作ってクエリを適当に打った)

# cat ~/.mysql_history
_HiStOrY_V2_
show\040databases;
quit;

\040はASCIIコードのスペースだけど、見づらいのでこれはスペースで表示させるようにする。

# sed "s/\\\040/ /g"  ~/.mysql_history
_HiStOrY_V2_
show databases;
quit;

sedコマンドについて参考になるサイト

日時が表示されてないのでだめ!

どうやらデフォで日時を取得する方法ないかも。。?   https://forums.mysql.com/read.php?10,400933 https://forums.mysql.com/read.php?10,400933,401057#msg-401057

そこで、 general_logが有効になっているかを確認する。 show variables like 'general_log'; もし、

+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| general_log   | ON    |
+---------------+-------+
1 row in set (0.00 sec)

ならラッキー、いける。 OFFなら自分にはお手上げ😭🙌

ONだったら、 show variables like 'general_log_file'; でわかるファイルをみてみる。

実験した

  1. デフォではOFFだったので、 mysql> set global general_log = 'ON';を設定。
  2. 色々コマンドを打つ。
  3. cat /var/lib/mysql/${general_log_file's value}.log
  4. 表示される例。
/usr/sbin/mysqld, Version: 8.0.25 (MySQL Community Server - GPL). started with:
Tcp port: 3306  Unix socket: /var/run/mysqld/mysqld.sock
Time                 Id Command    Argument
2021-08-16T08:48:21.048791Z	   15 Query	SELECT DATABASE()
2021-08-16T08:48:21.049349Z	   15 Init DB	hunadb
2021-08-16T08:48:21.052633Z	   15 Query	show databases
2021-08-16T08:48:21.053892Z	   15 Query	show tables
2021-08-16T08:48:21.055342Z	   15 Field List	piyo 
2021-08-16T08:48:32.444434Z	   15 Quit	
2021-08-16T08:48:34.020718Z	   16 Connect	root@localhost on  using Socket
2021-08-16T08:48:34.021351Z	   16 Query	select @@version_comment limit 1
2021-08-16T08:48:36.401359Z	   16 Query	show variables like 'general_log'
2021-08-16T08:48:46.839490Z	   16 Query	create table hunadb.piyo (id int, cost int)
2021-08-16T08:49:06.218438Z	   16 Query	create table hunadb.hiyo (id int, cost int)
2021-08-16T08:49:43.064247Z	   16 Query	truncate table hunadb.piyo
2021-08-16T08:49:48.068001Z	   16 Quit

↑の日時を解答として提出すれば良さそう!!

問2について

状況にて、バックアップの復旧方法を教えてくれてるのでそれをする。(括弧内は自分が打ったコマンド。ログも実際も私の物。)

  1. mysqldump --opt --single-transaction --master-data=2 --default-character-set=utf8mb4 --databases sysbench > /root/backup/backup.dump (を打つべきだけどこの時は手を抜いてmysqldump hunadb > dump.sql)
  2. mysql -u root -p < /root/backup/backup.dump(実際に打ったのは、mysql hunadb < dump.sql)
  3. .mysql_historyを確認してみる。
/usr/sbin/mysqld, Version: 8.0.25 (MySQL Community Server - GPL). started with:
Tcp port: 3306  Unix socket: /var/run/mysqld/mysqld.sock
Time                 Id Command    Argument
2021-08-16T08:48:21.048791Z	   15 Query	SELECT DATABASE()
2021-08-16T08:48:21.049349Z	   15 Init DB	hunadb
2021-08-16T08:48:21.052633Z	   15 Query	show databases
2021-08-16T08:48:21.053892Z	   15 Query	show tables
2021-08-16T08:48:21.055342Z	   15 Field List	piyo 
2021-08-16T08:48:32.444434Z	   15 Quit	
2021-08-16T08:48:34.020718Z	   16 Connect	root@localhost on  using Socket
2021-08-16T08:48:34.021351Z	   16 Query	select @@version_comment limit 1
2021-08-16T08:48:36.401359Z	   16 Query	show variables like 'general_log'
2021-08-16T08:48:46.839490Z	   16 Query	create table hunadb.piyo (id int, cost int)
2021-08-16T08:49:06.218438Z	   16 Query	create table hunadb.hiyo (id int, cost int)
2021-08-16T08:49:43.064247Z	   16 Query	truncate table hunadb.piyo
2021-08-16T08:49:48.068001Z	   16 Quit	
2021-08-16T09:43:42.989880Z	   17 Connect	root@localhost on  using Socket
2021-08-16T09:43:42.990214Z	   17 Query	select @@version_comment limit 1
2021-08-16T10:08:24.906898Z	   17 Query	SELECT * FROM msdb.dbo
2021-08-16T10:08:34.540831Z	   17 Query	show databases
2021-08-16T10:10:22.024284Z	   17 Quit	

--- ここでバックアップを取った --- ( mysqldump hunadb > dump.sql ) 

2021-08-16T10:10:31.893768Z	   18 Connect	root@localhost on  using Socket
2021-08-16T10:10:31.893799Z	   18 Connect	Access denied for user 'root'@'localhost' (using password: NO)
2021-08-16T10:11:05.935347Z	   19 Connect	root@localhost on  using Socket
2021-08-16T10:11:05.935406Z	   19 Connect	Access denied for user 'root'@'localhost' (using password: NO)
2021-08-16T10:11:29.099579Z	   20 Connect	root@localhost on  using Socket
2021-08-16T10:11:29.099723Z	   20 Query	/*!40100 SET @@SQL_MODE='' */
2021-08-16T10:11:29.099984Z	   20 Query	/*!40103 SET TIME_ZONE='+00:00' */
2021-08-16T10:11:29.100167Z	   20 Query	/*!80000 SET SESSION information_schema_stats_expiry=0 */
2021-08-16T10:11:29.100304Z	   20 Query	SET SESSION NET_READ_TIMEOUT= 86400, SESSION NET_WRITE_TIMEOUT= 86400
2021-08-16T10:11:29.101544Z	   20 Query	SHOW VARIABLES LIKE 'gtid\_mode'
2021-08-16T10:11:29.103106Z	   20 Query	SELECT LOGFILE_GROUP_NAME, FILE_NAME, TOTAL_EXTENTS, INITIAL_SIZE, ENGINE, EXTRA FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'UNDO LOG' AND FILE_NAME IS NOT NULL AND LOGFILE_GROUP_NAME IS NOT NULL AND LOGFILE_GROUP_NAME IN (SELECT DISTINCT LOGFILE_GROUP_NAME FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'DATAFILE' AND TABLESPACE_NAME IN (SELECT DISTINCT TABLESPACE_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA IN ('hunadb'))) GROUP BY LOGFILE_GROUP_NAME, FILE_NAME, ENGINE, TOTAL_EXTENTS, INITIAL_SIZE ORDER BY LOGFILE_GROUP_NAME
2021-08-16T10:11:29.110332Z	   20 Query	SELECT DISTINCT TABLESPACE_NAME, FILE_NAME, LOGFILE_GROUP_NAME, EXTENT_SIZE, INITIAL_SIZE, ENGINE FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'DATAFILE' AND TABLESPACE_NAME IN (SELECT DISTINCT TABLESPACE_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA IN ('hunadb')) ORDER BY TABLESPACE_NAME, LOGFILE_GROUP_NAME
2021-08-16T10:11:29.112213Z	   20 Query	SHOW VARIABLES LIKE 'ndbinfo\_version'
2021-08-16T10:11:29.114053Z	   20 Init DB	hunadb
2021-08-16T10:11:29.114225Z	   20 Query	show tables
2021-08-16T10:11:29.115254Z	   20 Query	LOCK TABLES `hiyo` READ /*!32311 LOCAL */,`piyo` READ /*!32311 LOCAL */
2021-08-16T10:11:29.116322Z	   20 Query	show table status like 'hiyo'
2021-08-16T10:11:29.117349Z	   20 Query	SET SQL_QUOTE_SHOW_CREATE=1
2021-08-16T10:11:29.117507Z	   20 Query	SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.117621Z	   20 Query	show create table `hiyo`
2021-08-16T10:11:29.117877Z	   20 Query	SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.118037Z	   20 Query	show fields from `hiyo`
2021-08-16T10:11:29.119264Z	   20 Query	show fields from `hiyo`
2021-08-16T10:11:29.120115Z	   20 Query	SELECT /*!40001 SQL_NO_CACHE */ * FROM `hiyo`
2021-08-16T10:11:29.120363Z	   20 Query	SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.120561Z	   20 Query	use `hunadb`
2021-08-16T10:11:29.120795Z	   20 Query	select @@collation_database
2021-08-16T10:11:29.121025Z	   20 Query	SHOW TRIGGERS LIKE 'hiyo'
2021-08-16T10:11:29.122437Z	   20 Query	SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.122713Z	   20 Query	SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.122975Z	   20 Query	SELECT COLUMN_NAME,                       JSON_EXTRACT(HISTOGRAM, '$."number-of-buckets-specified"')                FROM information_schema.COLUMN_STATISTICS                WHERE SCHEMA_NAME = 'hunadb' AND TABLE_NAME = 'hiyo'
2021-08-16T10:11:29.123610Z	   20 Query	SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.123849Z	   20 Query	show table status like 'piyo'
2021-08-16T10:11:29.124940Z	   20 Query	SET SQL_QUOTE_SHOW_CREATE=1
2021-08-16T10:11:29.125141Z	   20 Query	SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.125352Z	   20 Query	show create table `piyo`
2021-08-16T10:11:29.125741Z	   20 Query	SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.125958Z	   20 Query	show fields from `piyo`
2021-08-16T10:11:29.127051Z	   20 Query	show fields from `piyo`
2021-08-16T10:11:29.128547Z	   20 Query	SELECT /*!40001 SQL_NO_CACHE */ * FROM `piyo`
2021-08-16T10:11:29.128818Z	   20 Query	SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.128976Z	   20 Query	use `hunadb`
2021-08-16T10:11:29.129195Z	   20 Query	select @@collation_database
2021-08-16T10:11:29.129379Z	   20 Query	SHOW TRIGGERS LIKE 'piyo'
2021-08-16T10:11:29.130227Z	   20 Query	SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.130402Z	   20 Query	SET SESSION character_set_results = 'binary'
2021-08-16T10:11:29.130667Z	   20 Query	SELECT COLUMN_NAME,                       JSON_EXTRACT(HISTOGRAM, '$."number-of-buckets-specified"')                FROM information_schema.COLUMN_STATISTICS                WHERE SCHEMA_NAME = 'hunadb' AND TABLE_NAME = 'piyo'
2021-08-16T10:11:29.131010Z	   20 Query	SET SESSION character_set_results = 'utf8mb4'
2021-08-16T10:11:29.131201Z	   20 Query	UNLOCK TABLES
2021-08-16T10:11:29.132686Z	   20 Quit	

---- 多分ここまでがバックアップのためのコード ----

2021-08-16T10:12:17.133740Z	   22 Connect	root@localhost on  using Socket
2021-08-16T10:12:17.134045Z	   22 Query	select @@version_comment limit 1
2021-08-16T10:12:27.437396Z	   22 Query	SELECT DATABASE()
2021-08-16T10:12:27.437700Z	   22 Init DB	hunadb
2021-08-16T10:12:27.438758Z	   22 Query	show databases
2021-08-16T10:12:27.439753Z	   22 Query	show tables
2021-08-16T10:12:27.440834Z	   22 Field List	hiyo 
2021-08-16T10:12:27.442166Z	   22 Field List	piyo 
2021-08-16T10:12:31.924121Z	   22 Query	show tables
2021-08-16T10:12:40.044363Z	   22 Query	truncate table hiyo
2021-08-16T10:12:42.415083Z	   22 Query	show tables
2021-08-16T10:13:24.213758Z	   22 Query	drop table hiyo
2021-08-16T10:13:26.758317Z	   22 Query	show tables
2021-08-16T10:13:30.474725Z	   22 Quit	

---- ここで復元を始めた ---- ( mysql hunadb < dump.sql )

2021-08-16T10:13:58.825694Z	   23 Connect	root@localhost on hunadb using Socket
2021-08-16T10:13:58.826015Z	   23 Query	select @@version_comment limit 1
2021-08-16T10:13:58.826260Z	   23 Query	/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */
2021-08-16T10:13:58.826450Z	   23 Query	/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */
2021-08-16T10:13:58.826626Z	   23 Query	/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */
2021-08-16T10:13:58.826805Z	   23 Query	/*!50503 SET NAMES utf8mb4 */
2021-08-16T10:13:58.827039Z	   23 Query	/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */
2021-08-16T10:13:58.827220Z	   23 Query	/*!40103 SET TIME_ZONE='+00:00' */
2021-08-16T10:13:58.827404Z	   23 Query	/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */
2021-08-16T10:13:58.827586Z	   23 Query	/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */
2021-08-16T10:13:58.827784Z	   23 Query	/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */
2021-08-16T10:13:58.827967Z	   23 Query	/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */
2021-08-16T10:13:58.828183Z	   23 Query	DROP TABLE IF EXISTS `hiyo`
2021-08-16T10:13:58.832544Z	   23 Query	/*!40101 SET @saved_cs_client     = @@character_set_client */
2021-08-16T10:13:58.832736Z	   23 Query	/*!50503 SET character_set_client = utf8mb4 */
2021-08-16T10:13:58.832935Z	   23 Query	CREATE TABLE `hiyo` (
  `id` int DEFAULT NULL,
  `cost` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
2021-08-16T10:13:58.846132Z	   23 Query	/*!40101 SET character_set_client = @saved_cs_client */
2021-08-16T10:13:58.846305Z	   23 Query	LOCK TABLES `hiyo` WRITE
2021-08-16T10:13:58.847102Z	   23 Query	/*!40000 ALTER TABLE `hiyo` DISABLE KEYS */
2021-08-16T10:13:58.848155Z	   23 Query	/*!40000 ALTER TABLE `hiyo` ENABLE KEYS */
2021-08-16T10:13:58.849204Z	   23 Query	UNLOCK TABLES
2021-08-16T10:13:58.849372Z	   23 Query	DROP TABLE IF EXISTS `piyo`
2021-08-16T10:13:58.857241Z	   23 Query	/*!40101 SET @saved_cs_client     = @@character_set_client */
2021-08-16T10:13:58.857442Z	   23 Query	/*!50503 SET character_set_client = utf8mb4 */
2021-08-16T10:13:58.857621Z	   23 Query	CREATE TABLE `piyo` (
  `id` int DEFAULT NULL,
  `cost` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
2021-08-16T10:13:58.869241Z	   23 Query	/*!40101 SET character_set_client = @saved_cs_client */
2021-08-16T10:13:58.869410Z	   23 Query	LOCK TABLES `piyo` WRITE
2021-08-16T10:13:58.870143Z	   23 Query	/*!40000 ALTER TABLE `piyo` DISABLE KEYS */
2021-08-16T10:13:58.871175Z	   23 Query	/*!40000 ALTER TABLE `piyo` ENABLE KEYS */
2021-08-16T10:13:58.872237Z	   23 Query	UNLOCK TABLES
2021-08-16T10:13:58.872430Z	   23 Query	/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */
2021-08-16T10:13:58.872583Z	   23 Query	/*!40101 SET SQL_MODE=@OLD_SQL_MODE */
2021-08-16T10:13:58.872726Z	   23 Query	/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */
2021-08-16T10:13:58.872864Z	   23 Query	/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */
2021-08-16T10:13:58.873030Z	   23 Query	/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */
2021-08-16T10:13:58.873174Z	   23 Query	/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */
2021-08-16T10:13:58.873287Z	   23 Query	/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */
2021-08-16T10:13:58.873427Z	   23 Query	/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */
2021-08-16T10:13:58.873506Z	   23 Quit	

---- ここまでが復元された時のログ ----

2021-08-16T10:14:05.000746Z	   24 Connect	root@localhost on  using Socket
2021-08-16T10:14:05.001258Z	   24 Query	select @@version_comment limit 1
2021-08-16T10:14:12.021449Z	   24 Query	SELECT DATABASE()
2021-08-16T10:14:12.021944Z	   24 Init DB	hunadb
2021-08-16T10:14:12.023879Z	   24 Query	show databases
2021-08-16T10:14:12.025276Z	   24 Query	show tables
2021-08-16T10:14:12.026755Z	   24 Field List	hiyo 
2021-08-16T10:14:12.027480Z	   24 Field List	piyo 
2021-08-16T10:14:13.866961Z	   24 Query	show tables
2021-08-16T10:14:16.344213Z	   24 Quit	

ログの見方がよくわからないけど、これのバックアップが始まってそうな部分を見つけて、それ以外の部分のlogに書いてあるコマンドを打っていけばいいのでは?(筋肉で解決)


解説

公式の解説が丁寧なのでそちらを参照。

解説に対するメモ

  • 'DML' = Data Manipulation Language(select文、insert文など)参考
  • --base64-output=DECODE-ROWS はバイナリログが元々ROW形式なので読めるようにするためにつける。
  • -vv = --verbose --verbose (詳細なメッセージを表示)
  • -- CHANGE MASTER TO MASTER_LOG_FILE='binlog.000018', MASTER_LOG_POS=34626719;binlog.00001834626719が大事。
  • binlog.000018 からバックアップ後のデータを復旧する。
  • startpositionは34626719 になる。
  • mysqlbinlog は MySQLのバイナリログの解析に使われる。

試してみた

(私の環境に合わせたコマンドにしてる)

mysqldump --password=passwordh --opt --single-transaction --master-data=2 hunadb > dump.sql

--master-data=2が大事だったとは(ちゃんと調べてなきゃ。。)参考

dump.sql-- CHANGE MASTER TO のとこの情報をバックアップ以降の更新の始まりを知るべく確認。

binlogもあったのでこれに対して

mysqlbinlog --no-defaults --base64-output=DECODE-ROWS -vv --start-position=$MASTER_LOG_POS binlog.000002 | grep -B 10 truncate で、出てきた。

truncateを打った時のtimestampがわかったので、 問1は、select from_unixtime(timestamp);を打てば良さそう。

mysqlbinlog --no-defaults --start-position=$MASTER_LOG_POS --stop-position=$timestamp binlog.000018 | mysql -u root -p で、復旧の手順は行えることが確認できたと思う。(最初の設定ミスってて、データのバックアップをとってすぐにtrancateしたので確認できなかった😂)

感想

全然違くて🥺 DBの授業取ってたのに🥺

採点基準

  • 問1: 30%
  • 問2: 70%

Welcome to Nginx のページを表示したい!

解いた人:momom-i

参照した問題・解説のサイト:Welcome to Nginx のページを表示したい!

問題

あなたはローカルネットワーク上に web サーバを構築し、IPv6 アドレスを使用して web ページに接続できるようにセットアップをしています。 web ページへはhttp://nginx.icttoracon.netでアクセスできるようにしたいです。

nginx のホストには、すでに nginx のパッケージをインストール済みです。 CSR1000V と nginx のホストには固定で IPv6 アドレスを割り当てました。 クライアント(VNC Server)に IPv6 アドレスが自動設定されるように、CSR1000V には SLAAC の設定を行いました。

しかし、クライアント(VNC Server)のブラウザからhttp://nginx.icttoracon.netにアクセスしても Welcome to Nginx のページを表示させることができません。 このトラブルを解決し、Welcome to Nginx のページを表示させてください。

クライアントが増えても自動でアクセスできるよう、設定変更は CSR1000V と nginx ホストのみとしてください。 DNS サーバは CSR1000V を使用します。 各ノードには ssh/telnet 用に IPv4 アドレスが設定されていますので必要に応じて使用してください。 予選終了後に実環境で採点されるので、スコアサーバでの解答は不要です。

接続環境

HostProtocolIPv4 addressUser/Pass
CSR1000Vtelnet192.168.0.1admin/admin
nginxssh192.168.1.2admin/admin

問題文でされた操作

  1. CSR1000V と nginx のホストに固定で IPv6 アドレスを割り当て
  2. CSR1000V には SLAAC の設定

バグの内容

http://nginx.icttoracon.netにアクセスしても Welcome to Nginx のページを表示させることができない

理想の終了状態

Welcome to Nginx のページを表示

補足事項

  • 各ノードには ssh/telnet 用に IPv4 アドレスが設定されていますので必要に応じて使用
  • 設定変更は CSR1000V と nginx ホストのみ

考えられる原因

まず下記コマンドでクライアントからnginx.icttoracon.netが引けるか、IPv6 アドレスが引けるか調べる

# AAAAレコードはホスト名に対するIPv6アドレスが登録されている(ちなみにAAAAレコードはクアッドエーレコードって読むらしい!)
# +shortオプションで簡単にIPv6アドレスが登録されてるかどうか以外の情報を省ける
dig -t AAAA nginx.icttoracon.net +short
  • もし IPv4 でも引けないなら DNS の設定問題。(問題文的に IPv4 では引けそう?)IPv4 では引けるが IPv6 アドレスが引けなかったら、DNS のゾーンファイルに IPv6 の設定がなされてない。もしどちらも登録されていたら、nginx の問題で config の書き方不備。

クライアントから ping で IPv6 のアドレスにつながるかやってみる

# ping6は `ping -6`と同じ
ping6 nginx.icttoracon.net
  • もし通らなければ、SLAAC の自動 ip 割り当てあたりやパケットフィルタリングが問題。通るなら上記の問題になってくる気がする。

考えられる解決方法

DNS のゾーンファイルの不備を修正

# IPv6は省略法で書けるらしい
nginx.icttoracon.net. IN AAAA fc01::2

Nginx の config を修正

/etc/nginx/nginx.conf のlisten 80;の下に以下のような記述があるか確認

# IPv6のポート80でリッスンするよ〜という記述
listen [::]:80;

SLAAC の自動 IP 割り当て確認

基本的にこのページを参照した

show ipv6 address

で確認できる。もし割り当てがおかしい場合は、Cisco のグローバルコンフィグレーションモード(Cisco のルーターの特権モード。全体の設定に関わるようなことを行う時に入るモード)にconfigure terminalで入って、

(config)#ipv6 unicast-routing
# インターフェイス名を指定する。スロットは固定で0みたい
(config)#interface gigabitethernet 0/1
(config-if)#ipv6 enable

※Cisco のインターフェイスについてはこちら

パケットフィルタリング

ip6tables という IPv6 の iptables コマンドで確認ができるらしい!(参照)

ip6tables -L

もし 80 番ポートの設定がなければ、今回はクライアント側は変更しないから nginx で以下のコマンドで 80 番ポートの接続を許可する

# TCP80番ポートのアクセス許可
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT

※参考※

  • CSR1000V(Cisco Cloud Services Router 1000V):ソフトウェアルータ(参照)

  • SLAAC(スラーク):
    IPv6 アドレスを自動設定する技術の一つで、アドレスを発行するサーバなどを用意しなくても当該ネットワーク内のアドレスをホスト自身が設定する方式(概要説明参照 URL, 具体的な挙動のわかりやすい参照 URL(このページの「SLAAC による IPv6 アドレスの設定」))

  • VNC: ネットワークを通じて別のコンピュータに接続し、そのデスクトップ画面を呼び出して操作することができるリモートデスクトップソフトの一つ(参照)

  • IPv6 の記述法について:
    例. fc01::2 = fc01:0000:0000:0000:0000:0000:0000:0002/64(参照)


解説

本問題には 3 つの原因があります。 順を追って調べてみましょう。

疎通性確認

まずはクライアントマシンから nginx ホストまで IPv6 で疎通性があるか確認してみます。 簡単な確認ではありますが、トラブル原因のレイヤをある程度限定できます。

# ping6 ドメイン名ってやってたけど、普通にIPv6アドレスでpingするべきだったな^^;
ubuntu@ICTSC-VNC:~$ ping fc01::2 -c 4
PING fc01::2(fc01::2) 56 data bytes
64 bytes from fc01::2: icmp_seq=1 ttl=63 time=0.887 ms
64 bytes from fc01::2: icmp_seq=2 ttl=63 time=0.607 ms
64 bytes from fc01::2: icmp_seq=3 ttl=63 time=0.802 ms
64 bytes from fc01::2: icmp_seq=4 ttl=63 time=0.699 ms

--- fc01::2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3040ms
rtt min/avg/max/mdev = 0.607/0.748/0.887/0.110 ms

コマンドの結果から本問題は初期状態で IPv6 の疎通性があることが確認できます。

名前解決

名前解決ができるかどうか試してみましょう。 ドメイン名から IPv6 アドレスを取得するには AAAA レコードを参照します。 例として AAAA レコードを取得するコマンドを以下に示します。

ubuntu@ICTSC-VNC:~$ dig nginx.icttoracon.net AAAA

; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> nginx.icttoracon.net AAAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 40684
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;nginx.icttoracon.net.        IN  AAAA

;; Query time: 89 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Wed Dec 04 22:46:46 JST 2019
;; MSG SIZE  rcvd: 49
ubuntu@ICTSC-VNC:~$

AAAA レコードは取得できていません。 問題文で DNS サーバは CSR とされていますが、クライアントはどこを参照しているのでしょうか。

# grep -v: invert match
# grep -v "^#": #で始まるもの以外を取り出す = コメントアウト部分以外を取り出す
ubuntu@ICTSC-VNC:~$ cat /etc/resolv.conf | grep -v "^#"

nameserver 127.0.0.53
options edns0
search localdomain
# この/run/systemd/resolve/resolv.confは/etc/resolv.confのシンボリックリンク先
# `nameserver fc00::1`があるはず
ubuntu@ICTSC-VNC:~$ cat /run/systemd/resolve/resolv.conf | grep -v "^#"

nameserver 133.242.0.3
nameserver 133.242.0.4
search localdomain
ubuntu@ICTSC-VNC:~$

IPv4 で DNS サーバを受け取っているようですが、CSR の IP アドレスではありません。 1つ目の原因は DNS サーバ(CSR)が参照できていないことです。

問題文からクライアントの設定変更ではなくルータの設定変更で対応する方針であることがわかります。 クライアントの設定と CSR の設定を確認すると、クライアントの IPv6 アドレスは RA を用いた自動設定であることがわかります。 ただし DNS サーバのアドレスが配布されていません。 RA で IPv6 アドレスが設定されている場合は、以下の 2 つの方法で DNS サーバを配布することができます。

  • ステートレス DHCPv6
  • RA を用いた DNS 配布(RFC8106)

例として RA のみで DNS の配布を行います。 IOS-XE(Cisco の OS(参考))のコマンドリファレンスを参照すると、以下の設定で DNS の配布が行えそうです。

# conf t: さっきのconfigure terminalの省略形
csr1000v#conf t
Enter configuration commands, one per line.  End with CNTL/Z.
# int gi 1 = interface gigabitethernet 0/1と同じ
csr1000v(config)#int gi 1
csr1000v(config-if)#ipv6 nd ra dns server fc00::1

(ipv6 nd ra dns server fc00::1 の説明) クライアントで確認してみます。

ubuntu@ICTSC-VNC:~$ cat /run/systemd/resolve/resolv.conf | grep -v "^#"

nameserver 133.242.0.3
nameserver 133.242.0.4
nameserver fc00::1
search localdomain
ubuntu@ICTSC-VNC:~$ dig nginx.icttoracon.net AAAA

; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> nginx.icttoracon.net AAAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35863
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;nginx.icttoracon.net.        IN  AAAA

;; ANSWER SECTION:
nginx.icttoracon.net.    10  IN  AAAA    fc01::2

;; Query time: 12 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Wed Dec 04 22:51:49 JST 2019
;; MSG SIZE  rcvd: 77

ubuntu@ICTSC-VNC:~$

DNS サーバとして fc00::1 が設定され、AAAA レコードが正しく参照できています。

nginx 設定

IPv6 の疎通性があり、名前解決も行えているので一旦クライアントの作業を終え、nginx ホストを確認してみます。 まずは 80 ポートの使用状況を確認してみます。

[admin@nginx ~]$ sudo lsof -i:80
COMMAND  PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   1421  root    6u  IPv4   9815      0t0  TCP *:http (LISTEN)
nginx   1422 nginx    6u  IPv4   9815      0t0  TCP *:http (LISTEN)

type を見ると IPv4 となっており、nginx が IPv6 アドレスで待ち受けていないことがわかります。 2 つ目の原因は nginx は IPv6 アドレスで待ち受けていないことです。 nginx が IPv6 アドレスで待ち受けるよう、設定を変更します。

server {
    listen       80;
+   listen       [::]:80;
    server_name  localhost;

--- snip ---
# reload忘れてた^^;
[admin@nginx ~]$ sudo nginx -s reload
[admin@nginx ~]$ sudo nginx -s reload
[admin@nginx ~]$ sudo lsof -i:80
COMMAND  PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   1421  root    6u  IPv4   9815      0t0  TCP *:http (LISTEN)
nginx   1421  root   10u  IPv6  10932      0t0  TCP *:http (LISTEN)
nginx   1540 nginx    6u  IPv4   9815      0t0  TCP *:http (LISTEN)
nginx   1540 nginx   10u  IPv6  10932      0t0  TCP *:http (LISTEN)

フィルタリング設定

nginx が IPv6 で待ち受ける状態となりました。 しかしまだクライアントからアクセスができません。 nginx ホストのディストリビューションを確認してみます。

[admin@nginx ~]$ ls /etc | grep release
centos-release
redhat-release
system-release
system-release-cpe
[admin@nginx ~]$ cat /etc/centos-release
CentOS release 6.10 (Final)
[admin@nginx ~]$

CentOS6.10 であるため、フィルタリングは iptables で行っていると予想されます。(ここまで考えてなかった^^;) iptables のルールを確認してみましょう。

[admin@nginx ~]$ sudo iptables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     all  --  anywhere             anywhere            state RELATED,ESTABLISHED
ACCEPT     icmp --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
ACCEPT     tcp  --  anywhere             anywhere            state NEW tcp dpt:ssh
ACCEPT     tcp  --  anywhere             anywhere            state NEW tcp dpt:http
REJECT     all  --  anywhere             anywhere            reject-with icmp-host

一見すると問題が無いように見えますが、クライアントは Welcome to Nginx のページにアクセスできません。 それもそのはず、iptables は IPv4 のフィルタリング設定だからです。 実は初期状態から IPv4 で 80 ポートは許可されており、クライアントは IPv4 を用いて Welcome to Nginx のページを表示させることはできてました。

IPv6 のフィルタリングは ip6table で行います。 ip6table のルールを確認すると、80 ポートのアクセスを許可しているルールが無いことがわかります。

[admin@nginx ~]$ sudo ip6tables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     all      anywhere             anywhere            state RELATED,ESTABLISHED
ACCEPT     ipv6-icmp    anywhere             anywhere
ACCEPT     all      anywhere             anywhere
ACCEPT     udp      anywhere             fe80::/64           state NEW udp dpt:dhcpv6-client
ACCEPT     tcp      anywhere             anywhere            state NEW tcp dpt:ssh
REJECT     all      anywhere             anywhere            reject-with icmp6-adm-prohibited

3 つ目の原因は IPv6 の 80 ポートが拒否されていることです。 問題文には恒久的な設定ではなくて構わないとしか記載されていないので、80 ポートを許可する方法か、プロセスを停止する方法があります。 問題としてはどちらで行ってもいいですが、望ましいのは 80 ポートを許可する方法です。 ip6tables で 80 ポートを許可します。

# -m state: パケットの状態
# -m state NEW: 新規接続を対象にするという意味
# -I INPUT 6: 6番目に挿入
[admin@nginx ~]$ sudo ip6tables -I INPUT 6 -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
[admin@nginx ~]$ sudo ip6tables -I INPUT 6 -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
[admin@nginx ~]$ sudo ip6tables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     all      anywhere             anywhere            state RELATED,ESTABLISHED
ACCEPT     ipv6-icmp    anywhere             anywhere
ACCEPT     all      anywhere             anywhere
ACCEPT     udp      anywhere             fe80::/64           state NEW udp dpt:dhcpv6-client
ACCEPT     tcp      anywhere             anywhere            state NEW tcp dpt:ssh
ACCEPT     tcp      anywhere             anywhere            state NEW tcp dpt:http
REJECT     all      anywhere             anywhere            reject-with icmp6-adm-prohibited

クライアントでアクセスしてみると、Welcome to Nginx のページが表示されます。

すごく匿名ダイヤリー

解いた人:maimai-y

参照した問題・解説のサイト:すごく匿名ダイヤリー

問題文

匿名で日記が投稿できるサービス「すごく匿名ダイヤリー」を運営しています。 従来、フロントエンドとバックエンドを同じドメインで運用していましたが、 構成変更のため、バックエンドをサブドメインに変更する作業を行っています。

変更前: https://old-diary.ictsc.net/ https://old-diary.ictsc.net/api/

変更後: https://new-diary.ictsc.net/ https://api.new-diary.ictsc.net/

※VNCサーバのWebブラウザからのみ閲覧可能です

ソースコード内のドメインやパスは適切に書き換えましたが、何故か正常に動作しません。 変更前と同じように各機能が動作するよう、サーバにログインして原因調査 及び 修正を行ってください。

なお、サービスはメンテナンス中で限定公開としているため、対応中にサービス断が生じても問題ありません。 また、投稿データについてもバックアップから復元するので、(変更前/変更後環境共に)日記の追加・削除・スター追加は任意に実施して問題ありません。

今後の運用・開発を考慮し、変更は問題解決に必要な箇所に絞り、出来るだけ他に影響を与えないように直してください。 全てを直しきれない場合でも、可能なところまで直してください。

サービス仕様

  • 誰でも匿名で日記が投稿・閲覧できる
  • 投稿されている日記に対して誰でもスターを付けることができる
  • 日記は投稿したブラウザで閲覧すると削除ボタンが表示され、削除が可能 (期間/個数に制限あり)
  • フロントエンドはSPA(Single Page Application)として構築されている
  • 日記の取得/投稿/削除/スター追加はWebAPI経由でバックエンドと通信して実現する

// SPAとは(https://qiita.com/takanorip/items/82f0c70ebc81e9246c7a):

JavaScriptでDOMを操作しページを切り替える

解答方法

  • 修正 と 報告 の両方が必要です
  • 「変更後」のURLでサービスが正常に動作するよう、実際にサーバ上で修正を行ってください。
  • 解答から「原因と実施した修正内容」を報告してください。
    • 報告は最終的に行った内容のみで問題ありません (途中の試行錯誤は記載不要)
    • 具体的に記載してください (例: XXXを直した、ではなく XXXがXXXなので、XXXファイルのXXX部分にXXXXXXXXXを追加した 等)

ログイン情報

VNCサーバから $ ssh 192.168.0.80 -l admin → PW: USerPw@19

※ $ sudo su – にて rootユーザに昇格可能です


考えられる検証、修正手順

何故か正常に動作しません。

どこが正常に動いてないのか調べて、エラーメッセージを読めたら読む。

ソースコード内のドメインやパスは適切に書き換えましたが

DNS周りは大丈夫なのか調べる。


解説

この問題はICTSC2019 一次予選にて出題された APIが飛ばないんですけど… の実技出題を目的として作成しました。

// 完全に気づかなかった /(^O^)\

機能ごとに必要な対処が異なり、CrossOrigin通信におけるCORS, CSP, Cookieの取り扱いを把握していないと完答出来ない構成としています。

// CSPとは:セキュリティの為のHTTPレスポンスヘッダー (https://techblog.securesky-tech.com/entry/2020/05/21/)

STEP1, 日記一覧と日記を閲覧可能にする 前半 (CSPによる許可)

// https://new-diary.ictsc.net/しか信用できませんという設定になっているため、https://api.new-diary.ictsc.netを信用するようにする

https://new-diary.ictsc.net/ を閲覧するとブラウザアラートでError: Network Errorと表示されます。 これだけでは原因がわからないので、開発者ツール(F12)のコンソールを表示すると以下のエラーが表示されています。

Content Security Policy: ページの設定により次のリソースの読み込みをブロックしました: https://api.new-diary.ictsc.net/list (“connect-src”)

→ CSPの “connect-src” で https://api.new-diary.ictsc.net/list への接続が禁止されていることが分かります。

ページのソースを表示するとmetaタグでCSPが指定されている為、このhtmlを修正する必要があります。

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com;">

// connect-src <ドメイン>で、<ドメイン>のスクリプトだけを信用するという意味になる

修正すべきファイルの場所は動作しているWebサーバの設定ファイルから特定します。

# netstat -ntelpo | grep -e :443
// 接続待ち(Listen)のプロセスを調べる(?) | ポート番号は、httpsのデフォルトのポート番号である443に絞る
tcp6       0      0 :::443                  :::*                    LISTEN      0          26477      2044/httpd           off (0.00/0/0)
// httpdとは:dはデーモン Webサーバとしてのお仕事をしている常駐プログラム
# ps auxww | grep http[d]
// 全ユーザの全プロセス | httpdに絞る []で自分自身を避けてるらしい(https://webkaru.net/linux/ps-grep-exclude/)
root      2044  0.0  1.3 286180 13864 ?        Ss   17:59   0:00 /usr/sbin/httpd -DFOREGROUND
apache    2778  0.0  0.9 298736  9104 ?        S    18:48   0:00 /usr/sbin/httpd -DFOREGROUND
apache    2779  0.0  1.5 1356304 15728 ?       Sl   18:48   0:00 /usr/sbin/httpd -DFOREGROUND
apache    2780  0.0  1.6 1356172 16760 ?       Sl   18:48   0:00 /usr/sbin/httpd -DFOREGROUND
apache    2781  0.0  1.7 1487424 18028 ?       Sl   18:48   0:00 /usr/sbin/httpd -DFOREGROUND
apache    2993  0.0  1.6 1356308 17012 ?       Sl   18:48   0:00 /usr/sbin/httpd -DFOREGROUND
# /usr/sbin/httpd -S 2>&amp;1 | grep port
// バーチャルホストの設定を一覧表示した上でシンタックスチェック 標準エラー出力を標準出力に流す |
         port 443 namevhost fe80::9ea3:baff:fe30:1584 (/etc/httpd/conf.d/ssl.conf:40)
         port 443 namevhost old-diary.ictsc.net (/etc/httpd/conf.d/virtualhost.conf:6)
         port 443 namevhost new-diary.ictsc.net (/etc/httpd/conf.d/virtualhost.conf:32)
         port 443 namevhost api.new-diary.ictsc.net (/etc/httpd/conf.d/virtualhost.conf:51)
// Webサーバの設定ファイルは/etc/httpd/conf.d/virtualhost.conf
# grep Root -B1 /etc/httpd/conf.d/virtualhost.conf
  ServerName old-diary.ictsc.net
  DocumentRoot /var/www/old-front
--
  ServerName new-diary.ictsc.net
  DocumentRoot /var/www/new-front
--
  ServerName api.new-diary.ictsc.net
  DocumentRoot /var/www/new-api/public

/var/www/new-front/index.html に該当のmetaヘッダが含まれている為、 connect-src 'self';connect-src https://api.new-diary.ictsc.net; に編集すると、問題のエラーが解消します。

STEP2, 日記一覧と日記を閲覧可能にする 後半 (CORSによる許可)

// https://new-diary.ictsc.net オリジンを許可する

STEP1でCSPによるエラーは解消しましたが、まだ閲覧可能にはなりません。 再び https://new-diary.ictsc.net/ を開いてコンソールを確認すると、以下のエラーが表示されます。

クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://api.new-diary.ictsc.net/list にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Origin’ が足りない)。

記載の通り、CORSヘッダーの設定が必要となります。 https://developer.mozilla.org/ja/docs/Web/HTTP/CORS 設定場所についてはいくつか考えられますが、作問者の想定は以下の2通りです。

アプリケーション側に追加

/var/www/new-api/public/index.phpFastRoute\Dispatcher::FOUND 以下等に追加する

case FastRoute\Dispatcher::FOUND:
    $handler = $routeInfo[1];
    $vars = $routeInfo[2];
    header('Access-Control-Allow-Origin: https://new-diary.ictsc.net'); ★ 追加

Webサーバ(Apache)側に追加

/etc/httpd/conf.d/virtualhost.conf<Directory /var/www/new-api/public> 内等に追加し、httpdをreloadする

<Directory /var/www/new-api/public>
  Options Indexes FollowSymLinks
  AllowOverride All
  Require all granted
 
  Header set Access-Control-Allow-Origin https://new-diary.ictsc.net ★ 追加
</Directory>

※ 本問題ではブラウザ上で各機能が正しく動作していれば、追加場所や細かい記載方法等は不問としました。 ※ ただし、アプリケーションを1から作り直すような大幅な変更は認めていません。

以上の変更を行うと、日記一覧 及び 日記が閲覧可能となります。

STEP3, 日記の投稿を可能にする

// ヘッダを追加

「日記を書く」から日記を投稿すると、ブラウザアラートで投稿後の日記URLが受け取れませんでした。と表示されます。 また、コンソールにはsubmit_article https://new-diary.ictsc.net/app.js:109 と表示されます。 ただし、日記の投稿は正常に完了しており、その後のページ遷移のみ失敗しているようです。

エラーメッセージだけでは情報が足りないので、https://new-diary.ictsc.net/app.jsの該当処理を確認すると、 res.headers.location、つまりレスポンスのLocationヘッダが正常に取得出来ていないようです。

axios.post(api_url + 'article', params)
    .then(res => {
    if (!res.headers.location) { throw `投稿後の日記URLが受け取れませんでした。` }
    router.push(res.headers.location)
    })
    .catch(err => { console.error(err); alert(err) })
}

一方、開発者ツールのネットワークタブでAPIサーバからの応答を確認すると、 日記投稿後、Location: /article/21 のようにLocationヘッダを含むレスポンスが得られていると確認出来ます。

この解決には知識が必要となりますが、CORSでセーフリスト以外のレスポンスヘッダを利用する場合、 Access-Control-Expose-Headers ヘッダにて明示的に許可する必要があります。 https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Expose-Headers Locationヘッダはセーフリストに含まれていない為、STEP2の設定に以下のヘッダも追加する必要があります。

Access-Control-Expose-Headers: Location

ヘッダを追加すると、日記投稿後のエラーが解消し、投稿された日記ページにリダイレクトされるようになります。

STEP4, スターの追加を可能にする

// 定義されていないメソッドに対応

各記事のスター追加ボタン[★+]をクリックするとError: Network Errorが表示されます。 開発者ツールのコンソールには以下のように表示されます。

クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://api.new-diary.ictsc.net/article/26/star にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Origin’ が足りない)。
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://api.new-diary.ictsc.net/article/26/star にあるリモートリソースの読み込みは拒否されます (理由: CORS 要求が成功しなかった)。

また、開発者ツールのネットワークタブで通信を確認すると、 OPTIONSメソッドのリクエストが送信され、HTTP/1.1 405 Method Not Allowedのレスポンスが得られています。 しかし、https://new-diary.ictsc.net/app.jsにて利用されているメソッドはPUTです。

add_star: function () {
    axios.put(api_url + 'article/' + this.$route.params.id + '/star')
        .then(res => {
        this.article.star_count++
        })
        .catch(err => { console.error(err); alert(err) })
},

これは一次予選でも出題された プリフライトリクエストによる挙動です。 https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Preflighted_requests

OPTIONSメソッドに対して適切なCORSヘッダを応答する必要がありますが、 /var/www/new-api/public/index.php内でOPTIONSメソッドが定義されていない為、METHOD_NOT_ALLOWEDとして405の応答が発生しています。

作問者の想定解法は以下の2通りです。

ダミールートの追加

/var/www/new-api/public/index.php に ダミーのルートを追加する

$base = '/';
$dispatcher= FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $router) use ($base) {
    $router->addRoute('GET'    , $base.'list'                  , 'get_list');
    $router->addRoute('GET'    , $base.'article/{id:\d+}'      , 'get_article');
    $router->addRoute('POST'   , $base.'article'               , 'post_article');
    $router->addRoute('DELETE' , $base.'article/{id:\d+}'      , 'delete_article');
    $router->addRoute('PUT'    , $base.'article/{id:\d+}/star' , 'put_article_star');
    $router->addRoute('OPTIONS', $base.'{path:.*}'             , 'dummy');   ★ 追加
});
 
function dummy($vars, $pdo) {  ★ 追加
    return;
}

合わせてCORSヘッダの設定箇所に以下を追加する必要があります。

Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS

METHOD_NOT_ALLOWED発生時の処理に追加

/var/www/new-api/public/index.phpMETHOD_NOT_ALLOWEDが発生時した場合も、OPTIONSメソッドについては応答するように追加する

case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
    $allowedMethods = $routeInfo[1];
    if ($httpMethod == 'OPTIONS') {  ★ 追加
        header('Access-Control-Allow-Methods: OPTIONS, '.implode(', ', $allowedMethods));
        header('Access-Control-Allow-Origin: https://new-diary.ictsc.net');
        header('Access-Control-Expose-Headers: Location');
        break;
    }
    header('Allow: '.implode(', ', $allowedMethods));
    header('HTTP/1.1 405 Method Not Allowed');
    break;

上記どちらかの修正を行うと、スターの追加が可能となります。

STEP5, 日記の削除を可能にする

// Cookieに正しいsecret(パスワード)が保存されるように設定する

ここまでの対処でブラウザ操作で発生するエラーは解消しました。 しかし、問題文に書かれている日記の削除機能が見当たりません。

  • 日記は投稿したブラウザで閲覧すると削除ボタンが表示され、削除が可能 (期間/個数に制限あり)

https://new-diary.ictsc.net/app.js を確認すると、UI自体は存在するようですが、 article.authoredtrueにならなければ表示されないようです。

<div><span v-if="article.authored" class="delete_btn" v-on:click="delete_article()">この日記を削除する</span></div>

https://new-diary.ictsc.net/app.jsにはarticle.authoredを変更する処理が含まれておらず、 APIからの結果をそのまま受け入れています。

mounted: function () {
    axios.get(api_url + 'article/' + this.$route.params.id)
    .then(res => {
        if (!res.data) { throw `日記が見つかりませんでした。` }
        this.article = res.data;
    })
    .catch(err => { console.error(err); alert(err) })
},

API側の処理を /var/www/new-api/public/index.php から確認すると、 Cookieに正しいsecret(パスワード)が保存されている場合のみ、article.authoredtrueとなることが分かります。

function get_article($vars, $pdo) {
    $articleid = $vars['id'];
 
    $stmt = $pdo->prepare('SELECT id, title, content, star_count, secret_hash FROM article WHERE id = :id');
    $stmt->execute(array(':id' => $articleid));
    $result = $stmt->fetch();
    if (isset($_COOKIE['__Secure-article-'.$articleid])) {
        $secret_hash = $result['secret_hash'];
        $client_secret = $_COOKIE['__Secure-article-'.$articleid];
        $authored = password_verify($client_secret, $secret_hash);
    } else {
        $authored = false;
    }

記事の投稿時にはsetcookieが行われており、レスポンスヘッダからも確認できますが、 実際に投稿してもブラウザのCookieには保存されません。※ 開発者ツールのストレージタブにて確認出来ます。

function post_article($vars, $pdo) {
...
    header('HTTP/1.1 201 Created');
    header('Location: /article/'.$articleid);
    setcookie('__Secure-article-'.$articleid, $secret, time() + (365 * 86400), '

CrossOriginでCookieを設定させる場合は、リクエスト側でwithCredentialsの指定と、 レスポンス側でAccess-Control-Allow-Credentialsの指定が必要となります。 https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest/withCredentials https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

レスポンス側はこれまでのCORSヘッダと同様に以下のヘッダを追加します。

Access-Control-Allow-Credentials: true

リクエスト側については、/var/www/new-front/app.js からaxiosを利用して通信している為、 個別にwithCredentials: trueを指定するか、以下のようにデフォルト値を設定します。

axios.defaults.withCredentials = true;

双方を追加後に記事を投稿すると「この日記を削除する」ボタンが表示されるようになります。 実際の削除についてはDELETEメソッドを許可する必要があるため、追加していない場合はヘッダに追加します。

Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

以上で全ての機能が正常に動作するようになりました。 動作確認の上、「原因と修正内容」を解答すれば完了です。

採点結果について

本問題は「各機能の正常な動作」及び「修正箇所への言及」にて点数を加算しています。

各工程の正答率は「STEP1/2 41%」「STEP3 21%」「STEP4 23%」「STEP5 17%」となり、完答は「12%」でした。 STEP1/2までの修正についてはWebブラウザの開発者ツール(コンソール)で修正箇所が示されていますので、 普段から使い慣れている方は比較的容易に解決できる想定でした。 一方、STEP3/4/5についてはCORS/Cookieの知識 及び PHP/JavaScriptの読解が必要となる為、 Web技術に関するチームの実力差が顕著に出る結果となったように感じます。 特に上位チームは解答内容が丁寧かつ明確な内容で、完全に理解している様子でした。 (拙いコードを読解いただきありがとうございました……)

なお、全ての問題に対処出来たと思われるチームでも、 「解答で一部修正に言及していない」「デバッグ用のalertが削除されないまま残っている」 「解答では修正されているはずのファイルがサーバ上では修正されていない」等の理由で減点が発生しました。 また、STEP1/2の解決のみで問題クリアと判断した様子のチームも見受けられました。

いずれも解答提出前後の見直しで防げる内容となりますので、 今一度落ち着いて問題文と解答、修正後のサービス状況を確認いただければと思います。

参照した問題・解説のサイト: https://blog.icttoracon.net/2021/03/16/

いつの間にか復活している君

解いた人:とり

参照した問題・解説のサイト:いつの間にか復活している君

使用環境・ツール

  • docker
  • docker-compose

前提条件

  • ~/web-server/docker-compose.ymlがあり編集可能
    • わざわざ明記しているあたり、これを編集して問題を解決する必要がありそう
    • ⇨ ひっかけポイントだったらしいw そういうパターンもあるのね。
  • docker ps -aなどで確認するとコンテナが起動している

問題文でされた操作

  • docker-composeを使用してWEBサーバを構築した
  • 無事に起動し、WEBページも確認できたのですが、 再起動するとアクセスできなくなってしまった
    • 自動的に再起動するように記述してあるので不可解
  • トラブルシュートしているといつの間にかアクセスできるようになります

バグの内容

踏み台から $ curl 192.168.17.1 をしても応答がない

理想の終了状態

再起動しても踏み台から $ curl -I 192.168.17.1 をするとステータスコード200のレスポンスが返ってくる

考えられる検証、修正手順

  • docker build -t test:local . と Docker でビルドしてみて永遠と転送しているか確認
    • 大量のファイルを裏で Docker daemon が読み込んでいる
    • .dockerignore で、大量ファイルのあるディレクトリを除外しておき、docker-compose.json 内で別途マウント
    • 起動の時間によりそう

ref:docker-compose で一向にビルドがはじまらない、もしくは起動しない。はたまた忘れたころに起動する。

  • Webサーバーにアクセスできない時の確認ポイント(なさそう)
    • コンテナ内のWebサーバーがちゃんと起動して動いているか
      • 以下のようにして確認
      docker exec -it <起動したコンテナ名> bash
      curl http://localhost:8080/
      
      • エラーが返ってくる、レスポンスが帰ってこない場合はWebサーバーの起動がうまくいっていない
        • 今回の問題は時間が経てば直る問題なのでこれはなさそう
    • localhost以外ではアクセスできるか
      • localhostではなく、実IPアドレスでアクセスしたらどうか?
      • 以下のコマンドでコンテナに割り振られているネットワークアドレスがわかる
      docker inspect –format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}’ con_name
      curl http://172.17.0.2:8080/
      
      • これでうまくいった場合は、ポートなどの問題になる。
    • コンテナで特定のポートが公開されているか
      • ポートの公開を
      # Dockerfileファイル中に以下のような記述を追加し、8080番ポートを公開する
      EXPOSE 8080
      # composeファイル中にならこんな感じ
      expose:
      - '3306'
      - '8080'
      
      • yamlも修正可能なので、なくはないかもしれない。簡単すぎるが。
      • でも、時間が経てば直る問題なのでなさそう
    • 公開されたポートにつながるように設定できているか(ポートフォワード)
      • コンテナで公開されたポートにホストOSから「localhost:8081」のように接続するには、ホストOSの8081番ポートとコンテナの8080番ポートをつないであげる必要があります。
      • localhost:8081 -> コンテナ:8080
        • 「localhost:8081」に来たリクエストを「コンテナ:8080」に転送してあげるようにする
      docker run -p 8081:8080 –name <起動するコンテナ名>
      

Docker Compose restart の挙動

  • ホストOSを起動したタイミングであるアプリケーションを自動で立ち上げたい、 あるいは何らかの問題で落ちた時に、自動で再起動して欲しいというときにrestart使うらしい。
  • Docker 及び Compose では、 run/upの restart policy の設定することにより、 コンテナが停止した際の再起動にまつわる設定ができる。
オプション意味
no再起動しない (デフォルト)
on-failure[:max-retries]プロセスが 0 以外のステータスで終了した場合、 最大:max_retries の分だけ再起動を行う
always明示的に stop がされない限り、終了ステータスに関係なく常に再起動が行われる
unless-stopped最後にdocker daemon が起動していた際に ステータスが終了状態だった場合は再起動しない。それ以外はalwaysと同じ。

ちゃんと再起動の設定はしないといけない。

Q. 従属 コンテナのプロセスはそのまま? それとも再起動される?
A. そのまま

Q. コンテナ間の接続は再開される?
再起動後も接続は問題なさそう

ref: Docker Compose restart の挙動

Dockerデーモン、Dockerコンテナ、及びコンテナ内のサービスアプリの自動起動について

デフォルトは手動なのかな? 再起動の時には自動設定する必要があるっぽい。

Dockerデーモンの自動起動

ブート時に自動起動する

$ sudo systemctl enable docker
# 他のディストリビューションでは、次のように実行します
$ sudo chkconfig docker on

通常時の起動

$ sudo systemctl start docker
# 他のディストリビューションでは、次のように実行します
$ sudo service docker start

設定確認・状態確認は以下のコマンドで行います。

$ systemctl status docker

Dockerコンテナの自動起動

  • restartオプションを利用
    • Dockerが以上終了した場合に自動的に再起動させることが可能
    • さっきの上の表を参考
    • 「always」「unless-stopped」がコンテナの自動起動に利用可能
      • 「always」を指定した場合は、Dockerデーモン終了時のDockerコンテナの状態に関係なく自動起動されます
      • 「unless-stopped」は、Dockerデーモン終了時に停止状態(例えば「docker stop」コマンドにて停止)のコンテナは自動起動されません
  • Dockerホストのsystemdを利用する
    • 「docker start」と「docker stop」をsystemdに登録するだけ
    • systemdって起動するときに動いてくれるやつらしい

Dockerコンテナ内のサービスの自動起動

  • composeファイルに設定がちゃんと書いてあれば特に問題はなさそう

ref:

修正手順案詳細

  • $ curl 192.168.17.1docker ps -aを試してみる
  • 自動起動されてないようなら、systemctl status dockerで自動起動を調べる。
  • yamlのrestartオプションを確認して、もしオプションがうまく機能してなさそうなら設定してみる

解説

原因

デーモンの起動はできているが、dockerの自動起動が出来ていない

原因究明方法

初期状態を確認

$ curl 192.168.17.1
curl: (7) Failed to connect to 192.168.17.1 port 80: Connection refused

コンテナの様子を確認

user@docker:~$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                  PORTS                NAMES
4e7db3e7cc97        nginx:latest        "/docker-entrypoint.…"   3 minutes ago       Up Less than a second   0.0.0.0:80->80/tcp   web-server_nginx_1

もう一度curlを確認する

$ curl 192.168.17.1 -I
HTTP/1.1 200 OK
Server: nginx/1.19.6
Date: Sun, 07 Mar 2021 03:33:11 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 15 Dec 2020 13:59:38 GMT
Connection: keep-alive
ETag: "5fd8c14a-264"
Accept-Ranges: bytes

実はdockerコマンドを叩くとデーモンが起動するという罠があります.先ほどのdocker ps -aを見てみるとUp Less than a secondとあり,起動したてなのがわかります

さらにsystemctl status dockerで詳しく確認してみる。

ictsc@docker:~$ systemctl status docker
● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; disabled; vendor preset: enabled)
     Active: active (running) since Sun 2021-03-07 12:32:53 JST; 9min ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 2176 (dockerd)
      Tasks: 17
     Memory: 114.7M
     CGroup: /system.slice/docker.service
             ├─2176 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
             └─2362 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.18.0.2 -container-port 80
              
 
ictsc@docker:~$ systemctl is-enabled docker
disabled

↑ でdisabledになっているのが悪かった。

解決手順

解放は単純にenableするだけ。

systemctl enable docker

途中で上手くいっちゃうし、めっちゃ引っかかりそう。restartしてすぐ直るからググってもあんまり出てこなくてこういう問題は意外と厄介かも😅

その他リンク

時間上説明しなかったものたち。

Webが繋がらないという内容で調べたやつ

  • 汎用的に使えそうなやつ: https://web.plus-idea.net/on/docker-web-server-access-denied/
  • 全般的に使えそうなやつ: https://qiita.com/amuyikam/items/ef3f8e8e25c557f68f6a

Dockerデーモンが気になって調べたやつ

  • さわって理解する Docker 入門
    • Dockerデーモンは Linux のデーモンプロセスで、Docker Engine API が呼び出されるのを待ち受けています。Dockerデーモンは、呼び出された Docker Engine API に応じて、イメージのビルドやコンテナの起動などを行います。
  • Linux リテラシ - 第4回 デーモン
    • デーモンはユーザーが意識することがないような裏の部分で動いており、システムを維持したりユーザーにサービスを提供したりといったことを行っています

名前解決ができなくなった?

解いた人:momom-i

参照した問題・解説のサイト:名前解決ができなくなった?

前提条件

  • $ dig @192.168.2.131 pc1.ictsc.netで名前解決ができない
  • 権威サーバーを使っているキャッシュサーバーは一つしかない

問題文でされた操作

  1. 権威サーバで KSK ロールオーバーを行った
  2. $ dig @192.168.2.131 pc1.ictsc.netを実行した

バグの内容

クライアントからdig @192.168.2.131 pc1.ictsc.net +dnssecを実行しても,名前解決ができない.

理想の終了状態

クライアントから dig @192.168.2.131 pc1.ictsc.net +dnssec を実行して,dnssec の検証が成功して名前解決ができる.


考えられる原因

  • 権威サーバが落ちてるなど DNS サーバ自体に異常がある(KSK の話が出ているので可能性は低い)
  • DNSSEC 検証を有効にしているキャッシュサーバ等のトラストアンカーの更新がされていない
  • KSK ロールオーバーで一時的に DNS の応答サイズが大きくなっていて応答を正しく受け取れない

解決方法

基本的にここを参照した。

原因 1. DNS サーバ自体に異常がある

  1. dig コマンドの+norecあるなしで結果がどう変わるかを観察する。+norecをつけるとキャッシュ DNS サーバから権威サーバへの問い合わせ結果を表示するため、このオプションをつけた時だけ失敗する場合は、権威サーバに問題がある。逆にオプションをつけない時に失敗する場合は、キャッシュ DNS サーバの方に問題がある。

原因 2. トラストアンカーの更新がされていない

  1. 上記リンク「トラストアンカーの更新」を、みて DNSSEC 検証が有効かどうか、named.conf の option などで dnssec-validation が no など設定されているかを確認する。特に設定されてない場合 BIND9.5 以前のデフォルトは無効、9.5 以降は有効になってる。無効の場合、トラストアンカーの更新が原因ではない。
  2. 更新後の権威サーバ KSK があるか確認。(更新した、ってあるから多分ある)named.conf の trusted-keys ディレクティブを書き換える。

原因 3. 一時的に DNS の応答サイズが大きくなっていて応答を正しく受け取れない

  1. これが原因の場合、dig コマンドで flags に tc が立ってるか UDP では扱えません!みたいなエラーが出ると思われる。「DNS 応答サイズ増大への対応」に沿って確認しながら受け取れるようにする。

※参考※

  • DNS:
    DNS ( Domain Name System ) は、ドメイン名(コンピュータを識別する名称)を IP アドレスに自動的に変換してくれるアプリケーション層プロトコル(参照)

  • トラストアンカー:
    インターネットなどで行われる、 電子的な認証の手続きのために置かれる基点。(参照)

  • 権威サーバー:
    (DNS コンテンツサーバともいう) DNS において、あるゾーンの情報を保持し、他のサーバーに問い合わせることなく応答を返すことができるサーバーのこと(参照)

  • KSK ロールオーバー:
    KSK(鍵署名鍵)のロールオーバー(更新)。KSK(key string key)は DESSEC にて、公開鍵に署名するための鍵(参照)

  • DESSEC:
    電子署名の仕組みを応用し、DNS 応答の出自および DNS 応答の完全性を検証することができるもの(わかりやすい参照 URL)

  • dig コマンド:
    ip アドレスやネームサーバを DNS に問い合わせる BIND のコマンド。nslookup コマンドと似ていて、結果の出力が dig の方が詳細(参照)
    +dnnsec: DNSSEC 付きでの問い合わせ
    +cd: DNSSEC なし問い合わせ
    +norec: キャッシュ DNS サーバから権威サーバへの問い合わせ


解説

  1. まず、クライアントから権威サーバで名前解決をできるかを確認します。
# 192.168.2.20は権威サーバのIPアドレス

$ dig @192.168.2.20 pc1.ictsc.net

; <<>> DiG 9.11.20-RedHat-9.11.20-5.el8 <<>> @192.168.2.20 pc1.ictsc.net
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49438
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: fb2922914c429f5fa65713d2603b3d6486e5d706011553e6 (good)
;; QUESTION SECTION:
;pc1.ictsc.net.                 IN      A

;; ANSWER SECTION:
pc1.ictsc.net.          86400   IN      A       192.168.2.151

;; AUTHORITY SECTION:
ictsc.net.              86400   IN      NS      master-ns.ictsc.net.

;; ADDITIONAL SECTION:
master-ns.ictsc.net.    86400   IN      A       192.168.2.20

;; Query time: 1 msec
;; SERVER: 192.168.2.20#53(192.168.2.20)
;; WHEN: Sun Feb 28 15:51:16 JST 2021
;; MSG SIZE  rcvd: 126
  1. 次に,キャッシュサーバで名前解決ができるかを確認します.
$ dig @192.168.2.131 pc1.ictsc.net +dnssec
; <<>> DiG 9.11.20-RedHat-9.11.20-5.el8 <<>> @192.168.2.131 pc1.ictsc.net +dnssec
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 27168
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 956
; COOKIE: 6fbba7982beede01a541f72f603b3d788b89e7b7d3c4fbc1 (good)
;; QUESTION SECTION:
;pc1.ictsc.net.                 IN      A

;; Query time: 3 msec
;; SERVER: 192.168.2.131#53(192.168.2.131)
;; WHEN: Sun Feb 28 15:51:36 JST 2021
;; MSG SIZE  rcvd: 70
  1. キャッシュサーバで名前解決ができないことがわかったので、コマンド引数に+cd を追加して DNSSEC の検証をせずに名前解決を行います
$ dig @192.168.2.131 pc1.ictsc.net +cd

; <<>> DiG 9.11.20-RedHat-9.11.20-5.el8 <<>> @192.168.2.131 pc1.ictsc.net +cd
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25924
;; flags: qr rd ra cd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 956
; COOKIE: 67453de86c9abd57bee0655c603b3d852c5c6647f116587d (good)
;; QUESTION SECTION:
;pc1.ictsc.net.                 IN      A

;; ANSWER SECTION:
pc1.ictsc.net.          86329   IN      A       192.168.2.151

;; AUTHORITY SECTION:
ictsc.net.              86338   IN      NS      master-ns.ictsc.net.

;; Query time: 0 msec
;; SERVER: 192.168.2.131#53(192.168.2.131)
;; WHEN: Sun Feb 28 15:51:49 JST 2021
;; MSG SIZE  rcvd: 110

名前解決が成功するため,DNSSEC の検証に失敗していることがわかります。

  1. クライアントから署名の有効期限を確認します.2 月 10 日に署名の有効期限が切れていることが確認できます
$ dig @192.168.2.20 pc1.ictsc.net +dnssec

pc1.ictsc.net 86400 IN RRSIG A 8 3 86400 20210210004000 20210111004000 ...
  1. 権威サーバで新しい KSK 鍵のみを用いて、ゾーンに再署名を行います. (ゾーンの再署名も必要だったのか!)
$ sudo su
$ cd /var/named
$ vim ictsc.net.zone

$TTL 1D                                     ;

@         IN  SOA  master-ns.ictsc.net.  root.master-ns.ictsc.net. (  ;
                   2020123101             ;
                   3H                     ;
                   1H                     ;
                   1W                     ;
                   1H )                   ;

          IN  NS   master-ns.ictsc.net.  ;

cache-ns  IN  A    192.168.2.131         ;
master-ns IN  A    192.168.2.20         ;
pc1       IN  A    192.168.2.151         ;
pc2       IN  A    192.168.2.152         ;
pc3       IN  A    192.168.2.153         ;

$INCLUDE "Kictsc.net.+008+63885.key"
$INCLUDE "Kictsc.net.+008+63439.key"
$INCLUDE "Kictsc.net.+008+63829.key" # 古いksk鍵を削除

# -x: KSKの更新のみ
# -o: ゾーンの起点
# -k: 署名鍵
$ dnssec-signzone -x -o ictsc.net -k Kictsc.net.+008+63439.key ictsc.net.zone

Verifying the zone using the following algorithms: RSASHA256.
Zone fully signed:
Algorithm: RSASHA256: KSKs: 1 active, 0 stand-by, 0 revoked
                      ZSKs: 1 active, 0 present, 0 revoked
ictsc.net.zone.signed

※ dnssec-signzone: (参照) 6. ゾーンのリロードを行います.

$ rndc reload ictsc.net
  1. クライアント側で KSK 公開鍵を取得し,古いトラストアンカーを削除し新しいトラストアンカーの設定を行います.
# 257は現在の有効なKSKを示す
$ dig @192.168.2.20 ictsc.net dnskey | grep 'DNSKEY.*257'
ictsc.net.              86400   IN      DNSKEY  257 3 8 AwEAAePGnJDVqiEhjCRcnYYNP+Pf2DFnJwoj3sTlJwkh2aM1LZR4ajtR sxidDJi59Hf/lcwCBiEnW8eNvpuHz5NfrUTuc/hI/jKI38VkH4m+b68B feNyJtS9IUn8Naln/9r4hQBFCCEHJNmiMo5XnKdD3oEuDSgIsCeP8IOJ c1tlEcimy
fBfijuQleTr7MyoxW3iK0Q7kUuy8kIGelWKMogbUwrFFeBV CNvIAiofQOy7UDkjuGe9UpEXozZ5LNQkrBONzkUvr8Dt3YlhhWWYAjbX W5WzrLiQS9PTr3HMRlOOvTk4XlxQu0LDyqalyuBQnvMMg0AleQ7Q5c+M LU3l96yAg50=

$ sudo vim /var/named/chroot/etc/named.conf
trusted-keys {

        "ictsc.net."  257 3 8 "AwEAAePGnJDVqiEhjCRcnYYNP+Pf2DFnJwoj3sTlJwkh2aM1LZR4ajtR sxidDJi59Hf/lcwCBiEnW8eNvpuHz5NfrUTuc/hI/jKI38VkH4m+b68B feNyJtS9IUn8Naln/9r4hQBFCCEHJNmiMo5XnKdD3oEuDSgIsCeP8IOJ c1tlEcimyfBfijuQleTr7MyoxW3iK0Q7kU
uy8kIGelWKMogbUwrFFeBV CNvIAiofQOy7UDkjuGe9UpEXozZ5LNQkrBONzkUvr8Dt3YlhhWWYAjbX W5WzrLiQS9PTr3HMRlOOvTk4XlxQu0LDyqalyuBQnvMMg0AleQ7Q5c+M LU3l96yAg50=";
};

$ sudo systemctl restart named-chroot
  1. 最後に、クライアント側でキャッシュサーバを使用して,DNSSEC の検証が成功するかを確認します.
$ dig @192.168.2.131 pc1.ictsc.net +dnssec
; <<>> DiG 9.11.20-RedHat-9.11.20-5.el8 <<>> @192.168.2.131 pc1.ictsc.net +dnssec
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50967
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 956
; COOKIE: 11c4d3fa9bdf7a63dc315b95603de72eaeb7725d8de23282 (good)
;; QUESTION SECTION:
;pc1.ictsc.net.         IN  A
;; ANSWER SECTION:
pc1.ictsc.net.      86258   IN  A   192.168.2.151
pc1.ictsc.net.      86258   IN  RRSIG   A 8 3 86400 20210401061624 20210302061624 63885 ictsc.net. K2/YRHfbne6RIqBzkt0/bZ7T62QTNr52S/SGuD9omC5ClbsJjOydBvXm THSSR0BtxlzbSGVzhggkBDzXcfc7lS8Iv8tbUWDKlvNp+heAo+PhDnWY 8VZWolD2Y2n9HNuBNXhidvIXHyrJbuhtzbamelgnEDx9zKlazeGrbjSZ Kqo=
;; Query time: 0 msec
;; SERVER: 192.168.2.131#53(192.168.2.131)
;; WHEN: Tue Mar 02 16:20:14 JST 2021
;; MSG SIZE  rcvd: 255

flags に ad が含まれているため成功。ad は DNSSEC の検証に成功していることを表す。

DNSサーバを作りたかったらしい

解いた人:nonnonno

参照した問題・解説のサイト:DNSサーバを作りたかったらしい

概要

あなたは同僚から助けを求められた。彼は社内のDNSサーバの構築ログに基づいて環境構築を試みたが、テストとして実行したコマンドでは期待していた出力が行われなかったらしい。原因を調査して、エラーを解決してあげよう。

前提条件

  • ns01はmaster、ns02はslaveサーバとして機能させたい
  • トラブルに関係しない要素については変更しない
  • ns01,ns02についての接続情報が与えられている

初期状態

ns02でdig @localhost red.prob.final.ictsc.netが解決できない

終了状態

  • ns02でdig @localhost red.prob.final.ictsc.netが解決できる
  • 原因が特定されて報告される
    • トラブル解決前に期待されていた動作をしている

考えられる原因・解決方法

digコマンドについて

参考Qiita digコマンドは、DNSサーバに対して問い合わせを行い、その結果を表示する。

ちなみに

DNSサーバにはキャッシュDNSと権威DNSの二種類がある。 権威DNSにアクセスが集中しないよう、しばらくの間はキャッシュDNSサーバが権威DNSから受け取った応答を保持して、その内容を使ってクライアントに応答する。

digコマンドの話に戻るが、digコマンドはBIND 9に付属するコマンドである。
nslookupと機能が似ているが、digコマンドによって得られる情報量が格段に多い。

BIND 9とは

Berkley Internet Name Domainの略で、世界中で最も多く利用されているDNSサーバのこと。
他にもDNS機能を提供するソフトウェアは存在するが、BINDを使うサーバがほとんどである。

DNSサーバの構築トラブルシューティング
本問題のdigの実行結果がわからないので、考えられる要因が多岐にわたる

解法

今回の問題文に、『彼は社内のDNSサーバの構築ログに基づいて環境構築を試みたが、テストとして実行したコマンドでは期待していた出力が行われなかった』とあるので、ログを見る必要がある。
どのログを見るかについては、コマンドを起動するための設定ファイルに関するものを見る必要がありそう。
例えば、今回であればdigコマンドがうまくいっておらず、digコマンドの設定ファイルはBINDのものなので、 systemctl status bind9とする。

このログを見ると、BINDが設定ファイルのエラーによって起動していないことがわかる。
/etc/bind/named.conf.probについてエラーが出ているので、この設定部分を変更する必要がある。

viewとzoneとは

参考リンク https://ygg-tech.com/2020/09/bind_view/

viewは、一言で言ってしまえば「接続条件によってDNSクエリ結果を変える機能」である。
Bindの Ver.8 から使用可能であり、CentOS7であれば Ver.9 なので問題なく利用可能。 viewステートメントによって、アクセス元のIPアドレスに応じて、検索クエリに応答するゾーン情報を分けることができる。
活用する状況としては以下が挙げられる。

  • 社内にWebサーバがあり、複数のLANに足を出している
  • 各LANのクライアントには、自身が所属するLANのWebサーバのIPを返したい
  • DNSは1台で済ませたい

参考リンク https://ygg-tech.com/2020/09/bind_view/の画像の中では、

  • Secure LANに所属するクライアントには192.168.0.1を返す
  • Remote Access LANに所属するクライアントには、172.24.0.1を返す というフローが実現できる。

設定ファイルの書き方は参考リンク参照。

ちなみに、zoneをドメイン名や単一DNSサーバと関連づけることは間違いである。
DNSゾーンは複数のサブドメインを含むことができ、複数のゾーンが同じサーバに存在できる。
ゾーンファイルに、DNSサーバに保存されているゾーン内の全てのドメインの全てのレコードが含まれ、記載される。

解法に戻る

viewの説明箇所で記述したように、複数のviewを使う際に、同じ名前がついた実態は異なるzoneを含んでいても、クライアント毎に異なるレスポンスを返すことが可能である。
しかし時には、複数のviewが同一のzoneを持つことが望まれる場合もある。
そこで用いられるのが in-view zone オプションである。 in-view公式解説
これは一言で言うと、viewをまたいだゾーンの共有である。
in-view オプションによって、事前に設定されたviewの中で定義されたzoneを、viewが参照することができるようになる。
これは設定ファイルにおいて後の部分に記述されているviewを参照することはできない。

2つ以上のviewで同じzone(type slave)を設定するとエラーになるので、2回目以降に使用する際にはin-viewオプションを使うとエラーが回避できる。

BINDの設定方法

DNSサーバ構築手順参考リンク
全体設定ファイルnamed.confは、デフォルトでは/etc/named.confに配置されている。

ちなみに、BINDをchrootしておくと、bind サービスは /var/named/chroot より上のディレクトリには行けなくなり、仮に bind を乗っ取られても他のディレクトリには被害が及ばない。
つまり、セキュリティを確保することができる。

chrootの場合には、自動的に/var/named/chroot/etc/named配下に named.conf などの設定ファイルができている。

systemctl restart namedとして再起動ができる。
ちなみに、named-chrootをインストールした場合、

  • named → 自動起動 disable
  • named-chroot → 自動起動 enable

の設定をしないと、namedが二つ起動している状態になってしまうので注意が必要である。

Nginxが展開できない

解いた人:とり

参照した問題・解説のサイト:Nginxが展開できない

使用環境・ツール

  • Kubernetes
  • Nginx
  • Rook (解説に書いてあった)

Rookとは

kubernetes用のストレージオペレータ。ストレージをストレージソフトウェアを自己管理、自己スケーリング、および自己修復のストレージサービスに変えます。(公式ドキュメントから) なんとなくだけど、ストレージにPodの要素を加えることで、k8sの便利機能(スケーリングや修復)を扱えるようにしたサービスなのではないかと思った。(これはあくまで私の意見)

前提条件

  • /home/user/manifestにあるファイルの内容を変更してはいけない

問題文でされた操作

  • KubernetesのNginx用のマニフェストファイルをApplyした
VM名ホスト名
kzz-k8s-master192.168.12.1
kzz-k8s-node1192.168.12.2
kzz-k8s-node1192.168.12.3
kzz-k8s-node1192.168.12.4

バグの内容

  • NginxのDeploymentがPendingになっている

理想の終了状態

  • pendingとなっていたDeploymentが正常に稼働している。
  • 正常化したDeploymentによって稼働するNginxで、解答するチームの名前が書かれたWebサイトが確認できるようになっている。
  • 開始時と同じマニフェストのみが適用されている。

考えられる検証、修正手順

バグの原因を特定する案

  • ログがあるならログ的なのを確認する → k8s側では特になかった
    • 公式ドキュメントにデバッグ方法があった URL
  • deploymentやserviceのファイルを読んでみる
    • TCP80ポートが接続できる設定になっていない可能性もあると考えたが、その場合はrunning状態で外部IPに接続した時に画面が表示されないという現象が見られるはずなので今回のケースでは除外した。
  • Googleで「nginx Pending k8s」と検索してみた

→ k8sの問題だしこの辺りがあり得そう。今回はこの場合だと考えて答えを作っていく。

修正手順案詳細

デバッグを頑張る
※ ここではhostnamesがnamespaceの名前

  • サービスの存在確認
$ kubectl get svc hostnames
  • サービスはDNS名によって機能しているか
$ nslookup hostnames

Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

> これが失敗した場合、おそらくPodとServiceが異なるNamespaceにあるため、ネームスペースで修飾された名前を試す

DNSについて色々試す。

$ nslookup hostnames.default
> クロスネームスペース名を使用するようにアプリケーションを調整するか、同じNamespaceでアプリとServiceを実行する必要がある

$ nslookup hostnames.default.svc.cluster.local

$ nslookup hostnames.default.svc.cluster.local 10.0.0.10
> 完全修飾名では検索できるのに、相対名ではできない場合、Podの/etc/resolv.confファイルが正しいことを確認する必要があります。Pod内から実行します

いっぱいあって全部キャッチアップするのは無理な気がした。

リソースが不十分な場合

  • クラスターにノードを追加する
    • マニフェストをいじらなくてはいけないのかと思ったけど、kubeadmというコマンドを使うと実行中のk8sに対して追加できるらしい

ノードの追加

kubeadm join --token <token> <control-plane-host>:<control-plane-port> --discovery-token-ca-cert-hash sha256:<hash>

トークン一覧を見てみる

kubeadm token list

トークンは一時的なので、トークンの発行もできる。24hできれる

kubeadm token create
  • 不要なPodを削除する
    • これは問題的にできるのかな🤔 可能性としては低そう
  • Podがノードよりも大きくないことを確認する
    • cpu: 1の場合、cpu: 1.1を要求するPodは決してスケジュールされない
    • キャパシティを調べるコマンド
    kubectl get nodes -o yaml | egrep '\sname:|cpu:|memory:'
    kubectl get nodes -o json | jq '.items[] | {name: .metadata.name, cap: .status.capacity}'
    

hostPortの使用

  • PodをhostPortにバインドすると、Podをスケジュールできる場所の数が制限される
    • この辺りも微妙。おそらくこんな感じで設定するのだと考えられるが、これってマニフェストファイルをいじらないとダメな気がする🤔
    ports:
       - name: liveness-port
       containerPort: 8080
       hostPort: 8080
    

解説

めちゃ調べたんだけど全然違った!😭 むずい。

原因

Rookを削除する際にコンフィグ情報やログデータなどが保存されているdataDirHostPathを削除しなかったこと。

原因究明方法

/home/user/manifestには、Flannel 、MetalLB、Rook、NginxのDeploymentが書かれたtest-nginx.yamlなどのマニフェストが保存されています。test-nginx.yamlはPVC 、そのPVCを/usr/share/nginx/htmlにマウントするNginx のDeployment 及び、Nginx を公開するためのLoadBalancer Serviceを定義します。

ここで、なんのサービスを使っているのかを確認する。PVCはPersistentVolumeClaimの略でユーザーによって要求されるストレージのことらしい。(詳細)。ちなみにFlannelはネットワーキングをいい感じにするやつ。MetalLBはベアメタル用のロードバランサー。ベアメタルってなんだよと思って調べたら「OSやソフトウェアなどがインストールされていないまっさらなハードディスク(物理サーバー)」のことらしいです。ベアメタル覚えた!

問題環境では、問題名にあるNginx だけでなくPVC もPendingとなっています。 PendingになっているPVCをkubectl describe pvc cephfs-pvc で確認すると、failed to provision volume with StorageClassとエラーが出ています。

問題文だけでは知らんがな問題。kubectl describe pvc cephfs-pvc でpvcの状態をみれるのは覚えておいた方がいいかも。基本的にはkubectl describe <サービス> <namespace>でいけるのではないかと。覚えとけば、ぐぐりで乗り越えられそう・

このKubernetesクラスタではRookを利用し、CephをPersistent Volumeとして使っています。このことから、Rookの設定の異常等を予想し、Rookのエラーを確認する必要があります。そのため、/home/user/manifestにあるRookのマニフェスト(cluster.yaml)では、無効化されているcrash-collectorを有効化し適用します。立ち上がったcrash-collectorをkubectl describe で確認すると、Unable to attach or mount volumesとなっています。

ここでcrash-collectorを有効化し適用とあるが、crash-collector知らないとできない気がするな〜〜。無効化することができるのかは微妙。無効以外できたら60%くらいの点は入りそう。

Rookがマウントしてそうな場所を探すと、 cluster.yamlの33行目にdataDirHostPath: /var/lib/rookなる行があります。 そして、この直前の31行目のコメントに、 Important: if you reinstall the cluster, make sure you delete this directory from each host or else the mons will fail to start on the new cluster. と書かれています。

日本語に訳すと「クラスタを再インストールする場合は、各ホストからこのディレクトリを削除してください。そうしないと、新しいクラスタでのmonsの起動に失敗します。」みたいです。

解決手順

解決方法としては、RookのDocumentに書かれた通りにクラスターの清掃を行うだけ。

  • 適用されているRookを削除(ここでは6つのyamlに対してkubectl delete -f hoge.yamlをしている)
  • 該当するディレクトリ(/var/lib/rook)を削除
  • crash-collectorを無効化
    • crashモジュールが提供する機能をRookから使う用のコントローラーらしい
    • 無効にするのに設定がいるのかどうなのかよくわからなかった。
    • https://zenn.dev/satoru_takeuchi/articles/33560b2e753972
  • kubectl apply -f hoge.yamlをする
  • kubectl exec -it ”Pod名” -- bashでpodの中に入る
  • その中のShellで、 echo -e "<a>”チーム名”</a>" > /usr/share/nginx/html/index.htmlをする

便利そうなツールのメモ

何かがおかしい。。。。

解いた人:Hunachi

参照した問題・解説のサイト:何かがおかしい。。。。

使用環境・ツール

  • Go言語

ライブラリ

  • Cobra
    • モダンなCLIアプリケーションを簡単に作れるようにしてくれるライブラリ。

バグの内容

何かがおかしい。。。。の1枚目の画像を参照する。 R1にて自作のルーティングアプリケーションを作ったが、そのアプリケーションのミスでそれぞれのHostにpingを飛ばしてもパケットロスを起こしてしまう。

それぞれのHostにpingを飛ばしても、パケットロスを起こしてしまう。 (pingができる時と出来ないときがある。)

前提条件

  • kernelパラメーターの変更をしてはならない。

理想の終了状態

  • それぞれのHostにpingを飛ばして、パケットロスが起こらない状態にする。

その他、公開してくれている情報

ーー起動方法ーー

cd ~/go/src/github.com/yoneyan/ictsc-ace/cmd/routing go get . go build . sudo ./routing start eth1 eth2 eth3 eth4

(メモ:eth1とかの部分は書き換えてあげる必要がある)

Source Code


技術調査

調査方法

ログの仕込み

log.Println("label", "出力させたいもの")

コードを読んでみる

  • CLIアプリケーションを作るためのライブラリとしてCobraが使われてた。

  • buildとかはできた

    • NICを用意したりパケットを飛ばし合うのが無理だったので、実際に動かして確認はできなかった。
  • コマンドとしてあるもの

    • routor init
    • routor start ←今回叩いているコマンド
    • routor recieve
    • 未実装
      • routor init
      • routor store
      • routor router
  • ファイル別の実装されている内容

    • cmd/routing/routing.go
      • メイン関数が書いてあるだけ。
    • pkg/routing/arp/*
      • Macアドレスを取得するためのコード(arp.go)とMacアドレスの情報を入れるための構造体(interface.go)
    • pkg/routing/cmd/*
      • CLIのコマンドの実装
    • pkg/routing/route/*
      • 今回使われていない
    • pkg/routing/router/*
      • ルーティングの処理が書かれている部(router.go)とルーティング情報を詰めるための構造体(interface.go)
    • pkg/routing/tool/*
      • 今回使われていない
    • pkg/routing/interface.go
      • 今回使われていない

考えられる検証手順

  • コードを読む(読んだ)
  • Logを埋め込む
  • どんなパケットが落ちてるかみる

原因の候補

  • コードを読んだ感想
    • Ipv6に対応してないから?
      • これだったらトラコンじゃない。
    • MacアドレスとIPアドレスの組みが変わったときに対応できないから?

解説

原因

原因はGolangにてルーティングの実装に問題があることが原因です。 Golangにて、Channelを使った処理にミスが生じており、適切なNICにパケットを送り出せていないため起きている問題です。

HostA => HostD宛にPingを飛ばした場合でも、Channelの実装ミスによりHostA,B,C,Dのどれかにパケット転送してしまうというバグによって引き起こされる問題です。

解決方法

pkg/routing/router/router.go
 
 
 func routerReceive(device string) error {
                                if err != nil {
                                        log.Println(err)
                                }
+                       } else {
+                               packets <- msg
                        }
                }
        }()

解説に対するメモ

router.go内の、

msg := <-packets
			if msg.Interface == mac.String() {
				err := handle.WritePacketData(msg.Packets)
				if err != nil {
					log.Println(err)
				}
			}

の部分で、あるスレッド(goroutine)が指定したネットワークデバイス(NIC)で送信しないパケットを受け取った場合にパケットを破棄してしまっていることが原因。 本当は、自分が送信するパケットじゃなかったら他のNICを指定したスレッド(goroutine)に渡してあげないといけない。 と言うことに気が付けなくてピエン🥺

採点基準

パケットロスをせずにpingが飛ぶこと(100%)

ボク わるいフレームワークじゃないよ

解いた人:Hunachi

問題

参照した問題・解説のサイト:ボク わるいフレームワークじゃないよ

使用環境・ツール

  • Python
  • Django

バグの内容

curl 192.168.7.2:8000/message/ではエラーが返ってくる。

理想の終了状態

192.168.7.2:8000/message/にアクセスし、メッセージを確認できるようにすること

初期状態

/home/ictscpython3 manage.py runserver 192.168.7.2:8000を実行し、curl http://192.168.7.2:8000/message/実行してもメッセージが見れない。


考えられる検証、修正手順

  • curlを叩いた時のエラーの内容を確認する。
    • curl -vなどする。
  • python3 manage.py runserverでググる
    • djangoに関する情報ばっかり出てくるので,djangoが使われている?
  • 以下の技術調査をしたときの構成と比較してみておかしなとこがないかを確認🔍🔍

技術調査

Djangoわからんので,とりあえず使ってみる

  1. Djangoとは
    • Webアプリを簡単に作れるようにするためのPythonフレームワーク
    • https://www.djangoproject.com/
  2. Pycharmでプロジェクトを作った。
    • ぼたんぽちぽちするだけ。
  3. セットアップするためのコマンドをぽちぽち
    • python3 manage.py migrate
    • python3 manage.py createsuperuser
  4. 動いてるかの確認
    • python3 manage.py runserver
    •  Starting development server at http://127.0.0.1:8000/とのことなので, curl http://127.0.0.1:8000/
    •  なんかエラーもテキストも返ってこなかった.
    •  アクセスすると
    •  寂しいので, urlpatternspath('', admin.site.urls),を追加した。(←なぜかadminページを表示させる人。。)
    •  verboseオプションつけてリベンジだぜ!
    • < HTTP/1.1 302 Found < Date: Mon, 23 Aug 2021 10:00:00 GMT < Server: WSGIServer/0.2 CPython/3.9.6 < Content-Type: text/html; charset=utf-8 < Location: /admin/login/?next=/ とのことなので, curl http://127.0.0.1:8000/admin/login/ で結果を見てみる。
    • いい感じにhtmlが返ってきていた。
    •  せっかくなのでアクセスもしてみる。
    •  なんかいい感じに表示されていた。動いてそう。
  5. /message/時の動きも知りたい。
    • とりあえずhttp://127.0.0.1:8000/message/ができるようにしする。
    • 公式チュートリアル https://docs.djangoproject.com/en/3.2/intro/tutorial01/#the-development-server を参考に進めていく。
    • 王道を行きたいので公式の真似して(公式ではpollsだけど)message appを作ってみる。
    • python3 manage.py startapp message
    • なんかmessageディレクトリができた。楽ちんだ。
    • あとはチュートリアルを真似てviews.pyのコード書いた。
      • https://docs.djangoproject.com/en/3.2/intro/tutorial01/#write-your-first-view
      • def index(request): return HttpResponse("This is Message's View.")
    • urls.pyurlpatternspath('message/', message.views.index),追加.(チュートリアルより最短経路なので真面目に何か作るときはmessage.urls.pyも作るなどちゃんとしたがいいと思われる。)
    • python3 manage.py runserverし直す。
    • curl http://127.0.0.1:8000/message/
      • 結果:This is Message's View.%
  6. ホストを指定して実行してみる
    • ループバックアドレス以外を指定してみたいので,自分の環境で使えるIPアドレスを探した
      • 172.16.0.21が自分のPCに割り当てられてたのでこれを使う
      • もっといい方法色々ありそうだなぁ。
    • python3 manage.py runserver 172.16.0.21:8000
    • curl http://172.16.0.21:8000/message
    • なんかさっきと違う。。
    • サイトを見てみる
    • settings.pyALLOW_HOSTS = ['172.16.0.21']
      • 同じエラーを起こしてブログ書いてる人がいた。
    • 治った! http://172.16.0.21:8000/message
    • curl http://172.16.0.21:8000/message/
      • This is Message's View.%
    • http://172.16.0.21:8000もうまく動いてる!

解説

原因

Djangosettings.pyの中のALLOWED_HOSTSに何も指定されていなかったため、トラブルが発生していた。

解決方法は,↑の技術調査と同じ◎!

curlした時に,

<div id="explanation">
    <p>
      You're seeing this error because you have <code>DEBUG = True</code> in your
      Django settings file. Change that to <code>False</code>, and Django will
      display a standard page generated by the handler for this status code.
    </p>
  </div>

をみるとdebugがTrueになっているからバグってると言われるので,これをFalseにする。つまりsettings.pyDEBUG=Falseにする。

すると, CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.というエラーが出るので,ALLOW_HOSTS = ['172.16.0.21']としてあげる。

ふなちの感想

:100:

curlの結果だけで解決できるようになりたい!

採点基準

  1. curl http://192.168.7.2:8000/message/ 実行してもメッセージが見れない。(0%)
  2. curl http://192.168.7.2:8000/message/ でメッセージが確認でき、かつ回答時にメッセージを記載している(80%)
  3. curl http://192.168.7.2:8000/message/ でメッセージが確認でき、かつ回答時にメッセージを記載している(100%

インターネット壊れた

解いた人:nonnonno

参照した問題・解説のサイト:インターネット壊れた

使用環境・ツール

BGP(プロトコル)
ルータ
PC

問題文でされた操作

  • BGPの検証ネットワークを構築してみたところ、PC1<–>PC2間でpingが飛ばない
  • ただしR3実環境ではとある組織の所有物なので設定の変更はできないものとし、R3は何かしらのダイナミックのルーティングプロトコルが動作している

バグの内容

手元のPC(おそらくPC1)から $ ping 192.168.19.30 をしても応答がない。

理想の終了状態

PC1からPC2までpingが正しく飛ぶ。

考えられる原因とその検証・修正手順

pingが飛ばない原因について
参考

PCのNICの状態、IPアドレス設定など、PC側に問題がないかどうか

  • PCのNICにLANケーブルは接続されているか
  • PCのNICのLANアダプタの設定は有効か ifconfigコマンドで(aオプションとか付けるとよさそう)NIC情報を確認
    4行目冒頭あたりにUPと書いてあれば正常に稼働していることを意味する また、同じくifconfigコマンドで、割り振られているIPアドレスやサブネットマスク、デフォルトゲートウェイの設定情報を確認する

デフォルトゲートウェイへのpingを飛ばし、L2,L3デバイス設定の確認

デフォルトゲートウェイとなるL3スイッチorルータでICMPをブロックする設定がないかどうか確認
ACLの定義参考リンク参考、以下のように設定可能

Cisco(config)# access-list 101 permit icmp any any echo-reply
Cisco(config)# access-list 101 permit icmp any any time-exceeded
Cisco(config)# access-list 101 permit icmp any any unreachable
  • PCに設定しているIPアドレス、サブネットマスクの値が間違っている。

  • デフォルトゲートウェイのL3デバイスのIPアドレスの設定が間違っている。

  • デフォルトゲートウェイのL3デバイスのLANポートを有効化(no shut)していない。

  • デフォルトゲートウェイのL3デバイスのLANポートにストレートケーブルまたはクロスケーブルの適切なLANケーブルが接続されておらず、ポートLEDがグリーン状態ではない。

  • PCとデフォルトゲートウェイのL3デバイスの間にあるL2スイッチで、適切なVLANが設定されていないなど、L2スイッチに問題がある。

宛先デバイスに到達するまでの経路の確認

tracerouteコマンドで、経路を確認し、どのL3デバイスまで意図したようにルーティングできているか確認できる。

意図しないルーティングテーブルがないか確認

routeコマンドで、ルーティングテーブルの表示・設定を行う。

MTU値を確認

今回の問題では、ping自体も通らない。
ルータ間でMTUの値が異なると、OSPF(Open Shortest Path First、最短経路を割り出すためのプロトコル)が機能せず、ネイバー関係になることができない。
一方で、pingは通るのに他の通信データの送受信ができない、というケースもあるそうで、pingのデータサイズは通れるが、通信データのパケットサイズはMTUを超過しているために破棄されることがある。

解説

原因

R2とR3のルータ間のMTUが合っておらず、通信ができない

解決方法

両側のルータにアクセス可能な場合:Cisco解説サイト

今回の問題では、R3にアクセスすることができない。
よって、PC1からR2の192.168.19.26までpingを飛ばして、その経路上で、パケットがMTUを超過した際に返ってくるエラーメッセージからR3の192.168.19.25からR2の192.168.19.26までの間のMTU値を調べることができる。
具体的には以下。
ping -s MTU値 -M do IPアドレスとするため、今回はping -s MTU値 -M do 192.168.19.26

ちなみに、MTU値を変えて複数回pingをする場合、linuxがMTU値をキャッシュするためpingをする前にsudo ip route flush cacheを実行してキャッシュを削除する必要がある。

R3のMTU値が解ればR2のR3側のインターフェースに同じMTU値を設定すればOSPFのネイバーが確立できるのでEBGPのネイバーも張ることができ、PC1<–>PC2間でpingが通る。
MTUの設定は、#ip mtu 1400とすればできるはず。

参考リンクより

なお、R2のVyOS上でtcpdump(ネットワーク通信の生のデータをキャプチャし、その結果を出力してくれるキャプチャツール)を行って、OSPFのパケットの中身からMTU値を割り出す方法もある。

tcpdump使用法参考リンク

とんとんとんねる

解いた人:nonnonno

参照した問題・解説のサイト:とんとんとんねる

使用環境・ツール

コントロールプレーンがないユーザープレーン環境

コントロールプレーン・ユーザプレーンとは

コントロールプレーンとは、ネットワークの制御を担うもののこと。
単一のソフトウェアによってネットワークの構成や制御を柔軟かつ動的に行う技術の総称のことを指す。
ユーザプレーン機能は、無線アクセスネットワークとデータネットワークとの間のデータパケットを処理する。
ちなみにデータプレーンとは、あるインタフェースから別のインタフェースにパケット・フレームを転送する全ての機能とプロセス。

5Gをテーマとする上でコントロールプレーン・ユーザプレーンの働きを考える。
参考リンク

そもそも端末は、通信するときに2種類の信号を基地局とやり取りしている。1つは、端末がどの基地局と接続するのか、端末が通信できる状況にあるかどうかを判断する制御信号。この信号を「コントロールプレーン(Control-Plane)信号」といい、その頭文字をとってCプレーン信号と呼ぶ。

もう1つは、写真をダウンロードしたり、ウェブサイトでショッピングをしたりといった、実際のデータを伝送する信号。これを「ユーザーデータ(User-Plane)信号」、頭文字をとってUプレーン信号という。

CとUは端末と基地局の間でやりとりする信号のこと。
従来、端末が通信するとき、2つの信号を必ず同じ基地局とやりとりしていた。
5Gネットワークでは、このCプレーンとUプレーンを分離して、別の基地局と通信できるようにしている。
なお、4Gの基地局がカバーするエリアの中に5Gの基地局のエリアが含まれていることは事前知識。
Cプレーンは広範囲をカバーする4Gの基地局に接続し、Uプレーン信号は速度で勝る5Gの基地局に接続する。
そうすることで、基地局の切り替えがスムーズになり、まだ5Gに対応していないエリアでも、4G->5Gへの切り替えによる通信切断が防げる。

トポロジ

とんとんとんねるの一枚目の画像。

UPFとは

UPFはUser Plane Functionの略称で、スマホなどで利用するデータパケットの転送を担うルータのこと。

DNとは

DNはData Networkの略で、インターネットや何らかのサービスを提供するサーバが接続されたネットワークの総称である。

問題文でされた操作・バグの内容

5G環境を構築する実験環境のためのコントロールプレーンがないユーザープレーン環境で、手元のgNodeBから $ ping 192.168.21.50 をしても応答がない。

理想の終了状態

手元のgNodeBから $ ping 192.168.21.50 をすると応答が返ってくる。
通信はトンネルインターフェースで行うこと。

トンネルインタフェースとは

間に複数のネットワーク機器が挟まれた状態のネットワーク機器同士をあたかも直結しているかのように見せる、論理インタフェース。

考えられる原因とその検証・修正手順

今回はこのケースには当てはまらないが、一般的に、VPN接続自体が失敗する場合の原因のほとんどはVPNルータの設定ミスである。
宛先となる対向ルータのアドレスや事前共有鍵などの通信に必要な情報を合わせる必要があり、それらが揃っていない場合が多い。
以下に、VPN接続が確立しているがpingなど実際の通信ができない場合に考えられる原因を挙げる。
参考

パソコンのファイアウォールの設定

悪意のある第三者に端末の存在やOSなどの情報を知られて攻撃に悪用されることを防ぐため、ファイアウォールがpingを通さない設定になっている可能性がある。
そのため、一旦ファイアウォールをオフにしてみることが有効である。

パケットフィルタリングの設定ミス

ルータの設定として、pingなどのICMPのプロトコルを利用するものは通過するようにフィルターで定義されていないと、pingパケットは通過できない。

経路設定ミス

VPNでは、宛先ネットワークとして対向の拠点のネットワークアドレスを設定する必要があり、ミスが起きやすい箇所である。

解説

原因

GTPv1-Uと呼ばれる、モバイルにおけるトンネリングプロトコルのユーザプレーン通信を題材とした問題。
これはGTPv1-UにはTEIDと呼ばれるトンネリングのアンカーになるIDが存在するが、それらが設定されていないことに起因する。

GTPv1-Uとは

GTPとは、General Packet Radio System Tunneling Protocol というトンネリングプロトコルである。
GTPプロトコルにはU-planeとC-planeがある。

TEIDとは

Tunnel Endpoint Identifierの略称。
GTPで各セッションを認識するための識別子である。

本問題の解き方の鍵としては、GTPv1-Uがどのようなフォーマットで行われるかを理解する必要がある。

解決方法

tcpdumpを使いこなして解く。

サービスの障害やエラーを表示するロギング・サービスであるjournalctlで、実行コンフィグがあるディレクトリを確認できる。
すると、systemdの挙動のコンフィグが /opt/netprob/init.shで展開されていることがわかり、実行コンフィグがここにあることがわかる。
gNBというホストにおけるinit.shのコンフィグの --f-tid 200として、 hdr-creation 0 200 192.168.21.33 2152 として期待されている200のTEIDに揃える。

#!/bin/sh
sleep 0.1
ip addr add 10.0.0.1/24 dev lo
sh /home/adam/libgtp5gnl/tools/gtp5g-link add gtptun --ran &amp;
sleep 0.1
sh /home/adam/libgtp5gnl/tools/gtp5g-tunnel add far gtptun 1 --action 2
sh /home/adam/libgtp5gnl/tools/gtp5g-tunnel add far gtptun 2 --action 2 --hdr-creation 0 100 192.168.21.34 2152
sh /home/adam/libgtp5gnl/tools/gtp5g-tunnel add pdr gtptun 1 --pcd 1 --hdr-rm 0 --ue-ipv4 10.0.0.1 --f-teid 200 192.168.21.33 --far-id 1
sh /home/adam/libgtp5gnl/tools/gtp5g-tunnel add pdr gtptun 2 --pcd 2 --ue-ipv4 10.0.0.1 --far-id 2
ip r add 192.168.21.48/28 dev gtptun

受け取り側のTEIDと送られる側のTEIDを期待通りにしておく必要がある。

Server Sent Eventsが動作しない‼

解いた人:maimai-y

参照した問題・解説のサイト:Server Sent Eventsが動作しない‼

使用環境・ツール

  • nginx

nginxとは(https://wa3.i-3-i.info/word12859.html):

Apacheのライバル。「Webサーバのソフト」。

普通のコンピュータに「Webサーバのソフト」を入れることによって、Webサーバとしてのお仕事ができるコンピュータになります。

nginxの一番の特徴は、リバースプロキシの機能があることでしょうか。

問題文でされた操作

reverse proxyとして、nginxを構築した。

reverse proxyとは(https://wa3.i-3-i.info/word1755.html):

Webサーバさんの身代わりになってホームページのファイルを返してくれるサーバさんのこと。

ちなみに、リバースプロキシを使うメリットは

(1).身元を隠せる (2).負荷分散ができる

バグの内容

webページ自体は見れるが、sse(server sent events)はうまく動作しない。原因を糾明してsseが動作するようにしてほしい。

server sent eventsとは(qiita):

いわゆる "リアルタイム" イベントを サーバ -> クライアント に向けてプッシュできる技術です。

Websocketsとの違い:

Websockets は SSE に比べてより柔軟で強力な機能を持っていますが、実装の複雑さは SSE とは対象的に非常に高コストになりがちです。また大きな違いとして、Websockets では サーバ <-> クライアント 間での双方向のリアルタイム通信が可能ですが、SSE はあくまでも サーバ -> クライアント (一方向) のデータのプッシュのみをサポートしています。

理想の終了状態

Reverse Proxy上で $ curl -N localhost/eventをしたら、

$ curl -N localhost/event 
data: 1 
data: 2
data: 3 
...以下Ctrl+Cが入力されるまで続く

考えられる検証、修正手順

「sse nginx」だけでググったら1番目から6番目までバッファに注意であることがどこかしらに書いてあった。

  • https://qiita.com/okumurakengo/items/cbe6b3717b95944083a1#nginx%E3%81%A7%E5%AE%9F%E8%A1%8C

    • apacheだと特に問題なく動いたのですが、nginxだとうまく行きませんでした、 下のコードだと1秒おきに出力して欲しいのですが、バッファされているようだけのようでタイムアウトしてしまいました。

    • PHPのファイルにX-Accel-Buffering: noを追加するとうまく行きました。

    • nginxの設定ファイルをいじるという方法でも大丈夫なようです。

      gzip off;
      proxy_buffering off;
      fastcgi_keep_conn on;
      fastcgi_max_temp_file_size 0;
      fastcgi_buffering off;
      
  • https://qiita.com/willow-micro/items/5b245076101460d9dfd6

    • Nginxに接続すると,SSEの処理だけが絶望的に遅れる

      原因:Nginxのリバースプロキシによってバッファリングが行われるため

    • FlaskからJavascriptのEventSourceに渡すレスポンスに、ヘッダX-Accel-Buffering: noを付与する

    • Nginx側の設定に以下を追記すれば良いとの情報がみられる

      proxy_set_header Connection '';
      proxy_http_version 1.1;
      chunked_transfer_encoding off;
      proxy_buffering off;
      proxy_cache off;
      

解説

この問題でServer Sent Eventsがうまく動作しなかった原因は、/etc/nginx/nginx.conf内のproxy_buffering on;だったことです。nginxのproxy_bufferingが有効な状態だとnginxがバッファリングしてしまい、クライアントがレスポンスを受け取れません。

想定解

/etc/nginx/nginx.conf内のproxy_buffering on;proxy_buffering off;に置き換えて、nginxを再起動させること

その他解法

nginxがデフォルトで使用するhttp/1.0はkeep-aliveを使用することは非推奨とされているので、 /etc/nginx/nginx.conf内のproxy_buffering on;proxy_http_version 1.1;に置き換えて、nginxを再起動させることでもServer Sent Eventsを動作させることができます。

採点基準

部分点は付けず、 Reverse Proxy上で $ curl -N localhost/eventをしたら、

$ curl -N localhost/event` `data: 1` `data: 2` `data: 3` `...以下Ctrl+Cが入力されるまで続く

と返ってくるような解答に満点をつけました。

参照した問題・解説のサイト: https://blog.icttoracon.net/2020/11/02/

hostnameでつながらない!!

みんなでワイワイ解いた。

参照した問題・解説のサイト:hostnameでつながらない!!

使用環境・ツール

  • docker-compose
  • wordpress

問題文でされた操作

DockerでWordpressの構築を試みる
それにあたって、docker-compose.ymlファイルで指定したホスト名でデータベースに接続したい

バグの内容

~/wordpress/docker-compose.ymlを用いてdocker-compose upをした時にwordpressのコンテナがデータベース接続エラーのログを残して立ち上がらない。

理想の終了状態

curl localhost:8000 -Lで正常に200レスポンスが返ってくる。


考えられる検証、修正手順

バグの原因を特定する案

  • docker-compose.ymlを確認する
version: '3.3'
 
services:
   db: # DO NOT CHANGE THIS LINE
     image: mysql:5.7
     hostname: database # DO NOT CHANGE THIS LINE
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: 8MvAMcDAirP8
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: DDzk6ERU33Rc
 
   wp:
     depends_on:
       - db
     image: wordpress:latest
     hostname: wordpress
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: database:3306 # DO NOT CHANGE THIS LINE
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: DDzk6ERU33Rc
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data:

  • docker composeでwordpressを立ち上げる方法のymlファイルとの違いを確認する(特にDB周り)

    • depends_on:で依存関係を指定しているか?
    • mysqlの存在する場所を確認する。位置とか
      volumes:
         - db_data:/var/lib/mysql
      
  • mysqlのパスワードを確認

    • https://qiita.com/go_glzgo/items/3520818659a07bd17839
  • データベース自体が破損している

  • データベースサーバの異常

    • トラフィック急増による過負荷
  • wp-config.php ファイルのユーザー名とパスワードの情報が正しくない

    • WordPressでデータベースに接続する場合、設定情報はwp_config.phpに記述してある。この内容が、docker-compose.ymlで指定している内容と一致しているかを確認する。
  • db:3306 のデータベース サーバーに接続できない

バグの原因が見つかった際の手順

修正手順案

  • 今回のエラーは、
  • hogeコマンドを打つ

使用した修正手順(壊れた場合ように間違えて踏んでしまった手順は消さずに残しておく)

  1. docker-compose.ymlファイルを変更した。
****

解説

原因

  • 任意のホスト名でコンテナの名前解決を行うにはエイリアスの設定が必要だった
     networks:
       default:
         aliases:
           - database

解決方法(もっと詳しい説明を書き加える)

docker-compose.ymlを以下のように書き換える。

(# DO NOT CHANGE THIS LINEはヒントになるかもしれない)

version: '3.3'
 
services:
   db: # DO NOT CHANGE THIS LINE
     image: mysql:5.7
     hostname: database # DO NOT CHANGE THIS LINE
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: 8MvAMcDAirP8
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: DDzk6ERU33Rc
     networks:
       default:
         aliases:
           - database #☆
 
   wp:
     depends_on:
       - db
     image: wordpress:latest
     hostname: wordpress
     ports:
       - "8000:80"
     restart: always
     environment:
       # db:3306でないので動かない ☆
       WORDPRESS_DB_HOST: database:3306 # DO NOT CHANGE THIS LINE
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: DDzk6ERU33Rc
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data:

ダイエットしようぜ

解いた人:とり

参照した問題・解説のサイト:問題文

使用環境・ツール

  • Docker
  • Go

問題文でされた操作

  • GoでSHA256するhash.goを作成し、コンテナが欲しかったためDockerfileを作成
  • ビルドしてイメージを作成した

バグの内容

  • 作ったDockerImageが大きすぎてテンションが上がらない。
  • ヒント: あなたには、Dockerfileを編集したりビルドコマンドを変えたり、あるいはビルド後のイメージに対してなにかしたりしてイメージを小さくしてほしい。

条件

  • $ docker imagesで当該イメージを見ると796MBである。
  • Dockerのコマンドはsudoなしで実行できる。
  • hash.goを編集してはならない。
  • もしよければ$ make buildでDocker imageを作って欲しい。(任意)

理想の終了状態

  • $ docker imagesで当該イメージを見ると796MBより小さくなっている
    • 可能な限り小さくしてほしい

考えられる原因

  • Dockerfileに必要ないものを記述している
  • 軽量化を頑張っていない

解決方法

  • Multi-stage builds
  • Alpine Linux
  • 不要なパッケージやファイルの削除 https://qiita.com/ytanaka3/items/8c308db2ee58ea63626a

参考: YouTube

  • Small Base image
    • デフォルトで軽量化しているimageを提供している場合がある
      • node.jsならslimバージョンが存在する(Docker側から公式で)
    • 存在しない場合はAlpine Linuxをスタートにしてコンテナを作る
      • Alpine Linux?
  • Builder Pattern
    • インタープリター -> 直接コードを実行する
    • コンパイラ言語 -> コンパイルして実行する
      • コンパイラした時のツールは実際のDocker imageを実行する際には必要ない

重いやつ

FROM go:onbuild
EXPOSE 8080

軽めにしたやつ

FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp
EXPOSE 8080
ENTRYPOINT ./goapp

変更した点:

  • オンビルドイメージからAlpine Linuxに変更
    • いつも見るやつだ!!!

さらに軽く変更

FROM golang:alpine AD build-env
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp

FROM alpine
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=build-env .app/goapp /app

EXPOSE 8080
ENTRYPOINT ./goapp

変更した点:

  • 未処理のimegeをFROM alpineで使った
  • Alpine LinuxではSSL認証が入ってなくてHTTPSが失敗するため、ルートCA認証をインストールする
  • copyでコンパイル済みのコードを2こ目のコンテナにコピーする

これをマルチステージビルドというらしい! 700MB -> 12MBになる。わんちゃん問題はこれ参考にした説までもあるな🤔


解説

解法

  • ベースイメージをalpineベースのものに変える(312MB)
  • docker-slimを使う(57.9MB)
  • マルチステージビルドを使う(8.14MB)

得点

  • (ベースイメージをAlpineにして)サイズを400MB以下にした: 40%
  • (Docker-slimを使って)サイズを100MB以下にした: 70%
  • (Multi Stage Buildを使って)サイズを10MB以下にした: 100%

Multi Stage Buildをすべきみたいだ

解決方法(もっと詳しい説明を書き加える)

docker-slimを使う

  • 導入方法
$ curl -L -O https://github.com/docker-slim/docker-slim/releases/download/1.22/dist_linux.tar.gz
$ tar zxvf dist_linux.tar.gz
$ cd dist_linux/
$ ls
docker-slim  docker-slim-sensor
# 多分pathを通す。まあ通さなくても実行できるが
$ docker-slim [version|info|build|profile] [--http-probe|--remove-file-artifacts] <IMAGE_ID_OR_NAME>

https://qiita.com/ryuichi1208/items/c96d39a57e11d54f02bf

マルチステージビルドを使う

さっき説明した感じのやつ

FROM golang:1.11
WORKDIR /work
COPY ./hash.go /work
RUN go build -o app hash.go
 
FROM alpine:latest # FROM scratchでもいいっぽい。
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /work/app .
CMD ["./app"]

最初の4行でビルドし、次の5行でビルドした実行ファイルのみalpineにcopyするスタイル。

その他

  • go build -ldflags '-s -w' を使うと実行ファイルが小さくなるらしい

    • -ldflags '[pattern=]arg list'
      • arguments to pass on each go tool link invocation.(ツールリンクの起動時に渡される引数です。)
    • DWARF とシンボルテーブルの情報をバイナリに残さずコンパイルできる
      • https://qiita.com/kitsuyui/items/d03a9de90330d8c275c8
    • -ldflags="-w" でDWARFのシンボルテーブルを生成しないようにする
    • -ldflags="-s"によってデバッグのために使われるシンボルテーブルを全部生成しないようにする
      • http://tdoc.info/blog/2016/03/01/go_diet.html
      • このサイトにはUPXの話も載っている
  • UPX圧縮

    • ちょっとだけ(1MB)圧縮される
    • バイナリは半分くらいになる
    • コマンドでできるっぽい
    • https://future-architect.github.io/articles/20210520b/

備品は何処へ

解いた人:Hunachi

参照した問題・解説のサイト:備品は何処へ

使用環境・ツール

  • MySQL

初期状態

  • MySQLが起動しroot権限にて正常動作することを確認している
  • 使用するデータベースはListである
  • データベースListのテーブルには以下がある
    • Equipment_list(備品リスト)
    • Order_company_list(注文会社リスト)
    • Manufacturing_company_list(製造会社リスト)
    • Location_list(所在地リスト)

終了状態

  • 提出されたCSVデータは値段(priceカラム)の高い順位にソートされている
  • 提出時に付随するカラムは以下の通り
    • Equipment_list.ID
    • Equipment_list.Name
    • Order_company_list.Name
    • Manufacturing_company_list.Name
    • Equipment_list.Price

技術調査

MySQLの使い方

CSVを生成する方法

SELECT * FROM {DB_NAME} INFO OUTFILE 'hoge.csv'

INTO OUTFILE 'hoge.csv'のところで出力するファイルを指定している。

参考:https://qiita.com/catatsuy/items/9fdf4423d5f4885b9bf9 csvからデータのimportをする方法も書いてるサイト: https://proengineer.internous.co.jp/content/columnfeature/6776#section100

テーブルの結合方法

外部結合

条件に一致するもの同士を結合させるのに加えて、どちらかのテーブルにしか存在しないレコードも残して結合する。 https://zenn.dev/naoki_mochizuki/articles/60603b2cdc273cd51c59

LEFT OUTER JOINの場合は、先に書いたテーブル側のカラムが消えない。 RIGHT OUTER JOINの場合は、後に書いたテーブルのカラムが消えない。

SELECT * FROM hoge
    LEFT OUTER JOIN piyo ON hoge.id = piyo.id;

↑hogeのうち、どのpiyo.idとも一致しないhoge.idのレコードも残されてpiyoの方の値はnullになる。どのhoge.idとも一致しないレコードは消される。

内部結合

テーブルの指定したカラムが一致するものだけを結合する方法。 条件に合う相手がいないレコードは消される。 https://zenn.dev/naoki_mochizuki/articles/60603b2cdc273cd51c59

SELECT * FROM hoge
    INNER JOIN piyo ON hoge.id = piyo.id;
自然結合

結合条件として、同じ名前のカラム同士で結合する場合、NATURALを付けることで結合条件を省略することができる方法。 https://www.dbonline.jp/sqlite/join/index4.html

SELECT * FROM hoge
    NATURAL INNER JOIN piyo ON hoge.id = piyo.id;
複数テーブルの結合方法
SELECT * FROM hoge
    INNER JOIN piyo ON hoge.id = piyo.id
    INNER JOIN fuga ON piyo.f_id = fuga.id;

のように書けばいい。

解決方法

コーディングテストのようにいい感じのSELECT文を書けば良さそう。(トラブルシューティングではなさそう)

STEP 0.

DB(List)に接続する。

  • sudo mysql
  • mysql> use List;

一応テーブルの構造も確認する

  • mysql> desc List;

STEP 1.

各テーブルのカラムを確認するために、それぞれに対して mysql> SELECT * FROM {DB_NAME} をする。

IDの指定が被っている部分を確認して結合する時の条件を考える。

STEP 2.

SELECT文をかく。

  1. 必要なカラムを出力するようにする
SELECT e.id, e.name, o.name, m.name, e.price
  1. テーブルの結合を行う どういうデータが入ってるのかよくわからないので、内部結合で行うべきか外部結合で行うべきかわからないぞ。(内部結合で書いてみるぞい。)
FROM Equipment_list as e 
INNER JOIN Order_company_list as o ON {条件} 
INNER JOIN Manufacturing_company_list as m ON {条件}
INNER JOIN Location_list as l ON {条件}
  1. 並べ替える条件を入れる
ORDER BY e.price DESC
  1. CSVにするように
INTO OUTFILE 'submit.csv'
  1. 今までのを結合
SELECT e.id, e.name, o.name, m.name, e.price
INTO OUTFILE 'submit.csv'
FROM Equipment_list as e 
INNER JOIN Order_company_list as o ON {条件} 
INNER JOIN Manufacturing_company_list as m ON {条件}
INNER JOIN Location_list as l ON {条件}
ORDER BY e.price DESC;

解説

複数のテーブルから必要な情報を結合し情報を出力できるかという問題です。 余分なデータを表示させたくないため内部結合を使います。

Equipment_listのIDを各テーブルのIDと結合させ表示、そして条件に合わせて検索、ソートを行うという問題でした。

答え

select Equipment_list.ID,Equipment_list.Name,Order_company_list.Name,Manufacturing_company_list.Name,Equipment_list.Date,Equipment_list.Price  from Equipment_list 
inner join Order_company_list on Equipment_list.Order_company= Order_company_list.ID
inner join Manufacturing_company_list on Equipment_list.Manufacturing_campany = Manufacturing_company_list.ID
inner join Location_list on Equipment_list.Use_place = Location_list.ID
where Manufacturing_company_list.ID=5 and Location_list.ID<=5
order by Price DESC

で確認した後、

$(sudo) mysql -u root -p -D List -e " select Equipment_list.ID,Equipment_list.Name,Order_company_list.Name,Manufacturing_company_list.Name,Equipment_list.Price  from Equipment_list inner join Order_company_list on Equipment_list.Order_company= Order_company_list.ID inner join Manufacturing_company_list on Equipment_list.Manufacturing_company = Manufacturing_company_list.ID inner join Location_list on Equipment_list.Use_place = Location_list.ID where Manufacturing_company_list.ID=5 and Location_list.ID<=5 order by Price DESC;" |sed 's/\t/,/g'> re.csv

CSV形式で出力↑。

$cat re.csv
ID,Name,Name,Name,Price
24,pc_x,Order_company_E,Manufacturing_company_E,1600
17,pc_q,Order_company_J,Manufacturing_company_E,800

解説に対するメモ

コマンドラインのオプションたち 参考:https://qiita.com/yulily@github/items/54cb6ccaacf39977455c

自分が、特定の製造会社のパソコン(Manufacturing_company_E)、特定の建物(HQ)で相性が悪く交換するためリストを作成してほしいという条件をつけろという部分の意味が理解できてなくて、 where Manufacturing_company_list.ID=5 and Location_list.ID<=5 をつけ忘れたことが判明した。

本来すべきだったと思われる追加の作業

特定の条件を見つける。

SELECT * FROM Manufacturing_company_list WHERE Manufacturing_company_list.Name = 'Manufacturing_company_E';
SELECT * FROM Location_list WHERE Location_list.Name = 'HQ';

これらの結果から、以下を見出す必要があった。

where Manufacturing_company_list.ID=5 and Location_list.ID<=5

採点基準

  • 上記のカラムが表示できるSQL文を作成できている。[40%]
  • 正しく出力できている。[20%]
  • 売り上げ1位から順表示ができている。[40%]

jsonが壊れた!!

解いた人:maimai-y

参照した問題・解説のサイト:jsonが壊れた!!

使用環境・ツール

  • Go
  • json

問題文でされた操作

GoでAPIサーバを作った。

バグの内容

json文字列をparseするときにデータがlossしてしまう。

初期状態

VM名 gogo には、golang 1.14.6がインストールされている。 VMのホームディレクトリにはtestcode.go が置かれている。

$ go run testcode.go を実行すると以下の結果が得られる。

  • 実行結果
{10 Gopher 0}

理想の終了状態

コマンド $ go run testcode.go を実行すると

{10 Gopher passwordnohash 99}

上記の結果が得られる。

また、問題の解決が永続化されている。


考えられる検証、修正手順

バグの原因を特定する案

  • testcode.goの問題
    • 構造体とJSONの構造をそろえて定義してあるか確認する
    • nullがjsonに含まれていたときのことを考慮しているか確認する
      • バージョンによって、nullがjsonに含まれていると扱い方によってはエラーになる
      • はてブ
    • 文字コード周りを確認する
      • jsonの仕様的に文字列の中に'\n'を含んではいけない
      • はてブ
    • floatとint周りを確認
      • GoはJSONの中に含まれる数値を float64 型として扱う
      • qiita
  • jsonの問題
    • 形式がおかしくないか確認する

バグの原因が見つかった際の手順

  • testcode.goの問題
    • testcode.goを修正
  • jsonの問題
    • jsonを修正

修正手順案

  • エラーメッセージを読むと、testcode.goの問題のポツで書いた4つの原因のどれか、またはそれ以外かが分かる

  • もしエラーメッセージに情報が無ければ、ひとまずtestcode.goを確認した後、jsonを地道に確認


解説

原因

この問題はUserという構造体のtag指定が2点間違っていることが原因でした。

コンパイルでは見つけられない。

1

=を:に修正する。


変更前

json=""

変更後

json:""

実行結果

{10 Gopher passwordnohash 0}

2

“が抜けているため足す。


変更前

"access_count`

変更後

"access_count"`


・2だけを修正した場合

{10 Gopher 0}

・1と2を修正した場合

{10 Gopher passwordnohash 99}

解法2

go vet コマンドによる静的解析

  • go vetとは?
    • 本当に問題になるかは分からないし、ビルドは通せるんだけど、こういう懸念があるかもね 的な問題を コンパイラとは別の観点でチェックしサジェストをしてくれる、賢い子
    • qiita

networkが作成できない?

参照した問題・解説のサイト:networkが作成できない?

使用環境・ツール

  • docker-compose
  • NetBox

問題文でされた操作

  • 運用サーバに新しくインターフェイスを追加した。

バグの内容

  • NetBoxうまく起動できなくなった。

考えられる原因

使われている技術について調べてみる


解説

原因

解決方法


解説で出てきたでバック方法や解決方法について調べてみる

経路が受け取れない!

解いた人:nonnonno

参照した問題・解説のサイト:経路が受け取れない!

使用環境・ツール

  • ルータ
  • BGP (経路制御プロトコル)

問題文でされた操作

経路が受け取れない!の一枚目の画像を参照

AS65000, AS65001, AS65002に所属するルータを3つ作成し、これらのルータ全てからフルルートをAS65010宛に流した。

バグの内容

なぜかAS65010に所属するルータで経路を受け取れない

理想の終了状態

AS65010に所属するルータにおいて、3つのルータ全てからのフルルートを受信したい

そもそもBGPとは

Border Gateway Protocolの略称で、パケットの宛先を正確に把握し、維持していくための経路制御を行うための代表的なプロトコル。
BGPは、経路制御を行う組織ごとにインターネットの世界で唯一の番号が割り当てられ、個々の経路を識別するという特徴があるため、組織内での設計と機器の準備だけでは利用することができない。
その唯一の番号はAS (Autonomousu System) 番号と呼ばれ、IANA (Internet Assigned Numbers Authority) が管理する。
AS番号とIPアドレスの割り当てを受けた後、上流ISPや経路情報を持つ相互接続点であるIXに接続が必要である。
その後に、自組織のBGPルータと接続先ルータ間でピアと呼ばれる経路交換を行う設定を行う。
BGPの経路制御はこのピアを通して行われる。 ピア設定後、自組織のIPアドレスを接続先へピアを通して通知する必要があり、このことを「アドレスをアナウンスする」と言う。 ちなみに、アナウンスだけでなく、上流ISPからインターネット上の経路を取得することも必要。 BGPルータはピアを確立するために、メッセージ形式で機能情報を交換し続ける。

なお、BGPはマルチキャストで動的にネイバーを自動検出することはできず、自分のAS内のネットワークをBGPルートとしてアドバタイズするときはnetworkコマンドを使用する。
networkコマンドで通知するためには、前提としてそのルートをルーティングテーブルで学習していることが必要。
自分のAS内のネットワークを通知する必要がない場合には、networkコマンドの設定は不要。

考えられる検証、修正手順

  • ハードが正常に動いていない

    • dmesgコマンドなどでハードウェアの動作を確認する
    • BGPのデーモン自体が動いていない可能性がある BGP有効化など設定
  • 経路情報が間違っており、通信ができていない/ルーティングテーブルが間違っている

  • その他トラブルシューティングの原因切り分け手順は以下のリンク

BGPトラブルシューティングフローチャート、Cisco

BGPルートがアドバタイズされない場合のトラブルシューティング、Cisco

BGPの設定・確認参考、Juniper webページ

解説

解法

  • /var/log/messageを確認し、ルータのエラーメッセージが表示されていることを確認
  • vyos(ルータなどの機器が利用するネットワークOS)によるBGPプロセス全体が停止していることを
  • (今回はBGPが無効になっており、)外部からあまりにも経路広告が行われ、メモリがオーバーフローしていることを認識(予備知識?)

得点ポイント

  1. BGPデーモンが停止している
  2. 経路が多すぎてメモリがオーバーフローしている

またビルド失敗しちゃった~…

解いた人:Hunachi

参照した問題・解説のサイト:またビルド失敗しちゃった~…

使用環境・ツール

  • Docker
    • Dockerfile
  • Go

バグの内容

Dockerのマルチステージビルドを使って、Goのバイナリをコンテナ上で実行しようとしたらうまく立ち上がらない. Dockerfileが間違えている.

~/app/Dockerfileを用いてdocker image build -t ictsc2020:0.1 .したあとに、docker run -p 80:1323 [コンテナID]をするとエラーが表示され、コンテナ上のバイナリが正常に実行できない.

理想の終了状態

curl localhostWelcome to ICTSC2020!が返ってくる. また、問題の解決が永続化されている。


技術調査

Dockerのマルチステージビルドとは?

  • https://docs.docker.com/develop/develop-images/multistage-build/
    • ↑の訳らしい: https://qiita.com/carimatics/items/01663d32bf9983cfbcfe
  • 必要なバイナリorファイルだけをコピーしてきたイメージを楽に作ることができるようになる。

Dockerfileの内容をみてみる

// ~/app/Dockerfileの内容

1:FROM golang:1.15.0 AS builder
2:ENV GO111MODULE=on
3:ENV GOPATH=
4:COPY ./server/main.go ./
5:RUN go mod init ictsc2020
6:RUN go build -o /app ./main.go
 
 
7:FROM alpine:3.12
8:COPY --from=builder /app .
9:EXPOSE 1323
10:ENTRYPOINT ["./app"]

  • 1行目: 新しい(1つ目の)ビルドステージの開始

    • 1つ目のコンテナの作成
    • golang:1.15.0を親イメージとして指定
    • ビルドステージに builder という名前をつけている。(デフォルトでは 0 から連番で付けられる。)
  • 2行目: Go modulesを使うという指定

    • 何も指定しないor auto指定だとgo.modファイルがある時だけGo modulesを使うとみなされる。
    • Go modulesを使うと、importしたいライブラリと、ライブラリの依存ファイルをgo.modに書いてあげるとダウンロードしてくれてビルドしてくれるようになる。
  • 3行目:Goのファイルたちが置いてあるパスの指定

    • 特に指定してない。
    • import文の解決に使うために設定するらしい。
  • 4行目:./server/main.go を ./以下にコピー

  • 5行目:go moduleの作成

  • 6行目:main.goをビルドしてappバイナリファイルを生成する

  • 7行目: 新しい(2つ目の)ビルドステージの開始

    • 2つ目のコンテナの作成
    • alpine:3.12を親イメージとして指定
    • https://alpinelinux.org/
  • 8行目: builderという名前のビルドステージ(1つ目に作ったもの)の/appを.以下に持ってくる

  • 9行目: 1323番ポートを公開する

    • https://zenn.dev/suiudou/articles/5e1dfd1008bf29
  • 10行目: ./appを必ず実行する

    • https://pocketstudio.net/2020/01/31/cmd-and-entrypoint/

考えられる検証、修正手順

  • エラーの内容を確認する
  • main.goのコードがおかしい
    • main.goのコードを確認してみる
  • docker multistage build golang runtime errorでググって一番に出てきたやつ:https://stackoverflow.com/questions/56057688/issue-with-docker-multi-stage-builds
    • 解決手順
      • lddコマンドを使ってバイナリファイルの依存関係の確認
      • RUNコマンドにCGO_ENABLED=0を付け加えて実行し直してみる

原因ではなさそうなこと

  • imageは作成できており実行時エラーのため、Dockerファイル内のコマンドが途中で転けているとかではない
  • ポートの指定方法

解説

前提

golangで書いたプログラムをgo buildすると、基本的には静的リンクになるがnetパッケージを使用していた場合、自動的に動的リンクになる。(go1.4から)

原因

Dockerのマルチステージビルドをする際に、ビルドされたバイナリが動的リンクなせいでコンテナ間を移動させたあとに実行しようしてもうまくいかなかったため。

解決方法

  1. ~/app/Dockerfileの6行目のRUN go build -o /app ./main.goRUN CGO_ENABLED=0 go build -o /app ./main.goに変更
  2. コンテナをbuildし直す。 docker image build -t ictsc2020:0.1
  3. 実行し直す docker run -p 80:1323 [コンテナID]

想定外解放としてあったもの

  • 動的リンクのまま、lddコマンドを使用し依存しているライブラリを特定して、そのライブラリをbuilderコンテナから実行用のコンテナにコピー・配置する

解説に対するメモ

  • 実行時にNot Foundっていうエラーが起きた時は、動的リンクがうまくいっていないパターンも考えるといいっぽい。動的リンク

  • バイナリを静的にコンパイルさせる為にはCGO_ENABLED=0を付ける。

    • https://www.docker.com/blog/containerize-your-go-developer-environment-part-1/
  • lddコマンド

    • すべての依存関係のパス名をリストで表示してくれる。
    • 動的リンクやファイルの依存関係のエラーの時に役に立ちそう?
    • 多分 ldd app で調べることができる。
    • https://www.ibm.com/docs/ja/ssw_aix_71/l_commands/ldd.html
    • https://wa3.i-3-i.info/word13813.html

採点基準

  1. Dockerfileに正しい改善がなされている: 50%
  2. curl localhostをして、正しいレスポンスが返ってくる: 50%

WEB ページが見れない

解いた人:momom-i

参照した問題・解説のサイト:WEB ページが見れない

使用環境・ツール

  • apache

問題文でされた操作

  1. apacheのDocumentRootを/var/www/htmlから/home/user/htmlに変更
  2. $curl http://127.0.0.1/home.htmlを実行

バグの内容

webサーバ上で$curl http://127.0.0.1/home.htmlをすると403エラーが返ってくる

理想の終了状態

  1. webサーバ上で$curl http://127.0.0.1/home.htmlをするとページが返ってくる。
  2. 再起動後も問題が解決している

考えられる原因

  • 権限がない
  • ドキュメントルートを宣言してるhttpd.confの記述に問題あり

解決方法

  • home.htmlの読み取り権限を付与
  • ドキュメントルートまでのディレクトリにも読み取り権限を付与
  • httpd.confのDirectoryセクションの設定で/home/user/htmlなどのパスがRequire all deniedになっていたら変更する
  • SELinuxの無効化 getenforceコマンドを打ってEnforcingと出る場合は、有効になっているらしい
setenforce 0

上記のコマンドを打つことで一時的に無効化することができるが、サーバの再起動後も無効化し続けるには、/etc/selinux/configにおいて、SELINUX=enforcingと書かれているところをSELINUX=disabledとする

  • SELinuxの有効化の状態で/home/user/htmlhttpd_sys_content_tタイプを付与 SELinuxはもしユーザが乗っ取られても、例えばhttpd_sys_content_tというタイプがファイルやディレクトリについていたら、httpdが実行するスクリプトでの読み込みしか許可しないので安全だよ〜という風に強固なセキュリティを提供するツールである。
ls -Z

まず上のコマンドで現在のディレクトリのSELinuxのタイプを確認することができる。

chconコマンドで一時的な付与ができるが、今回は再起動しても付与した状態にしたいので、 chconコマンドでも、実行後新たにrestoreconでタイプ再付与などしない限り、付与状態が続く。が、永続的に付与する場合は以下のコマンドの通りにする。

semanage fcontext -a -t httpd_sys_content_t /home/user/html
restorecon -RF /home/user/html

(https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/security-enhanced_linux/sect-security-enhanced_linux-selinux_contexts_labeling_files-persistent_changes_semanage_fcontext 参照)

※参考※

  • ドキュメントルート:
    リクエストに対してどのファイルを送信するかを決定するときの Apache のデフォルトの動作は、リクエストの URL-Path (URL のホスト名と ポート番号の後に続く部分) を取り出して設定ファイルで指定されている DocumentRoot の最後に追加する、というものです。ですから、 DocumentRoot の下のディレクトリやファイルがウェブから見える基本のドキュメントの木構造を なします。(参考URLより)

  • SELinux:
    通常のパーミッションなどで防ぎきれないセキュリティを提供するもの。有効化していると余計な挙動をすることもあって、無効化することも多いらしいヨ。詳しいことは公式参考URLへ。(結構難しそう)

  • ドキュメントルート参考URL

  • [SELinux参考URL]

    1. https://eng-entrance.com/linux-selinux
    2. https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/security-enhanced_linux/chap-security-enhanced_linux-working_with_selinux#sect-Security-Enhanced_Linux-Working_with_SELinux-SELinux_Packages (公式)

解説

  1. エラーログを見る (さっきの回答でログを確認すること忘れてた〜〜)
(13)Permission denied: [client 127.0.0.1:45546] AH00035: access to /home.html denied
  1. /home/user/htmlに一般ユーザの実行権を与える
sudo chmod o+x -R /home/user/html
  1. 再びエラーログを見る
SELinux policy enabled; httpd running as context system_u:system_r:httpd_t:s0
  1. SELinuxコンテキストのラベル付けをする
sudo chcon -R -t httpd_sys_content_t /home/user/html

(今回は無効化はなしという制約があったっぽい。semanage & restoreconコマンドの方でも○みたい。) chconはSELinuxコンテキスト変更のコマンド(参考) -R: ディレクトリの変更 -t: タイプの変更。タイプとはSELinuxが定めているパーミッション制御方法のこと!httpd_sys_content_tの詳細は上に書いてあって、他のタイプについては、ここ

  1. またまたエラーログを見る
[authz_core:error] [pid 18739] [client 127.0.0.1:45548] AH01630: client denied by server configuration: /home/user/html/home.html
  1. /etc/httpd/conf/httpd.confに追記する
<Directory "/home/user/html">
    AllowOverride None
        # Allow open access:
    Require all granted
</Directory>
7. curl成功
※変更後のapache再起動は省略してるWEB ページが見れない

Docker 勉強会

書いた人:Hunachi

テキストハンズオン

*第一回勉強会*

開催日時:6/21 (月)21:30~23:00

内容(予定)

  1. 自己紹介と雑談
  2. Dockerとは何か
  3. コンテナを起動させてみる
  4. 今回使ったdocker runのオプション等について
  5. コンテナが起動されるときの挙動
  6. docker runのオプションを追加で調べてみる
  7.  Docker composeについて少し

次までの宿題or時間が余ったら(任意だけど、次回使うかも)

  • Docker(Docker compose)を使ってWordpressを動かしてみよう!

*第二回勉強会*

Docker について学ぶ

書いた人:Hunachi

Docker の使われ方

以下のような時に使われる

  • 基盤システム(インフラ)から切り離して、アプリケーションを実行したいとき
  • 基盤システムが異なる環境でもアプリケーションに同様の動作をさせたいとき
  • 同じ環境をささっと作りたいとき

などなど..

もっと詳しくDockerの基礎を学ぶ

http://docs.docker.jp/get-started/overview.html# を読んでいく。

仮想化とコンテナ

(ホスト型)仮想化とは、CPUやメモリ、I/Oまでも仮想化し、複数のOSを単一のシステム上で実行できるようにする技術。 これを実現するソフトウェアのことをハイパーバイザー(hypervisor)という。

hypervisor system image

コンテナ(型仮想化)とは、カーネルランドよりも上を仮想化する技術。つまり、コンテナが作られる土台となる基盤システムと互換性のあるコンテナしか作れない。

docker image

Docker の挙動

コンテナが起動される時の挙動

https://docs.docker.jp/engine/reference/commandline/run.html

https://docs.docker.jp/engine/introduction/understanding-docker.html の画像がわかりやすい。

コマンドたちの説明

docker run

指定したイメージ(image)からコンテナの作成、起動を行う。

使い方

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

オプション

[OPTIONS]の部分で指定できること

  • -d オプション

    • デタッチド・モードで起動する
    • このオプションを付けない時はフォアグラウンド・モードで起動される(コンソールに表示させている間だけコンテナが起動するようになる)
  • --name オプション

    • コンテナに名前をつけるためのオプション
    • --name container-name のように使う。
  • -p オプション

    • どのホストのポートでどのコンテナのポートを公開するか指定するためのオプション
    • -p host-port:container-port のように使う。(例:-p 8080:80)

Docker Composeとは

https://docs.docker.com/compose/

複数のコンテナを使って、アプリケーションを起動させたい時に使われる。 yamlファイルを使ってアプリケーションのサービス(コンテナたち)の設定を書いて、その設定でサービス(コンテナたち)を起動する。 複数のコンテナを使う場合はこの方法を取らないと通信の設定がとても面倒になったりする。

docker-compose.yamlがあるディレクトリ内(同じ階層)で

docker compose up

コマンドを実行すると起動することができる。 バックグラウンドで動かしたい場合は、-dをつけるのを忘れないように。

Docker を触ってみる

書いた人:Hunachi

Docker を使うための環境構築

Docker DesktopのDownloadとinstallをする。 https://www.docker.com/get-started

Dockerチュートリアルのコンテナを作る

  1. https://www.docker.com/101-tutorial の以下のコマンドを打つ。

docker run -dp 80:80 docker/getting-started

  1. http://localhostでチュートリアルが表示されているかを確認する。