Matt Rosencrantz | b0cd02d | 2015-08-17 17:37:06 -0700 | [diff] [blame] | 1 | // 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 | |
| 5 | package security |
| 6 | |
| 7 | import ( |
| 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. |
| 21 | type 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. |
| 28 | func 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 | |
| 72 | func 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) |
| 89 | func 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'. |
| 143 | func 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 | |
| 162 | func 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 | } |