supply chain consumer: refinements + test coverage
This commit is contained in:
parent
c99d0a1815
commit
ea9fbc1d7b
2 changed files with 58 additions and 4 deletions
|
|
@ -253,12 +253,16 @@ func (c *Consumer) ProcessToken(ctx context.Context, token *capability.Token) (*
|
|||
if token.AgentID != c.agentID.String() {
|
||||
log.Printf("[SECURITY] [agent] [supplychain] bind_check_failed token_id=%s token_agent_id=%s host_agent_id=%s",
|
||||
token.TokenID, token.AgentID, c.agentID)
|
||||
return nil, fmt.Errorf("token not bound to this host")
|
||||
err := fmt.Errorf("token not bound to this host")
|
||||
c.reportTokenProcessFailure(token, err)
|
||||
return nil, err
|
||||
}
|
||||
if !allowedCapabilityPackageType(token.PackageType) {
|
||||
log.Printf("[SECURITY] [agent] [supplychain] package_type_refused token_id=%s package_type=%s",
|
||||
token.TokenID, token.PackageType)
|
||||
return nil, fmt.Errorf("package_type %q not in allowlist", token.PackageType)
|
||||
err := fmt.Errorf("package_type %q not in allowlist", token.PackageType)
|
||||
c.reportTokenProcessFailure(token, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -278,6 +282,7 @@ func (c *Consumer) ProcessToken(ctx context.Context, token *capability.Token) (*
|
|||
if err != nil {
|
||||
log.Printf("[ERROR] [agent] [supplychain] token_process_failed token_id=%s package_type=%s error=%v",
|
||||
token.TokenID, token.PackageType, err)
|
||||
c.reportTokenProcessFailure(token, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -293,6 +298,19 @@ func (c *Consumer) ProcessToken(ctx context.Context, token *capability.Token) (*
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (c *Consumer) reportTokenProcessFailure(token *capability.Token, cause error) {
|
||||
if c.reporter == nil || token == nil {
|
||||
return
|
||||
}
|
||||
reason := "token_process_failed"
|
||||
if cause != nil {
|
||||
reason = cause.Error()
|
||||
}
|
||||
if err := c.reporter.ReportCapabilityResult(c.agentID, token.TokenID, "failed", reason, 1); err != nil {
|
||||
log.Printf("[WARNING] [agent] [supplychain] failure_result_report_failed token_id=%s error=%v", token.TokenID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessTokens runs ProcessToken for each token, continuing past individual
|
||||
// failures so one bad token does not strand the rest.
|
||||
func (c *Consumer) ProcessTokens(ctx context.Context, tokens []*capability.Token) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,25 @@ import (
|
|||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type recordingReporter struct {
|
||||
agentID uuid.UUID
|
||||
tokenID string
|
||||
decision string
|
||||
reason string
|
||||
exitCode int
|
||||
calls int
|
||||
}
|
||||
|
||||
func (r *recordingReporter) ReportCapabilityResult(agentID uuid.UUID, tokenID string, decision, reason string, exitCode int) error {
|
||||
r.agentID = agentID
|
||||
r.tokenID = tokenID
|
||||
r.decision = decision
|
||||
r.reason = reason
|
||||
r.exitCode = exitCode
|
||||
r.calls++
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSafeTokenFilename(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -162,7 +181,8 @@ func TestInstallDesktopBinaryBacksUpAndReplaces(t *testing.T) {
|
|||
func TestConsumerProcessTokenRejectsWrongAgentID(t *testing.T) {
|
||||
agentID := uuid.Must(uuid.NewV4())
|
||||
executor := NewExecutor("/nonexistent/helper")
|
||||
consumer := NewConsumer(agentID, executor, nil)
|
||||
reporter := &recordingReporter{}
|
||||
consumer := NewConsumer(agentID, executor, reporter)
|
||||
|
||||
wrongID := uuid.Must(uuid.NewV4())
|
||||
tok := &capability.Token{
|
||||
|
|
@ -179,12 +199,22 @@ func TestConsumerProcessTokenRejectsWrongAgentID(t *testing.T) {
|
|||
if got := err.Error(); got != "token not bound to this host" {
|
||||
t.Errorf("unexpected error: %v", got)
|
||||
}
|
||||
if reporter.calls != 1 {
|
||||
t.Fatalf("reporter calls = %d, want 1", reporter.calls)
|
||||
}
|
||||
if reporter.agentID != agentID || reporter.tokenID != "tok-1" || reporter.decision != "failed" || reporter.exitCode != 1 {
|
||||
t.Fatalf("reported result = %#v", reporter)
|
||||
}
|
||||
if reporter.reason != "token not bound to this host" {
|
||||
t.Fatalf("reported reason = %q", reporter.reason)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsumerProcessTokenRejectsDisallowedPackageType(t *testing.T) {
|
||||
agentID := uuid.Must(uuid.NewV4())
|
||||
executor := NewExecutor("/nonexistent/helper")
|
||||
consumer := NewConsumer(agentID, executor, nil)
|
||||
reporter := &recordingReporter{}
|
||||
consumer := NewConsumer(agentID, executor, reporter)
|
||||
|
||||
tok := &capability.Token{
|
||||
TokenID: "tok-2",
|
||||
|
|
@ -197,6 +227,12 @@ func TestConsumerProcessTokenRejectsDisallowedPackageType(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Fatal("expected error for disallowed package type, got nil")
|
||||
}
|
||||
if reporter.calls != 1 {
|
||||
t.Fatalf("reporter calls = %d, want 1", reporter.calls)
|
||||
}
|
||||
if reporter.tokenID != "tok-2" || reporter.decision != "failed" || reporter.reason != "package_type \"cargo\" not in allowlist" {
|
||||
t.Fatalf("reported result = %#v", reporter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsumerProcessTokensSummary(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue