client/tailscale: add APIs for auth key management. (#6715)
client/tailscale: add APIs for key management. Updates #502. Signed-off-by: David Anderson <danderson@tailscale.com>main
parent
b2d4abf25a
commit
041a0e3c27
@ -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 |
||||
} |
||||
Loading…
Reference in new issue