credentials

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package credentials はエージェントの認証情報ファイル(agent.json)を扱う(仕様 9 節)。恒久トークン、wg の秘密鍵、 証明書のハッシュ、最後の全体状態を 1 ファイルに持つ。仕様書では「認証情報ファイル」と呼ぶ(仕様 3 節の用語)。 登録の情報、wg の秘密鍵、最後に処理した全体状態を持つ。 一時ファイルに書いて rename するので、途中でクラッシュしても壊れない。

Index

Constants

View Source
const (
	ModeKernel    = "kernel"
	ModeUserspace = "userspace"
)

ModeKernel と ModeUserspace は Mode の値である(仕様 11a 節の WGFT_MODE と同じ語)。

View Source
const (
	Unknown  = flock.Unknown
	Absent   = flock.Absent
	Unlocked = flock.Unlocked
	Locked   = flock.Locked
)

状態の値は flock のものをそのまま使う。呼び出し側が flock を直接 import せずに済ませるため。

View Source
const MaxFileSize = 512 << 20

MaxFileSize は認証情報ファイルの大きさの上限である(仕様 9 節)。Load と ReadFile はこれより大きい ファイルを読まずに拒み、Save はこれより大きい中身を書かずに拒む。wgft が書いたファイルを wgft が 読めなくなることはない。

値は、エージェントが書きうる最大の大きさを上回るように決める。last_state は制御ストリームの 1 通の読み取りの上限の内に収まる。カーネルモードの公開の記録は、直近の公開と、収束が済んでいない 前の公開の列の上限の数の分だけ並び、1 つの公開の大きさは last_state のルールの数で決まる。許可一覧が 範囲のルールを多数の範囲に分ける場合と、公開しなかった理由の文言の長さは見積もりの外である(仕様 9 節)。 見積もりの外で上限を超える中身は、Save が誤りとして拒む。 最大の大きさの見積もりと、この値が上回ることは、internal/agent のテスト TestCredentialsFileSizeLimitCoversTheLargestFile が確かめる。

Variables

View Source
var ErrLocked = errors.New("credentials file is in use by another process")

ErrLocked は別のプロセスがロックを持っている。

View Source
var ErrTooLarge = errors.New("the credentials file is larger than wgft ever writes")

ErrTooLarge は、認証情報ファイルが MaxFileSize を超えることを示す。

Functions

func EnsureDataDir added in v0.4.0

func EnsureDataDir(dir string) error

EnsureDataDir はデータディレクトリを作る(無ければ)。既に存在するディレクトリはそのまま にする。Unix の os.MkdirAll が既存のディレクトリを chmod しないのと同じ扱いである。 WGFT_DATA_DIR は利用者が任意の場所を指せる設定なので、agent が作ったのではないディレクト リの ACL を書き換えることはしない。agent 自身が新規に作った場合だけ、Windows では secureNewDir が保護 DACL(SYSTEM・BUILTIN\Administrators・実行中の利用者だけ。仕様 9・ 11a 節)を付ける。ディレクトリの下に置く各ファイル(agent.json・.lock・.sock)は、 Save・Load・Acquire・制御ソケットがそれぞれ自分の分だけを個別に締める(SecureFile)。

func ErrorKind added in v1.2.0

func ErrorKind(err error) string

ErrorKind は、ファイル操作の誤りからパスを除いた種類だけを返す。*fs.PathError ならその下の誤り (permission denied など)の文言で、それ以外はそのままの文言である。起動時の片付けのログに 一時ファイルのパスを出さないために使う。

func LockPath

func LockPath(path string) string

LockPath は認証情報ファイルに対応するロックファイルの場所。

func ReadFile added in v1.2.0

func ReadFile(path string) ([]byte, os.FileInfo, error)

ReadFile は認証情報ファイルの中身を読む。最後の要素が symlink なら辿らず、通常のファイルでなければ 拒み、MaxFileSize を超えれば読まずに拒む(仕様 9・11 節)。種別と大きさは開いた記述子で確かめる。 返す FileInfo はその記述子の fstat である。root の CLI は、エージェントの利用者が書けるデータ ディレクトリの agent.json を読むので、パスの先を信頼しない。無ければ os.ErrNotExist を包んだ誤りを返す。 中身の締め直しはしないので、副作用を持たない読み取り(agent doctor)にも使える。

func RegisteredTunnelAddress added in v1.2.0

func RegisteredTunnelAddress(addr string) string

RegisteredTunnelAddress は、登録の応答のアドレス addr を TunnelAddress の値に写す。応答はアドレスだけを 返すが、帯の長さの付いた形も受け付ける。どちらでもなければ空を返し、記録は最初に適用した全体状態を待つ。

func SecureSocket added in v0.4.0

func SecureSocket(path string) error

SecureSocket は、Windows 以外ではこの修正より前と同じく chmod の失敗を無視する (元の os.Chmod(path, 0o600) の戻り値を見ない呼び出しと同じ)。

Types

type Credentials

type Credentials struct {
	Name                string       `json:"name"`                   // エージェント名(登録時に確定)
	Endpoint            string       `json:"endpoint"`               // エージェント用 API の host:port
	CertSHA256          string       `json:"cert_sha256"`            // エージェント用 API の証明書のハッシュ(hex)
	PermanentToken      string       `json:"permanent_token"`        // 恒久トークン
	UsedJoinTokenSHA256 string       `json:"used_join_token_sha256"` // 使用済み登録トークンのハッシュ(仕様 5.1 節)
	WGPrivateKey        string       `json:"wg_private_key"`         // base64
	LastState           *proto.State `json:"last_state"`             // 最後に処理した全体状態

	// Mode は転送の方式の記録である(仕様 9・11a 節)。起動のたびに設定の WGFT_MODE と照合する。
	// 空はユーザー空間モードの記録とみなす。記録の無い既存のファイルはユーザー空間モードで動いてきたので、
	// ユーザー空間モードのエージェントはこの項目を書かない
	Mode string `json:"mode,omitempty"`
	// PreviousWGPrivateKey は 1 つ前の wg の秘密鍵である(仕様 7b.4 節)。カーネルモードでだけ持つ。
	// rotate-key の途中で落ちて鍵がずれた wgft0 や、停止中の rotate-key の後に古い鍵のまま残った
	// wgft0 を、自分のものと判定するために使う。次に鍵を変えるまで残す。base64
	PreviousWGPrivateKey string `json:"previous_wg_private_key,omitempty"`
	// IPForwardEnabledAt は、カーネルモードのエージェントが net.ipv4.ip_forward を 0 から 1 に変えた
	// 日時である(仕様 7b.1・9 節)。撤去が戻す候補として示すために残す。エージェント自身は値を 0 に
	// 戻さず、この記録も消さない。変えたことが無ければ nil
	IPForwardEnabledAt *time.Time `json:"ip_forward_enabled_at,omitempty"`
	// KernelPublication は、カーネルモードの直近の公開の結果である(仕様 7b.4・9 節)。テーブルの公開に
	// 成功するたびに書き換える。止まっている間の agent doctor が実際のテーブルと比べる。中身は
	// internal/dataplane/linuxkernel/nft の AgentPublication の JSON で、その package は Linux でしか
	// ビルドしないので、ここでは形を持たずに保存する
	KernelPublication json.RawMessage `json:"kernel_publication,omitempty"`
	// KernelUnconverged は、conntrack の収束が済んでいない前の公開の列である(仕様 7b.4 節)。古い順に
	// 並ぶ。収束に失敗している間だけ持ち、収束が済めば消す。再起動の後も、その公開で成立したフローを
	// wgft のものと見分けるために残す。中身は KernelPublication と同じ形の JSON の配列である
	KernelUnconverged json.RawMessage `json:"kernel_unconverged,omitempty"`
	// TunnelAddress は、登録で割り当てられたトンネルのアドレスの記録である(仕様 9・11 節)。形は 2 つある。
	// "10.200.0.2/24" は帯の長さまで記録したもので、"10.200.0.2" は登録の応答だけから記録し、帯の長さを
	// まだ知らないものである。登録の応答はアドレスだけを返すので、長さは最初に適用した全体状態から記録する。
	// 記録の無いファイル(この項目より前の版が書いたもの)も、最初に適用した全体状態から記録する。
	// カーネルモードのエージェントは、記録と違う wg.address を拒む(CheckTunnelAddress)。登録のし直しだけが
	// 記録を置き換え、撤去は消さない
	TunnelAddress string `json:"tunnel_address,omitempty"`
}

Credentials は認証情報ファイルの中身。

func Load

func Load(path string) (*Credentials, error)

Load はファイルを読む。なければ os.ErrNotExist。読み方の制限は ReadFile と同じである。Windows では、 この修正より前に緩い ACL の下で作られていた既存のファイルがありうるため、読むたびに secureExisting で 単独に締め直す(仕様 9・11a 節)。締め直しは開いたファイルを閉じる前に行う。Windows の os.OpenFile は 削除の共有を許さずに開くので、開いている間はその名前を別のファイルに差し替えられず、締め直すのは 種別を確かめたファイルである。隣のファイルやディレクトリには触れない。Unix では secureExisting は 何もしない no-op で、この修正の前後で Load の挙動は変わらない(Unix の chmod はこの修正 より前から機能しており、締め直す理由が無いうえ、管理者が意図して絞った権限を緩めたり、 ファイルを所有しない構成で失敗させたりしないため)。

func LoadOrNew

func LoadOrNew(path string) (*Credentials, error)

LoadOrNew はファイルを読み、なければ空の Credentials を返す。

func (*Credentials) CheckTunnelAddress added in v1.2.0

func (f *Credentials) CheckTunnelAddress(got netip.Prefix) error

CheckTunnelAddress は、server から届いたトンネルのアドレス got を記録と照合する(仕様 11 節)。記録が 無ければ通す。帯の長さを記録していなければアドレスだけを、記録していれば長さまで比べる。違えば、 または記録が読めなければ *TunnelAddressMismatch を返す。記録は書き換えない。

func (*Credentials) EnsureKey

func (f *Credentials) EnsureKey() (created bool, err error)

EnsureKey は wg 鍵対がなければ生成する。鍵を作り直すのは agent rotate-key だけ。

func (*Credentials) KeepPreviousKey added in v1.2.0

func (f *Credentials) KeepPreviousKey()

KeepPreviousKey は、今の鍵を 1 つ前の鍵として移す(仕様 7b.4 節)。今の鍵が空なら、1 つ前の鍵を 空の値で上書きせずに残す。停止中の rotate-key を続けて 2 回実行した場合がこれに当たる。 カーネルモードの記録を持つファイルでだけ移す。ユーザー空間モードはカーネルに鍵を残さないので、 1 つ前の鍵を持つ理由が無い。

func (*Credentials) PreviousKey added in v1.2.0

func (f *Credentials) PreviousKey() (wgtypes.Key, error)

PreviousKey は 1 つ前の wg の秘密鍵である。記録が無ければゼロの鍵を返す。ゼロの鍵はどの インタフェースも自分のものにしない(仕様 7b.4 節)。

func (*Credentials) PrivateKey

func (f *Credentials) PrivateKey() (wgtypes.Key, error)

PrivateKey は wg の秘密鍵。

func (*Credentials) RecordTunnelAddress added in v1.2.0

func (f *Credentials) RecordTunnelAddress(p netip.Prefix) bool

RecordTunnelAddress は、適用できたトンネルのアドレス p を記録する。記録が無いか、帯の長さを記録して いない同じアドレスの記録なら p で置き換えて真を返す。記録と違う p では何もしない。記録を置き換える のは、ここと登録だけである。

func (*Credentials) RecordedMode added in v1.2.0

func (f *Credentials) RecordedMode() string

RecordedMode は記録されたモードである。記録が無ければユーザー空間モードとみなす(仕様 9 節)。

func (*Credentials) Save

func (f *Credentials) Save(path string) error

Save は一時ファイルに書いて rename する。パーミッションは 0600(Windows は保護 DACL。 仕様 9・11a 節)。

root の CLI(停止中の rotate-key、agent pubkey、agent teardown)も Save を呼ぶ。データディレクトリは エージェントの利用者のもので、その利用者は一時ファイルや path の名前を書き込みの途中で差し替えられる。 そのため、一時ファイルの権限と持ち主はパスではなく開いた記述子に対して設定する。path が既にあれば Lstat で種別を確かめ、通常のファイルでなければ何も書かずに拒む(仕様 9・11 節)。

type LeftoverResult added in v1.2.0

type LeftoverResult struct {
	Removed int // 消した一時ファイルの数
	Failed  int // 消せなかった一時ファイルの数
	// FirstFailure は最初に消せなかったときの誤りで、パスを含む。ログに出すときは ErrorKind で
	// パスを除く
	FirstFailure error
}

LeftoverResult は RemoveLeftoverTemps の結果である。

func RemoveLeftoverTemps added in v1.2.0

func RemoveLeftoverTemps(held *Lock, credentialsPath string) (LeftoverResult, error)

RemoveLeftoverTemps は、保存の途中でプロセスが強制終了されて残った一時ファイルを消す(仕様 9 節)。 一時ファイルは rename の前の agent.json の写しで、wg の秘密鍵と恒久トークンを含む。

呼び出し側は Acquire で認証情報ファイルのロックを取り、その Lock を渡す。ロックを持つ別の プロセスが保存している途中の一時ファイルを消さないためである。ロックを取らずに保存する書き手 (ロックファイルの無いデータディレクトリでの agent pubkey と停止中の rotate-key)と重なると、 Unix ではその書き手の rename が失敗する。Windows では、書き手が一時ファイルを共有を許さずに開いている間はこちらの削除が 失敗し、閉じてから rename するまでの間だけ書き手の rename が失敗する。どちらでも認証情報ファイルは 壊れない。この向きの失敗は許容する(仕様 9 節)。

対象は credentialsPath と同じディレクトリの直下にある、接頭辞の合う通常ファイルだけである。 再帰せず、名前の合うディレクトリと symlink も消さない。os.ReadDir の種別は lstat と同じで symlink を 辿らず、os.Remove は symlink の指す先ではなく名前そのものを消す。

消せないファイルがあっても残りは続けて消し、消した数と消せなかった数を LeftoverResult で返す。 誤りを返すのは、ロックを持たない場合と、ディレクトリを読めず残りの有無を確かめられなかった場合だけ である。

type Lock

type Lock = flock.Lock

Lock は取得済みの排他ロック。

func Acquire

func Acquire(path string) (*Lock, error)

Acquire はロックを取る。取れなければ ErrLocked。Windows では、ロックファイル自体も agent.json と同じ基準(SYSTEM・BUILTIN\Administrators・実行中の利用者だけ。仕様 11a 節) へ secureExisting で単独に締める。Unix では secureExisting は何もしない no-op で、この 修正の前後で Acquire の挙動(失敗時にロックを放さないことを含め)は変わらない。

type State added in v1.1.0

type State = flock.State

State は Inspect が読み取ったロックファイルの状態。

func Inspect added in v1.1.0

func Inspect(path string) (State, error)

Inspect はロックファイルを作らずにロックの状態を読む。認証情報ファイルのロックファイルが 無ければ Absent で、そのデータディレクトリにエージェントが稼働している証拠が無い。一度も 起動していない場合と、運用者がロックファイルを消した場合があり、この 2 つは区別できない (設計文書 10.2c 節)。

type TunnelAddressMismatch added in v1.2.0

type TunnelAddressMismatch struct {
	Recorded string       // agent.json の tunnel_address
	Got      netip.Prefix // 全体状態の wg.address
}

TunnelAddressMismatch は、server から届いたトンネルのアドレスが記録と違うことを表す(仕様 11 節)。 記録が読めない場合も、比べられないので同じ誤りにする。

func (*TunnelAddressMismatch) Error added in v1.2.0

func (e *TunnelAddressMismatch) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL