From 1fe44418755a48513d9af10ad0d187e327b68d20 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Mon, 21 Dec 2015 15:02:44 -0800 Subject: [PATCH] Send push information to trust code out-of-band The trust code used to parse the console output of `docker push` to extract the digest, tag, and size information and determine what to sign. This is fragile and might give an attacker control over what gets signed if the attacker can find a way to influence what gets printed as part of the push output. This commit sends the push metadata out-of-band. It introduces an `Aux` field in JSONMessage that can carry application-specific data alongside progress updates. Instead of parsing formatted output, the client looks in this field to get the digest, size, and tag from the push. Signed-off-by: Aaron Lehmann --- jsonmessage/jsonmessage.go | 11 ++++++++++- jsonmessage/jsonmessage_test.go | 6 +++--- progress/progress.go | 10 ++++++++++ streamformatter/streamformatter.go | 14 ++++++++++++-- streamformatter/streamformatter_test.go | 2 +- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/jsonmessage/jsonmessage.go b/jsonmessage/jsonmessage.go index 6447232..65cccbc 100644 --- a/jsonmessage/jsonmessage.go +++ b/jsonmessage/jsonmessage.go @@ -102,6 +102,8 @@ type JSONMessage struct { TimeNano int64 `json:"timeNano,omitempty"` Error *JSONError `json:"errorDetail,omitempty"` ErrorMessage string `json:"error,omitempty"` //deprecated + // Aux contains out-of-band data, such as digests for push signing. + Aux *json.RawMessage `json:"aux,omitempty"` } // Display displays the JSONMessage to `out`. `isTerminal` describes if `out` @@ -148,7 +150,7 @@ func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error { // DisplayJSONMessagesStream displays a json message stream from `in` to `out`, `isTerminal` // describes if `out` is a terminal. If this is the case, it will print `\n` at the end of // each line and move the cursor while displaying. -func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool) error { +func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(*json.RawMessage)) error { var ( dec = json.NewDecoder(in) ids = make(map[string]int) @@ -163,6 +165,13 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, return err } + if jm.Aux != nil { + if auxCallback != nil { + auxCallback(jm.Aux) + } + continue + } + if jm.Progress != nil { jm.Progress.terminalFd = terminalFd } diff --git a/jsonmessage/jsonmessage_test.go b/jsonmessage/jsonmessage_test.go index b81b656..558effc 100644 --- a/jsonmessage/jsonmessage_test.go +++ b/jsonmessage/jsonmessage_test.go @@ -168,7 +168,7 @@ func TestDisplayJSONMessagesStreamInvalidJSON(t *testing.T) { reader := strings.NewReader("This is not a 'valid' JSON []") inFd, _ = term.GetFdInfo(reader) - if err := DisplayJSONMessagesStream(reader, data, inFd, false); err == nil && err.Error()[:17] != "invalid character" { + if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err == nil && err.Error()[:17] != "invalid character" { t.Fatalf("Should have thrown an error (invalid character in ..), got [%v]", err) } } @@ -210,7 +210,7 @@ func TestDisplayJSONMessagesStream(t *testing.T) { inFd, _ = term.GetFdInfo(reader) // Without terminal - if err := DisplayJSONMessagesStream(reader, data, inFd, false); err != nil { + if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err != nil { t.Fatal(err) } if data.String() != expectedMessages[0] { @@ -220,7 +220,7 @@ func TestDisplayJSONMessagesStream(t *testing.T) { // With terminal data = bytes.NewBuffer([]byte{}) reader = strings.NewReader(jsonMessage) - if err := DisplayJSONMessagesStream(reader, data, inFd, true); err != nil { + if err := DisplayJSONMessagesStream(reader, data, inFd, true, nil); err != nil { t.Fatal(err) } if data.String() != expectedMessages[1] { diff --git a/progress/progress.go b/progress/progress.go index 1f3b34a..61315cb 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -16,6 +16,10 @@ type Progress struct { Current int64 Total int64 + // Aux contains extra information not presented to the user, such as + // digests for push signing. + Aux interface{} + LastUpdate bool } @@ -61,3 +65,9 @@ func Message(out Output, id, message string) { func Messagef(out Output, id, format string, a ...interface{}) { Message(out, id, fmt.Sprintf(format, a...)) } + +// Aux sends auxiliary information over a progress interface, which will not be +// formatted for the UI. This is used for things such as push signing. +func Aux(out Output, a interface{}) { + out.WriteProgress(Progress{Aux: a}) +} diff --git a/streamformatter/streamformatter.go b/streamformatter/streamformatter.go index d670018..ce6ea79 100644 --- a/streamformatter/streamformatter.go +++ b/streamformatter/streamformatter.go @@ -70,16 +70,26 @@ func (sf *StreamFormatter) FormatError(err error) []byte { } // FormatProgress formats the progress information for a specified action. -func (sf *StreamFormatter) FormatProgress(id, action string, progress *jsonmessage.JSONProgress) []byte { +func (sf *StreamFormatter) FormatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte { if progress == nil { progress = &jsonmessage.JSONProgress{} } if sf.json { + var auxJSON *json.RawMessage + if aux != nil { + auxJSONBytes, err := json.Marshal(aux) + if err != nil { + return nil + } + auxJSON = new(json.RawMessage) + *auxJSON = auxJSONBytes + } b, err := json.Marshal(&jsonmessage.JSONMessage{ Status: action, ProgressMessage: progress.String(), Progress: progress, ID: id, + Aux: auxJSON, }) if err != nil { return nil @@ -116,7 +126,7 @@ func (out *progressOutput) WriteProgress(prog progress.Progress) error { formatted = out.sf.FormatStatus(prog.ID, prog.Message) } else { jsonProgress := jsonmessage.JSONProgress{Current: prog.Current, Total: prog.Total} - formatted = out.sf.FormatProgress(prog.ID, prog.Action, &jsonProgress) + formatted = out.sf.FormatProgress(prog.ID, prog.Action, &jsonProgress, prog.Aux) } _, err := out.out.Write(formatted) if err != nil { diff --git a/streamformatter/streamformatter_test.go b/streamformatter/streamformatter_test.go index acf81be..438758d 100644 --- a/streamformatter/streamformatter_test.go +++ b/streamformatter/streamformatter_test.go @@ -73,7 +73,7 @@ func TestJSONFormatProgress(t *testing.T) { Total: 30, Start: 1, } - res := sf.FormatProgress("id", "action", progress) + res := sf.FormatProgress("id", "action", progress, nil) msg := &jsonmessage.JSONMessage{} if err := json.Unmarshal(res, msg); err != nil { t.Fatal(err)