-
Notifications
You must be signed in to change notification settings - Fork 303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Verify step signatures #2210
Verify step signatures #2210
Conversation
29b475c
to
ea0f66e
Compare
ece0f77
to
856cce4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good.
I think all my comments are just various degrees of nits.
But I would encourage exploring what putting this stuff in a different package would look like.
JobVerificationNoSignatureBehavior string | ||
JobVerificationInvalidSignatureBehavior string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe explain what these are. The other one seems obvious, but it deserves a doc comment too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see they're explained below, but this is a higher level. I think it's good to talk about them here as well.
65c156b
to
f4b2a6b
Compare
case "/jobs/" + jobID + "/chunks": | ||
sequence := req.URL.Query().Get("sequence") | ||
seqNo, _ := strconv.Atoi(sequence) | ||
r, _ := gzip.NewReader(bytes.NewBuffer(b)) | ||
uz, _ := io.ReadAll(r) | ||
t.logChunks[seqNo] = string(uz) | ||
rw.WriteHeader(http.StatusCreated) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
error handling? what's that?
5a4bc99
to
9ac378a
Compare
8e2c62c
to
06fc81e
Compare
402843b
to
02ffc45
Compare
02ffc45
to
95c2404
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good stuff ...but still reviewing...
95c2404
to
65dc8fd
Compare
@@ -34,6 +34,7 @@ import ( | |||
"github.com/mitchellh/go-homedir" | |||
"github.com/urfave/cli" | |||
"golang.org/x/exp/maps" | |||
"golang.org/x/exp/slices" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh goodness. When did we start depending on exp
packages?
At least these will be making their way into Go 1.21 largely the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
um, it's, um, it's hard to say, exactly, when (May 5th 2022) this started or who (extremely me) started it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Welp, looks like maps.Keys
isn't making it to stdlib maps
(yet): golang/go#61538
(slices.Contains
is still in, so I'll let this PR slide)
1f7845a
to
6cc495d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is shaping up to be amazing!
exit.Status = -1 | ||
exit.SignalReason = "job_verification_failed_with_error" | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think the if/else
chain I suggested last time looks better here.
I, too, generally prefer switches
to if/else
chains, but here:
- There is no added efficiency in using a
switch
as all the case statements have to be evaluated in order in the worst case as there is no value to switch on. - This statement leaks the
ise
variable. Theif/else
chain would scope it exclusively to the appropriate branch
But I'm not going to die on this hill.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I were to change anything, it would be to replace default:
with case err != nil:
, and not make err == nil
a case in the switch.
As in, the other side of "indent error flow" is "don't indent non-error flow".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
This is great! |
5bf9034
to
c78f0de
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huzzah! 🧃
@@ -18,6 +18,7 @@ type CommandStep struct { | |||
Command string `yaml:"command"` | |||
Plugins Plugins `yaml:"plugins,omitempty"` | |||
Signature *Signature `yaml:"signature,omitempty"` | |||
Matrix any `yaml:"matrix,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll also have to set the new field in unmarshalMap
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oooh good point
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and interpolate
...
@@ -34,6 +34,7 @@ import ( | |||
"github.com/mitchellh/go-homedir" | |||
"github.com/urfave/cli" | |||
"golang.org/x/exp/maps" | |||
"golang.org/x/exp/slices" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Welp, looks like maps.Keys
isn't making it to stdlib maps
(yet): golang/go#61538
(slices.Contains
is still in, so I'll let this PR slide)
clicommand/agent_start.go
Outdated
@@ -53,71 +54,90 @@ Example: | |||
|
|||
$ buildkite-agent start --token xxx` | |||
|
|||
var noSignatureBehaviors = []string{agent.VerificationBehaviourBlock, agent.VerificationBehaviourWarn} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used between signature-missing and signature-invalid, so perhaps
var noSignatureBehaviors = []string{agent.VerificationBehaviourBlock, agent.VerificationBehaviourWarn} | |
var verificationFailureBehaviors = []string{agent.VerificationBehaviourBlock, agent.VerificationBehaviourWarn} |
api/jobs.go
Outdated
ChunksFailedCount int `json:"chunks_failed_count,omitempty"` | ||
} | ||
|
||
func (j Job) ValuesForFields(fields []string) (map[string]string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Job
looks big enough that I'd make this receiver *Job
(for not-copying-it-over-stack reasons, not for modifying-the-fields reasons).
agent/verify_job.go
Outdated
// Now that the signature of the job's step is verified, we need to check if the fields on the job match those on the | ||
// step. If they don't, we need to fail the job |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to suggest it would be simpler to override BUILDKITE_COMMAND
with step.Command
, than to carefully compare each field.
But thinking about it, they would only differ if the backend (ours or the hypothetical attacker's) is doing crimes, in which case we probably shouldn't run anything. And the backend can't migrate away from passing BUILDKITE_COMMAND
without breaking every old agent version. (Ah, but it could sniff the agent version then vary the job...)
Might be worth documenting that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just to double check, are saying that it might be good to document that the only reason that step and job fields should differ is if an attacker is doing crimes on the backend?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might not be the only reason, but it definitely seems like a reason
agent/run_job.go
Outdated
r.verificationFailureLogs(err, r.InvalidSignatureBehavior) | ||
if r.InvalidSignatureBehavior == VerificationBehaviourBlock { | ||
exit.Status = -1 | ||
exit.SignalReason = "job_verification_failed_invalid_signature" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My initial thought was that AGENT_REFUSED
might already serve the purpose here, but I imagine you're trying to tick off the objective of showing verification failure in the timeline at the same time.
It feels a little strange to be embedding the failure mode into the signal reason like this. We probably don't want to pack signal reason with a whole bunch of values - more a class of error, rather than an error string, if that makes sense. Beyond feeling a bit wrong, think about how they're used - if a user wanted to catch the errors for an automatic retry, for example, they'd need to list them all. Have you considered one signal reason for these (perhaps VERIFICATION_FAILED
, and showing the actual error in the agent/job logs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If signal reason was designed for users to catch and retry, then I don't think we should be overloading its use to also display information in the timeline. Is there another way of creating a job event for signing failures? If not, I think we should examine building one when the time to build the timeline feature arrives.
cd13203
to
5777275
Compare
5777275
to
a279f07
Compare
Co-authored-by: Narthana Epa <narthana.epa@gmail.com>
a279f07
to
c53640e
Compare
This PR: Enables the buildkite-agent to verify incoming jobs against signatures uploaded in their pipeline uploads.
This is done by a process (vaguely):
Job
sent by the backend will (WIP) now include astep
attribute, which contains a subset of information contained in the step upload - at current, it's the step's command and signature, though as we sign more elements of the signature, this will expand to include other elementsstep
, and compares it to the signature shipped in the accept payloadJob
's elements match theStep
'spre-bootstrap
hook