mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-12 05:37:32 +00:00
cmd/tailscale,ipn: implement lock sign command
Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
@@ -430,6 +430,47 @@ func (b *LocalBackend) NetworkLockKeyTrustedForTest(keyID tkatype.KeyID) bool {
|
||||
return b.tka.authority.KeyTrusted(keyID)
|
||||
}
|
||||
|
||||
// NetworkLockSign signs the given node-key and submits it to the control plane.
|
||||
// rotationPublic, if specified, must be an ed25519 public key.
|
||||
func (b *LocalBackend) NetworkLockSign(nodeKey key.NodePublic, rotationPublic []byte) error {
|
||||
ourNodeKey, sig, err := func(nodeKey key.NodePublic, rotationPublic []byte) (key.NodePublic, tka.NodeKeySignature, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if b.tka == nil {
|
||||
return key.NodePublic{}, tka.NodeKeySignature{}, errNetworkLockNotActive
|
||||
}
|
||||
if !b.tka.authority.KeyTrusted(b.nlPrivKey.KeyID()) {
|
||||
return key.NodePublic{}, tka.NodeKeySignature{}, errors.New("this node is not trusted by network lock")
|
||||
}
|
||||
|
||||
p, err := nodeKey.MarshalBinary()
|
||||
if err != nil {
|
||||
return key.NodePublic{}, tka.NodeKeySignature{}, err
|
||||
}
|
||||
sig := tka.NodeKeySignature{
|
||||
SigKind: tka.SigDirect,
|
||||
KeyID: b.nlPrivKey.KeyID(),
|
||||
Pubkey: p,
|
||||
WrappingPubkey: rotationPublic,
|
||||
}
|
||||
sig.Signature, err = b.nlPrivKey.SignNKS(sig.SigHash())
|
||||
if err != nil {
|
||||
return key.NodePublic{}, tka.NodeKeySignature{}, fmt.Errorf("signature failed: %w", err)
|
||||
}
|
||||
return b.prefs.Persist().PublicNodeKey(), sig, nil
|
||||
}(nodeKey, rotationPublic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.logf("Generated network-lock signature for %v, submitting to control plane", nodeKey)
|
||||
if _, err := b.tkaSubmitSignature(ourNodeKey, sig.Serialize()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NetworkLockModify adds and/or removes keys in the tailnet's key authority.
|
||||
func (b *LocalBackend) NetworkLockModify(addKeys, removeKeys []tka.Key) (err error) {
|
||||
defer func() {
|
||||
@@ -817,3 +858,39 @@ func (b *LocalBackend) tkaDoDisablement(ourNodeKey key.NodePublic, head tka.AUMH
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (b *LocalBackend) tkaSubmitSignature(ourNodeKey key.NodePublic, sig tkatype.MarshaledSignature) (*tailcfg.TKASubmitSignatureResponse, error) {
|
||||
var req bytes.Buffer
|
||||
if err := json.NewEncoder(&req).Encode(tailcfg.TKASubmitSignatureRequest{
|
||||
Version: tailcfg.CurrentCapabilityVersion,
|
||||
NodeKey: ourNodeKey,
|
||||
Signature: sig,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("encoding request: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
req2, err := http.NewRequestWithContext(ctx, "GET", "https://unused/machine/tka/sign", &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("req: %w", err)
|
||||
}
|
||||
res, err := b.DoNoiseRequest(req2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resp: %w", err)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return nil, fmt.Errorf("request returned (%d): %s", res.StatusCode, string(body))
|
||||
}
|
||||
a := new(tailcfg.TKASubmitSignatureResponse)
|
||||
err = json.NewDecoder(&io.LimitedReader{R: res.Body, N: 1024 * 1024}).Decode(a)
|
||||
res.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding JSON: %w", err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
@@ -629,3 +629,83 @@ func TestTKADisable(t *testing.T) {
|
||||
t.Errorf("NetworkLockDisable() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTKASign(t *testing.T) {
|
||||
envknob.Setenv("TAILSCALE_USE_WIP_CODE", "1")
|
||||
temp := t.TempDir()
|
||||
os.Mkdir(filepath.Join(temp, "tka"), 0755)
|
||||
nodePriv := key.NewNode()
|
||||
toSign := key.NewNode()
|
||||
|
||||
// Make a fake TKA authority, to seed local state.
|
||||
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
|
||||
nlPriv := key.NewNLPrivate()
|
||||
key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
|
||||
chonk, err := tka.ChonkDir(filepath.Join(temp, "tka"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
authority, _, err := tka.Create(chonk, tka.State{
|
||||
Keys: []tka.Key{key},
|
||||
DisablementSecrets: [][]byte{tka.DisablementKDF(disablementSecret)},
|
||||
}, nlPriv)
|
||||
if err != nil {
|
||||
t.Fatalf("tka.Create() failed: %v", err)
|
||||
}
|
||||
|
||||
ts, client := fakeNoiseServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
switch r.URL.Path {
|
||||
case "/machine/tka/sign":
|
||||
body := new(tailcfg.TKASubmitSignatureRequest)
|
||||
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Version != tailcfg.CurrentCapabilityVersion {
|
||||
t.Errorf("sign CapVer = %v, want %v", body.Version, tailcfg.CurrentCapabilityVersion)
|
||||
}
|
||||
if body.NodeKey != nodePriv.Public() {
|
||||
t.Errorf("nodeKey = %v, want %v", body.NodeKey, nodePriv.Public())
|
||||
}
|
||||
|
||||
var sig tka.NodeKeySignature
|
||||
if err := sig.Unserialize(body.Signature); err != nil {
|
||||
t.Fatalf("malformed signature: %v", err)
|
||||
}
|
||||
|
||||
if err := authority.NodeKeyAuthorized(toSign.Public(), body.Signature); err != nil {
|
||||
t.Errorf("signature does not verify: %v", err)
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
if err := json.NewEncoder(w).Encode(tailcfg.TKASubmitSignatureResponse{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
default:
|
||||
t.Errorf("unhandled endpoint path: %v", r.URL.Path)
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cc := fakeControlClient(t, client)
|
||||
b := LocalBackend{
|
||||
varRoot: temp,
|
||||
cc: cc,
|
||||
ccAuto: cc,
|
||||
logf: t.Logf,
|
||||
tka: &tkaState{
|
||||
authority: authority,
|
||||
storage: chonk,
|
||||
},
|
||||
prefs: (&ipn.Prefs{
|
||||
Persist: &persist.Persist{PrivateNodeKey: nodePriv},
|
||||
}).View(),
|
||||
nlPrivKey: nlPriv,
|
||||
}
|
||||
|
||||
if err := b.NetworkLockSign(toSign.Public(), nil); err != nil {
|
||||
t.Errorf("NetworkLockSign() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user