1package state 2 3import ( 4 "fmt" 5 "regexp" 6 7 "github.com/hashicorp/go-memdb" 8 9 "github.com/hashicorp/consul/agent/consul/prepared_query" 10 "github.com/hashicorp/consul/agent/structs" 11) 12 13// preparedQueriesTableSchema returns a new table schema used for storing 14// prepared queries. 15func preparedQueriesTableSchema() *memdb.TableSchema { 16 return &memdb.TableSchema{ 17 Name: "prepared-queries", 18 Indexes: map[string]*memdb.IndexSchema{ 19 "id": { 20 Name: "id", 21 AllowMissing: false, 22 Unique: true, 23 Indexer: &memdb.UUIDFieldIndex{ 24 Field: "ID", 25 }, 26 }, 27 "name": { 28 Name: "name", 29 AllowMissing: true, 30 Unique: true, 31 Indexer: &memdb.StringFieldIndex{ 32 Field: "Name", 33 Lowercase: true, 34 }, 35 }, 36 "template": { 37 Name: "template", 38 AllowMissing: true, 39 Unique: true, 40 Indexer: &PreparedQueryIndex{}, 41 }, 42 "session": { 43 Name: "session", 44 AllowMissing: true, 45 Unique: false, 46 Indexer: &memdb.UUIDFieldIndex{ 47 Field: "Session", 48 }, 49 }, 50 }, 51 } 52} 53 54// validUUID is used to check if a given string looks like a UUID 55var validUUID = regexp.MustCompile(`(?i)^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$`) 56 57// isUUID returns true if the given string is a valid UUID. 58func isUUID(str string) bool { 59 const uuidLen = 36 60 if len(str) != uuidLen { 61 return false 62 } 63 64 return validUUID.MatchString(str) 65} 66 67// queryWrapper is an internal structure that is used to store a query alongside 68// its compiled template, which can be nil. 69type queryWrapper struct { 70 // We embed the PreparedQuery structure so that the UUID field indexer 71 // can see the ID directly. 72 *structs.PreparedQuery 73 74 // ct is the compiled template, or nil if the query isn't a template. The 75 // state store manages this and keeps it up to date every time the query 76 // changes. 77 ct *prepared_query.CompiledTemplate 78} 79 80// toPreparedQuery unwraps the internal form of a prepared query and returns 81// the regular struct. 82func toPreparedQuery(wrapped interface{}) *structs.PreparedQuery { 83 if wrapped == nil { 84 return nil 85 } 86 return wrapped.(*queryWrapper).PreparedQuery 87} 88 89// PreparedQueries is used to pull all the prepared queries from the snapshot. 90func (s *Snapshot) PreparedQueries() (structs.PreparedQueries, error) { 91 queries, err := s.tx.Get("prepared-queries", "id") 92 if err != nil { 93 return nil, err 94 } 95 96 var ret structs.PreparedQueries 97 for wrapped := queries.Next(); wrapped != nil; wrapped = queries.Next() { 98 ret = append(ret, toPreparedQuery(wrapped)) 99 } 100 return ret, nil 101} 102 103// PreparedQuery is used when restoring from a snapshot. For general inserts, 104// use PreparedQuerySet. 105func (s *Restore) PreparedQuery(query *structs.PreparedQuery) error { 106 // If this is a template, compile it, otherwise leave the compiled 107 // template field nil. 108 var ct *prepared_query.CompiledTemplate 109 if prepared_query.IsTemplate(query) { 110 var err error 111 ct, err = prepared_query.Compile(query) 112 if err != nil { 113 return fmt.Errorf("failed compiling template: %s", err) 114 } 115 } 116 117 // Insert the wrapped query. 118 if err := s.tx.Insert("prepared-queries", &queryWrapper{query, ct}); err != nil { 119 return fmt.Errorf("failed restoring prepared query: %s", err) 120 } 121 if err := indexUpdateMaxTxn(s.tx, query.ModifyIndex, "prepared-queries"); err != nil { 122 return fmt.Errorf("failed updating index: %s", err) 123 } 124 125 return nil 126} 127 128// PreparedQuerySet is used to create or update a prepared query. 129func (s *Store) PreparedQuerySet(idx uint64, query *structs.PreparedQuery) error { 130 tx := s.db.WriteTxn(idx) 131 defer tx.Abort() 132 133 if err := preparedQuerySetTxn(tx, idx, query); err != nil { 134 return err 135 } 136 137 return tx.Commit() 138} 139 140// preparedQuerySetTxn is the inner method used to insert a prepared query with 141// the proper indexes into the state store. 142func preparedQuerySetTxn(tx WriteTxn, idx uint64, query *structs.PreparedQuery) error { 143 // Check that the ID is set. 144 if query.ID == "" { 145 return ErrMissingQueryID 146 } 147 148 // Check for an existing query. 149 wrapped, err := tx.First("prepared-queries", "id", query.ID) 150 if err != nil { 151 return fmt.Errorf("failed prepared query lookup: %s", err) 152 } 153 existing := toPreparedQuery(wrapped) 154 155 // Set the indexes. 156 if existing != nil { 157 query.CreateIndex = existing.CreateIndex 158 query.ModifyIndex = idx 159 } else { 160 query.CreateIndex = idx 161 query.ModifyIndex = idx 162 } 163 164 // Verify that the query name doesn't already exist, or that we are 165 // updating the same instance that has this name. If this is a template 166 // and the name is empty then we make sure there's not an empty template 167 // already registered. 168 if query.Name != "" { 169 wrapped, err := tx.First("prepared-queries", "name", query.Name) 170 if err != nil { 171 return fmt.Errorf("failed prepared query lookup: %s", err) 172 } 173 other := toPreparedQuery(wrapped) 174 if other != nil && (existing == nil || existing.ID != other.ID) { 175 return fmt.Errorf("name '%s' aliases an existing query name", query.Name) 176 } 177 } else if prepared_query.IsTemplate(query) { 178 wrapped, err := tx.First("prepared-queries", "template", query.Name) 179 if err != nil { 180 return fmt.Errorf("failed prepared query lookup: %s", err) 181 } 182 other := toPreparedQuery(wrapped) 183 if other != nil && (existing == nil || existing.ID != other.ID) { 184 return fmt.Errorf("a query template with an empty name already exists") 185 } 186 } 187 188 // Verify that the name doesn't alias any existing ID. We allow queries 189 // to be looked up by ID *or* name so we don't want anyone to try to 190 // register a query with a name equal to some other query's ID in an 191 // attempt to hijack it. We also look up by ID *then* name in order to 192 // prevent this, but it seems prudent to prevent these types of rogue 193 // queries from ever making it into the state store. Note that we have 194 // to see if the name looks like a UUID before checking since the UUID 195 // index will complain if we look up something that's not formatted 196 // like one. 197 if isUUID(query.Name) { 198 wrapped, err := tx.First("prepared-queries", "id", query.Name) 199 if err != nil { 200 return fmt.Errorf("failed prepared query lookup: %s", err) 201 } 202 if wrapped != nil { 203 return fmt.Errorf("name '%s' aliases an existing query ID", query.Name) 204 } 205 } 206 207 // Verify that the session exists. 208 if query.Session != "" { 209 sess, err := firstWithTxn(tx, "sessions", "id", query.Session, nil) 210 if err != nil { 211 return fmt.Errorf("invalid session: %v", err) 212 } 213 if sess == nil { 214 return fmt.Errorf("invalid session %#v", query.Session) 215 } 216 } 217 218 // We do not verify the service here, nor the token, if any. These are 219 // checked at execute time and not doing integrity checking on them 220 // helps avoid bootstrapping chicken and egg problems. 221 222 // If this is a template, compile it, otherwise leave the compiled 223 // template field nil. 224 var ct *prepared_query.CompiledTemplate 225 if prepared_query.IsTemplate(query) { 226 var err error 227 ct, err = prepared_query.Compile(query) 228 if err != nil { 229 return fmt.Errorf("failed compiling template: %s", err) 230 } 231 } 232 233 // Insert the wrapped query. 234 if err := tx.Insert("prepared-queries", &queryWrapper{query, ct}); err != nil { 235 return fmt.Errorf("failed inserting prepared query: %s", err) 236 } 237 if err := tx.Insert(tableIndex, &IndexEntry{"prepared-queries", idx}); err != nil { 238 return fmt.Errorf("failed updating index: %s", err) 239 } 240 241 return nil 242} 243 244// PreparedQueryDelete deletes the given query by ID. 245func (s *Store) PreparedQueryDelete(idx uint64, queryID string) error { 246 tx := s.db.WriteTxn(idx) 247 defer tx.Abort() 248 249 if err := preparedQueryDeleteTxn(tx, idx, queryID); err != nil { 250 return fmt.Errorf("failed prepared query delete: %s", err) 251 } 252 253 return tx.Commit() 254} 255 256// preparedQueryDeleteTxn is the inner method used to delete a prepared query 257// with the proper indexes into the state store. 258func preparedQueryDeleteTxn(tx WriteTxn, idx uint64, queryID string) error { 259 // Pull the query. 260 wrapped, err := tx.First("prepared-queries", "id", queryID) 261 if err != nil { 262 return fmt.Errorf("failed prepared query lookup: %s", err) 263 } 264 if wrapped == nil { 265 return nil 266 } 267 268 // Delete the query and update the index. 269 if err := tx.Delete("prepared-queries", wrapped); err != nil { 270 return fmt.Errorf("failed prepared query delete: %s", err) 271 } 272 if err := tx.Insert(tableIndex, &IndexEntry{"prepared-queries", idx}); err != nil { 273 return fmt.Errorf("failed updating index: %s", err) 274 } 275 276 return nil 277} 278 279// PreparedQueryGet returns the given prepared query by ID. 280func (s *Store) PreparedQueryGet(ws memdb.WatchSet, queryID string) (uint64, *structs.PreparedQuery, error) { 281 tx := s.db.Txn(false) 282 defer tx.Abort() 283 284 // Get the table index. 285 idx := maxIndexTxn(tx, "prepared-queries") 286 287 // Look up the query by its ID. 288 watchCh, wrapped, err := tx.FirstWatch("prepared-queries", "id", queryID) 289 if err != nil { 290 return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err) 291 } 292 ws.Add(watchCh) 293 return idx, toPreparedQuery(wrapped), nil 294} 295 296// PreparedQueryResolve returns the given prepared query by looking up an ID or 297// Name. If the query was looked up by name and it's a template, then the 298// template will be rendered before it is returned. 299func (s *Store) PreparedQueryResolve(queryIDOrName string, source structs.QuerySource) (uint64, *structs.PreparedQuery, error) { 300 tx := s.db.Txn(false) 301 defer tx.Abort() 302 303 // Get the table index. 304 idx := maxIndexTxn(tx, "prepared-queries") 305 306 // Explicitly ban an empty query. This will never match an ID and the 307 // schema is set up so it will never match a query with an empty name, 308 // but we check it here to be explicit about it (we'd never want to 309 // return the results from the first query w/o a name). 310 if queryIDOrName == "" { 311 return 0, nil, ErrMissingQueryID 312 } 313 314 // Try first by ID if it looks like they gave us an ID. We check the 315 // format before trying this because the UUID index will complain if 316 // we look up something that's not formatted like one. 317 if isUUID(queryIDOrName) { 318 wrapped, err := tx.First("prepared-queries", "id", queryIDOrName) 319 if err != nil { 320 return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err) 321 } 322 if wrapped != nil { 323 query := toPreparedQuery(wrapped) 324 if prepared_query.IsTemplate(query) { 325 return idx, nil, fmt.Errorf("prepared query templates can only be resolved up by name, not by ID") 326 } 327 return idx, query, nil 328 } 329 } 330 331 // prep will check to see if the query is a template and render it 332 // first, otherwise it will just return a regular query. 333 prep := func(wrapped interface{}) (uint64, *structs.PreparedQuery, error) { 334 wrapper := wrapped.(*queryWrapper) 335 if prepared_query.IsTemplate(wrapper.PreparedQuery) { 336 render, err := wrapper.ct.Render(queryIDOrName, source) 337 if err != nil { 338 return idx, nil, err 339 } 340 return idx, render, nil 341 } 342 return idx, wrapper.PreparedQuery, nil 343 } 344 345 // Next, look for an exact name match. This is the common case for static 346 // prepared queries, and could also apply to templates. 347 { 348 wrapped, err := tx.First("prepared-queries", "name", queryIDOrName) 349 if err != nil { 350 return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err) 351 } 352 if wrapped != nil { 353 return prep(wrapped) 354 } 355 } 356 357 // Next, look for the longest prefix match among the prepared query 358 // templates. 359 { 360 wrapped, err := tx.LongestPrefix("prepared-queries", "template_prefix", queryIDOrName) 361 if err != nil { 362 return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err) 363 } 364 if wrapped != nil { 365 return prep(wrapped) 366 } 367 } 368 369 return idx, nil, nil 370} 371 372// PreparedQueryList returns all the prepared queries. 373func (s *Store) PreparedQueryList(ws memdb.WatchSet) (uint64, structs.PreparedQueries, error) { 374 tx := s.db.Txn(false) 375 defer tx.Abort() 376 377 // Get the table index. 378 idx := maxIndexTxn(tx, "prepared-queries") 379 380 // Query all of the prepared queries in the state store. 381 queries, err := tx.Get("prepared-queries", "id") 382 if err != nil { 383 return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err) 384 } 385 ws.Add(queries.WatchCh()) 386 387 // Go over all of the queries and build the response. 388 var result structs.PreparedQueries 389 for wrapped := queries.Next(); wrapped != nil; wrapped = queries.Next() { 390 result = append(result, toPreparedQuery(wrapped)) 391 } 392 return idx, result, nil 393} 394