【ハッキング・ラボ】ParrotOS の設定

ハッキング・ラボのつくりかたという書籍の読書ログです。

前回 M1 Macbook に UTM を使って ParrotOS を立てました。今回は、設定や情報の確認を行います。

M1 Mac で ParrotOS の起動からやりたい方は、↓をご覧ください。
【ハッキング・ラボ】M1 Macbook に UTM を使って ParrotOS を構築する

目次

パッケージのアップグレード

パッケージをアップグレードします。

「option + T」でターミナルを起動し、以下を実行します。

sudo apt clean
sudo apt update
sudo apt full-upgrade --fix-missing -y
sudo apt autoremove -y

時々、選択を求められることがありましたが、全て「return」キーを押して、デフォルトを選択しました。

システム情報を確認

書籍では、neofetchコマンドを使用しています。「Not Found」となり、どうやらコマンドがなさそうなので、インストールします。

sudo apt install neofetch

インストールしたら、再度実行してみます。

いけました。

AppArmor の導入

AppArmor は Linux 用のセキュリティモジュールらしいです。

まず、AppArmor が導入されていることを確認します。

sudo getenforce

私の場合は、Disabled と出力されました。無効状態のようです。

次に、AppArmor の状態を確認します。

sudo aa-status --enabled; echo $?

「0」と表示されれば、AppArmor が有効になっていると判断できます。

続いて、systemctl コマンドでサービスの状況を確認します。

sudo systemctl status apparmor.service

「active」になっていれば、サービスが稼働中です。

ここから、AppArmor による制限設定の仕方を確認します。

アプリケーションや、コマンドなどのプログラムに制限をかけるには、プロファイルを用意します。

制限のモードには、以下の2種類があります。

  • enforce:プロファイルで許可された動作のみ許可する。
  • complain:プロファイルで許可されない動作をログに記録するが、動作を禁止はしない。

各プログラムとモードの確認

sudo cat /sys/kernel/security/apparmor/profiles

プロファイル一覧を確認

sudo ls /etc/apparmor.d/

プロファイルを無効にする

ここでは、man コマンドのプロファイルを無効化しています。

sudo aa-disable /usr/bin/man

※ ちなみに、私の場合は aa-disable コマンドが not found と言われたので、
sudo apt isntall apparmor-utilsでインストールしました。

無効化されたプロファイルを確認する

sudo ls /etc/apparmor.d/disable

プロファイルを有効にする

// enforce に設定する場合
sudo aa-enforce /usr/bin/man

// complain に設定する場合
sudo aa-complain /usr/bin/man

ファイアウォールの確認

ParrotOS には、iptablesufw が導入されているようです。

ufw の状態を確認します。

sudo ufw status

「inactive」と表示されたら、ファイアウォールが非アクティブなので、全パケットを通します。

iptables の状態を確認します。

sudo iptables -L

INPUT, FORWARD, OUTPUT の3つのチェーンのルールが表示されます。

ufw を設定する

sudo ufw default deny
sudo ufw allow {開けるポート}
sudo ufw enable

おわりに

ParrotOS の設定の部分を読み進める中で、やっておこうと思ったものや、知らなかったもの、コマンドのインストールが必要だったものなどをメモとして残しておきます。

【ハッキング・ラボ】M1 Macbook に UTM を使って ParrotOS を構築する

ハッキング・ラボのつくりかたという本を買いました。 こちらは前作から完全版としてパワーアップし、5年ぶりに出版されたものです。
実は学生時代、前作に入門しかけたのがですが、当時なんの知識もなかった私は環境構築で挫折しました。
エンジニアになった今ならちょっとはわかるかも!ということでやっていきます。

目次

実行環境

今回私が使用する PC です。

UTM のインストール

はじめに、仮想化ソフトをインストールします。

仮想化ソフトとして書籍では VirtualBox をメインに説明されていますが、私は VirtualBox での ParrotOS の起動がうまくいかなかったので、今回は UTM という仮想化ソフトを使うことにしました。(早くコンテンツに入りたいので、ソフトウェアにこだわらずいきます。)

UTM は、Apple Silicon の Mac に仮想環境を構築できるようです。

https://mac.getutm.app/ から Download を選択してダウンロードします。ダウンロードが完了したら .dmg ファイルを実行してインストールします。

完了したら、Spotlight(Command + スペース)などから utm を起動してインストールできていることを確認します。

ParrotOSのダウンロード

UTM 上に構築する攻撃用 OS として ParrotOS を使用します。

https://www.parrotsec.org/download/ にアクセスし、「Security Edition」を選択します。Download できるものとして、

があるので、utm(Apple Silicon)を選択して、ダウンロードします。

ダウンロードされたら、zip ファイルを解凍して、.utm ファイルを得ます。

UTM で ParrotOS の仮想マシンを作成する

.utm ファイルが手に入ったら、任意のディレクトリに配置します。(わかりやすいところがいいと思います。)

次に、UTM を起動します。

「新規仮想マシンを作成」-> 「既存」-> 「開く...」から 先ほどの .utm を選択します。

UTM のウインドウのサイドメニューに Parrot Security が表示されると思うので、選択し、再生マークをクリックして、起動します。

こちらで完了です。

おわりに

UTM で ParrotOS を立ててみました。書籍の p.71 あたりまでの内容になります。 ユーザー、パスワード、キーボード設定などは行なっていないですが、細かい設定は読み進めつつやっていきます。 ちなみに ParrotOS の初期パスワードは、parrot らしいです。

続き読むのが楽しみです。

【Go/Gin】サーバーを graceful shutdown に対応させる

golang と gin を利用して構築した web サーバーを graceful shutdown できるようにしてみたいと思います。 graceful shutdown は、サーバーが稼働中に予期せず終了することによる問題を防ぐための仕組みです。

目次

サーバーを構築する

http://localhost:8080 にアクセスすると、5秒待ってからメッセージを返すサーバーを立ててみます。

// main.go
package main

import (
    "time"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.GET("/", func(c *gin.Context) {
        time.Sleep(5 * time.Second)

        c.JSON(200, gin.H{
            "message": "Hello",
        })
    })

    r.Run() // listen and serve on 0.0.0.0:8080
}

リクエストを送った後、5秒以内に、「Ctl + C」でサーバーを落とします。

$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
^Csignal: interrupt

メッセージが返ってくる前に、サーバーが終了しました。

graceful shutdown に対応させる

gin のドキュメントのようにコードを修正します。

// main.go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()
    router.GET("/", func(c *gin.Context) {
        time.Sleep(5 * time.Second)
        c.String(http.StatusOK, "Welcome Gin Server")
    })

    srv := &http.Server{
        Addr:    ":8080",
        Handler: router,
    }

    go func() {
        // service connections
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()

    // Wait for interrupt signal to gracefully shutdown the server with
    // a timeout of 5 seconds.
    quit := make(chan os.Signal)
    // kill (no param) default send syscanll.SIGTERM
    // kill -2 is syscall.SIGINT
    // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("Shutdown Server ...")

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("Server Shutdown:", err)
    }
    // catching ctx.Done(). timeout of 5 seconds.
    select {
    case <-ctx.Done():
        log.Println("timeout of 5 seconds.")
    }
    log.Println("Server exiting")
}

先ほどと同じように、リクエスト送った後、5秒以内に「Ctl + C」をしてみます。

$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
^C2023/09/10 00:25:27 Shutdown Server ...
[GIN] 2023/09/10 - 00:25:30 | 200 |  5.001055625s |             ::1 | GET      "/"
2023/09/10 00:25:32 timeout of 5 seconds.
2023/09/10 00:25:32 Server exiting

「Ctl + C」を実行した直後、^C2023/09/10 00:25:27 Shutdown Server ... が出力されますが、すぐにはサーバーが終了せず、リクエストを捌けています。

srv.Shutdown(ctx)が実行されると、サーバーは新しい接続を受け付けるのを停止し、既存の全ての接続が閉じるのを待つようになります。
ここで、srv.Shutdownはコンテキスト(ctx)を引数として受け取ります。このコンテキストは、サーバーがシャットダウンを完了するまでの最大時間(タイムアウト)を設定します。
サンプルコードでは、5秒に設定されています。

試しに、/にアクセスした時の処理での待機時間を、5秒から10秒に伸ばし、先ほど同様に「Ctl + C」を送ってみます。

// main.go
...
    router.GET("/", func(c *gin.Context) {
        time.Sleep(10 * time.Second)
        c.String(http.StatusOK, "Welcome Gin Server")
    })
...
$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
^C2023/09/10 00:33:58 Shutdown Server ...
2023/09/10 00:34:03 Server Shutdown:context deadline exceeded
exit status 1

サーバーの処理時間 > サーバーがシャットダウンを完了するまでの最大時間 となり、強制的にサーバーが落とされました。

おわりに

gin で立てたサーバーを graceful shutdown に対応させてみました。
簡単ですね!

【protocol buffers】protoファイルにコメントを書く

protocol buffers は割と使うのですが、protoファイルにコメントを書くと生成したコードにも各言語のコメントをつけてくれることを最近知りました。
全く難しいことないのですが、メモがてら。

目次

protoファイルにコメントを書く

// コメントのような感じで、コメントを書くことができます。
rpcやmessage、messageのfieldsなどに書いてみます。

syntax = "proto3";

option go_package = "pkg/proto";

package comment;

service CommentService {
  // Comment is the method for comment.
    rpc Comment(CommentRequest) returns (CommentResponse);
}

// CommentRequest is the request message for Comment method.
message CommentRequest {
  // id is the id of the comment.
  string id = 1;
}

// CommentResponse is the response message for Comment method.
message CommentResponse {
  // id is the id of the comment.
    string id = 1;
  string name = 2;
  User user = 3;
}

// User is the user message.
message User {
  string id = 1;
  string name = 2;
  string email = 3;
  string password = 4;
  string created_at = 5;
  string updated_at = 6;
}

以下のコマンドで、goファイルを生成します。comment.pb.gocomment_grpc.pb.goというファイルができます。

protoc --proto_path=. --go_out=. --go-grpc_out=. ./comment.proto

protocコマンドがない場合には、先に以下を実行してインストールします。

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
export PATH="$PATH:$(go env GOPATH)/bin"
...
// CommentRequest is the request message for Comment method.
type CommentRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    // id is the id of the comment.
    Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
}
...
// CommentServiceServer is the server API for CommentService service.
// All implementations must embed UnimplementedCommentServiceServer
// for forward compatibility
type CommentServiceServer interface {
    // Comment is the method for comment.
    Comment(context.Context, *CommentRequest) (*CommentResponse, error)
    mustEmbedUnimplementedCommentServiceServer()
}

protoファイルに書いたコメントが反映されています。

deprecatedオプション

非推奨の旨をコメントに書く場合には、deprecatedオプションが使えます。
deprecatedオプションを使うと、コンパイル。。。。

先程のprotoファイルのコメントを変更します。

...
// CommentResponse is the response message for Comment method.
message CommentResponse {
  // id is the id of the comment.
    string id = 1;
  string name = 2;
  // user is deprecated.
  User user = 3 [deprecated = true];
}

// User is the user message.
message User {
  option deprecated = true;
  string id = 1;
  string name = 2;
  string email = 3;
  string password = 4;
  string created_at = 5;
  string updated_at = 6;
}
...

生成したGoファイルは以下のようになりました。

...
// CommentResponse is the response message for Comment method.
type CommentResponse struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    // id is the id of the comment.
    Id   string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
    Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
    // user is deprecated.
    //
    // Deprecated: Do not use.
    User *User `protobuf:"bytes,3,opt,name=user,proto3" json:"user,omitempty"`
}

...

// User is the user message.
//
// Deprecated: Do not use.
type User struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Id        string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
    Name      string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
    Email     string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
    Password  string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"`
    CreatedAt string `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
    UpdatedAt string `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
}

Deprecated: コメントがつくことで、呼び出し側でも警告が出るのでわかりやすいですね。

おわりに

protoファイルにコメントを書いてみました。適切なコメントを残せるようになりたいですね。

Go の context を使ったキャンセル、タイムアウト

context でキャンセルやタイムアウトを実装してみます。

目次

キャンセル

context.WithCancelでコンテキストから子コンテキストとキャンセル関数を生成し、子コンテキストをfunに渡して サブの goroutine で実行します。
関数funは基本的には、1秒毎に"working"と出力し続けます。
main の goroutine では、5秒待機してからcancel()します。

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    go fun(ctx)

    time.Sleep(5 * time.Second)
    cancel()
    time.Sleep(5 * time.Second)
}

func fun(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("canceled")
            return
        default:
            fmt.Println("working")
            time.Sleep(1 * time.Second)
        }
    }
}
$ go run main.go
working
working
working
working
working
canceled

cancel()が実行されると、ctx.Done()のチャネルに送信されます。
funのselectでは、ctx.Done()のチャネルから受信されるため、case <-ctx.Done():のcase に落ちます。
子コンテキストを渡していたサブ goroutine の処理を終わらせることができました。

タイムアウト

上のキャンセルでは、cancel()を実行したタイミングでコンテキストをキャンセルさせていました。
タイムアウトでは、どれくらいの時間経過したらキャンセルするかを設定しておきます。

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    go fun(ctx)

    time.Sleep(5 * time.Second)
}

func fun(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("canceled")
            return
        default:
            fmt.Println("working")
            time.Sleep(1 * time.Second)
        }
    }
}

$ go run main.go
working
working
canceled

2秒経過するとタイムアウトになり、ctx.Done()のチャネルに送信されます。
タイムアウトした場合も、defer cancel()でコンテキストをクローズする必要があるっぽいです。

デッドライン

デッドラインは、キャンセルする時刻を設定します。

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Second))
    defer cancel()

    go fun(ctx)

    time.Sleep(5 * time.Second)
}

func fun(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("canceled")
            return
        default:
            fmt.Println("working")
            time.Sleep(1 * time.Second)
        }
    }
}

$ go run main.go
working
working
canceled

現在時刻の2秒後の時刻をデッドラインに指定しているため、タイムアウトと同じ挙動になります。

おわりに

コンテキストのキャンセルを試してみました。
無駄な処理や、goroutineリークを防げるようになりました〜。

Github ActionsでGoプログラムを定期実行させてみる。

Github ActionsでGolangのプログラムを定期実行させてみました。
この記事を読むことで、Goのプログラムを定期実行させられるようになります。

目次

定期実行するGolangプログラムを準備する

main.goを用意します。

package main

import (
    "log"
)

func main() {
    log.Println("run main.")
}

「run main」とログ出力するだけのプログラムです。

workflowsを準備する

リポジトリ内に.github/workflowsという名前でディレクトリを作成し 、以下のようなymlファイルを配置します。

on:
  schedule:
    - cron: '*/5 * * * *'

jobs:
  run:
    name: run
    runs-on: ubuntu-latest
    steps:
      - name: Set Up
        uses: actions/setup-go@v2
        with:
          go-version: 1.19

      - name: Checkout
        uses: actions/checkout@v2

      - name: Run
        run: go run main.go

ymlファイルの概要

on

プログラムを実行するトリガを設定します。

  • schedule : 定期実行の間隔を指定できます。
    分(00-59) 時(0-23) 日(1-31) 月(1-12) 曜日(0-6)の順で設定します。
    以下の*/5などのようにすると「5分ごとに」という設定ができます。
  schedule:
    // 9時から21時の間に5分間隔で実行
    - cron: "*/5 0-12 * * *"
その他のよく使うトリガ
  • push : プッシュ時
  • deployment : デプロイ時
  • pull_request : プルリクエストアクションが行われた時
  • release : リリースアクションが行われた時

jobs

実行するジョブを指定します。複数指定可能で、実行順序の指定もできるらしいです。
ジョブは仮想環境でインスタンスで実行されます。

name

ジョブ名

runs-on

ジョブのランナー

steps

アクションの集合。
同一ジョブのstepsは同一の仮想環境で実行される。

アクション

ジョブが実行する処理。
コマンド実行や公開アクションの実行ができる。

おわりに

簡単な定期実行を試してみました。
github actionsで色んなことしてみたいです。
https://docs.github.com/ja/actions