go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/appengine/coordinator/logPrefix.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package coordinator 16 17 import ( 18 "context" 19 "crypto/subtle" 20 "errors" 21 "fmt" 22 "time" 23 24 "go.chromium.org/luci/common/clock" 25 "go.chromium.org/luci/common/logging" 26 ds "go.chromium.org/luci/gae/service/datastore" 27 "go.chromium.org/luci/logdog/common/types" 28 ) 29 30 const ( 31 // RegistrationNonceTimeout is how long LogPrefix.IsRetry will consider 32 // a matching nonce to be valid. 33 RegistrationNonceTimeout = 15 * time.Minute 34 ) 35 36 // LogPrefix is a datastore model for a prefix space. All log streams sharing 37 // a prefix will have a LogPrefix entry to group under. 38 // 39 // A LogPrefix is keyed on the hash of its Prefix property. 40 // 41 // Prefix-scoped properties are used to control creation and modification 42 // attributes of log streams sharing the prefix. 43 type LogPrefix struct { 44 // ID is the LogPrefix's ID. It is an encoded hash value generated from the 45 // stream's Prefix field. 46 ID HashID `gae:"$id"` 47 48 // Schema is the datastore schema version for this object. This can be used 49 // to facilitate schema migrations. 50 // 51 // The current schema is currentSchemaVersion. 52 Schema string 53 54 // Created is the time when this stream was created. 55 Created time.Time `gae:",noindex"` 56 57 // Prefix is this log stream's prefix value. Log streams with the same prefix 58 // are logically grouped. 59 // 60 // This value should not be changed once populated, as it will invalidate the 61 // HashID. 62 Prefix string `gae:",noindex"` 63 64 // Realm is a full realm name ("<project>:<realm>") with ACLs for this prefix. 65 // 66 // It is set in RegisterStream and can't be changed afterwards. 67 Realm string 68 69 // Source is the (indexed) set of source strings sent by the prefix registrar. 70 Source []string 71 72 // Expiration is the time when this log prefix expires. Stream registrations 73 // for this prefix will fail after this point. 74 Expiration time.Time 75 76 // Secret is the Butler secret value for this prefix. All streams within 77 // the prefix share this secret value. 78 // 79 // This value may only be returned to LogDog services; it is not user-visible. 80 Secret []byte `gae:",noindex"` 81 82 // OpNonce is provided by the client when calling RegisterPrefix. If the 83 // client provides the same nonce on a subsequent invocation of 84 // RegisterPrefix, the server will respond with success instead of 85 // AlreadyExists. 86 // 87 // This must have a length of either 0 or types.OpNonceLength. 88 // 89 // The nonce has a valid lifetime of RegistrationNonceTimeout after Created. 90 OpNonce []byte `gae:",noindex"` 91 92 // extra causes datastore to ignore unrecognized fields and strip them in 93 // future writes. 94 extra ds.PropertyMap `gae:"-,extra"` 95 } 96 97 var _ interface { 98 ds.PropertyLoadSaver 99 } = (*LogPrefix)(nil) 100 101 // LogPrefixID returns the HashID for a specific prefix. 102 func LogPrefixID(prefix types.StreamName) HashID { 103 return makeHashID(string(prefix)) 104 } 105 106 // Load implements ds.PropertyLoadSaver. 107 func (p *LogPrefix) Load(pmap ds.PropertyMap) error { 108 if err := ds.GetPLS(p).Load(pmap); err != nil { 109 return err 110 } 111 112 // Validate the log prefix. Don't enforce HashID correctness, since datastore 113 // hasn't populated that field yet. 114 return p.validateImpl(false) 115 } 116 117 // Save implements ds.PropertyLoadSaver. 118 func (p *LogPrefix) Save(withMeta bool) (ds.PropertyMap, error) { 119 if err := p.validateImpl(true); err != nil { 120 return nil, err 121 } 122 p.Schema = CurrentSchemaVersion 123 124 return ds.GetPLS(p).Save(withMeta) 125 } 126 127 // IsRetry checks to see if this LogPrefix is still in the OpNonce 128 // window, and if nonce matches the one in this LogPrefix. 129 func (p *LogPrefix) IsRetry(c context.Context, nonce []byte) bool { 130 if len(nonce) != types.OpNonceLength { 131 logging.Infof(c, "user provided invalid nonce length (%d)", len(nonce)) 132 return false 133 } 134 if len(p.OpNonce) == 0 { 135 logging.Infof(c, "prefix %q has no associated nonce", p.Prefix) 136 return false 137 } 138 if clock.Now(c).After(p.Created.Add(RegistrationNonceTimeout)) { 139 logging.Infof(c, "prefix %q has expired nonce", p.Prefix) 140 return false 141 } 142 return subtle.ConstantTimeCompare(p.OpNonce, nonce) == 1 143 } 144 145 // getIDFromPrefix calculates the log stream's hash ID from its Prefix/Name 146 // fields, which must be populated else this function will panic. 147 func (p *LogPrefix) getIDFromPrefix() HashID { 148 if p.Prefix == "" { 149 panic("empty prefix") 150 } 151 return makeHashID(p.Prefix) 152 } 153 154 // Validate evaluates the state and data contents of the LogPrefix and returns 155 // an error if it is invalid. 156 func (p *LogPrefix) Validate() error { 157 return p.validateImpl(true) 158 } 159 160 func (p *LogPrefix) validateImpl(enforceHashID bool) error { 161 if enforceHashID { 162 // Make sure our Prefix and Name match the Hash ID. 163 if hid := p.getIDFromPrefix(); hid != p.ID { 164 return fmt.Errorf("hash IDs don't match (%q != %q)", hid, p.ID) 165 } 166 } 167 168 if err := types.StreamName(p.Prefix).Validate(); err != nil { 169 return fmt.Errorf("invalid prefix: %s", err) 170 } 171 if err := types.PrefixSecret(p.Secret).Validate(); err != nil { 172 return fmt.Errorf("invalid prefix secret: %s", err) 173 } 174 if p.Created.IsZero() { 175 return errors.New("created time is not set") 176 } 177 if l := len(p.OpNonce); l > 0 && l != types.OpNonceLength { 178 return fmt.Errorf("registration nonce has bad length (%d)", l) 179 } 180 return nil 181 }