コンテンツにスキップ

設定のリファレンス

tegata v0.3.1 の文書

tegata は 1 つの YAML のファイルを読みます。今のディレクトリの tegata.yaml か、-f <file> や TEGATA_FILE で渡したファイルです。中の相対のパスは、そのファイルのディレクトリからの相対です。知らないキーはエラーになるので、書き間違いを見落としません。

stores: { ... } # where secrets are stored (required; one store for now)
identities: { ... } # who reads secrets (optional)
providers: { ... } # external providers (optional; random, manual and value are built in)
resources: { ... } # what to create, where to store it, and who reads it (required)
destinations: { ... } # where copies of secrets are written, such as GitHub Actions secrets (optional)
environments: { ... } # staging, production, ...: what differs between them (optional)

クラウドのアカウントなしで動く完全な例は examples/minimal にあります。tegata 0.1 向けに書いたファイルは古い形を使っています。古い形を見てください。

名前 → 保存先。リソースは、この名前で保存先を指します。今は保存先を 1 つしか持てません。中には gcp と local のどちらか 1 つだけを書きます。

stores:
main:
gcp:
project: my-project
location: us-central1
キー 説明
project シークレットを置く Google Cloud のプロジェクト
location 新しいシークレットの複製を置くリージョン(複製が 1 つの、ユーザーが管理する複製)。すでにあるシークレットは変えない

tegata が Secret Manager に書くもの:

  • 管理するすべてのシークレットのラベル:managed-by=tegata と tegata-resource=<resource name>。tegata status はこれを使って、YAML にもうないシークレットを挙げます。
  • 各リソースの最初のシークレット(名前の順で)の注記:tegata-gen-<n>。prune していない世代ごとに 1 つで、その世代が足した版の番号と、プロバイダーの state を持ちます。プロバイダーの state はここにそのままの文字で入るので、プロバイダーは決してシークレットの値を入れてはいけません。
  • 読む権限:各シークレットの roles/secretmanager.secretAccessor のメンバーを、その readers のメンバーとちょうど同じにします。そのシークレットのほかの role の割り当ては、そのままにします。

認証情報は、この順で探します。

  1. TEGATA_GCP_ACCESS_TOKEN があれば、それ(OAuth のアクセストークン)
  2. Google Compute Engine の VM の上なら、メタデータのサーバーから取る VM のサービスアカウント
  3. それ以外は、gcloud auth print-access-token(自分のログイン)

apply、rotate、prune を流すには、そのアカウントが、シークレットを作り、版を足して無効にし、ラベルと注記を書き換え、シークレットに IAM のポリシーを付けられる必要があります(たとえば、プロジェクトの roles/secretmanager.admin)。render に要るのは roles/secretmanager.secretAccessor だけで、apply が読み手に与えます。

stores:
main:
local:
dir: ./store

シークレットごとに、dir の中の 1 つの JSON のファイルで、モード 0600 で書き、すべての版をそのままの文字で持ちます。tegata を試すときと試験に使ってください。読む権限は記録しますが、守らせはしません。

アイデンティティは、シークレットを読む IAM のメンバーにつける名前です。tegata render を流すサーバーのサービスアカウント、Secret Manager を自分で読むアプリケーション、CI のワークフローなどです。リソースは readers でアイデンティティを名指しして読む権限を与えます。アイデンティティそのものは何の権限も与えません。

identities:
app:
- serviceAccount:app-vm@my-project.iam.gserviceaccount.com
- serviceAccount:app-vm-2@my-project.iam.gserviceaccount.com
edge: [serviceAccount:edge-vm@my-project.iam.gserviceaccount.com]

各アイデンティティは、IAM のメンバーの一覧で、少なくとも 1 つ要ります:serviceAccount:...、user:...、group:...。

サーバーがどの雛形を埋めるかは、ここではなく、デプロイするところで決めます。アイデンティティが、自分の埋める雛形の中のすべての名前を読めるかを確かめるには、両方を tegata check に渡します:tegata check edge ./templates/edge。

外のプロバイダーは、tegata が操作のたびに 1 回動かす実行ファイルです(プロバイダーとの約束を見てください)。組み込みのプロバイダーの random、manual、value は、ここに書かなくてかまいません。

providers:
cloudflare:
source: github.com/k-kawa/tegata
version: "^0.3"
config:
account_id: 0123456789abcdef
credentials:
api_token: { env: TEGATA_CLOUDFLARE_TOKEN, optional: true }
キー 説明
command 実行ファイル。/ があれば YAML のファイルからの相対のパス。なければ PATH から、次に tegata の実行ファイルの隣から探す。command か source のどちらかを書く
source リリースにプロバイダーを置いている GitHub のリポジトリ、github.com/<owner>/<repo>。tegata init が入れる(プロバイダーを入れるを見てください)
version source のとき(必須):Cargo の書き方の版の要件。^1.2、~1.2.3、=1.2.3、>=1, <2、* など。1.2 だけなら ^1.2
public_key source のとき:リリースに署名している minisign の公開の鍵。github.com/k-kawa/tegata には要らない(鍵が組み込まれている)
config すべての操作に provider_config として渡す設定(アカウントの ID など。シークレットは決して書かない)。各プロバイダーが何を受け取るかはプロバイダーを見てください
credentials プロバイダーがその API を呼ぶのに要るシークレット。名前ごとに書く。下を見てください

各認証情報:

キー 説明
env 読む環境変数
from 読む認証情報の読み元:gcp の読み元なら { source, secret, version }(version の既定は、有効な最新のもの)、command の読み元なら { source, ref }
prompt tegata が聞くときの問い(既定は <provider> <name>)
optional true で、環境変数からも from からも値が取れなければ、tegata は聞かずにその認証情報を外し、どうするかはプロバイダーが決める(たとえば CLI のログインを使う)。既定は false

tegata は、1 回の実行につき 1 回、この順で探します。環境変数 env(あって空でなければ)、次に from、次に端末での隠した入力(端末の外ではエラー)。ただし optional なら聞きません。そのため、env で一度だけ from を上書きできます。認証情報はメモリーの中にだけ置き、決して書かず、ログにも出さず、エラーにも見せません。

プロバイダーの管理の認証情報、つまりプロバイダーがほかのトークンを作るためのトークンを、どこから取るかです。stores とは分けておきます。保存先のプロジェクトを管理する人は、その中のどれでも自分に読む権限を付けられるからです。

credential_sources:
admin:
gcp: { project: myorg-admin } # Secret Manager in another project
op:
command: [op, read] # any command; the reference is added as its last argument
providers:
cloudflare:
credentials:
api_token:
from: { source: admin, secret: cloudflare-admin-token }
env: TEGATA_CLOUDFLARE_TOKEN # wins when set
tailscale:
credentials:
client_secret: { from: { source: op, ref: "op://infra/tailscale/oauth-secret" } }
種類 読むもの
gcp: { project } そのプロジェクトの Secret Manager を、保存先と同じ Google の認証情報で(TEGATA_GCP_ACCESS_TOKEN、VM のサービスアカウント、gcloud)。アカウントに要るのは、そこで読むシークレットへの roles/secretmanager.secretAccessor だけ
command: [program, args...] シェルを通さずにプログラムを動かし、最後の引数に認証情報の ref を渡し、標準出力を取る(最後の改行 1 つは取り除く)。端末を渡すので、ロックの解除を求める道具(1Password)も使え、60 秒以内に終わること。失敗したときは終了コードを伝え、出力は決して見せない。/ のあるプログラムは、このファイルからの相対。例:1Password([op, read])、Vault([vault, kv, get, -field=token])、AWS Secrets Manager([aws, secretsmanager, get-secret-value, --query, SecretString, --output, text, --secret-id])、[pass, show]

各読み元には、種類をちょうど 1 つ書きます。管理の認証情報をどう置き、長く使う鍵なしで CI で tegata をどう動かすか:管理の認証情報と CI。

リソースは、プロバイダーが作る 1 つのもの、その出力を持つシークレット、それを誰が読むかです。

resources:
session_key:
provider: random
config: { bytes: 32 }
stores:
main:
- { name: SESSION_KEY, readers: [app] }
previous: { name: PREVIOUS_SESSION_KEY }
releases_read:
provider: cloudflare
type: api_token
config: { ... }
stores:
main:
- { output: id, name: RELEASES_ACCESS_KEY_ID, readers: [app] }
- { output: r2_secret_access_key, name: RELEASES_SECRET_ACCESS_KEY, readers: [app, edge] }

リソースの名前(session_key)には、英小文字、数字、-、_ を 63 文字まで使えます(ラベルの値として置くため)。

キー 説明
provider random、manual、value、または providers の下の名前
type プロバイダーのリソースの種類(api_token など)。組み込みのプロバイダーでは書かない
config このリソースの設定。プロバイダーに config として渡す
stores.<store> その保存先のシークレット:項目の一覧で、少なくとも 1 つ(下を見てください)
previous なくてもよい。{ name: <secret> }:最後の入れ替えの前の値を持つ、同じ保存先のシークレット。サービスがしばらく新旧両方の値を受け付けられるようにする(シークレットを入れ替えるを見てください)。リソースの最初のシークレット(名前の順で)の値を持ち、同じ読み手が読む。リソースにこれが要るのはいつか:シークレットに previous が要るとき

stores.<store> の下の各項目:

キー 説明
output このシークレットに置く、プロバイダーの出力。既定は value で、random、manual、cloudflare/tunnel_token が返すもの。各種類の出力はプロバイダーを見てください
name 保存先でのシークレットの名前
readers それを読めるアイデンティティ。既定はなし。読む権限は、これらのアイデンティティのメンバーとちょうど同じになる:1 つ足して tegata apply を流すと権限が付き、外して apply を流すと外れる

シークレットの名前には、英字、数字、-、_ を 255 文字まで使えます。雛形で使うには、名前が英大文字で始まり、英大文字、数字、_ だけでできている必要があります(たとえば SESSION_KEY)。各シークレットは、ちょうど 1 つのリソースに属します。

リソースの名前を変えても、そのシークレットと値は残ります。YAML にもうないリソースのラベルを持つシークレットがあれば、apply はそれを、今それを書いているリソースに移します。まだあるほかのリソースのラベルを持つシークレットは、エラーです。

プロバイダー 設定 出力
random bytes:8 から 256、既定は 32 OS から取ったランダムなバイト列を、小文字の 16 進で(32 バイトなら 64 文字)。どの出力の名前にも同じ値が入るので、output は書かなくてよい
manual なし tegata set <resource> で入れる値。apply はシークレットを作るが、値は作らない
value 出力の名前 → 文字列の map その文字列そのもの。雛形でシークレットの隣に要る、シークレットでない値のため(送り元のアドレス、バケットの名前)。apply のたびに読み直す

環境(ステージング、本番など)は 1 つの設定を共にし、違う必要のあるところだけが違います。保存先、各アイデンティティのメンバー、プロバイダーの設定と認証情報、リソースの設定、届け先です。誰が何を読むか(readers)は、どの環境でも同じです。

各環境の上書きは、そのためのファイルに書くか、その場に書きます。両方を混ぜてもかまいません。

# tegata.yaml: what every environment shares
stores:
main:
gcp: { location: asia-northeast1 } # the project comes from the environment
identities:
app: [] # the members come from the environment
environments:
staging: envs/staging.yaml # a file, relative to this one
production: # or inline
confirm: true
stores: { main: { gcp: { project: myapp-prod } } }
identities: { app: [serviceAccount:app@myapp-prod.iam.gserviceaccount.com] }
resources: { db_password: { config: { instance: prod-main } } }
resources:
db_password:
provider: gcp
type: sql_user
config: { user: app } # the instance comes from the environment
stores:
main:
- { output: password, name: DATABASE_PASSWORD, readers: [app] }
# envs/staging.yaml: only what differs in staging
stores: { main: { gcp: { project: myapp-staging } } }
identities: { app: [serviceAccount:app@myapp-staging.iam.gserviceaccount.com] }
resources: { db_password: { config: { instance: staging-main } } }

環境は -e <name>(または TEGATA_ENV)で選びます。設定に環境があるとき、render、init、check、env 以外のすべてのコマンドに要ります。既定の環境はありません。tegata env <name> で、上書きをまとめた環境の設定を出せます。

環境で書けるもの。 上書きは、共通の部分の上にまとめます。map はキーごとに、一覧とほかの値は置き換えます。

キー 説明
stores.<store> 保存先、またはその一部(ここではプロジェクト)
identities.<identity> メンバー。アイデンティティは共通の部分で書いておく必要がある。どの環境もメンバーを書くなら、[] で書いておく
providers.<provider>.config、.credentials プロバイダーの設定と認証情報(たとえば、本番の管理のトークンのための別のシークレットや環境変数)。プロバイダーは共通の部分にあること
credential_sources 管理の認証情報をどこから取るか(たとえば、本番は別のプロジェクトから)
resources.<resource>.config リソースの設定。リソースは共通の部分にあること:環境でリソースを足したり外したりはできない
destinations 届け先。名前ごとにまとめる
confirm true:保存先を変えるコマンドが、先に聞く(下)。環境の中にだけ書ける

ほかのものはエラーで、その環境のファイルを挙げます。まとめたあと、環境はそろっていなければなりません。保存先と、すべてのアイデンティティに少なくとも 1 つのメンバーです。

環境のファイルの中のパス(Terraform の dir など)は、ほかのすべてのパスと同じく、もとの設定のファイルからの相対です。

confirm: true にすると、apply、rotate、settle、prune、set、import は、その環境で何をするかを伝え、何かを変える前に環境の名前を求めます。スクリプトと CI では --yes で問いを飛ばします。端末の外で --yes がなければ断ります。そのため、値を標準入力で渡す set には --yes が要ります。--dry-run と、読むだけのコマンドは、決して聞きません。

$ tegata -e production rotate session_key
production: make a new generation of session_key
Type the environment name to continue: production

環境を分けたままにする。 シークレットには tegata-env=<name> のラベルが付き、コマンドは、ほかの環境のラベルの付いたシークレットには触りません。-e なしの tegata check はすべての環境を確かめ、同じ Google Cloud のプロジェクトを使ったり、同じ IAM のメンバーに読む権限を与えたりする 2 つの環境を挙げます。tegata.lock は共通です。どの環境も同じプロバイダーを使います。

環境は今の形(stores、identities)と一緒に使います。古い形のファイルは、先に migrate します。

届け先は、シークレットの写しを受け取ります。値が変わるたびに、tegata が最新の値をそこに書きます。今ある種類は GitHub Actions の secrets だけで、リポジトリの secrets か、そのリポジトリの環境の secrets です。

destinations:
releases-ci:
github: { repo: my-org/my-repo }
secrets: [RELEASES_ACCESS_KEY_ID, RELEASES_SECRET_ACCESS_KEY]
deploy:
github: { repo: my-org/my-repo, environment: production }
secrets:
DEPLOY_TOKEN: deploy_token_value # name on GitHub: secret name in tegata
キー 説明
github.repo owner/repo
github.environment なくてもよい。リポジトリの代わりに、この環境の secrets に書く。環境の secrets は保護の規則(レビュアーが要る、ブランチ)で絞れ、同じ名前のリポジトリの secrets より優先される
secrets 届けるシークレット:一覧(GitHub でも同じ名前)か、GitHub での名前 → tegata でのシークレットの名前の map。リソースのどのシークレットでもよく、previous のシークレットも届けられる

GitHub の secrets の名前には、英字、数字、_ を使え、数字や GITHUB_ で始めてはならず、大文字と小文字を区別しません。tegata はファイルを読むときにこれを確かめます。合わない名前は、map で名前を替えてください。

正は保存先のまま。 GitHub の secrets は書けても読み返せず、版も残らないので、届け先が受け取るのは写しだけです。tegata は、届けるたびに保存先のシークレットにそれを記録します(注記の tegata-delivered:書いた版、どこに、書いたすぐあとの GitHub の更新の時刻)。

コマンド 届け先で起きること
apply 最新の版をまだ届けていないシークレットと、tegata が書いたあとに GitHub で変わったり消えたりしたシークレットを書く(保存先の値が勝ち、tegata はそう伝える)。tegata が書いていない同じ名前の secret も上書きし、そのことも伝える。設定がもう挙げていない届け先(届け先から外したシークレット、そこで名前を替えたもの、届け先ごと外したもの)は GitHub から消す。対象になるのは、この環境で、この設定が書いているシークレットだけ:リソースを消すとき、保存先を共にするときを見てください。tegata が書いていない secret は、決して消さない
rotate、set、settle、import 変わった値(と、まだ届けていないもの)を書く。ワークフローは次の実行から新しい値を使う。再起動するものはない
prune 何もしない(GitHub は最新の値しか持たない)
status 届け先とシークレットごとに:届けた(版)、まだ届けていない、GitHub で変わった、なくなった。tegata が触らない、そこにあるほかの secrets も挙げ、環境ならリポジトリの secrets にもある名前も挙げる。設定が書いていない managed のシークレットは、まだどこに届いているかを挙げる
apply --dry-run 何を書き、何を消すかを見せる

ほかの環境のラベルの付いたシークレットは、決して届けません。その値をこの環境の届け先に届けてはならないからです。コマンドは、それを挙げてエラーで終わります。

リソースを消すとき、保存先を共にするとき

Section titled “リソースを消すとき、保存先を共にするとき”

複数の設定が、同じ保存先を使ってもかまいません。たとえば、2 つのリポジトリがそれぞれ自分の tegata.yaml を持ち、シークレットを 1 つの Google Cloud のプロジェクトに置く場合です。互いに触らないように、apply が届け先を消すのは、この設定が書いているシークレット(リソースの中の。previous も含む)で、ほかの環境のラベルの付いていないものだけです。保存先の中では、ほかの設定のシークレットと、リソースを消したシークレットは見分けがつかないので、tegata はどちらの届け先もそのままにします。

リソースと GitHub の写しを消すには、2 段に分けます。

  1. そのシークレットを destinations から外して apply を流す:写しが GitHub から消えます。
  2. リソースを外す。シークレットは保存先に残り、status は not in the config と挙げます。何も読まなくなったら、手で消してください。

先にリソースを外してしまったときは、status が、そのシークレットがまだどこに届いているかを示します(delivered to … (…; apply leaves it))。リソースを戻して上の手順に従うか、その GitHub の secrets を手で消してください。

保存先を共にする設定は、同じシークレットの名前を書かないでください。シークレットの持ち主は 1 つです。

書けなかったとき(権限がない、GitHub につながらない)も、値は保存先にあります。コマンドは、届けられなかったものを挙げてエラーで終わり、次の apply が届けます。

トークン: TEGATA_GITHUB_TOKEN、なければ gh コマンドのログイン(gh auth token)。fine-grained のトークンには、届ける先のリポジトリで、リポジトリの権限「Secrets」(リポジトリの secrets のため)か「Environments」(環境の secrets のため)の書き込みが要ります。tegata init がリリースを読むのに使う GITHUB_TOKEN とは別の環境変数にしてあるので、secrets を書けるトークンをうっかり使うことはありません。

Google Cloud で動かすなら、ワークフローが Workload Identity Federation で Secret Manager を直接読むことも考えてください。ワークフローのアイデンティティを、そのシークレットを読むアイデンティティのメンバーにすれば、GitHub にシークレットの写しを残さずに済みます。

雛形は、印の入ったテキストのファイルです。

印 置き換わるもの
__NAME__ シークレット NAME の値
__NAME|sha256__ 値の SHA-256(小文字の 16 進)。ハッシュだけを持つサービスのため

NAME は英大文字で始まり、英大文字、数字、_ だけでできています。__ と __ の間のほかのものはそのままにするので、ファイルの中の __init__ や ____ は印になりません。

tegata render <templates> <out> は、<templates> の直下のすべてのファイルを、各シークレットの有効な最新の版で埋め、結果を <out> に同じファイルの名前で書きます。<out> はモード 0700 で作り、各ファイルは一時的なファイルに書いて名前を変えるやり方で、モード 0600 で書くので、読む側が書きかけのファイルを見ることはありません。保存先にない名前や、呼んだ人が読めない名前は __NAME__ のまま残し、出力に挙げます。

render が読むのは保存先だけで、YAML は読まず、プロバイダーも決して動かしません。Secret Manager ではプロジェクトが要ります:--project <project>、または Compute Engine の VM なら、その VM のプロジェクトです。--store-dir <dir> を付けると、代わりに local の保存先を読みます。

managed のリソースが新しい値を作るたびに、その値は 1 つの世代になります。リソースの各シークレットに新しい版が 1 つずつと、プロバイダーの state(あとでそれを消すのに要るもの)です。

コマンド 変わるもの
apply シークレットにまだ値のない managed のリソースそれぞれに、最初の世代を作る。data のリソース(value、terraform)は、出力を読み直す。変わった出力は新しい版になり、古い版は無効にする
rotate <resource> 新しい世代を作る。リソースに previous があれば、先に今の値をそこに写す
settle <resource> 今の値を previous に写し、古い値が受け付けられないようにする
prune [<resource>] 最新より古いすべての世代について:プロバイダーに、それが作ったもの(API トークン、アクセスキー)を消させ、その世代の版を無効にする。previous の古い版も無効にする。版は無効にするだけで決して消さないので、必要なら保存先で有効に戻せる
set <resource> 値を手で、新しい世代として入れる。外のプロバイダーのために手で(か import で)入れた世代は、tegata が作ったものではないので prune は消さない。prune は、手で片付けるように伝える

値の種類ごとの入れ替えの手順:シークレットを入れ替える。

すぐに使えなくなる値。 値を 1 つしか持てない種類(データベースのユーザーのパスワード、Cloudflare Tunnel のシークレット)を書いているプロバイダーがあります。新しく作ると、古い値はすぐに使えなくなります。apply や rotate がそういう値を作る前に、tegata は何が止まるかと誰が読んでいるかを示し、yes と打つように求めます。--yes で問いを飛ばし(スクリプトのため)、--dry-run は何を作るかを見せるだけです。端末の外で --yes がなければ、tegata は断ります。

tegata 0.1 向けに書いたファイルは、store、roles、resources.*.secrets を使っています。tegata は今もこれを警告つきで読み、そのときの読む権限は、これまでどおり雛形から決まります。

store: { local: { dir: ./store } }
roles:
app:
member: serviceAccount:app-vm@my-project.iam.gserviceaccount.com
templates: ./templates/app # app reads every secret named in these templates
resources:
session_key:
provider: random
secrets: { SESSION_KEY: value } # secret name: provider output
previous: PREVIOUS_SESSION_KEY

1 つのファイルでは、どちらか一方の形を使います。混ぜるとエラーです。tegata migrate は、各シークレットの readers を雛形から埋めて(今 apply が与えているもの)、ファイルを今の形で出すので、そのあとの apply は保存先を何も変えません。コメントは残らず、role の templates も残りません。migrate は、それを比べる tegata check <identity> <templates> のコマンドを標準エラー出力に出すので、CI やデプロイのスクリプトに使ってください。今の形では、previous のシークレットは、写すもとの値の読み手が読みます。雛形がそれぞれに違う読み手を与えているときは、migrate は止まってそれを挙げるので、先に雛形をそろえてください。

ターミナルウィンドウ
tegata migrate > tegata.new.yaml
diff tegata.yaml tegata.new.yaml
mv tegata.new.yaml tegata.yaml
tegata check
tegata apply --dry-run

tegata init が YAML のファイルの隣に書き、コミットするためのファイルです。source のあるプロバイダーごとに、選んだ版、その版を選んだ版の要件、それに署名した公開の鍵、リリースのすべてのプラットフォームの実行ファイルの SHA-256 を記録します。プロバイダーを入れるを見てください。

環境変数 使い道
TEGATA_FILE 設定のファイル(-f の代わり)
TEGATA_ENV 環境(-e の代わり)
TEGATA_GCP_ACCESS_TOKEN Secret Manager(と gcp プロバイダー)のための OAuth のアクセストークン
TEGATA_CACHE_DIR tegata init がプロバイダーを入れる場所(既定は $XDG_CACHE_HOME/tegata、または ~/.cache/tegata)
TEGATA_PROVIDER_MIRROR プロバイダーを手元のディレクトリから入れる。中は <dir>/<source>/<version>/<file> の形
GITHUB_TOKEN tegata init が GitHub に送る(なくてもよい。非公開のリポジトリには要る)。なければ、API を使うのはリリースの一覧だけで、ファイルは公開のダウンロードの URL から取る
TEGATA_GITHUB_TOKEN GitHub Actions の secrets を書くためのトークン(届け先)。なければ gh auth token
TEGATA_GITHUB_API tegata init と届け先のための、GitHub の API の URL(既定は https://api.github.com)
TEGATA_GITHUB_DOWNLOAD tegata init のための、公開のダウンロードの URL のもと(既定は https://github.com)

credentials で名指しした環境変数も読みます。試験だけが使う環境変数(tegata とプロバイダーを偽のサーバーに向けるもの)はソースコードに挙げてあり、約束の一部ではありません。

このページの問題を報告する(GitHub。英語でも日本語でも)