Files
mesh-catalog/modules/claude-code/cmd/claude-code/forge.go
T

143 lines
5.7 KiB
Go

package main
// The forge's read credential in the agent's home (novox/hq ADR 0365 §4, issue 737).
//
// The forge's module mints each node's agent a token of its own account, `agent-<node>`, scoped to read
// repositories alone, and the vault keeps it for this module on this machine alone (provision `gitea-read`, issue
// 749). What arrives in this module's state is git's credential-store lines for it, one per origin the forge is
// reached at. This module places them in the agent's home, a file of mode 0600 written as the agent's account (ADR
// 0266: no other module writes there), and points every session's git at it through the managed settings'
// environment: the file's path, never its content. So an agent clones and fetches what its account may read, and no
// token is printed, typed on a command line or put in an environment a process listing shows.
//
// Git's own `store` helper reads the file; on a token the forge rejects it erases that line, and on a password a
// person typed elsewhere it adds one. Both are undone at the next look (every few seconds): the file holds what the
// forge issued, and nothing else. A value that is not credential lines — one the mesh made while the forge had not
// been asked yet — is not placed, and what was placed before is taken back.
import (
"bytes"
"errors"
"fmt"
"io/fs"
"net/url"
"os"
"path/filepath"
"strings"
)
// ForgeProvision is the provision whose credential this is, and the file this module's manifest has it delivered to.
const (
ForgeProvision = "gitea-read"
forgeSecret = "gitea-read.secret"
)
func (p Paths) forgeSecret() string { return filepath.Join(p.State, forgeSecret) }
// forgeCredentials is where the agent's git reads the credential: under the agent's home, in a directory of the
// mesh's own.
func (p Paths) forgeCredentials() string {
return filepath.Join(p.Home, ".config", "mesh", "forge.credentials")
}
// ForgeCredentialLines are the credential lines held, checked: each an http(s) origin with an account and a token,
// and nothing else. Anything else is refused, said without the value.
func ForgeCredentialLines(raw string) (string, error) {
var lines []string
for _, line := range strings.Split(raw, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
u, err := url.Parse(line)
if err != nil || (u.Scheme != "https" && u.Scheme != "http") || u.Host == "" || u.User == nil ||
u.User.Username() == "" || u.Path != "" || u.RawQuery != "" || u.Fragment != "" {
return "", errors.New("what the mesh holds for the forge's read credential is not git credential lines " +
"(the forge has not issued it yet, or something else was given)")
}
if pw, ok := u.User.Password(); !ok || pw == "" {
return "", errors.New("a forge credential line names no token")
}
lines = append(lines, line)
}
if len(lines) == 0 {
return "", errors.New("the forge's read credential is empty")
}
return strings.Join(lines, "\n") + "\n", nil
}
// ForgeCredentialHeld says whether this machine holds a forge read credential to place.
func ForgeCredentialHeld(p Paths) bool {
raw, err := os.ReadFile(p.forgeSecret())
if err != nil {
return false
}
_, err = ForgeCredentialLines(string(raw))
return err == nil
}
// PlaceForgeCredential puts the forge's read credential in the agent's home as it is held — written, rewritten when
// it differs, taken back when none is held — and says what it did, or "" when nothing changed. Never the value.
func PlaceForgeCredential(p Paths) (string, error) {
path := p.forgeCredentials()
raw, err := os.ReadFile(p.forgeSecret())
var lines string
var why error
switch {
case errors.Is(err, fs.ErrNotExist):
why = errors.New("this machine holds no forge read credential")
case err != nil:
return "", fmt.Errorf("the forge's read credential cannot be read: %w", err)
default:
lines, why = ForgeCredentialLines(string(raw))
}
if why != nil {
if _, err := fsys.Stat(path); errors.Is(err, fs.ErrNotExist) {
return "", nil
}
if err := fsys.Remove(path); err != nil && !errors.Is(err, fs.ErrNotExist) {
return "", fmt.Errorf("the forge credential at %s could not be taken back: %w", path, err)
}
return fmt.Sprintf("forge credential: taken back from %s: %v", path, why), nil
}
if held, err := fsys.ReadFile(path); err == nil && bytes.Equal(held, []byte(lines)) {
if info, err := fsys.Stat(path); err == nil && info.Mode().Perm() == 0o600 {
return "", nil
}
}
if err := fsys.MkdirAll(filepath.Dir(path), 0o700); err != nil {
return "", fmt.Errorf("the directory for the forge credential could not be made: %w", err)
}
tmp := path + ".mesh-tmp"
if err := fsys.WriteFile(tmp, []byte(lines), 0o600); err != nil {
return "", fmt.Errorf("the forge credential could not be written: %w", err)
}
if err := fsys.Chmod(tmp, 0o600); err != nil {
_ = fsys.Remove(tmp)
return "", fmt.Errorf("the forge credential could not be made 0600: %w", err)
}
if err := fsys.Rename(tmp, path); err != nil {
_ = fsys.Remove(tmp)
return "", fmt.Errorf("the forge credential could not be put in place: %w", err)
}
return fmt.Sprintf("forge credential: placed at %s for %d origin(s)", path, strings.Count(lines, "\n")), nil
}
// forgeEnv is what every session's environment carries for git to read the credential: the file's path in git's
// configuration from the environment, never the credential. Laid over the operator's `env`, the mesh's keys winning.
func forgeEnv(managed map[string]any, credentials string) {
if credentials == "" {
return
}
env := map[string]any{}
if held, ok := managed["env"].(map[string]any); ok {
for k, v := range held {
env[k] = v
}
}
env["GIT_CONFIG_COUNT"] = "1"
env["GIT_CONFIG_KEY_0"] = "credential.helper"
env["GIT_CONFIG_VALUE_0"] = "store --file=" + shellQuote(credentials)
managed["env"] = env
}