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 <aaron.lehmann@docker.com>
This commit is contained in:
parent
486bca6d94
commit
1fe4441875
5 changed files with 36 additions and 7 deletions
|
@ -102,6 +102,8 @@ type JSONMessage struct {
|
||||||
TimeNano int64 `json:"timeNano,omitempty"`
|
TimeNano int64 `json:"timeNano,omitempty"`
|
||||||
Error *JSONError `json:"errorDetail,omitempty"`
|
Error *JSONError `json:"errorDetail,omitempty"`
|
||||||
ErrorMessage string `json:"error,omitempty"` //deprecated
|
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`
|
// 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`
|
// 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
|
// 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.
|
// 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 (
|
var (
|
||||||
dec = json.NewDecoder(in)
|
dec = json.NewDecoder(in)
|
||||||
ids = make(map[string]int)
|
ids = make(map[string]int)
|
||||||
|
@ -163,6 +165,13 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if jm.Aux != nil {
|
||||||
|
if auxCallback != nil {
|
||||||
|
auxCallback(jm.Aux)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if jm.Progress != nil {
|
if jm.Progress != nil {
|
||||||
jm.Progress.terminalFd = terminalFd
|
jm.Progress.terminalFd = terminalFd
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ func TestDisplayJSONMessagesStreamInvalidJSON(t *testing.T) {
|
||||||
reader := strings.NewReader("This is not a 'valid' JSON []")
|
reader := strings.NewReader("This is not a 'valid' JSON []")
|
||||||
inFd, _ = term.GetFdInfo(reader)
|
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)
|
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)
|
inFd, _ = term.GetFdInfo(reader)
|
||||||
|
|
||||||
// Without terminal
|
// Without terminal
|
||||||
if err := DisplayJSONMessagesStream(reader, data, inFd, false); err != nil {
|
if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if data.String() != expectedMessages[0] {
|
if data.String() != expectedMessages[0] {
|
||||||
|
@ -220,7 +220,7 @@ func TestDisplayJSONMessagesStream(t *testing.T) {
|
||||||
// With terminal
|
// With terminal
|
||||||
data = bytes.NewBuffer([]byte{})
|
data = bytes.NewBuffer([]byte{})
|
||||||
reader = strings.NewReader(jsonMessage)
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if data.String() != expectedMessages[1] {
|
if data.String() != expectedMessages[1] {
|
||||||
|
|
|
@ -16,6 +16,10 @@ type Progress struct {
|
||||||
Current int64
|
Current int64
|
||||||
Total int64
|
Total int64
|
||||||
|
|
||||||
|
// Aux contains extra information not presented to the user, such as
|
||||||
|
// digests for push signing.
|
||||||
|
Aux interface{}
|
||||||
|
|
||||||
LastUpdate bool
|
LastUpdate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,3 +65,9 @@ func Message(out Output, id, message string) {
|
||||||
func Messagef(out Output, id, format string, a ...interface{}) {
|
func Messagef(out Output, id, format string, a ...interface{}) {
|
||||||
Message(out, id, fmt.Sprintf(format, a...))
|
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})
|
||||||
|
}
|
||||||
|
|
|
@ -70,16 +70,26 @@ func (sf *StreamFormatter) FormatError(err error) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatProgress formats the progress information for a specified action.
|
// 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 {
|
if progress == nil {
|
||||||
progress = &jsonmessage.JSONProgress{}
|
progress = &jsonmessage.JSONProgress{}
|
||||||
}
|
}
|
||||||
if sf.json {
|
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{
|
b, err := json.Marshal(&jsonmessage.JSONMessage{
|
||||||
Status: action,
|
Status: action,
|
||||||
ProgressMessage: progress.String(),
|
ProgressMessage: progress.String(),
|
||||||
Progress: progress,
|
Progress: progress,
|
||||||
ID: id,
|
ID: id,
|
||||||
|
Aux: auxJSON,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -116,7 +126,7 @@ func (out *progressOutput) WriteProgress(prog progress.Progress) error {
|
||||||
formatted = out.sf.FormatStatus(prog.ID, prog.Message)
|
formatted = out.sf.FormatStatus(prog.ID, prog.Message)
|
||||||
} else {
|
} else {
|
||||||
jsonProgress := jsonmessage.JSONProgress{Current: prog.Current, Total: prog.Total}
|
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)
|
_, err := out.out.Write(formatted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -73,7 +73,7 @@ func TestJSONFormatProgress(t *testing.T) {
|
||||||
Total: 30,
|
Total: 30,
|
||||||
Start: 1,
|
Start: 1,
|
||||||
}
|
}
|
||||||
res := sf.FormatProgress("id", "action", progress)
|
res := sf.FormatProgress("id", "action", progress, nil)
|
||||||
msg := &jsonmessage.JSONMessage{}
|
msg := &jsonmessage.JSONMessage{}
|
||||||
if err := json.Unmarshal(res, msg); err != nil {
|
if err := json.Unmarshal(res, msg); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
Loading…
Reference in a new issue