builder.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package tka
  4. import (
  5. "fmt"
  6. "os"
  7. "tailscale.com/types/tkatype"
  8. )
  9. // Types implementing Signer can sign update messages.
  10. type Signer interface {
  11. // SignAUM returns signatures for the AUM encoded by the given AUMSigHash.
  12. SignAUM(tkatype.AUMSigHash) ([]tkatype.Signature, error)
  13. }
  14. // UpdateBuilder implements a builder for changes to the tailnet
  15. // key authority.
  16. //
  17. // Finalize must be called to compute the update messages, which
  18. // must then be applied to all Authority objects using Inform().
  19. type UpdateBuilder struct {
  20. a *Authority
  21. signer Signer
  22. state State
  23. parent AUMHash
  24. out []AUM
  25. }
  26. func (b *UpdateBuilder) mkUpdate(update AUM) error {
  27. prevHash := make([]byte, len(b.parent))
  28. copy(prevHash, b.parent[:])
  29. update.PrevAUMHash = prevHash
  30. if b.signer != nil {
  31. sigs, err := b.signer.SignAUM(update.SigHash())
  32. if err != nil {
  33. return fmt.Errorf("signing failed: %v", err)
  34. }
  35. update.Signatures = append(update.Signatures, sigs...)
  36. }
  37. if err := update.StaticValidate(); err != nil {
  38. return fmt.Errorf("generated update was invalid: %v", err)
  39. }
  40. state, err := b.state.applyVerifiedAUM(update)
  41. if err != nil {
  42. return fmt.Errorf("update cannot be applied: %v", err)
  43. }
  44. b.state = state
  45. b.parent = update.Hash()
  46. b.out = append(b.out, update)
  47. return nil
  48. }
  49. // AddKey adds a new key to the authority.
  50. func (b *UpdateBuilder) AddKey(key Key) error {
  51. keyID, err := key.ID()
  52. if err != nil {
  53. return err
  54. }
  55. if _, err := b.state.GetKey(keyID); err == nil {
  56. return fmt.Errorf("cannot add key %v: already exists", key)
  57. }
  58. return b.mkUpdate(AUM{MessageKind: AUMAddKey, Key: &key})
  59. }
  60. // RemoveKey removes a key from the authority.
  61. func (b *UpdateBuilder) RemoveKey(keyID tkatype.KeyID) error {
  62. if _, err := b.state.GetKey(keyID); err != nil {
  63. return fmt.Errorf("failed reading key %x: %v", keyID, err)
  64. }
  65. return b.mkUpdate(AUM{MessageKind: AUMRemoveKey, KeyID: keyID})
  66. }
  67. // SetKeyVote updates the number of votes of an existing key.
  68. func (b *UpdateBuilder) SetKeyVote(keyID tkatype.KeyID, votes uint) error {
  69. if _, err := b.state.GetKey(keyID); err != nil {
  70. return fmt.Errorf("failed reading key %x: %v", keyID, err)
  71. }
  72. return b.mkUpdate(AUM{MessageKind: AUMUpdateKey, Votes: &votes, KeyID: keyID})
  73. }
  74. // SetKeyMeta updates key-value metadata stored against an existing key.
  75. //
  76. // TODO(tom): Provide an API to update specific values rather than the whole
  77. // map.
  78. func (b *UpdateBuilder) SetKeyMeta(keyID tkatype.KeyID, meta map[string]string) error {
  79. if _, err := b.state.GetKey(keyID); err != nil {
  80. return fmt.Errorf("failed reading key %x: %v", keyID, err)
  81. }
  82. return b.mkUpdate(AUM{MessageKind: AUMUpdateKey, Meta: meta, KeyID: keyID})
  83. }
  84. func (b *UpdateBuilder) generateCheckpoint() error {
  85. // Compute the checkpoint state.
  86. state := b.a.state
  87. for i, update := range b.out {
  88. var err error
  89. if state, err = state.applyVerifiedAUM(update); err != nil {
  90. return fmt.Errorf("applying update %d: %v", i, err)
  91. }
  92. }
  93. // Checkpoints cant specify a parent AUM.
  94. state.LastAUMHash = nil
  95. return b.mkUpdate(AUM{MessageKind: AUMCheckpoint, State: &state})
  96. }
  97. // checkpointEvery sets how often a checkpoint AUM should be generated.
  98. const checkpointEvery = 50
  99. // Finalize returns the set of update message to actuate the update.
  100. func (b *UpdateBuilder) Finalize(storage Chonk) ([]AUM, error) {
  101. var (
  102. needCheckpoint bool = true
  103. cursor AUMHash = b.a.Head()
  104. )
  105. for i := len(b.out); i < checkpointEvery; i++ {
  106. aum, err := storage.AUM(cursor)
  107. if err != nil {
  108. if err == os.ErrNotExist {
  109. // The available chain is shorter than the interval to checkpoint at.
  110. needCheckpoint = false
  111. break
  112. }
  113. return nil, fmt.Errorf("reading AUM: %v", err)
  114. }
  115. if aum.MessageKind == AUMCheckpoint {
  116. needCheckpoint = false
  117. break
  118. }
  119. parent, hasParent := aum.Parent()
  120. if !hasParent {
  121. // We've hit the genesis update, so the chain is shorter than the interval to checkpoint at.
  122. needCheckpoint = false
  123. break
  124. }
  125. cursor = parent
  126. }
  127. if needCheckpoint {
  128. if err := b.generateCheckpoint(); err != nil {
  129. return nil, fmt.Errorf("generating checkpoint: %v", err)
  130. }
  131. }
  132. // Check no AUMs were applied in the meantime
  133. if len(b.out) > 0 {
  134. if parent, _ := b.out[0].Parent(); parent != b.a.Head() {
  135. return nil, fmt.Errorf("updates no longer apply to head: based on %x but head is %x", parent, b.a.Head())
  136. }
  137. }
  138. return b.out, nil
  139. }
  140. // NewUpdater returns a builder you can use to make changes to
  141. // the tailnet key authority.
  142. //
  143. // The provided signer function, if non-nil, is called with each update
  144. // to compute and apply signatures.
  145. //
  146. // Updates are specified by calling methods on the returned UpdatedBuilder.
  147. // Call Finalize() when you are done to obtain the specific update messages
  148. // which actuate the changes.
  149. func (a *Authority) NewUpdater(signer Signer) *UpdateBuilder {
  150. return &UpdateBuilder{
  151. a: a,
  152. signer: signer,
  153. parent: a.Head(),
  154. state: a.state,
  155. }
  156. }