mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
## Overview Part of the **boundary correlation** feature. Fixes lazy creation of `boundary_sessions` rows so it works within the agent's RBAC constraints, and consumes the new `ConfinedProcessName` field reported by boundary. Pairs with coder/boundary#206, which adds `ConfinedProcessName` to `ReportBoundaryLogsRequest`. This branch bumps the `github.com/coder/boundary` module to pick up that work. ## Problem `ensureSession` did a pre-insert existence check via `GetBoundarySessionByID`. Agents are **not permitted to read boundary sessions**, so that read path is not viable when the session is created from an agent-reported log batch. ## Changes - **Remove the pre-insert read.** `ensureSession` now inserts directly and treats a primary-key unique violation as success, covering sessions already created by a prior batch, a reconnection, or another coderd replica — without requiring read access. - **Per-connection guard.** Add a mutex-protected `ensuredSessions` set so repeated log batches on the same connection skip the existence check and insert entirely, touching the database only for the logs. On a transient insert failure the session is left unmarked so the next batch retries. - **Consume `ConfinedProcessName`.** Pass `req.GetConfinedProcessName()` through to the session insert. - **Bump boundary module** from `v0.9.0` to `v0.9.1-0.20260706095856-35ba90f9e8b2`. - **Tests.** - Add `TestReportBoundaryLogsAgentRBAC` (`coderd/boundary_logs_test.go`), an integration test that connects as a real workspace agent, verifies the session and log are persisted under agent RBAC, and asserts the agent subject cannot read boundary sessions — guarding against reintroducing a pre-insert read. - Add `TestReportBoundaryLogsSessionGuard` (session inserted once across two batches, logs inserted per batch) and `TestReportBoundaryLogsSessionRetriedOnError` (insert retried after a transient error). - Regenerate `agent-firewall` CLI docs/golden files and adjust the clidocgen template to render the YAML path when a flag has no long name. > 🤖 This PR was opened by Coder Agents on behalf of @SasSwart.
159 lines
3.5 KiB
Go
159 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/acarl005/stripansi"
|
|
|
|
"github.com/coder/coder/v2/buildinfo"
|
|
"github.com/coder/coder/v2/scripts/atomicwrite"
|
|
"github.com/coder/flog"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
//go:embed command.tpl
|
|
var commandTemplateRaw string
|
|
|
|
var commandTemplate *template.Template
|
|
|
|
func init() {
|
|
commandTemplate = template.Must(
|
|
template.New("command.tpl").Funcs(template.FuncMap{
|
|
"visibleSubcommands": func(cmd *serpent.Command) []*serpent.Command {
|
|
var visible []*serpent.Command
|
|
for _, sub := range cmd.Children {
|
|
if sub.Hidden {
|
|
continue
|
|
}
|
|
visible = append(visible, sub)
|
|
}
|
|
return visible
|
|
},
|
|
"visibleOptions": func(cmd *serpent.Command) []serpent.Option {
|
|
var visible []serpent.Option
|
|
for _, opt := range cmd.Options {
|
|
if opt.Hidden {
|
|
continue
|
|
}
|
|
// Skip YAML-only options that have no CLI flag; documenting them
|
|
// as if they were flags is misleading in the CLI reference.
|
|
if opt.Flag == "" && opt.FlagShorthand == "" {
|
|
continue
|
|
}
|
|
visible = append(visible, opt)
|
|
}
|
|
return visible
|
|
},
|
|
"atRoot": func(cmd *serpent.Command) bool {
|
|
return cmd.FullName() == "coder"
|
|
},
|
|
"newLinesToBr": func(s string) string {
|
|
return strings.ReplaceAll(s, "\n", "<br/>")
|
|
},
|
|
"wrapCode": func(s string) string {
|
|
return fmt.Sprintf("<code>%s</code>", s)
|
|
},
|
|
"commandURI": fmtDocFilename,
|
|
"fullName": fullName,
|
|
"tableHeader": func() string {
|
|
return `| | |
|
|
| --- | --- |`
|
|
},
|
|
"typeHelper": func(opt *serpent.Option) string {
|
|
switch v := opt.Value.(type) {
|
|
case *serpent.Enum:
|
|
return strings.Join(v.Choices, "\\|")
|
|
case *serpent.EnumArray:
|
|
return fmt.Sprintf("[%s]", strings.Join(v.Choices, "\\|"))
|
|
default:
|
|
return v.Type()
|
|
}
|
|
},
|
|
},
|
|
).Parse(strings.TrimSpace(commandTemplateRaw)),
|
|
)
|
|
}
|
|
|
|
func fullName(cmd *serpent.Command) string {
|
|
if cmd.FullName() == "coder" {
|
|
return "coder"
|
|
}
|
|
return strings.TrimPrefix(cmd.FullName(), "coder ")
|
|
}
|
|
|
|
func fmtDocFilename(cmd *serpent.Command) string {
|
|
if cmd.FullName() == "coder" {
|
|
// Special case for index.
|
|
return "./index.md"
|
|
}
|
|
name := strings.ReplaceAll(fullName(cmd), " ", "_")
|
|
return fmt.Sprintf("%s.md", name)
|
|
}
|
|
|
|
func writeCommand(w io.Writer, cmd *serpent.Command) error {
|
|
var b strings.Builder
|
|
err := commandTemplate.Execute(&b, cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
content := stripansi.Strip(b.String())
|
|
|
|
// Remove the version and its right space, since during this script running
|
|
// there is no build info available
|
|
content = strings.ReplaceAll(content, buildinfo.Version()+" ", "")
|
|
|
|
// Remove references to the current working directory
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
content = strings.ReplaceAll(content, cwd, ".")
|
|
|
|
homedir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
content = strings.ReplaceAll(content, homedir, "~")
|
|
|
|
_, err = w.Write([]byte(content))
|
|
return err
|
|
}
|
|
|
|
func genTree(dir string, cmd *serpent.Command, wroteLog map[string]*serpent.Command) error {
|
|
if cmd.Hidden {
|
|
return nil
|
|
}
|
|
|
|
path := filepath.Join(dir, fmtDocFilename(cmd))
|
|
|
|
var buf strings.Builder
|
|
err := writeCommand(&buf, cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = atomicwrite.File(path, []byte(buf.String()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
flog.Successf(
|
|
"wrote\t%s",
|
|
path,
|
|
)
|
|
wroteLog[path] = cmd
|
|
for _, sub := range cmd.Children {
|
|
err = genTree(dir, sub, wroteLog)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|