Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ dist/
.env

.gocache

PR_MESSAGE.md
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ tscli agent update --dir .
| **Policy file (ACL)** | Fetch as raw HUJSON **or** canonical JSON |
| **Webhooks** | List, get, delete, **create** (generic / Slack) with subscription & provider validation |
| **Posture integrations** | List, get, create, patch existing integrations |
| **Services** | List, get, create / update, delete VIP services; list & approve associated devices |
| **Invites** | List / delete device- or user-invites |
| **Contacts** | Get & update contact emails |
| **Debug switch** | `--debug` or `TSCLI_DEBUG=1` prints full HTTP requests / responses to stderr |
Expand Down Expand Up @@ -150,9 +151,15 @@ make docs-serve # serve docs locally with docsify
| ----------------- | --------------------------------------- | ---------------- | ------- |
| Tailscale API key | `--api-key`, `-k` / `TAILSCALE_API_KEY` | `api-key` | — |
| Tailnet name | `--tailnet`, `-n` / `TAILSCALE_TAILNET` | `tailnet` | `-` |
| API base URL | — / `TSCLI_BASE_URL` | `base-url` | `https://api.tailscale.com` |
| User agent | — / `TSCLI_USER_AGENT` | `user-agent` | derived from build version / local git |
| Active profile | — | `active-tailnet` | — |
| Profile list | — | `tailnets` | `[]` |

`base-url` (and the OAuth token endpoint derived from it) must be `https://`, or `http://` restricted to a loopback host (`127.0.0.1`, `::1`, `localhost`) — any other scheme or non-loopback `http://` host is rejected, since your API key or OAuth client secret would otherwise be sent to that URL. There is no `--base-url` flag; set it via `TSCLI_BASE_URL` or a `base-url:` key in `~/.tscli.yaml`.

By default the `User-Agent` sent with every API request is derived from the build version (or, for a locally-built binary, this process's local git repository state). If you are embedding `pkg/tscli` as a library, set `user-agent` (via `TSCLI_USER_AGENT` or your own viper config) to avoid sending your own repository's git metadata to Tailscale's API.

```yaml
# ~/.tscli.yaml
output: pretty # other options are: human, json or yaml
Expand Down Expand Up @@ -313,6 +320,10 @@ tscli delete key --key key-id
| Update service | :white_check_mark: | `tscli set service --service <name> --body '<json>'` |
| Set service approval | :white_check_mark: | `tscli set service approval --service <name> --device <id> --approved=true` |
| Delete service | :white_check_mark: | `tscli delete service --service <name>` |
| **Tailnet lifecycle** | | |
| List tailnets | :white_check_mark: | `tscli list tailnets` |
| Create tailnet | :white_check_mark: | `tscli create tailnet --display-name <name>` |
| Delete tailnet | :white_check_mark: | `tscli delete tailnet --id <id>` |
| **Tailnet Settings** | | |
| Get tailnet settings | :white_check_mark: | `tscli get settings` |
| Update tailnet settings | :white_check_mark: | `tscli set settings --devices-approval …` |
Expand Down
3 changes: 1 addition & 2 deletions cmd/tscli/create/tailnet/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"

"github.com/jaxxstorm/tscli/pkg/config"
"github.com/jaxxstorm/tscli/pkg/oauth"
"github.com/jaxxstorm/tscli/pkg/output"
"github.com/jaxxstorm/tscli/pkg/tscli"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -38,7 +37,7 @@ func Command() *cobra.Command {
return err
}

tokenResp, err := oauth.ExchangeClientCredentials(cmd.Context(), creds.ClientID, creds.ClientSecret)
tokenResp, err := tscli.ExchangeOAuthClientCredentials(cmd.Context(), creds.ClientID, creds.ClientSecret)
if err != nil {
return fmt.Errorf("failed to exchange OAuth credentials: %w", err)
}
Expand Down
5 changes: 2 additions & 3 deletions cmd/tscli/create/token/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
"errors"
"fmt"

"github.com/jaxxstorm/tscli/pkg/oauth"
"github.com/jaxxstorm/tscli/pkg/output"
"github.com/jaxxstorm/tscli/pkg/tscli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
Expand All @@ -37,8 +37,7 @@ func Command() *cobra.Command {
},

RunE: func(cmd *cobra.Command, args []string) error {
// Use the OAuth library for token exchange
tokenResp, err := oauth.ExchangeClientCredentials(cmd.Context(), clientID, clientSecret)
tokenResp, err := tscli.ExchangeOAuthClientCredentials(cmd.Context(), clientID, clientSecret)
if err != nil {
return fmt.Errorf("failed to exchange OAuth credentials: %w", err)
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/tscli/delete/tailnet/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"

"github.com/jaxxstorm/tscli/pkg/config"
"github.com/jaxxstorm/tscli/pkg/oauth"
"github.com/jaxxstorm/tscli/pkg/output"
"github.com/jaxxstorm/tscli/pkg/tscli"
"github.com/spf13/cobra"
Expand All @@ -32,7 +31,7 @@ func Command() *cobra.Command {
return err
}

tokenResp, err := oauth.ExchangeClientCredentials(cmd.Context(), creds.ClientID, creds.ClientSecret)
tokenResp, err := tscli.ExchangeOAuthClientCredentials(cmd.Context(), creds.ClientID, creds.ClientSecret)
if err != nil {
return fmt.Errorf("failed to exchange OAuth credentials: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/tscli/delete/users/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (s *stubRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
}

func newStubClientWithUsers(users []tsapi.User, deleteError map[string]int) (*tsapi.Client, *stubRoundTripper, error) {
base, _ := url.Parse("http://fake")
base, _ := url.Parse("https://fake")
rt := &stubRoundTripper{users: users, deleteError: deleteError}
return &tsapi.Client{
BaseURL: base,
Expand Down
3 changes: 1 addition & 2 deletions cmd/tscli/list/tailnets/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"

"github.com/jaxxstorm/tscli/pkg/config"
"github.com/jaxxstorm/tscli/pkg/oauth"
"github.com/jaxxstorm/tscli/pkg/output"
"github.com/jaxxstorm/tscli/pkg/tscli"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -35,7 +34,7 @@ func Command() *cobra.Command {
return err
}

tokenResp, err := oauth.ExchangeClientCredentials(cmd.Context(), creds.ClientID, creds.ClientSecret)
tokenResp, err := tscli.ExchangeOAuthClientCredentials(cmd.Context(), creds.ClientID, creds.ClientSecret)
if err != nil {
return fmt.Errorf("failed to exchange OAuth credentials: %w", err)
}
Expand Down
56 changes: 54 additions & 2 deletions coverage/coverage-gaps.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"openapi_operations": 90,
"openapi_operations": 93,
"excluded_operations": [],
"in_scope_operations": 90,
"in_scope_operations": 93,
"manifest_commands": 103,
"excluded_commands": [
"agent init",
Expand All @@ -26,6 +26,7 @@
"delete /device/{deviceId}",
"delete /device/{deviceId}/attributes/{attributeKey}",
"delete /posture/integrations/{id}",
"delete /tailnet/{tailnet}",
"delete /tailnet/{tailnet}/keys/{keyId}",
"delete /tailnet/{tailnet}/logging/{logType}/stream",
"delete /tailnet/{tailnet}/oauth-apps/{appId}",
Expand All @@ -37,6 +38,7 @@
"get /device/{deviceId}/attributes",
"get /device/{deviceId}/device-invites",
"get /device/{deviceId}/routes",
"get /organizations/{organization}/tailnets",
"get /posture/integrations/{id}",
"get /tailnet/{tailnet}/acl",
"get /tailnet/{tailnet}/contacts",
Expand Down Expand Up @@ -83,6 +85,7 @@
"post /device/{deviceId}/name",
"post /device/{deviceId}/routes",
"post /device/{deviceId}/tags",
"post /organizations/{organization}/tailnets",
"post /tailnet/{tailnet}/acl",
"post /tailnet/{tailnet}/acl/preview",
"post /tailnet/{tailnet}/acl/validate",
Expand Down Expand Up @@ -213,6 +216,7 @@
"create key": "post /tailnet/{tailnet}/keys",
"create oauth-app": "post /tailnet/{tailnet}/oauth-apps",
"create posture-integration": "post /tailnet/{tailnet}/posture/integrations",
"create tailnet": "post /organizations/{organization}/tailnets",
"create webhook": "post /tailnet/{tailnet}/webhooks",
"delete device": "delete /device/{deviceId}",
"delete device invite": "delete /device-invites/{deviceInviteId}",
Expand All @@ -224,6 +228,7 @@
"delete oauth-app": "delete /tailnet/{tailnet}/oauth-apps/{appId}",
"delete posture-integration": "delete /posture/integrations/{id}",
"delete service": "delete /tailnet/{tailnet}/services/{serviceName}",
"delete tailnet": "delete /tailnet/{tailnet}",
"delete user": "post /users/{userId}/delete",
"delete user invite": "delete /user-invites/{userInviteId}",
"delete users": "get /tailnet/{tailnet}/users, post /users/{userId}/delete",
Expand Down Expand Up @@ -264,6 +269,7 @@
"list routes": "get /device/{deviceId}/routes",
"list services": "get /tailnet/{tailnet}/services",
"list services devices": "get /tailnet/{tailnet}/services/{serviceName}/devices",
"list tailnets": "get /organizations/{organization}/tailnets",
"list users": "get /tailnet/{tailnet}/users",
"list webhooks": "get /tailnet/{tailnet}/webhooks",
"set contact": "patch /tailnet/{tailnet}/contacts/{contactType}, post /tailnet/{tailnet}/contacts/{contactType}/resend-verification-email",
Expand Down Expand Up @@ -560,6 +566,12 @@
"get /device/{deviceId}/device-invites response [].multiUse",
"get /device/{deviceId}/device-invites response [].sharerId",
"get /device/{deviceId}/device-invites response [].tailnetId",
"get /organizations/{organization}/tailnets response tailnets",
"get /organizations/{organization}/tailnets response tailnets[]",
"get /organizations/{organization}/tailnets response tailnets[].createdAt",
"get /organizations/{organization}/tailnets response tailnets[].displayName",
"get /organizations/{organization}/tailnets response tailnets[].id",
"get /organizations/{organization}/tailnets response tailnets[].orgId",
"get /posture/integrations/{id} response clientId",
"get /posture/integrations/{id} response clientSecret",
"get /posture/integrations/{id} response cloudId",
Expand Down Expand Up @@ -779,6 +791,7 @@
"get /tailnet/{tailnet}/services response vipServices[].addrs",
"get /tailnet/{tailnet}/services response vipServices[].addrs[]",
"get /tailnet/{tailnet}/services response vipServices[].comment",
"get /tailnet/{tailnet}/services response vipServices[].displayName",
"get /tailnet/{tailnet}/services response vipServices[].name",
"get /tailnet/{tailnet}/services response vipServices[].ports",
"get /tailnet/{tailnet}/services response vipServices[].ports[]",
Expand All @@ -787,6 +800,7 @@
"get /tailnet/{tailnet}/services/{serviceName} response addrs",
"get /tailnet/{tailnet}/services/{serviceName} response addrs[]",
"get /tailnet/{tailnet}/services/{serviceName} response comment",
"get /tailnet/{tailnet}/services/{serviceName} response displayName",
"get /tailnet/{tailnet}/services/{serviceName} response name",
"get /tailnet/{tailnet}/services/{serviceName} response ports",
"get /tailnet/{tailnet}/services/{serviceName} response ports[]",
Expand Down Expand Up @@ -950,6 +964,16 @@
"post /device/{deviceId}/name request name",
"post /device/{deviceId}/tags request tags",
"post /device/{deviceId}/tags request tags[]",
"post /organizations/{organization}/tailnets request displayName",
"post /organizations/{organization}/tailnets response alreadyExists",
"post /organizations/{organization}/tailnets response createdAt",
"post /organizations/{organization}/tailnets response displayName",
"post /organizations/{organization}/tailnets response dnsName",
"post /organizations/{organization}/tailnets response id",
"post /organizations/{organization}/tailnets response oauthClient",
"post /organizations/{organization}/tailnets response oauthClient.id",
"post /organizations/{organization}/tailnets response oauthClient.secret",
"post /organizations/{organization}/tailnets response orgId",
"post /tailnet/{tailnet}/acl/preview response matches",
"post /tailnet/{tailnet}/acl/preview response matches[]",
"post /tailnet/{tailnet}/acl/preview response matches[].lineNumber",
Expand Down Expand Up @@ -1152,6 +1176,7 @@
"put /tailnet/{tailnet}/services/{serviceName} request addrs",
"put /tailnet/{tailnet}/services/{serviceName} request addrs[]",
"put /tailnet/{tailnet}/services/{serviceName} request comment",
"put /tailnet/{tailnet}/services/{serviceName} request displayName",
"put /tailnet/{tailnet}/services/{serviceName} request name",
"put /tailnet/{tailnet}/services/{serviceName} request ports",
"put /tailnet/{tailnet}/services/{serviceName} request ports[]",
Expand All @@ -1160,6 +1185,7 @@
"put /tailnet/{tailnet}/services/{serviceName} response addrs",
"put /tailnet/{tailnet}/services/{serviceName} response addrs[]",
"put /tailnet/{tailnet}/services/{serviceName} response comment",
"put /tailnet/{tailnet}/services/{serviceName} response displayName",
"put /tailnet/{tailnet}/services/{serviceName} response name",
"put /tailnet/{tailnet}/services/{serviceName} response ports",
"put /tailnet/{tailnet}/services/{serviceName} response ports[]",
Expand Down Expand Up @@ -1467,6 +1493,14 @@
"[].sharerId",
"[].tailnetId"
],
"get /organizations/{organization}/tailnets response": [
"tailnets",
"tailnets[]",
"tailnets[].createdAt",
"tailnets[].displayName",
"tailnets[].id",
"tailnets[].orgId"
],
"get /posture/integrations/{id} response": [
"clientId",
"clientSecret",
Expand Down Expand Up @@ -1713,6 +1747,7 @@
"vipServices[].addrs",
"vipServices[].addrs[]",
"vipServices[].comment",
"vipServices[].displayName",
"vipServices[].name",
"vipServices[].ports",
"vipServices[].ports[]",
Expand All @@ -1723,6 +1758,7 @@
"addrs",
"addrs[]",
"comment",
"displayName",
"name",
"ports",
"ports[]",
Expand Down Expand Up @@ -1939,6 +1975,20 @@
"tags",
"tags[]"
],
"post /organizations/{organization}/tailnets request": [
"displayName"
],
"post /organizations/{organization}/tailnets response": [
"alreadyExists",
"createdAt",
"displayName",
"dnsName",
"id",
"oauthClient",
"oauthClient.id",
"oauthClient.secret",
"orgId"
],
"post /tailnet/{tailnet}/acl/preview response": [
"matches",
"matches[]",
Expand Down Expand Up @@ -2198,6 +2248,7 @@
"addrs",
"addrs[]",
"comment",
"displayName",
"name",
"ports",
"ports[]",
Expand All @@ -2208,6 +2259,7 @@
"addrs",
"addrs[]",
"comment",
"displayName",
"name",
"ports",
"ports[]",
Expand Down
Loading
Loading