Skip to content

Commit 248c0c0

Browse files
committed
Cleaned up Claude's initial impl, had it generate exuietsnive tests, TRhey are... a bit much :-) Will delete later.
1 parent 6470213 commit 248c0c0

File tree

3 files changed

+427
-253
lines changed

3 files changed

+427
-253
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ Actually, interestingly, we might be able to use openssh's ssh-agent to do this,
4242
`epithet-agent` -> `[ssh-agent]` where it invokes ssh-agent commands on the agent as needed. We can optimize a number of things by keeping track of the certificates they are issued, so we can query the cert (and keys) without spawning a child process. Communication with the agent can be done via [ssh-agent-client-rs](https://github.com/nresare/ssh-agent-client-rs) which seems very fit for purpose.
4343

4444
We should consider destination constraining the target host for these agents. Need to think about abuse vectors if we don't do that.
45+
46+
## TODO
47+
48+
- **Implement a less strict netstring parser**: The current auth plugin protocol uses the `markdingo/netstring` library which strictly rejects whitespace between netstrings. This makes debugging auth plugins difficult since developers can't use `println()` for debugging output. We should implement a custom netstring parser that tolerates whitespace (spaces, tabs, `\n`, `\r`) between netstrings while still being strict about the netstring format itself. This would maintain protocol compatibility while significantly improving developer experience when writing auth plugins.

pkg/broker/auth.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ const (
2020
)
2121

2222
// AuthOutput represents the result of an auth command invocation
23-
type AuthOutput struct {
24-
Token []byte // Authentication token (mutually exclusive with Error)
23+
type authOutput struct {
24+
Token string // Authentication token (mutually exclusive with Error)
2525
State []byte // Updated state blob (optional)
2626
Error string // Auth failure message (mutually exclusive with Token)
2727
}
@@ -65,13 +65,13 @@ func EncodeAuthInput(state []byte) ([]byte, error) {
6565

6666
// DecodeAuthOutput parses auth command stdout.
6767
// Reads keyed netstrings until EOF, validating protocol rules.
68-
func DecodeAuthOutput(stdout []byte) (*AuthOutput, error) {
68+
func DecodeAuthOutput(stdout []byte) (*authOutput, error) {
6969
if len(stdout) == 0 {
7070
return nil, errors.New("auth command returned empty output")
7171
}
7272

7373
dec := netstring.NewDecoder(bytes.NewReader(stdout))
74-
output := &AuthOutput{}
74+
output := &authOutput{}
7575
hasToken := false
7676
hasError := false
7777

@@ -95,7 +95,7 @@ func DecodeAuthOutput(stdout []byte) (*AuthOutput, error) {
9595
if hasError {
9696
return nil, errors.New("protocol violation: cannot have both token and error")
9797
}
98-
output.Token = value
98+
output.Token = string(value)
9999
hasToken = true
100100

101101
case KeyError:
@@ -115,7 +115,7 @@ func DecodeAuthOutput(stdout []byte) (*AuthOutput, error) {
115115

116116
// Validate that we got either a token or an error
117117
if !hasToken && !hasError {
118-
return nil, errors.New("protocol violation: must have either token or error field")
118+
return nil, errors.New("protocol violation: must have either token or error field and not both")
119119
}
120120

121121
return output, nil
@@ -124,20 +124,20 @@ func DecodeAuthOutput(stdout []byte) (*AuthOutput, error) {
124124
// Run executes the auth command with the current state and updates state based on output.
125125
// Returns AuthOutput containing token/error and updated state.
126126
// The command line is rendered as a mustache template with the provided attrs.
127-
func (h *Auth) Run(attrs any) (*AuthOutput, error) {
127+
func (h *Auth) Run(attrs any) (string, error) {
128128
h.lock.Lock()
129129
defer h.lock.Unlock()
130130

131131
// Render the command line template
132132
cmdLine, err := mustache.Render(h.cmdLine, attrs)
133133
if err != nil {
134-
return nil, fmt.Errorf("failed to render command template: %w", err)
134+
return "", fmt.Errorf("failed to render command template: %w", err)
135135
}
136136

137137
// Encode input (current state)
138138
input, err := EncodeAuthInput(h.state)
139139
if err != nil {
140-
return nil, fmt.Errorf("failed to encode auth input: %w", err)
140+
return "", fmt.Errorf("failed to encode auth input: %w", err)
141141
}
142142

143143
// Execute the auth command
@@ -150,17 +150,22 @@ func (h *Auth) Run(attrs any) (*AuthOutput, error) {
150150
err = cmd.Run()
151151
if err != nil {
152152
// Non-zero exit is an unexpected error (not auth failure)
153-
return nil, fmt.Errorf("auth command failed: %w: %s", err, stderr.String())
153+
return "", fmt.Errorf("auth command failed: %w: %s", err, stderr.String())
154154
}
155155

156156
// Decode the output
157157
output, err := DecodeAuthOutput(stdout.Bytes())
158158
if err != nil {
159-
return nil, fmt.Errorf("failed to decode auth output: %w", err)
159+
return "", fmt.Errorf("failed to decode auth output: %w", err)
160+
}
161+
162+
if output.Error != "" {
163+
// error reported from auth plugin!
164+
return "", errors.New(output.Error)
160165
}
161166

162167
// Update stored state (even if empty - plugin might be clearing state)
163168
h.state = output.State
164169

165-
return output, nil
170+
return output.Token, nil
166171
}

0 commit comments

Comments
 (0)