fix: address final review findings for master/agent phase 1 (#6)

- inventory: resolve the image reference via ContainerInspect when a moved
  tag makes ContainerList report the image ID
- store: allow reusing a revoked agent's name (partial unique index) and
  mark the revoked agent's compose/container services removed
- web: publish a services event on revoke; no-store on the one-time token page
- webauth: reject empty OIDC state; accept the CSRF form token from the body only
- agent conn: bound the WebSocket handshake to 30s
- hub: log agent_replaced when a connection replaces an older one
This commit is contained in:
2026-10-12 15:16:29 +02:00
parent 4f092eaf79
commit 0e9e34db06
12 changed files with 211 additions and 35 deletions
+21 -4
View File
@@ -24,7 +24,8 @@ func scanAgent(row pgx.Row) (model.Agent, error) {
return a, err
}
// CreateAgent registers an agent. Returns model.ErrConflict if the name is taken.
// CreateAgent registers an agent. Returns model.ErrConflict if an active
// (non-revoked) agent already has the name.
func (s *Store) CreateAgent(ctx context.Context, name, tokenHash string) (model.Agent, error) {
a, err := scanAgent(s.pool.QueryRow(ctx,
`INSERT INTO agents (name, token_hash) VALUES ($1, $2) RETURNING `+agentColumns, name, tokenHash))
@@ -62,13 +63,29 @@ func (s *Store) AgentByTokenHash(ctx context.Context, hash string) (model.Agent,
return scanAgent(s.pool.QueryRow(ctx, `SELECT `+agentColumns+` FROM agents WHERE token_hash = $1`, hash))
}
// RevokeAgent marks the agent revoked. Its token stops working immediately;
// closing a live connection is the hub's job.
// RevokeAgent marks the agent revoked and its Compose and plain-container
// services removed, in one transaction. Swarm rows are shared with other
// managers and left alone. Its token stops working immediately; closing a
// live connection is the hub's job.
func (s *Store) RevokeAgent(ctx context.Context, id int64, at time.Time) error {
if _, err := s.pool.Exec(ctx,
tx, err := s.pool.Begin(ctx)
if err != nil {
return fmt.Errorf("revoke agent: begin: %w", err)
}
defer func() { _ = tx.Rollback(ctx) }()
if _, err := tx.Exec(ctx,
`UPDATE agents SET revoked_at = $2 WHERE id = $1 AND revoked_at IS NULL`, id, at); err != nil {
return fmt.Errorf("revoke agent: %w", err)
}
if _, err := tx.Exec(ctx,
`UPDATE services SET removed_at = $2
WHERE agent_id = $1 AND kind IN ('compose', 'container') AND removed_at IS NULL`, id, at); err != nil {
return fmt.Errorf("revoke agent: remove services: %w", err)
}
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("revoke agent: commit: %w", err)
}
return nil
}