Skip to content

Commit 03f8524

Browse files
dandersoncoadler
authored andcommitted
client/tailscale: add APIs for auth key management. (tailscale#6715)
client/tailscale: add APIs for key management. Updates tailscale#502. Signed-off-by: David Anderson <[email protected]>
1 parent 9b96f6c commit 03f8524

File tree

1 file changed

+144
-0
lines changed

1 file changed

+144
-0
lines changed

client/tailscale/keys.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package tailscale
6+
7+
import (
8+
"bytes"
9+
"context"
10+
"encoding/json"
11+
"fmt"
12+
"net/http"
13+
"time"
14+
)
15+
16+
// Key represents a Tailscale API or auth key.
17+
type Key struct {
18+
ID string `json:"id"`
19+
Created time.Time `json:"created"`
20+
Expires time.Time `json:"expires"`
21+
Capabilities KeyCapabilities `json:"capabilities"`
22+
}
23+
24+
// KeyCapabilities are the capabilities of a Key.
25+
type KeyCapabilities struct {
26+
Devices KeyDeviceCapabilities `json:"devices,omitempty"`
27+
}
28+
29+
// KeyDeviceCapabilities are the device-related capabilities of a Key.
30+
type KeyDeviceCapabilities struct {
31+
Create KeyDeviceCreateCapabilities `json:"create"`
32+
}
33+
34+
// KeyDeviceCreateCapabilities are the device creation capabilities of a Key.
35+
type KeyDeviceCreateCapabilities struct {
36+
Reusable bool `json:"reusable"`
37+
Ephemeral bool `json:"ephemeral"`
38+
Preauthorized bool `json:"preauthorized"`
39+
Tags []string `json:"tags,omitempty"`
40+
}
41+
42+
// Keys returns the list of keys for the current user.
43+
func (c *Client) Keys(ctx context.Context) ([]string, error) {
44+
path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys", c.baseURL(), c.tailnet)
45+
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
b, resp, err := c.sendRequest(req)
51+
if err != nil {
52+
return nil, err
53+
}
54+
if resp.StatusCode != http.StatusOK {
55+
return nil, handleErrorResponse(b, resp)
56+
}
57+
58+
var keys []struct {
59+
ID string `json:"id"`
60+
}
61+
if err := json.Unmarshal(b, &keys); err != nil {
62+
return nil, err
63+
}
64+
ret := make([]string, 0, len(keys))
65+
for _, k := range keys {
66+
ret = append(ret, k.ID)
67+
}
68+
return ret, nil
69+
}
70+
71+
// CreateKey creates a new key for the current user. Currently, only auth keys
72+
// can be created. Returns the key itself, which cannot be retrieved again
73+
// later, and the key metadata.
74+
func (c *Client) CreateKey(ctx context.Context, caps KeyCapabilities) (string, *Key, error) {
75+
bs, err := json.Marshal(caps)
76+
if err != nil {
77+
return "", nil, err
78+
}
79+
80+
path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys", c.baseURL(), c.tailnet)
81+
req, err := http.NewRequestWithContext(ctx, "POST", path, bytes.NewReader(bs))
82+
if err != nil {
83+
return "", nil, err
84+
}
85+
86+
b, resp, err := c.sendRequest(req)
87+
if err != nil {
88+
return "", nil, err
89+
}
90+
if resp.StatusCode != http.StatusOK {
91+
return "", nil, handleErrorResponse(b, resp)
92+
}
93+
94+
var key struct {
95+
Key
96+
Secret string `json:"key"`
97+
}
98+
if err := json.Unmarshal(b, &key); err != nil {
99+
return "", nil, err
100+
}
101+
return key.Secret, &key.Key, nil
102+
}
103+
104+
// Key returns the metadata for the given key ID. Currently, capabilities are
105+
// only returned for auth keys, API keys only return general metadata.
106+
func (c *Client) Key(ctx context.Context, id string) (*Key, error) {
107+
path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys/%s", c.baseURL(), c.tailnet, id)
108+
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
b, resp, err := c.sendRequest(req)
114+
if err != nil {
115+
return nil, err
116+
}
117+
if resp.StatusCode != http.StatusOK {
118+
return nil, handleErrorResponse(b, resp)
119+
}
120+
121+
var key Key
122+
if err := json.Unmarshal(b, &key); err != nil {
123+
return nil, err
124+
}
125+
return &key, nil
126+
}
127+
128+
// DeleteKey deletes the key with the given ID.
129+
func (c *Client) DeleteKey(ctx context.Context, id string) error {
130+
path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys/%s", c.baseURL(), c.tailnet, id)
131+
req, err := http.NewRequestWithContext(ctx, "DELETE", path, nil)
132+
if err != nil {
133+
return err
134+
}
135+
136+
b, resp, err := c.sendRequest(req)
137+
if err != nil {
138+
return err
139+
}
140+
if resp.StatusCode != http.StatusOK {
141+
return handleErrorResponse(b, resp)
142+
}
143+
return nil
144+
}

0 commit comments

Comments
 (0)