| OLD | NEW |
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package swarming | 5 package swarming |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bytes" | 8 "bytes" |
| 9 "fmt" | 9 "fmt" |
| 10 "net/http" | 10 "net/http" |
| 11 "net/url" | 11 "net/url" |
| 12 "strings" | 12 "strings" |
| 13 "time" | 13 "time" |
| 14 | 14 |
| 15 "golang.org/x/net/context" | 15 "golang.org/x/net/context" |
| 16 | 16 |
| 17 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1" | 17 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1" |
| 18 "github.com/luci/luci-go/common/errors" | 18 "github.com/luci/luci-go/common/errors" |
| 19 "github.com/luci/luci-go/common/logging" | 19 "github.com/luci/luci-go/common/logging" |
| 20 "github.com/luci/luci-go/common/proto/google" | 20 "github.com/luci/luci-go/common/proto/google" |
| 21 miloProto "github.com/luci/luci-go/common/proto/milo" | 21 miloProto "github.com/luci/luci-go/common/proto/milo" |
| 22 "github.com/luci/luci-go/common/sync/parallel" | 22 "github.com/luci/luci-go/common/sync/parallel" |
| 23 "github.com/luci/luci-go/logdog/client/annotee" | 23 "github.com/luci/luci-go/logdog/client/annotee" |
| 24 "github.com/luci/luci-go/logdog/client/coordinator" | 24 "github.com/luci/luci-go/logdog/client/coordinator" |
| 25 "github.com/luci/luci-go/logdog/common/types" | 25 "github.com/luci/luci-go/logdog/common/types" |
| 26 "github.com/luci/luci-go/milo/api/resp" | 26 "github.com/luci/luci-go/milo/api/resp" |
| 27 "github.com/luci/luci-go/milo/appengine/common" |
| 27 "github.com/luci/luci-go/milo/appengine/logdog" | 28 "github.com/luci/luci-go/milo/appengine/logdog" |
| 28 "github.com/luci/luci-go/server/auth" | 29 "github.com/luci/luci-go/server/auth" |
| 29 ) | 30 ) |
| 30 | 31 |
| 31 // errNotMiloJob is returned if a Swarming task is fetched that does not self- | 32 // errNotMiloJob is returned if a Swarming task is fetched that does not self- |
| 32 // identify as a Milo job. | 33 // identify as a Milo job. |
| 33 var errNotMiloJob = errors.New("Not a Milo Job") | 34 var errNotMiloJob = errors.New("Not a Milo Job or access denied") |
| 34 | 35 |
| 35 // SwarmingTimeLayout is time layout used by swarming. | 36 // SwarmingTimeLayout is time layout used by swarming. |
| 36 const SwarmingTimeLayout = "2006-01-02T15:04:05.999999999" | 37 const SwarmingTimeLayout = "2006-01-02T15:04:05.999999999" |
| 37 | 38 |
| 38 // logDogFetchTimeout is the amount of time to wait while fetching a LogDog | 39 // logDogFetchTimeout is the amount of time to wait while fetching a LogDog |
| 39 // stream before we time out the fetch. | 40 // stream before we time out the fetch. |
| 40 const logDogFetchTimeout = 30 * time.Second | 41 const logDogFetchTimeout = 30 * time.Second |
| 41 | 42 |
| 42 // Swarming task states.. | 43 // Swarming task states.. |
| 43 const ( | 44 const ( |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 138 // If the log fetch was cancelled, this is undefined. | 139 // If the log fetch was cancelled, this is undefined. |
| 139 log string | 140 log string |
| 140 } | 141 } |
| 141 | 142 |
| 142 // swarmingFetch fetches (in parallel) the components that it is configured to | 143 // swarmingFetch fetches (in parallel) the components that it is configured to |
| 143 // fetch. | 144 // fetch. |
| 144 // | 145 // |
| 145 // After fetching, an ACL check is performed to confirm that the user is | 146 // After fetching, an ACL check is performed to confirm that the user is |
| 146 // permitted to view the resulting data. If this check fails, get returns | 147 // permitted to view the resulting data. If this check fails, get returns |
| 147 // errNotMiloJob. | 148 // errNotMiloJob. |
| 148 // | |
| 149 // TODO(hinoka): Make this ACL check something more specific than the | |
| 150 // resence of the "allow_milo" dimension. | |
| 151 func swarmingFetch(c context.Context, svc swarmingService, taskID string, req sw
armingFetchParams) ( | 149 func swarmingFetch(c context.Context, svc swarmingService, taskID string, req sw
armingFetchParams) ( |
| 152 *swarmingFetchResult, error) { | 150 *swarmingFetchResult, error) { |
| 153 | 151 |
| 154 // logErr is managed separately from other fetch errors, since in some | 152 // logErr is managed separately from other fetch errors, since in some |
| 155 // situations it's acceptable to not have a log stream. | 153 // situations it's acceptable to not have a log stream. |
| 156 var logErr error | 154 var logErr error |
| 157 var fr swarmingFetchResult | 155 var fr swarmingFetchResult |
| 158 | 156 |
| 159 // Special Context to enable the cancellation of log fetching. | 157 // Special Context to enable the cancellation of log fetching. |
| 160 logsCancelled := false | 158 logsCancelled := false |
| (...skipping 26 matching lines...) Expand all Loading... |
| 187 // explicitly. | 185 // explicitly. |
| 188 fr.log, logErr = svc.getTaskOutput(logCtx, taskI
D) | 186 fr.log, logErr = svc.getTaskOutput(logCtx, taskI
D) |
| 189 return nil | 187 return nil |
| 190 } | 188 } |
| 191 } | 189 } |
| 192 }) | 190 }) |
| 193 if err != nil { | 191 if err != nil { |
| 194 return nil, err | 192 return nil, err |
| 195 } | 193 } |
| 196 | 194 |
| 197 » // Current ACL implementation: error if this is not a Milo job. | 195 » // Current ACL implementation: |
| 196 » // If allow_milo:1 is present, it is a public job. Don't bother with AC
L check. |
| 197 » // If it is not present, check the luci_project tag, and see if user is
allowed |
| 198 » // to access said project. |
| 198 switch { | 199 switch { |
| 199 case req.fetchReq: | 200 case req.fetchReq: |
| 200 » » if !isMiloJob(fr.req.Tags) { | 201 » » if !isAllowed(c, fr.req.Tags) { |
| 201 return nil, errNotMiloJob | 202 return nil, errNotMiloJob |
| 202 } | 203 } |
| 203 | 204 |
| 204 case req.fetchRes: | 205 case req.fetchRes: |
| 205 » » if !isMiloJob(fr.res.Tags) { | 206 » » if !isAllowed(c, fr.res.Tags) { |
| 206 return nil, errNotMiloJob | 207 return nil, errNotMiloJob |
| 207 } | 208 } |
| 208 | 209 |
| 209 default: | 210 default: |
| 210 // No metadata to decide if this is a Milo job, so assume that i
t is not. | 211 // No metadata to decide if this is a Milo job, so assume that i
t is not. |
| 211 return nil, errNotMiloJob | 212 return nil, errNotMiloJob |
| 212 } | 213 } |
| 213 | 214 |
| 214 if req.fetchRes && logErr != nil { | 215 if req.fetchRes && logErr != nil { |
| 215 switch fr.res.State { | 216 switch fr.res.State { |
| (...skipping 442 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 658 | 659 |
| 659 func infoComponent(st resp.Status, label, text string) *resp.BuildComponent { | 660 func infoComponent(st resp.Status, label, text string) *resp.BuildComponent { |
| 660 return &resp.BuildComponent{ | 661 return &resp.BuildComponent{ |
| 661 Type: resp.Summary, | 662 Type: resp.Summary, |
| 662 Label: label, | 663 Label: label, |
| 663 Text: []string{text}, | 664 Text: []string{text}, |
| 664 Status: st, | 665 Status: st, |
| 665 } | 666 } |
| 666 } | 667 } |
| 667 | 668 |
| 668 func isMiloJob(tags []string) bool { | 669 // isAllowed checks if: |
| 670 // 1. allow_milo:1 is present. If so, it's a public job. |
| 671 // 2. luci_project is present, and if the logged in user has access to that proj
ect. |
| 672 func isAllowed(c context.Context, tags []string) bool { |
| 669 for _, t := range tags { | 673 for _, t := range tags { |
| 670 if t == "allow_milo:1" { | 674 if t == "allow_milo:1" { |
| 671 return true | 675 return true |
| 672 } | 676 } |
| 673 } | 677 } |
| 678 for _, t := range tags { |
| 679 if strings.HasPrefix(t, "luci_project:") { |
| 680 sp := strings.SplitN(t, ":", 2) |
| 681 if len(sp) != 2 { |
| 682 return false |
| 683 } |
| 684 logging.Debugf(c, "Checking if user has access to %s", s
p[1]) |
| 685 // sp[1] is the project ID. |
| 686 allowed, err := common.IsAllowed(c, sp[1]) |
| 687 if err != nil { |
| 688 logging.WithError(err).Errorf(c, "could not perf
orm acl check") |
| 689 return false |
| 690 } |
| 691 return allowed |
| 692 } |
| 693 } |
| 674 return false | 694 return false |
| 675 } | 695 } |
| 676 | 696 |
| 677 // taskPageURL returns a URL to a human-consumable page of a swarming task. | 697 // taskPageURL returns a URL to a human-consumable page of a swarming task. |
| 678 // Supports server aliases. | 698 // Supports server aliases. |
| 679 func taskPageURL(swarmingHostname, taskID string) string { | 699 func taskPageURL(swarmingHostname, taskID string) string { |
| 680 return fmt.Sprintf("https://%s/user/task/%s", swarmingHostname, taskID) | 700 return fmt.Sprintf("https://%s/user/task/%s", swarmingHostname, taskID) |
| 681 } | 701 } |
| 682 | 702 |
| 683 // botPageURL returns a URL to a human-consumable page of a swarming bot. | 703 // botPageURL returns a URL to a human-consumable page of a swarming bot. |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 733 for _, tag := range v { | 753 for _, tag := range v { |
| 734 var value string | 754 var value string |
| 735 parts := strings.SplitN(tag, ":", 2) | 755 parts := strings.SplitN(tag, ":", 2) |
| 736 if len(parts) == 2 { | 756 if len(parts) == 2 { |
| 737 value = parts[1] | 757 value = parts[1] |
| 738 } | 758 } |
| 739 res[parts[0]] = value | 759 res[parts[0]] = value |
| 740 } | 760 } |
| 741 return res | 761 return res |
| 742 } | 762 } |
| OLD | NEW |