diff --git a/client/tailscale/keys.go b/client/tailscale/keys.go new file mode 100644 index 000000000..ddd0c94fe --- /dev/null +++ b/client/tailscale/keys.go @@ -0,0 +1,144 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tailscale + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" +) + +// Key represents a Tailscale API or auth key. +type Key struct { + ID string `json:"id"` + Created time.Time `json:"created"` + Expires time.Time `json:"expires"` + Capabilities KeyCapabilities `json:"capabilities"` +} + +// KeyCapabilities are the capabilities of a Key. +type KeyCapabilities struct { + Devices KeyDeviceCapabilities `json:"devices,omitempty"` +} + +// KeyDeviceCapabilities are the device-related capabilities of a Key. +type KeyDeviceCapabilities struct { + Create KeyDeviceCreateCapabilities `json:"create"` +} + +// KeyDeviceCreateCapabilities are the device creation capabilities of a Key. +type KeyDeviceCreateCapabilities struct { + Reusable bool `json:"reusable"` + Ephemeral bool `json:"ephemeral"` + Preauthorized bool `json:"preauthorized"` + Tags []string `json:"tags,omitempty"` +} + +// Keys returns the list of keys for the current user. +func (c *Client) Keys(ctx context.Context) ([]string, error) { + path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys", c.baseURL(), c.tailnet) + req, err := http.NewRequestWithContext(ctx, "GET", path, nil) + if err != nil { + return nil, err + } + + b, resp, err := c.sendRequest(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, handleErrorResponse(b, resp) + } + + var keys []struct { + ID string `json:"id"` + } + if err := json.Unmarshal(b, &keys); err != nil { + return nil, err + } + ret := make([]string, 0, len(keys)) + for _, k := range keys { + ret = append(ret, k.ID) + } + return ret, nil +} + +// CreateKey creates a new key for the current user. Currently, only auth keys +// can be created. Returns the key itself, which cannot be retrieved again +// later, and the key metadata. +func (c *Client) CreateKey(ctx context.Context, caps KeyCapabilities) (string, *Key, error) { + bs, err := json.Marshal(caps) + if err != nil { + return "", nil, err + } + + path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys", c.baseURL(), c.tailnet) + req, err := http.NewRequestWithContext(ctx, "POST", path, bytes.NewReader(bs)) + if err != nil { + return "", nil, err + } + + b, resp, err := c.sendRequest(req) + if err != nil { + return "", nil, err + } + if resp.StatusCode != http.StatusOK { + return "", nil, handleErrorResponse(b, resp) + } + + var key struct { + Key + Secret string `json:"key"` + } + if err := json.Unmarshal(b, &key); err != nil { + return "", nil, err + } + return key.Secret, &key.Key, nil +} + +// Key returns the metadata for the given key ID. Currently, capabilities are +// only returned for auth keys, API keys only return general metadata. +func (c *Client) Key(ctx context.Context, id string) (*Key, error) { + path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys/%s", c.baseURL(), c.tailnet, id) + req, err := http.NewRequestWithContext(ctx, "GET", path, nil) + if err != nil { + return nil, err + } + + b, resp, err := c.sendRequest(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, handleErrorResponse(b, resp) + } + + var key Key + if err := json.Unmarshal(b, &key); err != nil { + return nil, err + } + return &key, nil +} + +// DeleteKey deletes the key with the given ID. +func (c *Client) DeleteKey(ctx context.Context, id string) error { + path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys/%s", c.baseURL(), c.tailnet, id) + req, err := http.NewRequestWithContext(ctx, "DELETE", path, nil) + if err != nil { + return err + } + + b, resp, err := c.sendRequest(req) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return handleErrorResponse(b, resp) + } + return nil +}