builder.go 5.1 KB

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