Documentation
¶
Overview ¶
Package gitgateway implements the authenticating reverse proxy that sits between a sandbox's credential-less git and the real upstream forge (GitHub / Bitbucket). It is the enforcement half of the git gateway design in docs/plans/git-gateway-cutover.md ("gateway の認可モデル: 単一サーバ + job token の path prefix") and docs/plans/container-based-boid.md ("gateway の実現方式: Go 標準 ReverseProxy の自作薄層").
This package (PR3 of the cutover plan) is self-contained and inert: it is not wired into internal/server or the dispatcher yet (that is PR4), and nothing in the running daemon constructs a Server today. It purposefully avoids importing internal/dispatcher or internal/db so a sandbox test run (which cannot build the sqlite-backed layers) can still build and test this package on its own — secret resolution and notification are expressed as small function-typed seams (SecretResolver, UpstreamAuthFailureNotifier) that PR4 adapts to the real internal/dispatcher.SecretStore and internal/notify.
Index ¶
- Constants
- func GenerateToken() string
- type CredentialProvider
- type Entry
- type Forge
- type HostForgeConfig
- type NotifierFuncs
- type Operation
- type Permission
- type Registry
- func (r *Registry) Authorize(token string, repo RepoKey, op Operation) (allowed, tokenValid bool)
- func (r *Registry) Lookup(token string) (Entry, bool)
- func (r *Registry) Register(repos map[RepoKey]Permission) string
- func (r *Registry) RegisterToken(token string, repos map[RepoKey]Permission)
- func (r *Registry) Unregister(token string)
- type RepoKey
- type SecretResolver
- type Server
- type UpstreamAuthFailureNotifier
Constants ¶
const ( EndpointInfoRefs = "info/refs" EndpointUploadPack = "git-upload-pack" EndpointReceivePack = "git-receive-pack" )
Smart HTTP endpoint names (git-http-backend(1)). These are the only three endpoints the gateway forwards — docs/plans/git-gateway-cutover.md PR3: "git smart HTTP は 2 endpoint のみ".
const PathPrefix = "/j/"
PathPrefix is the fixed prefix every gateway route starts with: /j/<job-token>/<host>/<owner>/<repo>[.git]/<endpoint>.
Variables ¶
This section is empty.
Functions ¶
func GenerateToken ¶
func GenerateToken() string
GenerateToken returns a random hex-encoded job token. It uses the same crypto/rand + 16-byte scheme as internal/sandbox's broker token registry (docs/plans/git-gateway-cutover.md PR3: 「job token は broker token と 同じ方式 (crypto/rand、後で PR4 で dispatch 時登録・job 終了時失効)」). PR3 only defines the registry API and token shape; the dispatch-time register/unregister lifecycle lands in PR4.
Types ¶
type CredentialProvider ¶
type CredentialProvider struct {
// contains filtered or unexported fields
}
CredentialProvider injects forge-appropriate Basic auth into upstream requests, resolving the actual token value through a SecretResolver.
func NewCredentialProvider ¶
func NewCredentialProvider(hosts []HostForgeConfig, resolver SecretResolver) *CredentialProvider
NewCredentialProvider builds a CredentialProvider from a list of per-host forge configs and the resolver used to fetch each config's secret value.
func (*CredentialProvider) Configured ¶
func (c *CredentialProvider) Configured() bool
Configured reports whether c has any secret resolver wired at all. It distinguishes two very different failure modes at the caller (Server.ServeHTTP):
- resolver == nil: the daemon has no secret store configured at all (internal/server/wire.go builds a CredentialProvider with a nil resolver when config.KeyFilePath is unset) — a systemic "credentials aren't set up yet" state that is true for every host and every request, not specific to this one.
- resolver present but returns an error for a specific key: an ordinary per-key miss (e.g. a HostForgeConfig.SecretKey reference that was never `boid secret set`), which Inject still surfaces per-request via its usual error return.
This distinction exists so ServeHTTP can fail fast (403/503, no upstream contact) for the former without spamming NotifyCredentialError on every gateway request once real (non-inert) traffic starts flowing (docs/plans/git-gateway-cutover.md PR5 review: 「PR5 で clone が実行され 始めると user-visible noise になる」), while leaving the latter's existing fail-open + notify behavior (docs/plans/git-gateway-cutover.md PR4/PR3) completely unchanged.
func (*CredentialProvider) Inject ¶
func (c *CredentialProvider) Inject(req *http.Request, host string) error
Inject resolves host's configured secret and sets Basic auth on req using the forge's username convention. It returns an error (and leaves req unmodified) if host has no configured forge, no resolver is set, or the secret can't be resolved — callers log this rather than fail the request outright, since a misconfigured host is a config problem, not grounds to crash the gateway (docs/plans/git-gateway-cutover.md: 「gateway 自体は 落とさない」, said of upstream 401s but applied here in the same spirit).
func (*CredentialProvider) SchemeFor ¶
func (c *CredentialProvider) SchemeFor(host string) string
SchemeFor returns the upstream request scheme for host: the configured override if present, otherwise "https".
type Entry ¶
type Entry struct {
Token string
Repos map[RepoKey]Permission
}
Entry is one job token's authorization: which repos it may reach and at what permission level. docs/plans/git-gateway-cutover.md PR3 の許可集合 (自 project は fetch+push or fetch のみ、workspace peer は fetch のみ、 read-only 追加許可 repo も fetch のみ) はすべて呼び出し側 (PR4 の dispatch 配線) が Repos map の構築時に表現する — Registry 自体は集合の形を知らない。
type Forge ¶
type Forge string
Forge identifies which upstream git host convention to use for Basic auth.
type HostForgeConfig ¶
type HostForgeConfig struct {
// Host is the upstream host as it appears in the gateway route path
// (e.g. "github.com"), used as the lookup key.
Host string `yaml:"host"`
// Forge selects the Basic-auth username convention and (in future
// callers) any forge-specific behavior.
Forge Forge `yaml:"forge"`
// SecretKey is a reference into the secret store
// (internal/dispatcher/secret_store.go); never a plaintext token.
SecretKey string `yaml:"secret_key"`
// Scheme overrides the upstream request scheme; empty means "https".
// Production forge hosts always use https — this only exists so tests
// can point a HostForgeConfig at a plaintext httptest upstream.
Scheme string `yaml:"-"`
}
HostForgeConfig declares how the gateway authenticates requests to one upstream host: which forge convention to use and which secret-store key holds the token. "設定 1 フィールドで forge 種別を持てば足りる" per the container-based-boid.md token 戦略 section — Forge is that field.
type NotifierFuncs ¶
type NotifierFuncs struct {
UpstreamAuthFailure func(host string, repo RepoKey)
CredentialError func(host string, repo RepoKey, err error)
}
NotifierFuncs adapts plain functions to UpstreamAuthFailureNotifier. Either field may be left nil, in which case the corresponding notification is a no-op — callers that only care about one of the two failure modes (e.g. tests) do not need to stub both.
func (NotifierFuncs) NotifyCredentialError ¶
func (f NotifierFuncs) NotifyCredentialError(host string, repo RepoKey, err error)
NotifyCredentialError implements UpstreamAuthFailureNotifier.
func (NotifierFuncs) NotifyUpstreamAuthFailure ¶
func (f NotifierFuncs) NotifyUpstreamAuthFailure(host string, repo RepoKey)
NotifyUpstreamAuthFailure implements UpstreamAuthFailureNotifier.
type Operation ¶
type Operation string
Operation is the git operation implied by a request, derived from the endpoint name (and, for info/refs, the ?service= query parameter) rather than the HTTP method — docs/plans/git-gateway-cutover.md PR3: "push は git-receive-pack、fetch は git-upload-pack で判別 (endpoint 名 + service query param 両方)".
type Permission ¶
type Permission int
Permission is the access level a job token has on one repo.
const ( // PermFetch allows git-upload-pack only (read/clone/fetch). PermFetch Permission = iota + 1 // PermFetchPush allows both git-upload-pack and git-receive-pack. PermFetchPush )
func (Permission) Allows ¶
func (p Permission) Allows(op Operation) bool
Allows reports whether this permission covers the given operation.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the job-token → allowed-repo-set store. It is a deliberately simple sync.RWMutex-guarded map (docs/plans/git-gateway-cutover.md PR3 実装ヒント: 「job token レジストリは sync.RWMutex で守った map[token]entry の素朴実装で足りる」); PR4 adds the dispatch-time Register/Unregister call sites and a richer lifecycle, but the shape here doesn't need to change for that.
func (*Registry) Authorize ¶
Authorize reports whether token may perform op against repo. tokenValid is false when the token is unknown/expired (caller should respond 401); tokenValid is true but allowed is false when the token is valid but the repo/operation isn't in its allowed set (caller should respond 403).
func (*Registry) Lookup ¶
Lookup returns the entry for token, if any. A nil Registry behaves as an always-empty one (every token reports invalid) rather than panicking, so a Server constructed without a registry fails closed with 401s instead of crashing.
func (*Registry) Register ¶
func (r *Registry) Register(repos map[RepoKey]Permission) string
Register creates a new entry with a freshly generated token and returns it.
func (*Registry) RegisterToken ¶
func (r *Registry) RegisterToken(token string, repos map[RepoKey]Permission)
RegisterToken creates (or replaces) an entry under an explicit token. This is useful for callers (and tests) that already have a token value to correlate with — e.g. PR4 dispatch, which will want the gateway job token alongside the job id in logs.
func (*Registry) Unregister ¶
Unregister revokes a token. A subsequent Lookup/Authorize for it reports the token as invalid. Unregistering an unknown token is a no-op.
type RepoKey ¶
type RepoKey string
RepoKey identifies a repo by its normalized "<host>/<owner>/<repo>" form — always suffix-free. docs/plans/git-gateway-cutover.md PR3 節の設計調整: PR2 の Opus レビューで NormalizeOriginURL の HTTPS/SSH 非対称 (HTTPS 入力は suffix なし passthrough、SSH 入力は suffix 付与) が浮上した。 gateway はこの非対称を吸収する層として、".git" suffix の有無を問わず 同じ RepoKey に正規化する — 登録時 (Registry.Register) も lookup 時 (parsePath 経由) も必ずこの型を経由させ、吸収ロジックを一箇所に閉じる。
func NewRepoKey ¶
NewRepoKey builds a normalized RepoKey from path components, stripping a ".git" suffix from repo if present.
type SecretResolver ¶
SecretResolver resolves a secret-store key reference to its plaintext value. config.yaml (PR4) carries only the key reference — never the plaintext token (docs/plans/git-gateway-cutover.md: 「config.yaml の gateway ブロックは forge 種別と secret key 参照のみ持つ」). This is a plain function type, mirroring internal/sandbox.SecretResolver's shape, so PR4 can adapt internal/dispatcher.SecretStore.Get to it with a one-line closure instead of gitgateway importing the dispatcher package (which would drag the sqlite-backed internal/db build into this otherwise db-free package).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the git gateway's HTTP handler: a thin net/http/httputil. ReverseProxy wrapper that does path-based authorization (Registry) and forge credential injection (CredentialProvider) around the standard library's streaming transport. It never buffers request bodies — packfile POSTs are streamed straight through to the upstream forge (docs/plans/git-gateway-cutover.md PR3: 「ボディは無バッファ転送必須」).
Server has been wired into the running daemon since PR4 (docs/plans/git-gateway-cutover.md): internal/server/wire.go constructs one (Registry + CredentialProvider + notifier) and Server.Start/Stop own its listener lifecycle alongside the daemon's other subservers. PR5 is the first PR whose traffic actually reaches it in practice (the runner's sandbox-internal clone sequence, gated behind the still-inert sandbox.CloneSpec opt-in) — until a caller sets that, this handler serves zero real requests.
func NewServer ¶
func NewServer(registry *Registry, credentials *CredentialProvider, notifier UpstreamAuthFailureNotifier) *Server
NewServer builds a Server. credentials may be nil (requests are proxied without auth injection — useful for tests against an upstream that doesn't require it); notifier may be nil (defaults to NoopNotifier).
func (*Server) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP parses the request path, authorizes it against the registry, and — if allowed — rewrites it to the upstream forge URL and proxies it with credentials injected. Unrecognized paths get 404; unknown/expired tokens get 401; well-formed-but-disallowed repo/operation combinations get 403; anything else about the request shape (missing/bad ?service=, wrong method) gets 400/405.
type UpstreamAuthFailureNotifier ¶
type UpstreamAuthFailureNotifier interface {
NotifyUpstreamAuthFailure(host string, repo RepoKey)
NotifyCredentialError(host string, repo RepoKey, err error)
}
UpstreamAuthFailureNotifier is invoked on two distinct failure modes so an operator can tell them apart (docs/plans/git-gateway-cutover.md PR4, flagged in PR3's review: 「config error (認証注入失敗) と token 失効を differentiate できると良い」):
- NotifyUpstreamAuthFailure: the upstream forge responded with 401 to a request that WAS sent with injected credentials — the token itself is wrong/expired/revoked ("rotate the token"; docs/plans/container-based-boid.md 「token 戦略」: 「失効前提の運用」。両 forge とも token は失効前提).
- NotifyCredentialError: credential injection itself failed before the request ever reached the upstream — an unconfigured host, a missing resolver, or a secret-store lookup error ("fix the gateway config").
The gateway always logs a warning on either condition and additionally calls the matching hook, but never aborts serving other requests because of it (docs/plans/git-gateway-cutover.md: 「gateway 自体は落とさない」).
var NoopNotifier UpstreamAuthFailureNotifier = NotifierFuncs{}
NoopNotifier discards notifications. It is the default when a Server is constructed with a nil notifier.