package oauth import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "time" "golang.org/x/oauth2/clientcredentials" "tsrun/internal/vault" ) const tokenURL = "https://api.tailscale.com/api/v2/oauth/token" type createKeyRequest struct { Capabilities capabilities `json:"capabilities"` ExpirySecs int `json:"expirySeconds,omitempty"` Description string `json:"description,omitempty"` } type capabilities struct { Devices devicesCaps `json:"devices"` } type devicesCaps struct { Create deviceCreate `json:"create"` } type deviceCreate struct { Reusable bool `json:"reusable"` Ephemeral bool `json:"ephemeral"` Preauthorized bool `json:"preauthorized"` Tags []string `json:"tags,omitempty"` } type createKeyResponse struct { ID string `json:"id"` Key string `json:"key"` } func CreateEphemeralAuthKey(ctx context.Context, payload vault.Payload, description string) (string, error) { config := &clientcredentials.Config{ ClientID: payload.OAuthClientID, ClientSecret: payload.OAuthClientSecret, TokenURL: tokenURL, } httpClient := config.Client(ctx) httpClient.Timeout = 20 * time.Second body, err := json.Marshal(createKeyRequest{ Capabilities: capabilities{Devices: devicesCaps{Create: deviceCreate{ Reusable: false, Ephemeral: true, Preauthorized: true, Tags: payload.Tags, }}}, ExpirySecs: 3600, Description: description, }) if err != nil { return "", err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.tailscale.com/api/v2/tailnet/-/keys", bytes.NewReader(body)) if err != nil { return "", err } req.Header.Set("Content-Type", "application/json") resp, err := httpClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() bodyBytes, err := io.ReadAll(resp.Body) if err != nil { return "", err } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return "", fmt.Errorf("tailscale key request failed: %s", resp.Status) } var out createKeyResponse if err := json.Unmarshal(bodyBytes, &out); err != nil { return "", err } if out.Key == "" { return "", fmt.Errorf("tailscale key request returned empty key") } return out.Key, nil }