You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale/client/tailscale/keys.go

148 lines
3.8 KiB
Go

// 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) {
keyRequest := struct {
Capabilities KeyCapabilities `json:"capabilities"`
}{caps}
bs, err := json.Marshal(keyRequest)
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
}