blob: 8cdca3e8efc084544fbfc36c5c8025f8da037670 [file] [log] [blame]
Matt Rosencrantzb0cd02d2015-08-17 17:37:06 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package security
6
7import (
8 "sync"
9 "time"
10
11 "v.io/v23"
12 "v.io/v23/context"
13 "v.io/v23/security"
14 "v.io/v23/vdl"
15 "v.io/v23/vtrace"
16)
17
18// If this is attached to the context, we will not fetch discharges.
19// We use this to prevent ourselves from fetching discharges in the
20// process of fetching discharges, thus creating an infinite loop.
21type skipDischargesKey struct{}
22
23// PrepareDischarges retrieves the caveat discharges required for using blessings
24// at server. The discharges are either found in the dischargeCache, in the call
25// options, or requested from the discharge issuer indicated on the caveat.
26// Note that requesting a discharge is an rpc call, so one copy of this
27// function must be able to successfully terminate while another is blocked.
28func PrepareDischarges(
29 ctx *context.T,
30 blessings security.Blessings,
31 impetus security.DischargeImpetus,
32 expiryBuffer time.Duration) map[string]security.Discharge {
33 tpCavs := blessings.ThirdPartyCaveats()
34 if skip, _ := ctx.Value(skipDischargesKey{}).(bool); skip || len(tpCavs) == 0 {
35 return nil
36 }
37 ctx = context.WithValue(ctx, skipDischargesKey{}, true)
38
39 // Make a copy since this copy will be mutated.
40 var caveats []security.Caveat
41 var filteredImpetuses []security.DischargeImpetus
42 for _, cav := range tpCavs {
43 // It shouldn't happen, but in case there are non-third-party
44 // caveats, drop them.
45 if tp := cav.ThirdPartyDetails(); tp != nil {
46 caveats = append(caveats, cav)
47 filteredImpetuses = append(filteredImpetuses, filteredImpetus(tp.Requirements(), impetus))
48 }
49 }
50 bstore := v23.GetPrincipal(ctx).BlessingStore()
51 // Gather discharges from cache.
52 discharges, rem := discharges(bstore, caveats, impetus)
53 if rem > 0 {
54 // Fetch discharges for caveats for which no discharges were
55 // found in the cache.
56 if ctx != nil {
57 var span vtrace.Span
58 ctx, span = vtrace.WithNewSpan(ctx, "Fetching Discharges")
59 defer span.Finish()
60 }
61 fetchDischarges(ctx, caveats, filteredImpetuses, discharges, expiryBuffer)
62 }
63 ret := make(map[string]security.Discharge, len(discharges))
64 for _, d := range discharges {
65 if d.ID() != "" {
66 ret[d.ID()] = d
67 }
68 }
69 return ret
70}
71
72func discharges(bs security.BlessingStore, caveats []security.Caveat, imp security.DischargeImpetus) (out []security.Discharge, rem int) {
73 out = make([]security.Discharge, len(caveats))
74 for i := range caveats {
75 out[i] = bs.Discharge(caveats[i], imp)
76 if out[i].ID() == "" {
77 rem++
78 }
79 }
80 return
81}
82
83// fetchDischarges fills out by fetching discharges for caveats from the
84// appropriate discharge service. Since there may be dependencies in the
85// caveats, fetchDischarges keeps retrying until either all discharges can be
86// fetched or no new discharges are fetched.
87// REQUIRES: len(caveats) == len(out)
88// REQUIRES: caveats[i].ThirdPartyDetails() != nil for 0 <= i < len(caveats)
89func fetchDischarges(
90 ctx *context.T,
91 caveats []security.Caveat,
92 impetuses []security.DischargeImpetus,
93 out []security.Discharge,
94 expiryBuffer time.Duration) {
95 bstore := v23.GetPrincipal(ctx).BlessingStore()
96 var wg sync.WaitGroup
97 for {
98 type fetched struct {
99 idx int
100 discharge security.Discharge
101 caveat security.Caveat
102 impetus security.DischargeImpetus
103 }
104 discharges := make(chan fetched, len(caveats))
105 want := 0
106 for i := range caveats {
107 if !shouldFetchDischarge(out[i], expiryBuffer) {
108 continue
109 }
110 want++
111 wg.Add(1)
112 go func(i int, ctx *context.T, cav security.Caveat) {
113 defer wg.Done()
114 tp := cav.ThirdPartyDetails()
115 var dis security.Discharge
116 ctx.VI(3).Infof("Fetching discharge for %v", tp)
117 if err := v23.GetClient(ctx).Call(ctx, tp.Location(), "Discharge",
118 []interface{}{cav, impetuses[i]}, []interface{}{&dis}); err != nil {
119 ctx.VI(3).Infof("Discharge fetch for %v failed: %v", tp, err)
120 return
121 }
122 discharges <- fetched{i, dis, caveats[i], impetuses[i]}
123 }(i, ctx, caveats[i])
124 }
125 wg.Wait()
126 close(discharges)
127 var got int
128 for fetched := range discharges {
129 bstore.CacheDischarge(fetched.discharge, fetched.caveat, fetched.impetus)
130 out[fetched.idx] = fetched.discharge
131 got++
132 }
133 if want > 0 {
134 ctx.VI(3).Infof("fetchDischarges: got %d of %d discharge(s) (total %d caveats)", got, want, len(caveats))
135 }
136 if got == 0 || got == want {
137 return
138 }
139 }
140}
141
142// filteredImpetus returns a copy of 'before' after removing any values that are not required as per 'r'.
143func filteredImpetus(r security.ThirdPartyRequirements, before security.DischargeImpetus) (after security.DischargeImpetus) {
144 if r.ReportServer && len(before.Server) > 0 {
145 after.Server = make([]security.BlessingPattern, len(before.Server))
146 for i := range before.Server {
147 after.Server[i] = before.Server[i]
148 }
149 }
150 if r.ReportMethod {
151 after.Method = before.Method
152 }
153 if r.ReportArguments && len(before.Arguments) > 0 {
154 after.Arguments = make([]*vdl.Value, len(before.Arguments))
155 for i := range before.Arguments {
156 after.Arguments[i] = vdl.CopyValue(before.Arguments[i])
157 }
158 }
159 return
160}
161
162func shouldFetchDischarge(dis security.Discharge, expiryBuffer time.Duration) bool {
163 if dis.ID() == "" {
164 return true
165 }
166 expiry := dis.Expiry()
167 if expiry.IsZero() {
168 return false
169 }
170 return expiry.Before(time.Now().Add(expiryBuffer))
171}