/

問1 経路問題 その1

問題文

下記の図で、vmx2からvmx1のge-0/0/2へpingが飛ばない。 logを見て、考えられる原因について最もふさわしいものを選べ。なお、aclなどfilteringの設定はなく、vmx2の設定は適切であるとする。また、本問においてvmx2のgwはxrv1であるとする。

file
file
  • A: xrv1の物理インタフェースがdownしている
  • B: vmx1からの戻りの経路がない
  • C: vmx1とxrv1の間のvlan設定が間違っている
  • D: ospfのAD値設定が間違っている

解説

Bが正解。vmx1からの戻りの経路がないため。
pingの送信元インタフェースであるvmx2の192.168.1.4/24について、vmx1のshow routeには情報がないことが読める。
pingが通るためには、end-end間に存在する各機器において、往路、復路とも経路を保持している必要がある。往路の経路があるからpingが通るはず、と勘違いしてトラブルシューティングを長引かせてしまった経験をしたことがある人は、すぐに気づく問題である。

  • A:誤り。show ip routeでconnectedもospf経路交換も見えるため、IFは生きていると考えられる。
  • C:誤り。vmx1が10.0.1.20/30の経路をOSPFで受け取っていること等から、疎通性は正常であると判断できる。
  • D:誤り。AD値の設定は正常に見える。

問2 経路問題 その2

下記の図で、vmx2からvmx1のge-0/0/2へpingが飛ばない。logを見て、考えられる原因について最もふさわしいものを選べ。なお、aclなどfilteringの設定はなく、vmx2の設定は適切であるとする。また、本問においてvmx2のgwはxrv1であるとする。

file
  • A: xrv1のstatic routeの設定が原因となっている
  • B: vmx1のルーティングにかかわるプロセスがdownしている
  • C: vmx1において、xrv1とは反対側の上流に経路が向いていることが予想される
  • D: vmx1とxrv1の間のvlan設定が間違っている

解説

Aが正解。xrv1に/32のstatic routeが記載されており、10.0.1.5への経路がxrv2の方に向いているため。
10.0.1.4/30のエントリが見えるが、そのすぐ下に/32があり、ロンゲストマッチの原則から10.0.1.5/32への通信はxrv2の方へ向く。
本問は簡単な例だが、このようにいつの間にか経路が意図しない方に向いていることや、それを原因とする故障はたまに発生する。

  • B:誤り。show ip routeの結果を見る限り、考えにくい。
  • C:誤り。そうなっているかもしれないが、そもそもvmx1まで通信が明らかに到達しない。
  • D:誤り。10.0.1.4/30がdirectly connectedである点、10.0.0.1/32などをOSPFで受けとっている点から、疎通性は正常であると判断できる。

問3 経路情報の伝搬

IPルーティングに関する説明で正しいものを選べ。

  • A: 経路集約を行うことでルータ間での経路交換のメッセージ量やメモリの消費量を低減させることができる。
  • B: BGPは多量の経路を扱うプロトコルであるため、経路情報を可逆圧縮したNLRIと呼ばれるメッセージを用いて経路交換を行う。これは経路集約と呼ばれ、異なるメーカ間での相互接続性を確保するためにLZMAアルゴリズムが用いられている。
  • C: RIPやOSPFでは類似したアドレスの情報を多量に交換するため、各ルータの交換する経路情報はZIP形式で圧縮されている。
  • D: eBGPの設定されているルータはフルルートを保持しており、インターネット上の全ネットワークに接続性を持つこのため、eBGPルータは他のBGPルータに対して0.0.0.0/0の経路のみを配信する。

解説

Aが正しい回答です。経路集約を行うことで交換する経路数が減少するため、メッセージの量や、ルータの保持する経路を減らすことができます。
B,Cは誤りです。OSPFやBGPでは可逆圧縮を用いた経路の交換は行われません。
Dは誤りです。フルルートやそれに相当する経路を持つルータで0.0.0.0/0を生成して他のルータに広告することはよくありますが、iBGPルータあるいはRRルータとの間ではBGPで学習したすべての経路を交換します。

問4 ドキュメント記載に最適なアドレスは?

あなたは研究室のネットワークを管理している。あなたは後輩のために”他の1台のホストへの多量のIPv4 pingの送り方”という例を研究室の運用ドキュメントに記載することにした。このような場合に以下の中で【最も適した】アドレスはどれか?

ping -f <ipv4アドレスの宛先>

条件:後輩たちははNWに馴染みがないため、コマンド例をそのままコピーアンドペーストしてしまうことがある。この際に実際に存在するホストに対してパケットが送信されてしまい迷惑がかかることがないようにしたい。

  • A: 192.0.2.100
  • B: 0.0.0.0
  • C: 255.255.255.255
  • D 172.32.0.1
  • E 10.255.255.255
  • F 192.0.3.100
  • G 127.0.0.1

解説

正解A: ドキュメント向けアドレスであり適切です。[RFC5737] IPv4 Address Blocks Reserved for Documentationに記載があります。この中では、以下のようなアドレスがドキュメント向けに予約されています。

192.0.2.0/24 (TEST-NET-1)
198.51.100.0/24 (TEST-NET-2)
203.0.113.0/24 (TEST-NET-3)

他の選択肢は以下の通り誤りとなります。
B, C, Eはホストを指定するには不適切なアドレスです。D,FはGlobalアドレスであり、例示としては考えられますが、今回の条件と照らし合わせると不適切です。GはloopbackのIPアドレス空間なので他のホストではありません。RFC5735: Special Use IPv4 Addressesに記載があります。

問5 NAT

IPv4環境におけるNATの説明のうち正しいものを選べ

  • A: NAPTを利用することで1つのグローバルアドレスしか所持しない環境でも複数のノードがインターネット上のノードと同時に通信できる。しかし、DNSはプロトコル上、送り元、送信先のポートが53でなければならないため、同時にDNSサーバと通信できるノードは1台のみである。
  • B: NATによりHTTPSを用いてインターネット上のWebサーバと通信をすることが可能である。しかし、クライアント(送信元)のIPアドレスが変換されていると、クライアントはサーバ証明書の確認を行うことはできない。
  • C: NATはプライベートIPアドレスをグローバルIPアドレスに変換する技術であるため、宛先をプライベートIPアドレスに変換することはできない。
  • D: NATはプライベートIPアドレスをグローバルIPアドレスに変換する技術であるため、グローバルIPアドレスをグローバルIPアドレスに変換することはできない。
  • E: 他の選択肢はすべて誤り。

解説

この問題は出題が不適切であるため、C, D, Eを正答としています。
当初はEのみが正当である(C,Dが不適切である)としていました。これは、Linuxおよびいくつかの主要なルータのNAT機能においてプライベートアドレスアドレス空間であるかの判定は行われておらず、グローバルアドレス同士あるいはプライベートアドレス同士のNATが記載できるためです。しかし、NATに関するStandards TrackやInformationalの主要なRFCでは、Privateアドレス空間とExternalアドレス空間(グローバルアドレス空間)を前提として論じられているため、C, D も正答としました。

Aの前半は正しいが後半は誤りです。DNSの通信において、Query送出側のポートが53である制限はありません。B前半は正しいですが後半は誤りです。NAT処理を行ってもHTTPSのサーバの証明に影響はありません。

問6 IPv6

IPv6に関する説明のうち正しいものを選べ。

  • A IPv6ではMACアドレスからユニークに生成されるEUI-64アドレスをホスト部に含むアドレスが広く使われる。このため、IPv6を用いた通信ではMACアドレスを含まないEthernetヘッダが用いられている。
  • B IPv6ではIPv4の上に成り立つネットワークであるため、ホストや途中のルータではIPv4が有効になっていなければならない。
  • C 同一のネットワークに所属するIPv6ホストは送り元と宛て先でペイロードの交換を行う前にNLRIメッセージを交換し、到達性の確認してからペイロードの転送を行う。
  • D IPv4ではEthernet上のL2アドレスの解決にはARPプロトコルが用いられる。IPv6ではARPは用いられず、OSPFv3を用いてL2アドレスを相互に交換しMACアドレスを解決してフレームの転送が行われる。
  • E: 他の選択肢はすべて誤り。

解説

Eが正解です。

他の選択肢が不適切な理由は以下の通りです。
AはIPv6の通信においてもEthernetの通信にMACアドレスが不要になるわけではありません。前半部分については正しいですが、EthernetフレームにはMACアドレスが従来通り必要です。
Bについて、 IPv6はIPv4上のネットワークに構築されるわけではありません。IPv4で接続されたネットワークは不要です。
Cについて、同一ネットワークの通信に選択肢の動作は不要です。NLRIはBGPにおいて用いられる経路情報です。
Dは誤りです。IPv6においてEthernetアドレスの解決にはNeighbor Discoveryの仕組みが用いられます。

問7 OSPF その1

※本問題中の「図:Routing-K1」は他の問題に含まれる「図:Routing-K1」と同様です。

以下のpingの結果として適切なものはどれか。理由が述べられているものについては最も適切な理由なものを選べ。

R1#ping 192.168.0.2
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 192.168.0.2, timeout is 2 seconds:
(略)
  • 1: 成功する。
  • 2: 失敗する。R1とR2のMTUサイズが異なるのでPath MTU Discoveryが失敗しIPパケットが破棄される。
  • 3: 失敗する。R1とR2のMTUサイズが異なりOSPFのMTU mismatchによりOSPFのネイバー関係が確立できないため
  • 4: 失敗する。R1のOSPFはconnected経路をredistributeしていないため、R2はR1に付与されているアドレスに到達できない。

解説

続く4問は同じ構成となるため、状態を説明します。

  • このネットワークはR1, R2間のOSPFがMTU mismatchでOSPFのneighbor関係を確立されていない以外は正常な設定です。
  • OSPFのnetworkコマンドのワイルドカードマスクが/32で書かれているものと/24で書かれているものが存在しますが、今回の設問範囲では影響がありません。

回答に際してポイントとなるのは以下の点でした。

  • OSPFがR1-R2間でneighborを確立できていないこと
  • R1,R2間のI/FでOSPFが確立されていない場合でも、図の状態ではOSPF経路に載るため、R2からR3に対してこの経路は広告されること

以上を元に以下解説していきます。

1が正解。R1,R2間ではOSPFが確立されていないものの、Connectedな経路です。I/FのMTUサイズが意図的に変更されているが、例では明らかに小さいサイズのパケットです。
2,3,4はconnectedの通信には関係がありません。

問8 OSPF その2

※本問題中の「図:Routing-K1」は他の問題に含まれる「図:Routing-K1」と同様です。
以下のpingの結果として適切なものはどれか。理由が述べられているものについては最も適切な理由なものを選べ。

R1#ping 192.168.255.2 source 192.168.255.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 192.168.255.2, timeout is 2 seconds:
Packet sent with a source address of 192.168.255.1
(略)
  • 1: 成功する。
  • 2: 失敗する。R1とR2のMTUサイズが異なるのでPath MTU Discoveryが失敗しIPパケットが破棄される。
  • 3: 失敗する。R1とR2のMTUサイズが異なりOSPFのMTU mismatchによりOSPFのネイバー関係が確立できず、R1は対象アドレスの経路情報を持たない。
  • 4: 失敗する。R1のLoopback0のサブネットマスクは/32であるが、OSPF設定でnetworkコマンドのワイルドカードマスクが/24であるため、Loopback0のアドレスは広告されない。
  • 5: 失敗する。R1のOSPF設定でpassive-interface Loopback0が設定されているため、Loopback0を送り元アドレスとするIPパケットは送出できない。

解説

3が正解。OSPFのMTU mistmatchによりR1-R2間のOSPFの状態はEXSTARTのままで確立できていません。このため、R1はR2のLo0に関する経路を持たず、pingは失敗します。

他の選択肢は以下の点が誤り。
1は上記により誤りです。
2について、IPのPath MTU Discoveryと今回の結果には関係はありません。
4について、該当のnetworkコマンドはLoopback0を含む/24のアドレスを持つインタフェースでOSPFを有効にすることを示します。このため、マスク長が異なるためLoopback0のアドレスが配信されないわけではありません。
5についてpassive-interface Loopback0はOSPFに関する設定です。これをsourceとするIPパケットの送出を禁止するわけではありません。

問9 OSPF その3

※本問題中の「図:Routing-K1」は他の問題に含まれる「図:Routing-K1」と同様です。

以下のpingの結果として適切なものはどれか。理由が述べられているものについては最も適切な理由なものを選べ。

R3#ping 192.168.0.2 source 192.168.255.3
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 192.168.0.2, timeout is 2 seconds:
Packet sent with a source address of 192.168.255.3
  • 1: 成功する。
  • 2: 失敗する。R1とR2のMTUサイズが異なりOSPFのMTU mismatchによりOSPFのネイバー関係が確立できず、R2はR3に対してこのOSPF確立が失敗したconnected経路の情報を広告しない。
  • 3: 失敗する。R3のLoopback0のネットマスクは/32であるが、OSPF設定でnetworkコマンドのマスクアドレスが/24であるため、Loopback0のアドレスはR2に広告されず、R2は応答できない。
  • 4: 失敗する。R1とR2のMTUサイズが異なりOSPFのMTU mismatchによりOSPFのネイバー関係が確立できていないため、R2とR3の間のOSPF関係を確立できない。

解説

1の通り成功します。R2-R3間のOSPFは正常であり、関係する経路情報はR2,R3に伝搬されているためです。

2はR2においてあるインタフェースのOSPF確立がMTU mismatch起因で失敗しても、そのインタフェースがOSPFに参加していればそのインタフェースにかかわる情報はConnected経路としてR3に広告されます。
3について該当のnetworkコマンドはLoopback0を含む/24のアドレスを持つインタフェースでOSPFを有効にすることを示めします。このため、マスク長が異なるためLoopback0のアドレスが配信されないわけではありません。
4についてR2あるインタフェースのOSPF確立がMTU mismatch起因で失敗しても他のインタフェースでOSPFの確立ができないわけではありません。

問10 OSPF その4

※本問題中の「図:Routing-K1」は他の問題に含まれる「図:Routing-K1」と同様です。

以下のpingの結果として適切なものはどれか。理由が述べられているものについては最も適切な理由なものを選べ。

R3#ping 192.168.0.1 source 192.168.255.3
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 192.168.0.1, timeout is 2 seconds:
Packet sent with a source address of 192.168.255.3
  • 1: 成功する。
  • 2: 失敗する。R1とR2のMTUサイズが異なりOSPFのMTU mismatchによりOSPFのネイバー関係が確立できていないため、R2はR3がOSPF経由で学習した経路宛のパケットを破棄する。
  • 3: 失敗する。R1とR2のMTUサイズが異なりOSPFのMTU mismatchによりOSPFのネイバー関係が確立できていないため、R1はR3のLoopback0の経路を持たない。
  • 4: 失敗する。R1とR2のMTUサイズが異なりOSPFのMTU mismatchによりOSPFのネイバー関係が確立できていないため、R3はR1向けの該当経路を持たない。

解説

3が正解です。R3からのパケットはR1に到達しますが、R1がR3の経路を持たないため、pingは失敗します。R2は192.168.0.0/24をOSPF経路として持ち、R3に広告できます。このため、R3は192.168.0.1宛のパケットをR2に送出できます。R2はR1にパケットをルーティングをしますが、到着したR1では192.168.255.3への経路を(R1はR2とOSPFが確立できていないため)知らないため、パケットを破棄します。

他の選択肢は以下の点が誤りです。
1は以上の通りpingできないため誤りとなります。
2についてはR2はネイバー確立が失敗したインタフェースへのパケット転送を破棄するわけではありません。
4についてはR2のconnected経路をR3は受け取っているため、R3からのパケットはR1に到達できます。このため、この理由は適切でありません。

 /

問題文

あなたはサーバホスティング会社に勤めているシステムエンジニアです。
顧客より、下記のとおり依頼をいただきました。

前任者退職に伴って確認しようと思いサーバ内を確認していたところ、
間違えてFWサーバとwebサーバを再起動してしまいました。
再起動前はwebページに正常にアクセスできていたのですが、アクセスできなくなってしまったため、
いい感じに直していただきたいです。

しかし、セキュリティを考えていた設定とかもあると思うので、外部アクセスなどへはそのまま利用してほしいです。
あと、監視サーバからのURL監視も通るようにしてほしいです。こちらは特に制限をかける必要はありません。
でも、サーバの担当者が出張中で認証情報がわからないので、監視サーバへはログインできないです。
また、再発しないように設定いただけると嬉しいです。

変更した設定などはコマンドレベルでご報告いただけますようお願いします。

対応してほしいところ

上記依頼に則った上で、下記を満たすように修正してください。

  • VNCから以下のwebページへHTTPアクセスが正常に通るようにしてください。
    • 対象URL: http://www.qwe.example/
    • monitorから正常に監視ができるようにしてください。
    • FWサーバ 2222番ポートへの接続で、webへssh接続できるようにしてください。
  • 構成
  • アクセス可能
    • VNC(踏み台、クライアントPCと想定してください)
    • firewall01 (192.168.0.100/10.111.100.1 admin:[email protected])
    • web01 (10.111.100.10 admin:[email protected] ※FWサーバよりアクセス可能)
  • アクセス不可
    • monitor(192.168.0.200)
      • ※ 例示用URLのため、VNCのhostsに記載して名前解決しています
      • ※ 192.168.128.0/17 のIP帯からwebサーバへのランダムアクセスを行っています。
      • 問題環境の都合上ではありますが、一般userのアクセスと考えて対応してください。

構成図

( Internet )
      |
+-----+-----+      +-----+-----+
|    VNC    |      |  Monitor  |         
+-----+-----+      +-----+-----+
      |                  |
------+------------------+------- 192.168.0.0/16
      |
+-----+-----+
|    FW     |                
+-----+-----+
      |
------+-------------------------- 10.111.100.0/24
      |
+-----+-----+
|    WEB    |                
+-----+-----+

問題解説

本問題の要件としては下記の通りです。

  • VNC – NAT(firewall) – web の通信が通らないため直す
  • Monitor(監視サーバ)からの通信を、正常に通す
  • 外部アクセス(※記載の 192.168.128.0/17)のwebアクセスを可能にした状態で、既存の攻撃対策ルールを適用する

問題文より、firewall および webを再起動してしまった影響と読み解けるので、両サーバの設定を確認します。
今回問題が発生していた箇所は両サーバのfirewall設定が永続化しておらず、
再起動したためロールバックしてしまった、というシナリオでした。不]具合が発生していた箇所を記載します。

iptables NAT設定 (FWサーバ)

入っているNAT設定は WEBサーバ(10.111.100.10)に対する SSH(22) と HTTP(80)のDNAT設定です。
DNATルールにおかしい箇所があります。

誤っている箇所

  • -A PREROUTING -s 192.168.0.100/32 -p tcp -m tcp –dport 2222 -j DNAT –to-destination 10.111.10.10:22
  • -A PREROUTING -s 192.168.0.100/32 -p tcp -m tcp –dport 80 -j DNAT –to-destination 10.111.10.10:80

通信元ポートが22/80だったアドレスを”10.111.10.10″に変換となってしまっています。(第3オクテットが違います)
下記の例の様に直すと、正しいDNAT設定となります。\
また、前任者がsaveした際にできたであろう、 /etc/sysconfig/iptables.save をリストアする事によっても正しいDNAT設定に直す事も可能です。~
※後述のFORWARDルールについてはこちらをリストアしても直っていません。

解答例

  • -A PREROUTING -s 192.168.0.100/32 -p tcp -m tcp –dport 2222 -j DNAT –to-destination 10.111.100.10:22
  • -A PREROUTING -s 192.168.0.100/32 -p tcp -m tcp –dport 80 -j DNAT –to-destination 10.111.100.10:80

これでVNCからWEBサーバに対するNATでのSSH接続が可能となります。

iptables FORWARDルール (FWサーバ)

iptablesでの攻撃対策ルールについてです。
FWサーバの各ルールは基本的に各接続許可/拒否をログ出力するように設定されています。
FWサーバ(192.168.0.100)の/var/log/messagesを見てみると下記のような出力が確認できます。

Nov 16 06:21:07 firewall01 kernel: iptables : FORWARD_ALLOW IN=eth0 OUT=eth1 SRC=192.168.0.200 DST=192.168.100.10 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=64121 DF PROTO=TCP SPT=42576 DPT=80 WINDOW=29200 RES=0x00 SYN URGP=0 
Nov 16 06:21:08 firewall01 kernel: iptables : FORWARD_ALLOW IN=eth0 OUT=eth1 SRC=192.168.0.200 DST=192.168.100.10 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=64122 DF PROTO=TCP SPT=42576 DPT=80 WINDOW=29200 RES=0x00 SYN URGP=0 
Nov 16 06:21:10 firewall01 kernel: iptables : FORWARD_ALLOW IN=eth0 OUT=eth1 SRC=192.168.0.200 DST=192.168.100.10 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=64123 DF PROTO=TCP SPT=42576 DPT=80 WINDOW=29200 RES=0x00 SYN URGP=0 
Nov 16 06:21:11 firewall01 kernel: SYN_FLOOD IN=eth0 OUT=eth1 SRC=192.168.0.200 DST=192.168.100.10 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=15639 DF PROTO=TCP SPT=42636 DPT=80 WINDOW=29200 RES=0x00 SYN URGP=0 

監視サーバ(192.168.0.200)からのアクセスがどうやらSYN_FLOODとして判定され拒否されていそうです。
FORWARDルールを確認してみます。

-A FORWARD -s 192.168.0.0/16 -p tcp -m state --state NEW -m tcp --dport 22 -j FORWARD_ALLOW
-A FORWARD -s 192.168.0.0/16 -d 10.111.100.10/32 -p tcp -m tcp --dport 80 -j SYN_CHECK
-A FORWARD -s 192.168.0.200/32 -d 10.111.100.10/32 -p tcp -m state --state NEW -m tcp --dport 80 -j FORWARD_ALLOW 
-A FORWARD ! -s 192.168.0.200/32 -d 10.111.100.10/32 -p tcp -m state --state NEW -m tcp --dport 80 -j FORWARD_ALLOW 
-A FORWARD -s 192.168.0.0/16 -d 10.111.100.10/32 -p tcp -m state --state NEW -m tcp --dport 22 -j FORWARD_ALLOW 
-A FORWARD -s 192.168.0.0/16 -d 10.111.100.10/32 -p tcp -m tcp --dport 22 -j ACCEPT 
-A FORWARD -d 10.111.100.10/32 -p tcp -m tcp --dport 443 -j FORWARD_ALLOW 
-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT 

ここで気にするべき点は、iptablesの上から順にルールを読む動作です。
WEBサーバのhttpに対するアクセスをした場合、一番最初に読むFORWARDルールは

-A FORWARD -s 192.168.0.0/16 -d 10.111.100.10/32 -p tcp -m tcp –dport 80 -j SYN_CHECK

となります。

上記FORWARDルールの順だと、依頼されている

あと、監視サーバからのURL監視も通るようにしてほしいです。こちらは特に制限をかける必要はありません

にマッチせず、WEBサーバのHTTPに対するFORWARD通信はすべて下記のSYN_CHECKルールに適用されてしまいます。
SYN_CHECKルールは15秒間に5回アクセスが発生した場合一時的にアクセスを拒否する。という動作をします。
※問題用として拒否制限はかなり厳しくしてあります。

-A SYN_CHECK -p tcp -m tcp -m state --state RELATED,ESTABLISHED -j ACCEPT 
-A SYN_CHECK -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m recent --rcheck --seconds 20 --name SYN_ATTACK --rsource -j REJECT --reject-with icmp-port-unreachable 
-A SYN_CHECK -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m recent --rcheck --seconds 15 --hitcount 5 --name SYN_sorce --rsource -j SYN_FLOOD 
-A SYN_CHECK -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m recent --set --name SYN_sorce --rsource 
-A SYN_CHECK -p tcp -m tcp -j RETURN 
-A SYN_FLOOD -m recent --set --name SYN_ATTACK --rsource -j LOG --log-prefix "iptables : SYN_FLOOD " 
-A SYN_FLOOD -j REJECT --reject-with icmp-port-unreachable 

その為、監視サーバ(192.168.0.200)のFORWARDは真っ先に許可してあげる必要があります。
下記の様に順番を入れ替えます。

-A FORWARD -s 192.168.0.0/16 -p tcp -m state --state NEW -m tcp --dport 22 -j FORWARD_ALLOW 
-A FORWARD -s 192.168.0.200/32 -d 10.111.100.10/32 -p tcp -m state --state NEW -m tcp --dport 80 -j FORWARD_ALLOW ★このルールをSYN_CHECKルールより上に入れる。
-A FORWARD -s 192.168.0.0/16 -d 10.111.100.10/32 -p tcp -m tcp --dport 80 -j SYN_CHECK 
-A FORWARD ! -s 192.168.0.200/32 -d 10.111.100.10/32 -p tcp -m state --state NEW -m tcp --dport 80 -j FORWARD_ALLOW 
-A FORWARD -s 192.168.0.0/16 -d 10.111.100.10/32 -p tcp -m state --state NEW -m tcp --dport 22 -j FORWARD_ALLOW 
-A FORWARD -s 192.168.0.0/16 -d 10.111.100.10/32 -p tcp -m tcp --dport 22 -j ACCEPT 
-A FORWARD -d 10.111.100.10/32 -p tcp -m tcp --dport 443 -j FORWARD_ALLOW 
-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT 

これでFWサーバの問題点はすべて解決しました。

firewalld (webサーバ)

WEBサーバでの問題点です。
問題文の通り、依頼主はFWサーバとWEBサーバを再起動してしまっています。
その為、webサーバ側にも何か起こっている可能性があります。
今回は、firewalldの許可リストからhttpが外れている事象でした。

# systemctl status firewalld.service
● firewalld.service - firewalld - dynamic firewall daemon
   Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled)
   Active: active (running) since 土 2019-12-07 14:28:58 JST; 1 day 4h ago
     Docs: man:firewalld(1)
 Main PID: 569 (firewalld)
   CGroup: /system.slice/firewalld.service
           └─569 /usr/bin/python2 -Es /usr/sbin/firewalld --nofork --nopid

firewalldは上がっているので、ルールを確認します。

# firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources: 
  services: dhcpv6-client ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

httpのアクセスが許可されていなさそうです。

その為下記のコマンドで永続許可します。
※80番ポート許可でもOKです。

# firewall-cmd --zone=public --add-service=http --permanent
# firewall-cmd --reload

これですべての問題点が解決できた事になります。

 /

問題文

職場でkubernetesを用いてRedmineを導入することになった。
上司が構築作業をしていたが、どうもうまくRedmineが起動しないらしい。

部下のあなたがk8sが得意ということだったので、構築作業の続きをすることになった。
kubernetesが動作するサーバーにRedmine用のManifestが適用されているが、どうも正常起動していないようだ。

原因を究明を行い、Redmineを使える状態にして解決方法を報告してください。

問題のゴール

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

情報

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

制限事項

  • 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)を修正

問題解説

Kubernetes上にRedmineサービスを稼働させる問題です。
出題時にはRedmineを構成するRedmine-Pod, MariaDB-PodがPendingとなっており、利用不可の状態です。
コンテナが稼働しない原因を突き止め対処することでRedmineサービスを稼働させることができます。

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

kubectl get podでコンテナの状態を見ます。

[[email protected] ~]# 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

redmineとmariadbが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のtaintskubectl describe nodesで確認します。

[[email protected] ~]# 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

Taintsがnode-role.kubernetes.io/master:NoScheduleのため、Labelsにmasterが指定されているこのノードにはPodをScheduleすることができません。
今回はシングルノードのMasterノードでデプロイさせたいので、このtaintsを削除します。

[[email protected] ~]# kubectl taint nodes k8smaster1 node-role.kubernetes.io/master:NoSchedule-
node/k8smaster1 untainted

これでPodがノードにScheduleされるようになりました。

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

再度kubectl get podで確認すると以下となりImagePullBackOffとなっています。

[[email protected] ~]# 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

kubectl desctibe pod <Pod名>でEventを確認すると以下のエラーが確認できます。

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

コンテナイメージのpullの際に192.168.0.101:443への接続ができていません。
情報で示されているように192.168.0.101はContainer-Registryです。

/etc/hosts192.168.0.101 private-registry.localの記載があり、正しいことがわかります。

試しにDockerでImageのpullに失敗したコンテナイメージを手動でdocker pull private-registry.local/bitnami/mariadb:10.3.20-debian-9-r0してみます。
この操作は正常にpullすることができ、docker imagesでも正しくコンテナイメージがあることが確認できました。

[[email protected] ~]# docker images
REPOSITORY                               TAG                   IMAGE ID            CREATED             SIZE
private-registry.local/bitnami/mariadb   10.3.20-debian-9-r0   36300b3aaaa0        3 weeks ago         289 MB

ここで再度k8s上のPodの状態を確認しますが、エラーに変化はありません。

[[email protected] ~]# 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

さらに詳細にみるために全namespaceを対象にget podします。

[[email protected] ~]# kubectl get pod --all-namespaces
NAMESPACE     NAME                                  READY   STATUS             RESTARTS   AGE
default       my-release-mariadb-0                  0/1     ImagePullBackOff   0          9d
default       my-release-redmine-859cf77958-n95j5   0/1     ImagePullBackOff   0          9d
kube-system   coredns-74c9d4d795-hj9tn              1/1     Running            3          9d
kube-system   coredns-74c9d4d795-l7949              0/1     Pending            0          9d
kube-system   dns-autoscaler-7d95989447-crv67       1/1     Running            3          9d
kube-system   kube-apiserver-k8smaster1             1/1     Running            3          9d
kube-system   kube-controller-manager-k8smaster1    1/1     Running            3          9d
kube-system   kube-proxy-mm8ld                      1/1     Running            3          9d
kube-system   kube-scheduler-k8smaster1             1/1     Running            3          9d
kube-system   nodelocaldns-wgq47                    1/1     Running            3          9d
kube-system   weave-net-z97jl                       2/2     Running            6          9d

RunningのPodがあるのに、docker imagesでは手動でpullしたイメージしかありませんでした。

何かおかしいですね。Dockerが使われていないのかもしれません。

k8sのコンテナランライムを確認するためにkubeletのユニットファイルを確認します。
ファイル/etc/systemd/system/kubelet.serviceに記載の環境変数ファイル/etc/kubernetes/kubelet.envには以下の記載があります。

KUBELET_ARGS="--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
--config=/etc/kubernetes/kubelet-config.yaml \
--kubeconfig=/etc/kubernetes/kubelet.conf \
--pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.1 \
--container-runtime=remote \
--container-runtime-endpoint=/var/run/crio/crio.sock \
--runtime-cgroups=/systemd/system.slice \
 --node-labels=  "

container-runtimecrioが指定されていることがわかります。
しっかりとサービスが動いていますね。

[[email protected] ~]# systemctl status crio
● crio.service - Open Container Initiative Daemon
   Loaded: loaded (/usr/lib/systemd/system/crio.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2019-12-03 00:39:10 JST; 49min ago
     Docs: https://github.com/kubernetes-sigs/cri-o
 Main PID: 1076 (crio)
   CGroup: /system.slice/crio.service
           └─1076 /usr/bin/crio

crioはCRIなのでcrictlによる操作が可能です。
crictl imagesの結果ではrunningだったPodのコンテナイメージがありました。

[[email protected] ~]# crictl images
IMAGE                                              TAG                 IMAGE ID            SIZE
docker.io/coredns/coredns                          1.6.0               680bc53e5985f       42.3MB
docker.io/weaveworks/weave-kube                    2.4.0               86ff1a48ce14d       134MB
docker.io/weaveworks/weave-npc                     2.4.0               647ad6d59818d       49.5MB
gcr.io/google-containers/kube-apiserver            v1.15.3             5eb2d3fc7a44e       208MB
gcr.io/google-containers/kube-controller-manager   v1.15.3             e77c31de55475       160MB
gcr.io/google-containers/kube-proxy                v1.15.3             232b5c7931462       84.3MB
gcr.io/google-containers/kube-scheduler            v1.15.3             703f9c69a5d57       82.7MB
k8s.gcr.io/cluster-proportional-autoscaler-amd64   1.6.0               dfe4432cd2e2b       48.9MB
k8s.gcr.io/k8s-dns-node-cache                      1.15.4              3f8330c31e7d5       64.3MB
k8s.gcr.io/pause                                   3.1                 da86e6ba6ca19       747kB
gcr.io/google-containers/pause                     3.1                 da86e6ba6ca19       747kB

k8sのコンテナランタイムとしてcri-oが動作していたので、80ポートでアクセスさせるために、cri-oのprivate-resigtryへのinsecure-registryを追加しましょう。

214  # insecure_registries is used to skip TLS verification when pulling images.
215  insecure_registries = [
216    "10.233.0.0/24",
217    "private-registry.local"
218  ]

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

crioの設定を反映させた後、再度k8sのpodを確認します。

[[email protected] ~]# 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がエラーになっています。
コンテナの立ち上げは行われているので、コンテナのlogを確認します。

[[email protected] ~]# 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 [email protected]
 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

'/bitnami/mariadb/data': Permission denied ディレクトリの権限不足のようですね。

/root/ictsc_problem_manifestsにあるk8sManifestを読み解くと、/var/opt/pv{1,2}にPersistentVolumeがあることがわかります。
kubectl get pvの結果よりmariaDBに対応するPathにchmod で権限を付与します。

[[email protected] 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

[[email protected] ]# chmod -R 777 /var/opt/pv1/

再度k8sのPodの状況を確認すると正常にRedmineのPodが稼働していることが確認できました。

[[email protected] opt]# kubectl get pod
NAME                                  READY   STATUS             RESTARTS   AGE
my-release-mariadb-0                  1/1     Running            9          9d
my-release-redmine-859cf77958-n95j5   1/1     Running            5          9d

VNCのブラウザからRedmineのページが閲覧できれば完了です。

 /

問題文

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

変更前:
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経由でバックエンドと通信して実現する

解答方法

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

ログイン情報

VNCサーバから
$ ssh 192.168.0.80 -l admin
→ PW: [email protected]

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

問題解説

この問題はICTSC2019 一次予選にて出題された APIが飛ばないんですけど… の実技出題を目的として作成しました。
機能ごとに必要な対処が異なり、CrossOrigin通信におけるCORS, CSP, Cookieの取り扱いを把握していないと完答出来ない構成としています。

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

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;">

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

# netstat -ntelpo | grep -e :443
tcp6       0      0 :::443                  :::*                    LISTEN      0          26477      2044/httpd           off (0.00/0/0)
# ps auxww | grep http[d]
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)
# 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による許可)

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, 日記の削除を可能にする

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

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

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), '/', 'new-diary.ictsc.net', $_SERVER["HTTPS"]);

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の解決のみで問題クリアと判断した様子のチームも見受けられました。

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