mirror of https://github.com/tailscale/tailscale/
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>pull/6643/head
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