github.com/ethersphere/bee/v2@v2.2.0/pkg/api/accesscontrol.go (about) 1 // Copyright 2024 The Swarm 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 api 6 7 import ( 8 "context" 9 "crypto/ecdsa" 10 "encoding/hex" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "io" 15 "net/http" 16 "time" 17 18 "github.com/btcsuite/btcd/btcec/v2" 19 "github.com/ethersphere/bee/v2/pkg/accesscontrol" 20 "github.com/ethersphere/bee/v2/pkg/crypto" 21 "github.com/ethersphere/bee/v2/pkg/file/loadsave" 22 "github.com/ethersphere/bee/v2/pkg/file/redundancy" 23 "github.com/ethersphere/bee/v2/pkg/jsonhttp" 24 "github.com/ethersphere/bee/v2/pkg/postage" 25 "github.com/ethersphere/bee/v2/pkg/storage" 26 "github.com/ethersphere/bee/v2/pkg/storer" 27 "github.com/ethersphere/bee/v2/pkg/swarm" 28 "github.com/gorilla/mux" 29 ) 30 31 type addressKey struct{} 32 33 const granteeListEncrypt = true 34 35 // getAddressFromContext is a helper function to extract the address from the context. 36 func getAddressFromContext(ctx context.Context) swarm.Address { 37 v, ok := ctx.Value(addressKey{}).(swarm.Address) 38 if ok { 39 return v 40 } 41 return swarm.ZeroAddress 42 } 43 44 // setAddressInContext sets the swarm address in the context. 45 func setAddressInContext(ctx context.Context, address swarm.Address) context.Context { 46 return context.WithValue(ctx, addressKey{}, address) 47 } 48 49 // GranteesPatchRequest represents a request to patch the list of grantees. 50 type GranteesPatchRequest struct { 51 // Addlist contains the list of grantees to add. 52 Addlist []string `json:"add"` 53 54 // Revokelist contains the list of grantees to revoke. 55 Revokelist []string `json:"revoke"` 56 } 57 58 // GranteesPatchResponse represents the response structure for patching grantees. 59 type GranteesPatchResponse struct { 60 // Reference represents the swarm address. 61 Reference swarm.Address `json:"ref"` 62 // HistoryReference represents the reference to the history of an access control entry. 63 HistoryReference swarm.Address `json:"historyref"` 64 } 65 66 // GranteesPostRequest represents the request structure for adding grantees. 67 type GranteesPostRequest struct { 68 // GranteeList represents the list of grantees to be saves on Swarm. 69 GranteeList []string `json:"grantees"` 70 } 71 72 // GranteesPostResponse represents the response structure for adding grantees. 73 type GranteesPostResponse struct { 74 // Reference represents the saved grantee list Swarm address. 75 Reference swarm.Address `json:"ref"` 76 // HistoryReference represents the reference to the history of an access control entry. 77 HistoryReference swarm.Address `json:"historyref"` 78 } 79 80 // GranteesPatch represents a structure for modifying the list of grantees. 81 type GranteesPatch struct { 82 // Addlist is a list of ecdsa.PublicKeys to be added to a grantee list. 83 Addlist []*ecdsa.PublicKey 84 // Revokelist is a list of ecdsa.PublicKeys to be removed from a grantee list 85 Revokelist []*ecdsa.PublicKey 86 } 87 88 // actDecryptionHandler is a middleware that looks up and decrypts the given address, 89 // if the act headers are present. 90 func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { 91 return func(h http.Handler) http.Handler { 92 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 93 logger := s.logger.WithName("act_decryption_handler").Build() 94 paths := struct { 95 Address swarm.Address `map:"address,resolve" validate:"required"` 96 }{} 97 if response := s.mapStructure(mux.Vars(r), &paths); response != nil { 98 response("invalid path params", logger, w) 99 return 100 } 101 102 headers := struct { 103 Timestamp *int64 `map:"Swarm-Act-Timestamp"` 104 Publisher *ecdsa.PublicKey `map:"Swarm-Act-Publisher"` 105 HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` 106 Cache *bool `map:"Swarm-Cache"` 107 }{} 108 if response := s.mapStructure(r.Header, &headers); response != nil { 109 response("invalid header params", logger, w) 110 return 111 } 112 113 // Try to download the file wihthout decryption, if the act headers are not present 114 if headers.Publisher == nil || headers.HistoryAddress == nil { 115 h.ServeHTTP(w, r) 116 return 117 } 118 119 timestamp := time.Now().Unix() 120 if headers.Timestamp != nil { 121 timestamp = *headers.Timestamp 122 } 123 124 cache := true 125 if headers.Cache != nil { 126 cache = *headers.Cache 127 } 128 ctx := r.Context() 129 ls := loadsave.NewReadonly(s.storer.Download(cache)) 130 reference, err := s.accesscontrol.DownloadHandler(ctx, ls, paths.Address, headers.Publisher, *headers.HistoryAddress, timestamp) 131 if err != nil { 132 logger.Debug("access control download failed", "error", err) 133 logger.Error(nil, "access control download failed") 134 switch { 135 case errors.Is(err, accesscontrol.ErrNotFound): 136 jsonhttp.NotFound(w, "act or history entry not found") 137 case errors.Is(err, accesscontrol.ErrInvalidTimestamp): 138 jsonhttp.BadRequest(w, "invalid timestamp") 139 case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity): 140 jsonhttp.BadRequest(w, "invalid public key") 141 case errors.Is(err, accesscontrol.ErrUnexpectedType): 142 jsonhttp.BadRequest(w, "failed to create history") 143 default: 144 jsonhttp.InternalServerError(w, errActDownload) 145 } 146 return 147 } 148 h.ServeHTTP(w, r.WithContext(setAddressInContext(ctx, reference))) 149 }) 150 } 151 } 152 153 // actEncryptionHandler is a middleware that encrypts the given address using the publisher's public key, 154 // uploads the encrypted reference, history and kvs to the store. 155 func (s *Service) actEncryptionHandler( 156 ctx context.Context, 157 w http.ResponseWriter, 158 putter storer.PutterSession, 159 reference swarm.Address, 160 historyRootHash swarm.Address, 161 ) (swarm.Address, error) { 162 publisherPublicKey := &s.publicKey 163 ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) 164 storageReference, historyReference, encryptedReference, err := s.accesscontrol.UploadHandler(ctx, ls, reference, publisherPublicKey, historyRootHash) 165 if err != nil { 166 return swarm.ZeroAddress, err 167 } 168 // only need to upload history and kvs if a new history is created, 169 // meaning that the publisher uploaded to the history for the first time 170 if !historyReference.Equal(historyRootHash) { 171 err = putter.Done(storageReference) 172 if err != nil { 173 return swarm.ZeroAddress, fmt.Errorf("done split key-value store failed: %w", err) 174 } 175 err = putter.Done(historyReference) 176 if err != nil { 177 return swarm.ZeroAddress, fmt.Errorf("done split history failed: %w", err) 178 } 179 } 180 181 w.Header().Set(SwarmActHistoryAddressHeader, historyReference.String()) 182 return encryptedReference, nil 183 } 184 185 // actListGranteesHandler is a middleware that decrypts the given address and returns the list of grantees, 186 // only the publisher is authorized to access the list. 187 func (s *Service) actListGranteesHandler(w http.ResponseWriter, r *http.Request) { 188 logger := s.logger.WithName("act_list_grantees_handler").Build() 189 paths := struct { 190 GranteesAddress swarm.Address `map:"address,resolve" validate:"required"` 191 }{} 192 if response := s.mapStructure(mux.Vars(r), &paths); response != nil { 193 response("invalid path params", logger, w) 194 return 195 } 196 197 headers := struct { 198 Cache *bool `map:"Swarm-Cache"` 199 }{} 200 if response := s.mapStructure(r.Header, &headers); response != nil { 201 response("invalid header params", logger, w) 202 return 203 } 204 cache := true 205 if headers.Cache != nil { 206 cache = *headers.Cache 207 } 208 publisher := &s.publicKey 209 ls := loadsave.NewReadonly(s.storer.Download(cache)) 210 grantees, err := s.accesscontrol.Get(r.Context(), ls, publisher, paths.GranteesAddress) 211 if err != nil { 212 logger.Debug("could not get grantees", "error", err) 213 logger.Error(nil, "could not get grantees") 214 jsonhttp.NotFound(w, "granteelist not found") 215 return 216 } 217 granteeSlice := make([]string, len(grantees)) 218 for i, grantee := range grantees { 219 granteeSlice[i] = hex.EncodeToString(crypto.EncodeSecp256k1PublicKey(grantee)) 220 } 221 jsonhttp.OK(w, granteeSlice) 222 } 223 224 // actGrantRevokeHandler is a middleware that makes updates to the list of grantees, 225 // only the publisher is authorized to perform this action. 226 func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) { 227 logger := s.logger.WithName("act_grant_revoke_handler").Build() 228 229 if r.Body == http.NoBody { 230 logger.Error(nil, "request has no body") 231 jsonhttp.BadRequest(w, errInvalidRequest) 232 return 233 } 234 235 paths := struct { 236 GranteesAddress swarm.Address `map:"address,resolve" validate:"required"` 237 }{} 238 if response := s.mapStructure(mux.Vars(r), &paths); response != nil { 239 response("invalid path params", logger, w) 240 return 241 } 242 243 headers := struct { 244 BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` 245 SwarmTag uint64 `map:"Swarm-Tag"` 246 Pin bool `map:"Swarm-Pin"` 247 Deferred *bool `map:"Swarm-Deferred-Upload"` 248 HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address" validate:"required"` 249 }{} 250 if response := s.mapStructure(r.Header, &headers); response != nil { 251 response("invalid header params", logger, w) 252 return 253 } 254 255 historyAddress := swarm.ZeroAddress 256 if headers.HistoryAddress != nil { 257 historyAddress = *headers.HistoryAddress 258 } 259 260 var ( 261 tag uint64 262 err error 263 deferred = defaultUploadMethod(headers.Deferred) 264 ) 265 266 if deferred || headers.Pin { 267 tag, err = s.getOrCreateSessionID(headers.SwarmTag) 268 if err != nil { 269 logger.Debug("get or create tag failed", "error", err) 270 logger.Error(nil, "get or create tag failed") 271 switch { 272 case errors.Is(err, storage.ErrNotFound): 273 jsonhttp.NotFound(w, "tag not found") 274 default: 275 jsonhttp.InternalServerError(w, "cannot get or create tag") 276 } 277 return 278 } 279 } 280 281 body, err := io.ReadAll(r.Body) 282 if err != nil { 283 if jsonhttp.HandleBodyReadError(err, w) { 284 return 285 } 286 logger.Debug("read request body failed", "error", err) 287 logger.Error(nil, "read request body failed") 288 jsonhttp.InternalServerError(w, "cannot read request") 289 return 290 } 291 292 gpr := GranteesPatchRequest{} 293 if len(body) > 0 { 294 err = json.Unmarshal(body, &gpr) 295 if err != nil { 296 logger.Debug("unmarshal body failed", "error", err) 297 logger.Error(nil, "unmarshal body failed") 298 jsonhttp.InternalServerError(w, "error unmarshaling request body") 299 return 300 } 301 } 302 303 grantees := GranteesPatch{} 304 parsedAddlist, err := parseKeys(gpr.Addlist) 305 if err != nil { 306 logger.Debug("add list key parse failed", "error", err) 307 logger.Error(nil, "add list key parse failed") 308 jsonhttp.BadRequest(w, "invalid add list") 309 return 310 } 311 grantees.Addlist = append(grantees.Addlist, parsedAddlist...) 312 313 parsedRevokelist, err := parseKeys(gpr.Revokelist) 314 if err != nil { 315 logger.Debug("revoke list key parse failed", "error", err) 316 logger.Error(nil, "revoke list key parse failed") 317 jsonhttp.BadRequest(w, "invalid revoke list") 318 return 319 } 320 grantees.Revokelist = append(grantees.Revokelist, parsedRevokelist...) 321 322 ctx := r.Context() 323 putter, err := s.newStamperPutter(ctx, putterOptions{ 324 BatchID: headers.BatchID, 325 TagID: tag, 326 Pin: headers.Pin, 327 Deferred: deferred, 328 }) 329 if err != nil { 330 logger.Debug("putter failed", "error", err) 331 logger.Error(nil, "putter failed") 332 switch { 333 case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable): 334 jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist") 335 case errors.Is(err, postage.ErrNotFound): 336 jsonhttp.NotFound(w, "batch with id not found") 337 case errors.Is(err, errInvalidPostageBatch): 338 jsonhttp.BadRequest(w, "invalid batch id") 339 case errors.Is(err, errUnsupportedDevNodeOperation): 340 jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation) 341 default: 342 jsonhttp.BadRequest(w, nil) 343 } 344 return 345 } 346 347 granteeref := paths.GranteesAddress 348 publisher := &s.publicKey 349 ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) 350 gls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) 351 granteeref, encryptedglref, historyref, actref, err := s.accesscontrol.UpdateHandler(ctx, ls, gls, granteeref, historyAddress, publisher, grantees.Addlist, grantees.Revokelist) 352 if err != nil { 353 logger.Debug("failed to update grantee list", "error", err) 354 logger.Error(nil, "failed to update grantee list") 355 switch { 356 case errors.Is(err, accesscontrol.ErrNotFound): 357 jsonhttp.NotFound(w, "act or history entry not found") 358 case errors.Is(err, accesscontrol.ErrNoGranteeFound): 359 jsonhttp.BadRequest(w, "remove from empty grantee list") 360 case errors.Is(err, accesscontrol.ErrUnexpectedType): 361 jsonhttp.BadRequest(w, "failed to create history") 362 default: 363 jsonhttp.InternalServerError(w, errActGranteeList) 364 } 365 return 366 } 367 368 err = putter.Done(actref) 369 if err != nil { 370 logger.Debug("done split act failed", "error", err) 371 logger.Error(nil, "done split act failed") 372 jsonhttp.InternalServerError(w, "done split act failed") 373 return 374 } 375 376 err = putter.Done(historyref) 377 if err != nil { 378 logger.Debug("done split history failed", "error", err) 379 logger.Error(nil, "done split history failed") 380 jsonhttp.InternalServerError(w, "done split history failed") 381 return 382 } 383 384 err = putter.Done(granteeref) 385 if err != nil { 386 logger.Debug("done split grantees failed", "error", err) 387 logger.Error(nil, "done split grantees failed") 388 jsonhttp.InternalServerError(w, "done split grantees failed") 389 return 390 } 391 392 jsonhttp.OK(w, GranteesPatchResponse{ 393 Reference: encryptedglref, 394 HistoryReference: historyref, 395 }) 396 } 397 398 // actCreateGranteesHandler is a middleware that creates a new list of grantees, 399 // only the publisher is authorized to perform this action. 400 func (s *Service) actCreateGranteesHandler(w http.ResponseWriter, r *http.Request) { 401 logger := s.logger.WithName("acthandler").Build() 402 403 if r.Body == http.NoBody { 404 logger.Error(nil, "request has no body") 405 jsonhttp.BadRequest(w, errInvalidRequest) 406 return 407 } 408 409 headers := struct { 410 BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` 411 SwarmTag uint64 `map:"Swarm-Tag"` 412 Pin bool `map:"Swarm-Pin"` 413 Deferred *bool `map:"Swarm-Deferred-Upload"` 414 HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` 415 }{} 416 if response := s.mapStructure(r.Header, &headers); response != nil { 417 response("invalid header params", logger, w) 418 return 419 } 420 421 historyAddress := swarm.ZeroAddress 422 if headers.HistoryAddress != nil { 423 historyAddress = *headers.HistoryAddress 424 } 425 426 var ( 427 tag uint64 428 err error 429 deferred = defaultUploadMethod(headers.Deferred) 430 ) 431 432 if deferred || headers.Pin { 433 tag, err = s.getOrCreateSessionID(headers.SwarmTag) 434 if err != nil { 435 logger.Debug("get or create tag failed", "error", err) 436 logger.Error(nil, "get or create tag failed") 437 switch { 438 case errors.Is(err, storage.ErrNotFound): 439 jsonhttp.NotFound(w, "tag not found") 440 default: 441 jsonhttp.InternalServerError(w, "cannot get or create tag") 442 } 443 return 444 } 445 } 446 447 body, err := io.ReadAll(r.Body) 448 if err != nil { 449 if jsonhttp.HandleBodyReadError(err, w) { 450 return 451 } 452 logger.Debug("read request body failed", "error", err) 453 logger.Error(nil, "read request body failed") 454 jsonhttp.InternalServerError(w, "cannot read request") 455 return 456 } 457 458 gpr := GranteesPostRequest{} 459 if len(body) > 0 { 460 err = json.Unmarshal(body, &gpr) 461 if err != nil { 462 logger.Debug("unmarshal body failed", "error", err) 463 logger.Error(nil, "unmarshal body failed") 464 jsonhttp.InternalServerError(w, "error unmarshaling request body") 465 return 466 } 467 } 468 469 list, err := parseKeys(gpr.GranteeList) 470 if err != nil { 471 logger.Debug("create list key parse failed", "error", err) 472 logger.Error(nil, "create list key parse failed") 473 jsonhttp.BadRequest(w, "invalid grantee list") 474 return 475 } 476 477 ctx := r.Context() 478 putter, err := s.newStamperPutter(ctx, putterOptions{ 479 BatchID: headers.BatchID, 480 TagID: tag, 481 Pin: headers.Pin, 482 Deferred: deferred, 483 }) 484 if err != nil { 485 logger.Debug("putter failed", "error", err) 486 logger.Error(nil, "putter failed") 487 switch { 488 case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable): 489 jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist") 490 case errors.Is(err, postage.ErrNotFound): 491 jsonhttp.NotFound(w, "batch with id not found") 492 case errors.Is(err, errInvalidPostageBatch): 493 jsonhttp.BadRequest(w, "invalid batch id") 494 case errors.Is(err, errUnsupportedDevNodeOperation): 495 jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation) 496 default: 497 jsonhttp.BadRequest(w, nil) 498 } 499 return 500 } 501 502 publisher := &s.publicKey 503 ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) 504 gls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) 505 granteeref, encryptedglref, historyref, actref, err := s.accesscontrol.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, historyAddress, publisher, list, nil) 506 if err != nil { 507 logger.Debug("failed to create grantee list", "error", err) 508 logger.Error(nil, "failed to create grantee list") 509 switch { 510 case errors.Is(err, accesscontrol.ErrNotFound): 511 jsonhttp.NotFound(w, "act or history entry not found") 512 case errors.Is(err, accesscontrol.ErrUnexpectedType): 513 jsonhttp.BadRequest(w, "failed to create history") 514 default: 515 jsonhttp.InternalServerError(w, errActGranteeList) 516 } 517 return 518 } 519 520 err = putter.Done(actref) 521 if err != nil { 522 logger.Debug("done split act failed", "error", err) 523 logger.Error(nil, "done split act failed") 524 jsonhttp.InternalServerError(w, "done split act failed") 525 return 526 } 527 528 err = putter.Done(historyref) 529 if err != nil { 530 logger.Debug("done split history failed", "error", err) 531 logger.Error(nil, "done split history failed") 532 jsonhttp.InternalServerError(w, "done split history failed") 533 return 534 } 535 536 err = putter.Done(granteeref) 537 if err != nil { 538 logger.Debug("done split grantees failed", "error", err) 539 logger.Error(nil, "done split grantees failed") 540 jsonhttp.InternalServerError(w, "done split grantees failed") 541 return 542 } 543 544 jsonhttp.Created(w, GranteesPostResponse{ 545 Reference: encryptedglref, 546 HistoryReference: historyref, 547 }) 548 } 549 550 func parseKeys(list []string) ([]*ecdsa.PublicKey, error) { 551 parsedList := make([]*ecdsa.PublicKey, 0, len(list)) 552 for _, g := range list { 553 h, err := hex.DecodeString(g) 554 if err != nil { 555 return []*ecdsa.PublicKey{}, fmt.Errorf("failed to decode grantee: %w", err) 556 } 557 k, err := btcec.ParsePubKey(h) 558 if err != nil { 559 return []*ecdsa.PublicKey{}, fmt.Errorf("failed to parse grantee public key: %w", err) 560 } 561 parsedList = append(parsedList, k.ToECDSA()) 562 } 563 564 return parsedList, nil 565 }