Compare commits

...

68 Commits

Author SHA1 Message Date
Johan Siebens 46cce89e0e chore: go mod tidy 2024-02-07 08:54:58 +01:00
Johan Siebens 128ed22bde feat: add support for search domains in dns config 2024-02-07 08:13:44 +01:00
Johan Siebens 5d1ac326ea fix: check if tailnet with name already exists 2024-02-06 21:39:30 +01:00
Johan Siebens 7eb808c71c fix: add ssh rules to default acl policy 2024-02-06 21:31:10 +01:00
Johan Siebens d8f0492940 feat: add device aliases 2024-02-06 08:03:50 +01:00
Johan Siebens b8b1075389 chore(deps): upgrade some dependencies 2024-02-06 07:41:28 +01:00
Johan Siebens 9f3a6bbcec feat: save tokens for multiple ionscale servers 2024-02-05 17:21:21 +01:00
Johan Siebens cce0fd08b0 chore: refactor target and tailnet selection in commands 2024-02-05 17:21:21 +01:00
Johan Siebens 58634fc98e chore: go mod tidy 2024-02-04 16:43:56 +01:00
Johan Siebens 280ee7e1b6 feat: validate iam policy filters 2024-02-04 16:42:41 +01:00
Johan Siebens b8c752d04a fix: use default and additional scopes correctly 2024-02-03 10:44:36 +01:00
Johan Siebens dfd2fe9fdd chore: bump version 2024-02-03 09:43:42 +01:00
Johan Siebens 25203d3cca fix: little layout issue 2024-02-03 09:42:38 +01:00
Johan Siebens 0f54539302 chore: bump base image 2024-02-03 09:12:19 +01:00
Johan Siebens dea60272b7 fix: cli also accepts IONSCALE_KEYS_SYSTEM_ADMIN_KEY env variable 2024-02-03 09:06:43 +01:00
Johan Siebens 5e43014a09 feat: remove inactive emphemeral machines when server starts; rename reaper to worker 2024-02-03 09:04:33 +01:00
Johan Siebens 9748955f18 fix: some small logging fixes 2024-02-02 08:57:23 +01:00
Johan Siebens 44b6b20361 feat: store acme certificates in db 2024-02-01 15:29:23 +01:00
Johan Siebens cbde00c9f5 chore: replace duplicate template code with templ 2024-01-26 13:27:35 +01:00
Johan Siebens 8f2c198bfe fix: avoid peer lookup if not needed 2024-01-25 08:58:18 +01:00
Johan Siebens 8f998b05f7 feat: acl grants 2024-01-25 07:40:15 +01:00
Johan Siebens 3fccde2932 feat: also accept hujson files 2024-01-20 10:44:20 +01:00
Johan Siebens 7fa31bdf1f feat: add support for protocol in acl rules 2024-01-19 10:21:30 +01:00
Johan Siebens 980ab1bc46 fix: send empty PacketFilter when no rules match 2024-01-19 09:56:28 +01:00
Johan Siebens 123ca99665 fix: mark query feature request as incomplete when necessary 2024-01-19 07:59:22 +01:00
Johan Siebens 0c5e586cf9 chore: upgrade actions 2024-01-15 16:06:30 +01:00
Johan Siebens 79bc3bffb1 chore: upgrade codeql actions 2024-01-15 16:03:48 +01:00
Johan Siebens 452c5ee516 chore: add workflow to label and close stale issues 2024-01-15 16:01:06 +01:00
Johan Siebens c1ea283e6d fix: incorrect splitting of alias and port ranges 2024-01-15 12:14:53 +01:00
Johan Siebens 6a5d44882a chore(deps): use renamed mockoidc module 2024-01-11 09:25:19 +01:00
Johan Siebens cbcbd61c3e feat: remove support for non-noise clients 2024-01-10 11:05:07 +01:00
Johan Siebens b083e2631a chore(deps): upgrade dependencies 2024-01-10 08:20:44 +01:00
Johan Siebens 4587ed8eaa chore: restructure test setup and add some initial web login flow tests 2024-01-10 08:03:22 +01:00
Johan Siebens 3118d2e573 chore: fix integration tests 2024-01-09 10:00:02 +00:00
Johan Siebens 7e1d90590d chore: take binaries from official docker image 2024-01-09 09:22:37 +00:00
Johan Siebens 1b66b1e9be fix: incorrect index 2024-01-06 16:48:43 +01:00
Johan Siebens 35e13a0698 chore: add idea and git folders 2024-01-05 14:58:15 +01:00
Johan Siebens 951d0f299e chore: add simple acl test 2024-01-05 10:45:02 +01:00
Johan Siebens d10a022f29 chore: use require and asserts 2024-01-05 10:32:54 +01:00
Johan Siebens 9b5f045849 feat: add support for node attributes 2024-01-05 10:03:09 +01:00
Johan Siebens 8a3f47490e chore: capmap vs capabilities 2024-01-04 17:02:11 +01:00
Johan Siebens c76c2f16dd chore: upgrade to latest tailscale version 2024-01-04 16:34:47 +01:00
Johan Siebens dd2e783d8e chore: ignore own machine id when notifying an update 2024-01-04 10:09:56 +01:00
Johan Siebens 473c3370ce chore: move mapping logic to seperate struct 2024-01-04 09:23:35 +01:00
Johan Siebens d6cc55cf5b chore: remove unused method 2024-01-03 08:46:01 +01:00
Johan Siebens 9808860412 feat: add support for 'always' value in ssh check period 2024-01-02 14:36:04 +01:00
Johan Siebens 2bc03b895b fix: add autogroup:member checks in ssh policies 2024-01-02 13:58:58 +01:00
Johan Siebens 54fa423acd feat: add support for autogroup:tagged 2024-01-02 09:32:12 +01:00
Johan Siebens a303de71ee feat: add support for autogroup:member 2024-01-02 09:24:08 +01:00
Johan Siebens cdbecf04fc chore: improve test setup 2023-12-31 12:54:56 +01:00
Johan Siebens 75b58d0784 feat: add query feature endpoint for 'serve' support 2023-12-29 16:02:20 +01:00
Johan Siebens 038c0afa8b fix: add unique constraint to index 2023-12-29 09:51:58 +01:00
Johan Siebens d9fafdcfd2 fix: add missing https capability 2023-12-28 11:49:49 +01:00
Johan Siebens 9b8782cccf fix: issue when enabling/disabling https certs 2023-12-28 11:33:39 +01:00
Johan Siebens ea658a0e81 chore(deps): upgrade tailscale deps 2023-12-28 11:10:59 +01:00
Johan Siebens e31ce67f84 feat: add support for ssh check periods 2023-12-28 08:34:25 +01:00
Johan Siebens d5ca503318 chore: generate with recent tools 2023-12-28 07:54:28 +01:00
Johan Siebens 4cab4dfb9a chore: run test with latest tailscale version 2023-12-27 09:28:27 +01:00
Johan Siebens 515f441dae chore: bump to latest version 2023-12-27 09:28:10 +01:00
Johan Siebens 9ac4c85c99 feat: add version column to machines list 2023-12-27 09:27:41 +01:00
Johan Siebens 60a2faec4a chore(ci): add latest release versions 2023-12-23 11:17:55 +01:00
Johan Siebens 339b9cfd37 fix: lazy load snowflake id generator 2023-12-23 11:16:10 +01:00
Johan Siebens d0eac84271 chore(deps): upgrade some dependencies 2023-12-23 08:48:16 +01:00
Johan Siebens f193afa146 chore: update base image 2023-12-23 07:48:30 +01:00
Johan Siebens cf67f6cf64 chore: update workflows and goreleaser 2023-12-23 07:29:58 +01:00
Johan Siebens 1ac3aa36ba chore(deps): upgrade tailscale dependency 2023-07-21 08:20:51 +02:00
Johan Siebens 9fd4e5fee4 fix: log error when starting server fails 2023-04-15 08:26:08 +02:00
Johan Siebens 326860c941 fix: panic when user is not authorized 2023-04-08 09:56:37 +02:00
115 changed files with 5121 additions and 3921 deletions
+3
View File
@@ -0,0 +1,3 @@
.git
.idea
tests
+3 -2
View File
@@ -27,9 +27,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: "1.20"
go-version-file: 'go.mod'
cache: true
- name: Build
run: |
go test -v -short ./...
+17 -10
View File
@@ -1,6 +1,10 @@
name: Integration Tests
on: workflow_dispatch
on:
workflow_dispatch: {}
pull_request:
branches:
- main
jobs:
integration:
@@ -9,13 +13,15 @@ jobs:
fail-fast: false
matrix:
ts_version:
- "1.36.0"
- "1.34.1"
- "1.32.3"
- "1.30.2"
- "1.28.0"
- "1.26.2"
- "1.24.2"
- "v1.56"
- "v1.54"
- "v1.52"
- "v1.50"
- "v1.48"
- "v1.46"
- "v1.44"
- "v1.42"
- "v1.40"
env:
IONSCALE_TESTS_TS_TARGET_VERSION: ${{ matrix.ts_version }}
steps:
@@ -24,9 +30,10 @@ jobs:
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: "1.20"
go-version-file: 'go.mod'
cache: true
- name: Run Integration Tests
run: |
go test -v ./tests
-42
View File
@@ -1,42 +0,0 @@
name: nightly
on: workflow_dispatch
permissions:
contents: write
packages: write
id-token: write
jobs:
release:
runs-on: ubuntu-latest
env:
DOCKER_CLI_EXPERIMENTAL: "enabled"
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Docker Login
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: "1.20"
- name: Install cosign
uses: sigstore/cosign-installer@v2.8.1
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
distribution: goreleaser-pro
version: latest
args: release --nightly --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
+8 -8
View File
@@ -3,7 +3,7 @@ name: release
on:
push:
tags:
- '*'
- 'v*'
permissions:
contents: write
@@ -29,17 +29,17 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: "1.20"
go-version-file: 'go.mod'
cache: true
- name: Install cosign
uses: sigstore/cosign-installer@v2.8.1
uses: sigstore/cosign-installer@v3.1.1
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser-pro
distribution: goreleaser
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+5 -5
View File
@@ -15,15 +15,15 @@ jobs:
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: go
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
trivy:
name: Trivy
@@ -33,7 +33,7 @@ jobs:
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Run Trivy vulnerability scanner
@@ -43,6 +43,6 @@ jobs:
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v1
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
+22
View File
@@ -0,0 +1,22 @@
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
days-before-issue-stale: 90
days-before-issue-close: 7
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 90 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}
+3 -4
View File
@@ -1,8 +1,5 @@
project_name: ionscale
nightly:
name_template: '{{ incminor .Version }}-dev'
before:
hooks:
- go mod tidy
@@ -44,7 +41,7 @@ docker_manifests:
image_templates:
- ghcr.io/jsiebens/{{ .ProjectName }}:{{ .Version }}-amd64
- ghcr.io/jsiebens/{{ .ProjectName }}:{{ .Version }}-arm64
- name_template: ghcr.io/jsiebens/{{ .ProjectName }}:{{ if .IsNightly }}dev{{ else }}latest{{ end }}
- name_template: ghcr.io/jsiebens/{{ .ProjectName }}:latest
image_templates:
- ghcr.io/jsiebens/{{ .ProjectName }}:{{ .Version }}-amd64
- ghcr.io/jsiebens/{{ .ProjectName }}:{{ .Version }}-arm64
@@ -59,6 +56,7 @@ signs:
- '--output-certificate=${certificate}'
- '--output-signature=${signature}'
- '${artifact}'
- '--yes'
artifacts: checksum
docker_signs:
@@ -70,6 +68,7 @@ docker_signs:
args:
- sign
- '${artifact}'
- '--yes'
archives:
- format: binary
+1 -1
View File
@@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM:-linux/amd64} alpine:3.17.2
FROM --platform=${BUILDPLATFORM:-linux/amd64} alpine:3.19.1
COPY ionscale /usr/local/bin/ionscale
+2
View File
@@ -1,10 +1,12 @@
init:
go install github.com/a-h/templ/cmd/templ@latest
go install github.com/bufbuild/buf/cmd/buf@latest
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install github.com/bufbuild/connect-go/cmd/protoc-gen-connect-go@latest
generate:
templ generate
buf generate proto
format:
+137 -110
View File
@@ -1,171 +1,198 @@
module github.com/jsiebens/ionscale
go 1.20
go 1.21
require (
github.com/99designs/keyring v1.2.2
github.com/a-h/templ v0.2.543
github.com/apparentlymart/go-cidr v1.1.0
github.com/bufbuild/connect-go v1.5.2
github.com/bufbuild/connect-go v1.10.0
github.com/caarlos0/env/v6 v6.10.1
github.com/caddyserver/certmagic v0.17.2
github.com/coreos/go-oidc/v3 v3.5.0
github.com/glebarez/sqlite v1.7.0
github.com/go-gormigrate/gormigrate/v2 v2.0.2
github.com/caddyserver/certmagic v0.20.0
github.com/coreos/go-oidc/v3 v3.9.0
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2
github.com/glebarez/sqlite v1.10.0
github.com/go-gormigrate/gormigrate/v2 v2.1.1
github.com/go-jose/go-jose/v3 v3.0.1
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/hashicorp/go-bexpr v0.1.11
github.com/hashicorp/go-hclog v1.4.0
github.com/hashicorp/go-bexpr v0.1.13
github.com/hashicorp/go-multierror v1.1.1
github.com/imdario/mergo v0.3.13
github.com/imdario/mergo v0.3.16
github.com/jsiebens/go-edit v0.1.0
github.com/klauspost/compress v1.16.0
github.com/labstack/echo-contrib v0.14.1
github.com/labstack/echo/v4 v4.10.2
github.com/libdns/azure v0.2.0
github.com/jsiebens/mockoidc v0.1.0-rc2
github.com/klauspost/compress v1.17.4
github.com/labstack/echo-contrib v0.15.0
github.com/labstack/echo/v4 v4.11.4
github.com/libdns/azure v0.3.0
github.com/libdns/cloudflare v0.1.0
github.com/libdns/digitalocean v0.0.0-20220518195853-a541bc8aa80f
github.com/libdns/digitalocean v0.0.0-20230728223659-4f9064657aea
github.com/libdns/googleclouddns v1.1.0
github.com/libdns/libdns v0.2.1
github.com/libdns/route53 v1.3.1
github.com/libdns/route53 v1.3.3
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/pointerstructure v1.2.1
github.com/mr-tron/base58 v1.2.0
github.com/nleeper/goment v1.4.4
github.com/ory/dockertest/v3 v3.9.1
github.com/prometheus/client_golang v1.14.0
github.com/ory/dockertest/v3 v3.10.0
github.com/prometheus/client_golang v1.18.0
github.com/rodaine/table v1.1.0
github.com/sony/sonyflake v1.1.0
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
github.com/sony/sonyflake v1.2.0
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
github.com/travisjeffery/certmagic-sqlstorage v1.1.1
github.com/xhit/go-str2duration/v2 v2.1.0
golang.org/x/crypto v0.7.0
golang.org/x/net v0.8.0
golang.org/x/oauth2 v0.6.0
golang.org/x/sync v0.1.0
google.golang.org/protobuf v1.29.0
gopkg.in/square/go-jose.v2 v2.6.0
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.18.0
golang.org/x/net v0.20.0
golang.org/x/oauth2 v0.16.0
golang.org/x/sync v0.6.0
google.golang.org/protobuf v1.32.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.4.8
gorm.io/gorm v1.24.6
gorm.io/plugin/prometheus v0.0.0-20230109022219-ee24990c7392
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
tailscale.com v1.36.2
gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.5
gorm.io/plugin/prometheus v0.1.0
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a
tailscale.com v1.56.1
)
require (
cloud.google.com/go/compute v1.18.0 // indirect
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect
github.com/aws/aws-sdk-go-v2 v1.17.6 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.16 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.16 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.27.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
github.com/aws/aws-sdk-go-v2/config v1.26.3 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.36.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/containerd/continuity v0.4.3 // indirect
github.com/coreos/go-iptables v0.7.0 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/digitalocean/godo v1.98.0 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/cli v23.0.1+incompatible // indirect
github.com/docker/docker v23.0.1+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/dblohm7/wingoes v0.0.0-20240108175832-49174e152ce1 // indirect
github.com/digitalocean/godo v1.107.0 // indirect
github.com/docker/cli v25.0.2+incompatible // indirect
github.com/docker/docker v25.0.2+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.14.1 // indirect
github.com/glebarez/go-sqlite v1.21.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.3.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/pgx/v5 v5.5.1 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/jsimonetti/rtnetlink v1.3.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/lib/pq v1.10.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mdlayher/netlink v1.7.1 // indirect
github.com/mdlayher/socket v0.4.0 // indirect
github.com/mholt/acmez v1.1.0 // indirect
github.com/miekg/dns v1.1.51 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.5.0 // indirect
github.com/mholt/acmez v1.2.0 // indirect
github.com/miekg/dns v1.1.57 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
github.com/opencontainers/runc v1.1.4 // indirect
github.com/opencontainers/image-spec v1.1.0-rc6 // indirect
github.com/opencontainers/runc v1.1.12 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect
github.com/tkuchiki/go-timezone v0.2.2 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.24.0 // indirect
go4.org/intern v0.0.0-20230205224052-192e9f60865c // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
go.opentelemetry.io/otel v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.17.0 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/api v0.112.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/grpc v1.53.0 // indirect
modernc.org/libc v1.22.3 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.21.0 // indirect
nhooyr.io/websocket v1.8.7 // indirect
google.golang.org/api v0.155.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
google.golang.org/grpc v1.60.1 // indirect
gotest.tools/v3 v3.4.0 // indirect
modernc.org/libc v1.40.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/sqlite v1.28.0 // indirect
nhooyr.io/websocket v1.8.10 // indirect
)
+392 -358
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -42,7 +42,7 @@ func (p *OIDCProvider) GetLoginURL(redirectURI, state string) string {
ClientSecret: p.clientSecret,
RedirectURL: redirectURI,
Endpoint: p.provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
Scopes: p.scopes,
}
return oauth2Config.AuthCodeURL(state, oauth2.ApprovalForce)
@@ -54,7 +54,7 @@ func (p *OIDCProvider) Exchange(redirectURI, code string) (*User, error) {
ClientSecret: p.clientSecret,
RedirectURL: redirectURI,
Endpoint: p.provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
Scopes: p.scopes,
}
oauth2Token, err := oauth2Config.Exchange(context.Background(), code)
-173
View File
@@ -1,173 +0,0 @@
package bind
import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/jsiebens/ionscale/internal/util"
"github.com/klauspost/compress/zstd"
"github.com/labstack/echo/v4"
"io/ioutil"
"sync"
"tailscale.com/smallzstd"
"tailscale.com/types/key"
)
type Factory func(c echo.Context) (Binder, error)
type Binder interface {
BindRequest(c echo.Context, v interface{}) error
WriteResponse(c echo.Context, code int, v interface{}) error
Marshal(compress string, v interface{}) ([]byte, error)
Peer() key.MachinePublic
}
func DefaultBinder(machineKey key.MachinePublic) Factory {
return func(c echo.Context) (Binder, error) {
return &defaultBinder{machineKey: machineKey}, nil
}
}
func BoxBinder(controlKey key.MachinePrivate) Factory {
return func(c echo.Context) (Binder, error) {
idParam := c.Param("id")
id, err := util.ParseMachinePublicKey(idParam)
if err != nil {
return nil, err
}
return &boxBinder{
controlKey: controlKey,
machineKey: *id,
}, nil
}
}
type defaultBinder struct {
machineKey key.MachinePublic
}
func (d *defaultBinder) BindRequest(c echo.Context, v interface{}) error {
body, err := ioutil.ReadAll(c.Request().Body)
if err != nil {
return err
}
return json.Unmarshal(body, v)
}
func (d *defaultBinder) WriteResponse(c echo.Context, code int, v interface{}) error {
marshalled, err := json.Marshal(v)
if err != nil {
return err
}
c.Response().WriteHeader(code)
_, err = c.Response().Write(marshalled)
return err
}
func (d *defaultBinder) Marshal(compress string, v interface{}) ([]byte, error) {
var payload []byte
marshalled, err := json.Marshal(v)
if err != nil {
return nil, err
}
if compress == "zstd" {
payload = zstdEncode(marshalled)
} else {
payload = marshalled
}
data := make([]byte, 4)
binary.LittleEndian.PutUint32(data, uint32(len(payload)))
data = append(data, payload...)
return data, nil
}
func (d *defaultBinder) Peer() key.MachinePublic {
return d.machineKey
}
type boxBinder struct {
controlKey key.MachinePrivate
machineKey key.MachinePublic
}
func (b *boxBinder) BindRequest(c echo.Context, v interface{}) error {
body, err := ioutil.ReadAll(c.Request().Body)
if err != nil {
return err
}
decrypted, ok := b.controlKey.OpenFrom(b.machineKey, body)
if !ok {
return fmt.Errorf("unable to decrypt payload")
}
return json.Unmarshal(decrypted, v)
}
func (b *boxBinder) WriteResponse(c echo.Context, code int, v interface{}) error {
marshalled, err := json.Marshal(v)
if err != nil {
return err
}
encrypted := b.controlKey.SealTo(b.machineKey, marshalled)
c.Response().WriteHeader(code)
_, err = c.Response().Write(encrypted)
return err
}
func (b *boxBinder) Marshal(compress string, v interface{}) ([]byte, error) {
var payload []byte
marshalled, err := json.Marshal(v)
if err != nil {
return nil, err
}
if compress == "zstd" {
encoded := zstdEncode(marshalled)
payload = b.controlKey.SealTo(b.machineKey, encoded)
} else {
payload = b.controlKey.SealTo(b.machineKey, marshalled)
}
data := make([]byte, 4)
binary.LittleEndian.PutUint32(data, uint32(len(payload)))
data = append(data, payload...)
return data, nil
}
func (b *boxBinder) Peer() key.MachinePublic {
return b.machineKey
}
func zstdEncode(in []byte) []byte {
encoder := zstdEncoderPool.Get().(*zstd.Encoder)
out := encoder.EncodeAll(in, nil)
encoder.Close()
zstdEncoderPool.Put(encoder)
return out
}
var zstdEncoderPool = &sync.Pool{
New: func() any {
encoder, err := smallzstd.NewEncoder(nil, zstd.WithEncoderLevel(zstd.SpeedFastest))
if err != nil {
panic(err)
}
return encoder
},
}
+22 -68
View File
@@ -2,45 +2,25 @@ package cmd
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/bufbuild/connect-go"
"github.com/jsiebens/go-edit/editor"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
"github.com/spf13/cobra"
"io/ioutil"
"github.com/tailscale/hujson"
"os"
)
func getACLConfigCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "get-acl-policy",
Short: "Get the ACL policy",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(cmd *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
resp, err := client.GetACLPolicy(context.Background(), connect.NewRequest(&api.GetACLPolicyRequest{TailnetId: tailnet.Id}))
resp, err := tc.Client().GetACLPolicy(cmd.Context(), connect.NewRequest(&api.GetACLPolicyRequest{TailnetId: tc.TailnetID()}))
if err != nil {
return err
}
@@ -59,35 +39,16 @@ func getACLConfigCommand() *cobra.Command {
}
func editACLConfigCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "edit-acl-policy",
Short: "Edit the ACL policy",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(cmd *cobra.Command, args []string) error {
edit := editor.NewDefaultEditor([]string{"IONSCALE_EDITOR", "EDITOR"})
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
resp, err := client.GetACLPolicy(context.Background(), connect.NewRequest(&api.GetACLPolicyRequest{TailnetId: tailnet.Id}))
resp, err := tc.Client().GetACLPolicy(cmd.Context(), connect.NewRequest(&api.GetACLPolicyRequest{TailnetId: tc.TailnetID()}))
if err != nil {
return err
}
@@ -104,12 +65,17 @@ func editACLConfigCommand() *cobra.Command {
defer os.Remove(s)
next, err = hujson.Standardize(next)
if err != nil {
return err
}
var policy = &api.ACLPolicy{}
if err := json.Unmarshal(next, policy); err != nil {
return err
}
_, err = client.SetACLPolicy(context.Background(), connect.NewRequest(&api.SetACLPolicyRequest{TailnetId: tailnet.Id, Policy: policy}))
_, err = tc.Client().SetACLPolicy(cmd.Context(), connect.NewRequest(&api.SetACLPolicyRequest{TailnetId: tc.TailnetID(), Policy: policy}))
if err != nil {
return err
}
@@ -123,25 +89,23 @@ func editACLConfigCommand() *cobra.Command {
}
func setACLConfigCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "set-acl-policy",
Short: "Set ACL policy",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var file string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.Flags().StringVar(&file, "file", "", "Path to json file with the acl configuration")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(cmd *cobra.Command, args []string) error {
rawJson, err := ioutil.ReadFile(file)
content, err := os.ReadFile(file)
if err != nil {
return err
}
rawJson, err := hujson.Standardize(content)
if err != nil {
return err
}
@@ -151,17 +115,7 @@ func setACLConfigCommand() *cobra.Command {
return err
}
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
_, err = client.SetACLPolicy(context.Background(), connect.NewRequest(&api.SetACLPolicyRequest{TailnetId: tailnet.Id, Policy: policy}))
_, err = tc.Client().SetACLPolicy(cmd.Context(), connect.NewRequest(&api.SetACLPolicyRequest{TailnetId: tc.TailnetID(), Policy: policy}))
if err != nil {
return err
}
+11 -16
View File
@@ -1,7 +1,6 @@
package cmd
import (
"context"
"fmt"
"github.com/bufbuild/connect-go"
"github.com/jsiebens/ionscale/pkg/client/ionscale"
@@ -20,24 +19,14 @@ func authCommand() *cobra.Command {
}
func authLoginCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "login",
SilenceUsage: true,
}
var target = Target{}
target.prepareCommand(command)
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
})
command.RunE = func(cmd *cobra.Command, args []string) error {
req := &api.AuthenticateRequest{}
stream, err := client.Authenticate(context.Background(), connect.NewRequest(req))
stream, err := tc.Client().Authenticate(cmd.Context(), connect.NewRequest(req))
if err != nil {
return err
}
@@ -48,7 +37,13 @@ func authLoginCommand() *cobra.Command {
if len(resp.Token) != 0 {
fmt.Println()
fmt.Println("Success.")
if err := ionscale.SessionToFile(resp.Token, resp.TailnetId); err != nil {
tailnetId := uint64(0)
if resp.TailnetId != nil {
tailnetId = *resp.TailnetId
}
if err := ionscale.StoreAuthToken(tc.Addr(), resp.Token, tailnetId); err != nil {
fmt.Println()
fmt.Println("Your api token:")
fmt.Println()
+15 -58
View File
@@ -1,7 +1,6 @@
package cmd
import (
"context"
"fmt"
"github.com/bufbuild/connect-go"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
@@ -28,40 +27,23 @@ func authkeysCommand() *cobra.Command {
}
func createAuthkeysCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "create",
Short: "Creates a new auth key in the specified tailnet",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var ephemeral bool
var preAuthorized bool
var tags []string
var expiry string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.Flags().BoolVar(&ephemeral, "ephemeral", false, "When enabled, machines authenticated by this key will be automatically removed after going offline.")
command.Flags().StringSliceVar(&tags, "tag", []string{}, "Machines authenticated by this key will be automatically tagged with these tags")
command.Flags().StringVar(&expiry, "expiry", "180d", "Human-readable expiration of the key")
command.Flags().BoolVar(&preAuthorized, "pre-authorized", false, "Generate an auth key which is pre-authorized.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
var expiryDur *durationpb.Duration
if expiry != "" && expiry != "none" {
@@ -73,13 +55,13 @@ func createAuthkeysCommand() *cobra.Command {
}
req := &api.CreateAuthKeyRequest{
TailnetId: tailnet.Id,
TailnetId: tc.TailnetID(),
Ephemeral: ephemeral,
PreAuthorized: preAuthorized,
Tags: tags,
Expiry: expiryDur,
}
resp, err := client.CreateAuthKey(context.Background(), connect.NewRequest(req))
resp, err := tc.Client().CreateAuthKey(cmd.Context(), connect.NewRequest(req))
if err != nil {
return err
@@ -99,25 +81,19 @@ func createAuthkeysCommand() *cobra.Command {
}
func deleteAuthKeyCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "delete",
Short: "Delete a specified auth key",
SilenceUsage: true,
}
})
var authKeyId uint64
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&authKeyId, "id", 0, "Auth Key ID")
command.RunE = func(command *cobra.Command, args []string) error {
grpcClient, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.DeleteAuthKeyRequest{AuthKeyId: authKeyId}
if _, err := grpcClient.DeleteAuthKey(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().DeleteAuthKey(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -130,34 +106,15 @@ func deleteAuthKeyCommand() *cobra.Command {
}
func listAuthkeysCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "list",
Short: "List all auth keys for a given tailnet",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
req := &api.ListAuthKeysRequest{TailnetId: tailnet.Id}
resp, err := client.ListAuthKeys(context.Background(), connect.NewRequest(req))
command.RunE = func(cmd *cobra.Command, args []string) error {
req := &api.ListAuthKeysRequest{TailnetId: tc.TailnetID()}
resp, err := tc.Client().ListAuthKeys(cmd.Context(), connect.NewRequest(req))
if err != nil {
return err
-1
View File
@@ -67,7 +67,6 @@ func configureCommand() *cobra.Command {
if acme {
c.Tls.AcmeEnabled = true
c.Tls.AcmeEmail = email
c.Tls.AcmePath = filepath.Join(dataDir, "acme")
} else {
c.Tls.CertFile = certFile
c.Tls.KeyFile = keyFile
+16 -32
View File
@@ -1,12 +1,12 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"github.com/bufbuild/connect-go"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
"github.com/spf13/cobra"
"github.com/tailscale/hujson"
"gopkg.in/yaml.v2"
"os"
"tailscale.com/tailcfg"
@@ -26,25 +26,18 @@ func systemCommand() *cobra.Command {
}
func getDefaultDERPMap() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "get-derp-map",
Short: "Get the DERP Map configuration",
SilenceUsage: true,
}
})
var asJson bool
var target = Target{}
target.prepareCommand(command)
command.Flags().BoolVar(&asJson, "json", false, "When enabled, render output as json otherwise yaml")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
resp, err := client.GetDefaultDERPMap(context.Background(), connect.NewRequest(&api.GetDefaultDERPMapRequest{}))
command.RunE = func(cmd *cobra.Command, args []string) error {
resp, err := tc.Client().GetDefaultDERPMap(cmd.Context(), connect.NewRequest(&api.GetDefaultDERPMapRequest{}))
if err != nil {
return err
@@ -81,29 +74,28 @@ func getDefaultDERPMap() *cobra.Command {
}
func setDefaultDERPMap() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "set-derp-map",
Short: "Set the DERP Map configuration",
SilenceUsage: true,
}
})
var file string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&file, "file", "", "Path to json file with the DERP Map configuration")
command.RunE = func(command *cobra.Command, args []string) error {
grpcClient, err := target.createGRPCClient()
command.RunE = func(cmd *cobra.Command, args []string) error {
content, err := os.ReadFile(file)
if err != nil {
return err
}
rawJson, err := os.ReadFile(file)
rawJson, err := hujson.Standardize(content)
if err != nil {
return err
}
resp, err := grpcClient.SetDefaultDERPMap(context.Background(), connect.NewRequest(&api.SetDefaultDERPMapRequest{Value: rawJson}))
resp, err := tc.Client().SetDefaultDERPMap(cmd.Context(), connect.NewRequest(&api.SetDefaultDERPMapRequest{Value: rawJson}))
if err != nil {
return err
}
@@ -122,22 +114,14 @@ func setDefaultDERPMap() *cobra.Command {
}
func resetDefaultDERPMap() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "reset-derp-map",
Short: "Reset the DERP Map to the default configuration",
SilenceUsage: true,
}
})
var target = Target{}
target.prepareCommand(command)
command.RunE = func(command *cobra.Command, args []string) error {
grpcClient, err := target.createGRPCClient()
if err != nil {
return err
}
if _, err := grpcClient.ResetDefaultDERPMap(context.Background(), connect.NewRequest(&api.ResetDefaultDERPMapRequest{})); err != nil {
command.RunE = func(cmd *cobra.Command, args []string) error {
if _, err := tc.Client().ResetDefaultDERPMap(cmd.Context(), connect.NewRequest(&api.ResetDefaultDERPMapRequest{})); err != nil {
return err
}
+61 -86
View File
@@ -1,7 +1,6 @@
package cmd
import (
"context"
"fmt"
"github.com/bufbuild/connect-go"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
@@ -12,69 +11,22 @@ import (
)
func getDNSConfigCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "get-dns",
Short: "Get DNS configuration",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
req := api.GetDNSConfigRequest{TailnetId: tailnet.Id}
resp, err := client.GetDNSConfig(context.Background(), connect.NewRequest(&req))
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.GetDNSConfigRequest{TailnetId: tc.TailnetID()}
resp, err := tc.Client().GetDNSConfig(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
}
config := resp.Msg.Config
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 1, '\t', 0)
defer w.Flush()
fmt.Fprintf(w, "%s\t\t%v\n", "MagicDNS", config.MagicDns)
fmt.Fprintf(w, "%s\t\t%v\n", "HTTPS Certs", config.HttpsCerts)
fmt.Fprintf(w, "%s\t\t%v\n", "Override Local DNS", config.OverrideLocalDns)
if config.MagicDns {
fmt.Fprintf(w, "MagicDNS\t%s\t%s\n", config.MagicDnsSuffix, "100.100.100.100")
}
for k, r := range config.Routes {
for i, t := range r.Routes {
if i == 0 {
fmt.Fprintf(w, "SplitDNS\t%s\t%s\n", k, t)
} else {
fmt.Fprintf(w, "%s\t%s\n", "", t)
}
}
}
for i, t := range config.Nameservers {
if i == 0 {
fmt.Fprintf(w, "%s\t%s\t%s\n", "Global", "", t)
} else {
fmt.Fprintf(w, "%s\t%s\t%s\n", "", "", t)
}
}
printDnsConfig(config)
return nil
}
@@ -83,39 +35,25 @@ func getDNSConfigCommand() *cobra.Command {
}
func setDNSConfigCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "set-dns",
Short: "Set DNS config",
SilenceUsage: true,
}
})
var nameservers []string
var magicDNS bool
var httpsCerts bool
var overrideLocalDNS bool
var tailnetID uint64
var tailnetName string
var target = Target{}
var searchDomains []string
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.Flags().StringSliceVarP(&nameservers, "nameserver", "", []string{}, "Machines on your network will use these nameservers to resolve DNS queries.")
command.Flags().BoolVarP(&magicDNS, "magic-dns", "", false, "Enable MagicDNS for the specified Tailnet")
command.Flags().BoolVarP(&magicDNS, "https-certs", "", false, "Enable HTTPS Certificates for the specified Tailnet")
command.Flags().BoolVarP(&httpsCerts, "https-certs", "", false, "Enable HTTPS Certificates for the specified Tailnet")
command.Flags().BoolVarP(&overrideLocalDNS, "override-local-dns", "", false, "When enabled, connected clients ignore local DNS settings and always use the nameservers specified for this Tailnet")
command.Flags().StringSliceVarP(&searchDomains, "search-domain", "", []string{}, "Custom DNS search domains.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
var globalNameservers []string
var routes = make(map[string]*api.Routes)
@@ -134,15 +72,17 @@ func setDNSConfigCommand() *cobra.Command {
}
req := api.SetDNSConfigRequest{
TailnetId: tailnet.Id,
TailnetId: tc.TailnetID(),
Config: &api.DNSConfig{
MagicDns: magicDNS,
OverrideLocalDns: overrideLocalDNS,
Nameservers: globalNameservers,
Routes: routes,
HttpsCerts: httpsCerts,
SearchDomains: searchDomains,
},
}
resp, err := client.SetDNSConfig(context.Background(), connect.NewRequest(&req))
resp, err := tc.Client().SetDNSConfig(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
@@ -150,20 +90,55 @@ func setDNSConfigCommand() *cobra.Command {
config := resp.Msg.Config
var allNameservers = config.Nameservers
for i, j := range config.Routes {
for _, n := range j.Routes {
allNameservers = append(allNameservers, fmt.Sprintf("%s:%s", i, n))
}
if resp.Msg.Message != "" {
fmt.Println(resp.Msg.Message)
fmt.Println()
}
fmt.Printf("%-*v%v\n", 25, "Magic DNS Enabled:", config.MagicDns)
fmt.Printf("%-*v%v\n", 25, "Override Local DNS:", config.OverrideLocalDns)
fmt.Printf("%-*v%v\n", 25, "Nameservers:", strings.Join(allNameservers, ","))
printDnsConfig(config)
return nil
}
return command
}
func printDnsConfig(config *api.DNSConfig) {
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 1, '\t', 0)
defer w.Flush()
fmt.Fprintf(w, "%s\t\t%v\n", "MagicDNS", config.MagicDns)
fmt.Fprintf(w, "%s\t\t%v\n", "HTTPS Certs", config.HttpsCerts)
fmt.Fprintf(w, "%s\t\t%v\n", "Override Local DNS", config.OverrideLocalDns)
if config.MagicDns {
fmt.Fprintf(w, "MagicDNS\t%s\t%s\n", config.MagicDnsSuffix, "100.100.100.100")
}
for k, r := range config.Routes {
for i, t := range r.Routes {
if i == 0 {
fmt.Fprintf(w, "SplitDNS\t%s\t%s\n", k, t)
} else {
fmt.Fprintf(w, "%s\t%s\n", "", t)
}
}
}
for i, t := range config.Nameservers {
if i == 0 {
fmt.Fprintf(w, "%s\t%s\t%s\n", "Global", "", t)
} else {
fmt.Fprintf(w, "%s\t%s\t%s\n", "", "", t)
}
}
for i, t := range config.SearchDomains {
if i == 0 {
fmt.Fprintf(w, "%s\t%s\t%s\n", "Search Domains", t, "")
} else {
fmt.Fprintf(w, "%s\t%s\t%s\n", "", t, "")
}
}
}
-52
View File
@@ -1,52 +0,0 @@
package cmd
import (
"context"
"fmt"
"github.com/bufbuild/connect-go"
"github.com/jsiebens/ionscale/pkg/client/ionscale"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
apiconnect "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1/ionscalev1connect"
"github.com/spf13/cobra"
)
func checkRequiredTailnetAndTailnetIdFlags(cmd *cobra.Command, args []string) error {
savedTailnetID, err := ionscale.TailnetFromFile()
if err != nil {
return err
}
if savedTailnetID == 0 && !cmd.Flags().Changed("tailnet") && !cmd.Flags().Changed("tailnet-id") {
return fmt.Errorf("flag --tailnet or --tailnet-id is required")
}
if cmd.Flags().Changed("tailnet") && cmd.Flags().Changed("tailnet-id") {
return fmt.Errorf("flags --tailnet and --tailnet-id are mutually exclusive")
}
return nil
}
func findTailnet(client apiconnect.IonscaleServiceClient, tailnet string, tailnetID uint64) (*api.Tailnet, error) {
savedTailnetID, err := ionscale.TailnetFromFile()
if err != nil {
return nil, err
}
if savedTailnetID == 0 && tailnetID == 0 && tailnet == "" {
return nil, fmt.Errorf("requested tailnet not found or you are not authorized for this tailnet")
}
tailnets, err := client.ListTailnets(context.Background(), connect.NewRequest(&api.ListTailnetsRequest{}))
if err != nil {
return nil, err
}
for _, t := range tailnets.Msg.Tailnet {
if t.Id == savedTailnetID || t.Id == tailnetID || t.Name == tailnet {
return t, nil
}
}
return nil, fmt.Errorf("requested tailnet not found or you are not authorized for this tailnet")
}
+22 -68
View File
@@ -2,45 +2,25 @@ package cmd
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/bufbuild/connect-go"
"github.com/jsiebens/go-edit/editor"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
"github.com/spf13/cobra"
"io/ioutil"
"github.com/tailscale/hujson"
"os"
)
func getIAMPolicyCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "get-iam-policy",
Short: "Get the IAM policy",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(cmd *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
resp, err := client.GetIAMPolicy(context.Background(), connect.NewRequest(&api.GetIAMPolicyRequest{TailnetId: tailnet.Id}))
resp, err := tc.Client().GetIAMPolicy(cmd.Context(), connect.NewRequest(&api.GetIAMPolicyRequest{TailnetId: tc.TailnetID()}))
if err != nil {
return err
}
@@ -59,35 +39,16 @@ func getIAMPolicyCommand() *cobra.Command {
}
func editIAMPolicyCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "edit-iam-policy",
Short: "Edit the IAM policy",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(cmd *cobra.Command, args []string) error {
edit := editor.NewDefaultEditor([]string{"IONSCALE_EDITOR", "EDITOR"})
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
resp, err := client.GetIAMPolicy(context.Background(), connect.NewRequest(&api.GetIAMPolicyRequest{TailnetId: tailnet.Id}))
resp, err := tc.Client().GetIAMPolicy(cmd.Context(), connect.NewRequest(&api.GetIAMPolicyRequest{TailnetId: tc.TailnetID()}))
if err != nil {
return err
}
@@ -102,6 +63,11 @@ func editIAMPolicyCommand() *cobra.Command {
return err
}
next, err = hujson.Standardize(next)
if err != nil {
return err
}
defer os.Remove(s)
var policy = &api.IAMPolicy{}
@@ -109,7 +75,7 @@ func editIAMPolicyCommand() *cobra.Command {
return err
}
_, err = client.SetIAMPolicy(context.Background(), connect.NewRequest(&api.SetIAMPolicyRequest{TailnetId: tailnet.Id, Policy: policy}))
_, err = tc.Client().SetIAMPolicy(cmd.Context(), connect.NewRequest(&api.SetIAMPolicyRequest{TailnetId: tc.TailnetID(), Policy: policy}))
if err != nil {
return err
}
@@ -123,25 +89,23 @@ func editIAMPolicyCommand() *cobra.Command {
}
func setIAMPolicyCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "set-iam-policy",
Short: "Set IAM policy",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var file string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.Flags().StringVar(&file, "file", "", "Path to json file with the acl configuration")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(cmd *cobra.Command, args []string) error {
rawJson, err := ioutil.ReadFile(file)
content, err := os.ReadFile(file)
if err != nil {
return err
}
rawJson, err := hujson.Standardize(content)
if err != nil {
return err
}
@@ -151,17 +115,7 @@ func setIAMPolicyCommand() *cobra.Command {
return err
}
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
_, err = client.SetIAMPolicy(context.Background(), connect.NewRequest(&api.SetIAMPolicyRequest{TailnetId: tailnet.Id, Policy: policy}))
_, err = tc.Client().SetIAMPolicy(cmd.Context(), connect.NewRequest(&api.SetIAMPolicyRequest{TailnetId: tc.TailnetID(), Policy: policy}))
if err != nil {
return err
}
+54 -138
View File
@@ -1,7 +1,6 @@
package cmd
import (
"context"
"fmt"
"github.com/bufbuild/connect-go"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
@@ -17,7 +16,7 @@ import (
func machineCommands() *cobra.Command {
command := &cobra.Command{
Use: "machines",
Aliases: []string{"machine"},
Aliases: []string{"machine", "devices", "device"},
Short: "Manage ionscale machines",
SilenceUsage: true,
}
@@ -39,27 +38,20 @@ func machineCommands() *cobra.Command {
}
func getMachineCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "get",
Short: "Retrieve detailed information for a machine",
SilenceUsage: true,
}
})
var machineID uint64
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID.")
_ = command.MarkFlagRequired("machine-id")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.GetMachineRequest{MachineId: machineID}
resp, err := client.GetMachine(context.Background(), connect.NewRequest(&req))
resp, err := tc.Client().GetMachine(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
}
@@ -151,27 +143,20 @@ func getMachineCommand() *cobra.Command {
}
func deleteMachineCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "delete",
Short: "Deletes a machine",
SilenceUsage: true,
}
})
var machineID uint64
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID.")
_ = command.MarkFlagRequired("machine-id")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.DeleteMachineRequest{MachineId: machineID}
if _, err := client.DeleteMachine(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().DeleteMachine(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -184,27 +169,20 @@ func deleteMachineCommand() *cobra.Command {
}
func expireMachineCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "expire",
Short: "Expires a machine",
SilenceUsage: true,
}
})
var machineID uint64
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID.")
_ = command.MarkFlagRequired("machine-id")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.ExpireMachineRequest{MachineId: machineID}
if _, err := client.ExpireMachine(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().ExpireMachine(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -217,27 +195,20 @@ func expireMachineCommand() *cobra.Command {
}
func authorizeMachineCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "authorize",
Short: "Authorizes a machine",
SilenceUsage: true,
}
})
var machineID uint64
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID.")
_ = command.MarkFlagRequired("machine-id")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.AuthorizeMachineRequest{MachineId: machineID}
if _, err := client.AuthorizeMachine(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().AuthorizeMachine(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -250,40 +221,21 @@ func authorizeMachineCommand() *cobra.Command {
}
func listMachinesCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "list",
Short: "List machines",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
req := api.ListMachinesRequest{TailnetId: tailnet.Id}
resp, err := client.ListMachines(context.Background(), connect.NewRequest(&req))
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.ListMachinesRequest{TailnetId: tc.TailnetID()}
resp, err := tc.Client().ListMachines(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
}
tbl := table.New("ID", "TAILNET", "NAME", "IPv4", "IPv6", "AUTHORIZED", "EPHEMERAL", "LAST_SEEN", "TAGS")
tbl := table.New("ID", "TAILNET", "NAME", "IPv4", "IPv6", "AUTHORIZED", "EPHEMERAL", "VERSION", "LAST_SEEN", "TAGS")
for _, m := range resp.Msg.Machines {
var lastSeen = "N/A"
if m.Connected {
@@ -294,7 +246,7 @@ func listMachinesCommand() *cobra.Command {
lastSeen = mom.FromNow()
}
}
tbl.AddRow(m.Id, m.Tailnet.Name, m.Name, m.Ipv4, m.Ipv6, m.Authorized, m.Ephemeral, lastSeen, strings.Join(m.Tags, ","))
tbl.AddRow(m.Id, m.Tailnet.Name, m.Name, m.Ipv4, m.Ipv6, m.Authorized, m.Ephemeral, m.ClientVersion, lastSeen, strings.Join(m.Tags, ","))
}
tbl.Print()
@@ -305,27 +257,20 @@ func listMachinesCommand() *cobra.Command {
}
func getMachineRoutesCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "get-routes",
Short: "Show routes advertised and enabled by a given machine",
SilenceUsage: true,
}
})
var machineID uint64
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID.")
_ = command.MarkFlagRequired("machine-id")
command.RunE = func(command *cobra.Command, args []string) error {
grpcClient, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.GetMachineRoutesRequest{MachineId: machineID}
resp, err := grpcClient.GetMachineRoutes(context.Background(), connect.NewRequest(&req))
resp, err := tc.Client().GetMachineRoutes(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
}
@@ -339,29 +284,23 @@ func getMachineRoutesCommand() *cobra.Command {
}
func enableMachineRoutesCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "enable-routes",
Short: "Enable routes for a given machine",
SilenceUsage: true,
}
})
var machineID uint64
var routes []string
var replace bool
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID")
command.Flags().StringSliceVar(&routes, "routes", []string{}, "List of routes to enable")
command.Flags().BoolVar(&replace, "replace", false, "Replace current enabled routes with this new list")
_ = command.MarkFlagRequired("machine-id")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
for _, r := range routes {
if _, err := netaddr.ParseIPPrefix(r); err != nil {
return err
@@ -369,7 +308,7 @@ func enableMachineRoutesCommand() *cobra.Command {
}
req := api.EnableMachineRoutesRequest{MachineId: machineID, Routes: routes, Replace: replace}
resp, err := client.EnableMachineRoutes(context.Background(), connect.NewRequest(&req))
resp, err := tc.Client().EnableMachineRoutes(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
}
@@ -383,27 +322,21 @@ func enableMachineRoutesCommand() *cobra.Command {
}
func disableMachineRoutesCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "disable-routes",
Short: "Disable routes for a given machine",
SilenceUsage: true,
}
})
var machineID uint64
var routes []string
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID")
command.Flags().StringSliceVar(&routes, "routes", []string{}, "List of routes to enable")
_ = command.MarkFlagRequired("machine-id")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
for _, r := range routes {
if _, err := netaddr.ParseIPPrefix(r); err != nil {
return err
@@ -411,7 +344,7 @@ func disableMachineRoutesCommand() *cobra.Command {
}
req := api.DisableMachineRoutesRequest{MachineId: machineID, Routes: routes}
resp, err := client.DisableMachineRoutes(context.Background(), connect.NewRequest(&req))
resp, err := tc.Client().DisableMachineRoutes(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
}
@@ -425,27 +358,20 @@ func disableMachineRoutesCommand() *cobra.Command {
}
func enableExitNodeCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "enable-exit-node",
Short: "Enable given machine as an exit node",
SilenceUsage: true,
}
})
var machineID uint64
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID")
_ = command.MarkFlagRequired("machine-id")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.EnableExitNodeRequest{MachineId: machineID}
resp, err := client.EnableExitNode(context.Background(), connect.NewRequest(&req))
resp, err := tc.Client().EnableExitNode(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
}
@@ -459,27 +385,21 @@ func enableExitNodeCommand() *cobra.Command {
}
func disableExitNodeCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "disable-exit-node",
Short: "Disable given machine as an exit node",
SilenceUsage: true,
}
})
var machineID uint64
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID")
_ = command.MarkFlagRequired("machine-id")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.DisableExitNodeRequest{MachineId: machineID}
resp, err := client.DisableExitNode(context.Background(), connect.NewRequest(&req))
resp, err := tc.Client().DisableExitNode(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
}
@@ -512,22 +432,18 @@ func disableMachineKeyExpiryCommand() *cobra.Command {
return configureSetMachineKeyExpiryCommand(command, true)
}
func configureSetMachineKeyExpiryCommand(command *cobra.Command, v bool) *cobra.Command {
func configureSetMachineKeyExpiryCommand(cmdTmpl *cobra.Command, disable bool) *cobra.Command {
command, tc := prepareCommand(false, cmdTmpl)
var machineID uint64
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID")
_ = command.MarkFlagRequired("machine-id")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
req := api.SetMachineKeyExpiryRequest{MachineId: machineID, Disabled: v}
_, err = client.SetMachineKeyExpiry(context.Background(), connect.NewRequest(&req))
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.SetMachineKeyExpiryRequest{MachineId: machineID, Disabled: disable}
_, err := tc.Client().SetMachineKeyExpiry(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
}
+1 -1
View File
@@ -24,7 +24,7 @@ func serverCommand() *cobra.Command {
return err
}
return server.Start(c)
return server.Start(command.Context(), c)
}
return command
+64 -306
View File
@@ -1,7 +1,6 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"github.com/bufbuild/connect-go"
@@ -50,23 +49,14 @@ func tailnetCommand() *cobra.Command {
}
func listTailnetsCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "list",
Short: "List available Tailnets",
SilenceUsage: true,
}
})
var target = Target{}
target.prepareCommand(command)
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
resp, err := client.ListTailnets(context.Background(), connect.NewRequest(&api.ListTailnetsRequest{}))
command.RunE = func(cmd *cobra.Command, args []string) error {
resp, err := tc.Client().ListTailnets(cmd.Context(), connect.NewRequest(&api.ListTailnetsRequest{}))
if err != nil {
return err
@@ -85,17 +75,15 @@ func listTailnetsCommand() *cobra.Command {
}
func createTailnetsCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "create",
Short: "Create a new Tailnet",
SilenceUsage: true,
}
})
var name string
var domain string
var email string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVarP(&name, "name", "n", "", "")
command.Flags().StringVar(&domain, "domain", "", "")
@@ -111,7 +99,7 @@ func createTailnetsCommand() *cobra.Command {
return nil
}
command.RunE = func(command *cobra.Command, args []string) error {
command.RunE = func(cmd *cobra.Command, args []string) error {
dnsConfig := defaults.DefaultDNSConfig()
aclPolicy := defaults.DefaultACLPolicy()
@@ -134,12 +122,7 @@ func createTailnetsCommand() *cobra.Command {
}
}
client, err := target.createGRPCClient()
if err != nil {
return err
}
resp, err := client.CreateTailnet(context.Background(), connect.NewRequest(&api.CreateTailnetRequest{
resp, err := tc.Client().CreateTailnet(cmd.Context(), connect.NewRequest(&api.CreateTailnetRequest{
Name: name,
IamPolicy: iamPolicy,
AclPolicy: aclPolicy,
@@ -161,37 +144,18 @@ func createTailnetsCommand() *cobra.Command {
}
func deleteTailnetCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "delete",
Short: "Delete a tailnet",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var force bool
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.Flags().BoolVar(&force, "force", false, "When enabled, force delete the specified Tailnet even when machines are still available.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
_, err = client.DeleteTailnet(context.Background(), connect.NewRequest(&api.DeleteTailnetRequest{TailnetId: tailnet.Id, Force: force}))
command.RunE = func(cmd *cobra.Command, args []string) error {
_, err := tc.Client().DeleteTailnet(cmd.Context(), connect.NewRequest(&api.DeleteTailnetRequest{TailnetId: tc.TailnetID(), Force: force}))
if err != nil {
return err
}
@@ -205,36 +169,18 @@ func deleteTailnetCommand() *cobra.Command {
}
func getDERPMap() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "get-derp-map",
Short: "Get the DERP Map configuration",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var asJson bool
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.Flags().BoolVar(&asJson, "json", false, "When enabled, render output as json otherwise yaml")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
resp, err := client.GetDERPMap(context.Background(), connect.NewRequest(&api.GetDERPMapRequest{TailnetId: tailnet.Id}))
command.RunE = func(cmd *cobra.Command, args []string) error {
resp, err := tc.Client().GetDERPMap(cmd.Context(), connect.NewRequest(&api.GetDERPMapRequest{TailnetId: tc.TailnetID()}))
if err != nil {
return err
@@ -271,40 +217,23 @@ func getDERPMap() *cobra.Command {
}
func setDERPMap() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "set-derp-map",
Short: "Set the DERP Map configuration",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var file string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.Flags().StringVar(&file, "file", "", "Path to json file with the DERP Map configuration")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
rawJson, err := os.ReadFile(file)
if err != nil {
return err
}
resp, err := client.SetDERPMap(context.Background(), connect.NewRequest(&api.SetDERPMapRequest{TailnetId: tailnet.Id, Value: rawJson}))
resp, err := tc.Client().SetDERPMap(cmd.Context(), connect.NewRequest(&api.SetDERPMapRequest{TailnetId: tc.TailnetID(), Value: rawJson}))
if err != nil {
return err
}
@@ -323,33 +252,14 @@ func setDERPMap() *cobra.Command {
}
func resetDERPMap() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "reset-derp-map",
Short: "Reset the DERP Map to the default configuration",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
if _, err := client.ResetDERPMap(context.Background(), connect.NewRequest(&api.ResetDERPMapRequest{TailnetId: tailnet.Id})); err != nil {
command.RunE = func(cmd *cobra.Command, args []string) error {
if _, err := tc.Client().ResetDERPMap(cmd.Context(), connect.NewRequest(&api.ResetDERPMapRequest{TailnetId: tc.TailnetID()})); err != nil {
return err
}
@@ -362,38 +272,19 @@ func resetDERPMap() *cobra.Command {
}
func enableFileSharingCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "enable-file-sharing",
Aliases: []string{"enable-taildrop"},
Short: "Enable Taildrop, the file sharing feature",
SilenceUsage: true,
}
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
})
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.EnableFileSharingRequest{
TailnetId: tailnet.Id,
TailnetId: tc.TailnetID(),
}
if _, err := client.EnableFileSharing(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().EnableFileSharing(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -404,38 +295,19 @@ func enableFileSharingCommand() *cobra.Command {
}
func disableFileSharingCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "disable-file-sharing",
Aliases: []string{"disable-taildrop"},
Short: "Disable Taildrop, the file sharing feature",
SilenceUsage: true,
}
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
})
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.DisableFileSharingRequest{
TailnetId: tailnet.Id,
TailnetId: tc.TailnetID(),
}
if _, err := client.DisableFileSharing(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().DisableFileSharing(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -446,37 +318,18 @@ func disableFileSharingCommand() *cobra.Command {
}
func enableServiceCollectionCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "enable-service-collection",
Short: "Enable monitoring live services running on your networks machines.",
SilenceUsage: true,
}
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
})
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.EnableServiceCollectionRequest{
TailnetId: tailnet.Id,
TailnetId: tc.TailnetID(),
}
if _, err := client.EnableServiceCollection(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().EnableServiceCollection(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -487,37 +340,18 @@ func enableServiceCollectionCommand() *cobra.Command {
}
func disableServiceCollectionCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "disable-service-collection",
Short: "Disable monitoring live services running on your networks machines.",
SilenceUsage: true,
}
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
})
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.DisableServiceCollectionRequest{
TailnetId: tailnet.Id,
TailnetId: tc.TailnetID(),
}
if _, err := client.DisableServiceCollection(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().DisableServiceCollection(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -528,37 +362,18 @@ func disableServiceCollectionCommand() *cobra.Command {
}
func enableSSHCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "enable-ssh",
Short: "Enable ssh access using tailnet and ACLs.",
SilenceUsage: true,
}
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
})
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.EnableSSHRequest{
TailnetId: tailnet.Id,
TailnetId: tc.TailnetID(),
}
if _, err := client.EnableSSH(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().EnableSSH(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -569,37 +384,18 @@ func enableSSHCommand() *cobra.Command {
}
func disableSSHCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "disable-ssh",
Short: "Disable ssh access using tailnet and ACLs.",
SilenceUsage: true,
}
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
})
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.DisableSSHRequest{
TailnetId: tailnet.Id,
TailnetId: tc.TailnetID(),
}
if _, err := client.DisableSSH(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().DisableSSH(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -610,37 +406,18 @@ func disableSSHCommand() *cobra.Command {
}
func enableMachineAuthorizationCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "enable-machine-authorization",
Short: "Enable machine authorization.",
SilenceUsage: true,
}
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
})
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.EnableMachineAuthorizationRequest{
TailnetId: tailnet.Id,
TailnetId: tc.TailnetID(),
}
if _, err := client.EnableMachineAuthorization(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().EnableMachineAuthorization(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
@@ -651,37 +428,18 @@ func enableMachineAuthorizationCommand() *cobra.Command {
}
func disableMachineAuthorizationCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "disable-machine-authorization",
Short: "Disable machine authorization.",
SilenceUsage: true,
}
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
})
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.DisableMachineAuthorizationRequest{
TailnetId: tailnet.Id,
TailnetId: tc.TailnetID(),
}
if _, err := client.DisableMachineAuthorization(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().DisableMachineAuthorization(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
+91 -16
View File
@@ -1,60 +1,135 @@
package cmd
import (
"fmt"
"github.com/bufbuild/connect-go"
"github.com/jsiebens/ionscale/internal/config"
"github.com/jsiebens/ionscale/pkg/client/ionscale"
ionscalev1 "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1/ionscalev1connect"
"github.com/spf13/cobra"
)
const (
ionscaleSystemAdminKey = "IONSCALE_SYSTEM_ADMIN_KEY"
ionscaleKeysSystemAdminKey = "IONSCALE_KEYS_SYSTEM_ADMIN_KEY"
ionscaleAddr = "IONSCALE_ADDR"
ionscaleInsecureSkipVerify = "IONSCALE_SKIP_VERIFY"
)
type Target struct {
type TargetContext interface {
Client() api.IonscaleServiceClient
Addr() string
TailnetID() uint64
}
type target struct {
addr string
insecureSkipVerify bool
systemAdminKey string
tailnetID uint64
tailnetName string
client api.IonscaleServiceClient
tailnet *ionscalev1.Tailnet
}
func (t *Target) prepareCommand(cmd *cobra.Command) {
func prepareCommand(enableTailnetSelector bool, cmd *cobra.Command) (*cobra.Command, TargetContext) {
t := &target{}
cmd.Flags().StringVar(&t.addr, "addr", "", "Addr of the ionscale server, as a complete URL")
cmd.Flags().BoolVar(&t.insecureSkipVerify, "tls-skip-verify", false, "Disable verification of TLS certificates")
cmd.Flags().StringVar(&t.systemAdminKey, "system-admin-key", "", "If specified, the given value will be used as the key to generate a Bearer token for the call. This can also be specified via the IONSCALE_ADMIN_KEY environment variable.")
}
func (t *Target) createGRPCClient() (api.IonscaleServiceClient, error) {
addr := t.getAddr()
skipVerify := t.getInsecureSkipVerify()
systemAdminKey := t.getSystemAdminKey()
auth, err := ionscale.LoadClientAuth(systemAdminKey)
if err != nil {
return nil, err
if enableTailnetSelector {
cmd.Flags().StringVar(&t.tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
cmd.Flags().Uint64Var(&t.tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
}
return ionscale.NewClient(auth, addr, skipVerify)
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
addr := t.getAddr()
skipVerify := t.getInsecureSkipVerify()
systemAdminKey := t.getSystemAdminKey()
auth, err := ionscale.LoadClientAuth(addr, systemAdminKey)
if err != nil {
return err
}
client, err := ionscale.NewClient(auth, addr, skipVerify)
if err != nil {
return err
}
t.client = client
if enableTailnetSelector {
savedTailnetID := auth.TailnetID()
if savedTailnetID == 0 && !cmd.Flags().Changed("tailnet") && !cmd.Flags().Changed("tailnet-id") {
return fmt.Errorf("flag --tailnet or --tailnet-id is required")
}
if cmd.Flags().Changed("tailnet") && cmd.Flags().Changed("tailnet-id") {
return fmt.Errorf("flags --tailnet and --tailnet-id are mutually exclusive")
}
tailnets, err := t.client.ListTailnets(cmd.Context(), connect.NewRequest(&ionscalev1.ListTailnetsRequest{}))
if err != nil {
return err
}
for _, tailnet := range tailnets.Msg.Tailnet {
if tailnet.Id == savedTailnetID || tailnet.Id == t.tailnetID || tailnet.Name == t.tailnetName {
t.tailnet = tailnet
break
}
}
if t.tailnet == nil {
return fmt.Errorf("requested tailnet not found or you are not authorized for this tailnet")
}
}
return nil
}
return cmd, t
}
func (t *Target) getAddr() string {
func (t *target) getAddr() string {
if len(t.addr) != 0 {
return t.addr
}
return config.GetString(ionscaleAddr, "https://localhost:8443")
}
func (t *Target) getInsecureSkipVerify() bool {
func (t *target) getInsecureSkipVerify() bool {
if t.insecureSkipVerify {
return true
}
return config.GetBool(ionscaleInsecureSkipVerify, false)
}
func (t *Target) getSystemAdminKey() string {
func (t *target) getSystemAdminKey() string {
if len(t.systemAdminKey) != 0 {
return t.systemAdminKey
}
return config.GetString(ionscaleSystemAdminKey, "")
return config.GetString(ionscaleSystemAdminKey, config.GetString(ionscaleKeysSystemAdminKey, ""))
}
func (t *target) Addr() string {
return t.getAddr()
}
func (t *target) Client() api.IonscaleServiceClient {
return t.client
}
func (t *target) TailnetID() uint64 {
if t.tailnet == nil {
return 0
}
return t.tailnet.Id
}
+10 -36
View File
@@ -1,7 +1,6 @@
package cmd
import (
"context"
"fmt"
"github.com/bufbuild/connect-go"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
@@ -24,34 +23,15 @@ func userCommands() *cobra.Command {
}
func listUsersCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(true, &cobra.Command{
Use: "list",
Short: "List users",
SilenceUsage: true,
}
})
var tailnetID uint64
var tailnetName string
var target = Target{}
target.prepareCommand(command)
command.Flags().StringVar(&tailnetName, "tailnet", "", "Tailnet name. Mutually exclusive with --tailnet-id.")
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
tailnet, err := findTailnet(client, tailnetName, tailnetID)
if err != nil {
return err
}
req := api.ListUsersRequest{TailnetId: tailnet.Id}
resp, err := client.ListUsers(context.Background(), connect.NewRequest(&req))
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.ListUsersRequest{TailnetId: tc.TailnetID()}
resp, err := tc.Client().ListUsers(cmd.Context(), connect.NewRequest(&req))
if err != nil {
return err
@@ -70,27 +50,21 @@ func listUsersCommand() *cobra.Command {
}
func deleteUserCommand() *cobra.Command {
command := &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "delete",
Short: "Deletes a user",
SilenceUsage: true,
}
})
var userID uint64
var target = Target{}
target.prepareCommand(command)
command.Flags().Uint64Var(&userID, "user-id", 0, "User ID.")
_ = command.MarkFlagRequired("user-id")
command.RunE = func(command *cobra.Command, args []string) error {
client, err := target.createGRPCClient()
if err != nil {
return err
}
command.RunE = func(cmd *cobra.Command, args []string) error {
req := api.DeleteUserRequest{UserId: userID}
if _, err := client.DeleteUser(context.Background(), connect.NewRequest(&req)); err != nil {
if _, err := tc.Client().DeleteUser(cmd.Context(), connect.NewRequest(&req)); err != nil {
return err
}
+4 -17
View File
@@ -1,7 +1,6 @@
package cmd
import (
"context"
"fmt"
"github.com/bufbuild/connect-go"
"github.com/jsiebens/ionscale/internal/version"
@@ -10,14 +9,11 @@ import (
)
func versionCommand() *cobra.Command {
var command = &cobra.Command{
command, tc := prepareCommand(false, &cobra.Command{
Use: "version",
Short: "Display version information",
SilenceUsage: true,
}
var target = Target{}
target.prepareCommand(command)
})
command.Run = func(cmd *cobra.Command, args []string) {
clientVersion, clientRevision := version.GetReleaseInfo()
@@ -27,16 +23,7 @@ Client:
Git Revision: %s
`, clientVersion, clientRevision)
client, err := target.createGRPCClient()
if err != nil {
fmt.Printf(`
Server:
Error: %s
`, err)
return
}
resp, err := client.GetVersion(context.Background(), connect.NewRequest(&api.GetVersionRequest{}))
resp, err := tc.Client().GetVersion(cmd.Context(), connect.NewRequest(&api.GetVersionRequest{}))
if err != nil {
fmt.Printf(`
Server:
@@ -50,7 +37,7 @@ Server:
Addr: %s
Version: %s
Git Revision: %s
`, target.getAddr(), resp.Msg.Version, resp.Msg.Revision)
`, tc.Addr(), resp.Msg.Version, resp.Msg.Revision)
}
-2
View File
@@ -115,7 +115,6 @@ func defaultConfig() *Config {
ForceHttps: true,
AcmeEnabled: false,
AcmeCA: certmagic.LetsEncryptProductionCA,
AcmePath: "./acme",
},
PollNet: PollNet{
KeepAliveInterval: defaultKeepAliveInterval,
@@ -157,7 +156,6 @@ type Tls struct {
AcmeEnabled bool `yaml:"acme,omitempty" env:"ACME_ENABLED"`
AcmeEmail string `yaml:"acme_email,omitempty" env:"ACME_EMAIL"`
AcmeCA string `yaml:"acme_ca,omitempty" env:"ACME_CA"`
AcmePath string `yaml:"acme_path,omitempty" env:"ACME_PATH"`
}
type PollNet struct {
+7 -4
View File
@@ -1,6 +1,7 @@
package core
import (
"slices"
"sync"
"time"
)
@@ -11,7 +12,7 @@ type PollMapSessionManager interface {
Register(tailnetID uint64, machineID uint64, ch chan *Ping)
Deregister(tailnetID uint64, machineID uint64)
HasSession(tailnetID uint64, machineID uint64) bool
NotifyAll(tailnetID uint64)
NotifyAll(tailnetID uint64, ignoreMachineIDs ...uint64)
}
func NewPollMapSessionManager() PollMapSessionManager {
@@ -82,13 +83,15 @@ func (n *pollMapSessionManager) HasSession(tailnetID uint64, machineID uint64) b
return false
}
func (n *pollMapSessionManager) NotifyAll(tailnetID uint64) {
func (n *pollMapSessionManager) NotifyAll(tailnetID uint64, ignoreMachineIDs ...uint64) {
n.RLock()
defer n.RUnlock()
if ss := n.data[tailnetID]; ss != nil {
for _, p := range ss {
p <- &Ping{}
for i, p := range ss {
if !slices.Contains(ignoreMachineIDs, i) {
p <- &Ping{}
}
}
}
}
@@ -11,8 +11,8 @@ const (
inactivityTimeout = 30 * time.Minute
)
func StartReaper(repository domain.Repository, sessionManager PollMapSessionManager) {
r := &reaper{
func StartWorker(repository domain.Repository, sessionManager PollMapSessionManager) {
r := &worker{
sessionManager: sessionManager,
repository: repository,
}
@@ -20,19 +20,20 @@ func StartReaper(repository domain.Repository, sessionManager PollMapSessionMana
go r.start()
}
type reaper struct {
type worker struct {
sessionManager PollMapSessionManager
repository domain.Repository
}
func (r *reaper) start() {
func (r *worker) start() {
r.deleteInactiveEphemeralNodes()
t := time.NewTicker(ticker)
for range t.C {
r.reapInactiveEphemeralNodes()
r.deleteInactiveEphemeralNodes()
}
}
func (r *reaper) reapInactiveEphemeralNodes() {
func (r *worker) deleteInactiveEphemeralNodes() {
ctx := context.Background()
now := time.Now().UTC()
@@ -41,6 +42,7 @@ func (r *reaper) reapInactiveEphemeralNodes() {
if err != nil {
return
}
var removedNodes = make(map[uint64][]uint64)
for _, m := range machines {
if now.After(m.LastSeen.Add(inactivityTimeout)) {
+7 -6
View File
@@ -2,6 +2,7 @@ package database
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/go-gormigrate/gormigrate/v2"
@@ -23,17 +24,17 @@ type dbLock interface {
UnlockErr(error) error
}
func OpenDB(config *config.Database, logger *zap.Logger) (domain.Repository, error) {
func OpenDB(config *config.Database, logger *zap.Logger) (*sql.DB, domain.Repository, error) {
db, lock, err := createDB(config, logger)
if err != nil {
return nil, err
return nil, nil, err
}
_ = db.Use(prometheus.New(prometheus.Config{StartServer: false}))
sqlDB, err := db.DB()
if err != nil {
return nil, err
return nil, nil, err
}
sqlDB.SetMaxOpenConns(config.MaxOpenConns)
@@ -44,14 +45,14 @@ func OpenDB(config *config.Database, logger *zap.Logger) (domain.Repository, err
repository := domain.NewRepository(db)
if err := lock.Lock(); err != nil {
return nil, err
return nil, nil, err
}
if err := lock.UnlockErr(migrate(db)); err != nil {
return nil, err
return nil, nil, err
}
return repository, nil
return sqlDB, repository, nil
}
func createDB(config *config.Database, logger *zap.Logger) (*gorm.DB, dbLock, error) {
@@ -0,0 +1,23 @@
package migration
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
"time"
)
func m202312271200_account_last_authenticated() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "202312271200",
Migrate: func(db *gorm.DB) error {
type Account struct {
LastAuthenticated *time.Time
}
return db.AutoMigrate(
&Account{},
)
},
Rollback: nil,
}
}
@@ -0,0 +1,25 @@
package migration
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
func m202312290900_machine_indeces() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "202312290900",
Migrate: func(db *gorm.DB) error {
type Machine struct {
Name string `gorm:"index:idx_tailnet_id_name,unique,priority:2"`
NameIdx uint64 `gorm:"index:idx_tailnet_id_name,unique,sort:desc,priority:3"`
}
db.Migrator().DropIndex(&Machine{}, "idx_tailnet_id_name")
return db.AutoMigrate(
&Machine{},
)
},
Rollback: nil,
}
}
@@ -0,0 +1,27 @@
package migration
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
func m202401061400_machine_indeces() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "202401061400",
Migrate: func(db *gorm.DB) error {
type Machine struct {
ID uint64 `gorm:"primaryKey;autoIncrement:false;index:idx_tailnet_id_id,priority:2"`
Name string `gorm:"index:idx_tailnet_id_name,unique,priority:2"`
NameIdx uint64 `gorm:"index:idx_tailnet_id_name,unique,sort:desc,priority:3"`
TailnetID uint64 `gorm:"index:idx_tailnet_id_id,priority:1;index:idx_tailnet_id_name,priority:1"`
}
db.Migrator().DropIndex(&Machine{}, "idx_tailnet_id_name")
return db.AutoMigrate(
&Machine{},
)
},
Rollback: nil,
}
}
@@ -16,6 +16,9 @@ func Migrations() []*gormigrate.Migration {
m202211031100_add_authorized_column(),
m202212201300_add_user_id_column(),
m202212270800_machine_indeces(),
m202312271200_account_last_authenticated(),
m202312290900_machine_indeces(),
m202401061400_machine_indeces(),
}
return migrations
}
+19 -3
View File
@@ -5,12 +5,14 @@ import (
"errors"
"github.com/jsiebens/ionscale/internal/util"
"gorm.io/gorm"
"time"
)
type Account struct {
ID uint64 `gorm:"primary_key"`
ExternalID string
LoginName string
ID uint64 `gorm:"primary_key"`
ExternalID string
LoginName string
LastAuthenticated *time.Time
}
func (r *repository) GetOrCreateAccount(ctx context.Context, externalID, loginName string) (*Account, bool, error) {
@@ -43,3 +45,17 @@ func (r *repository) GetAccount(ctx context.Context, id uint64) (*Account, error
return &account, nil
}
func (r *repository) SetAccountLastAuthenticated(ctx context.Context, accountID uint64) error {
now := time.Now().UTC()
tx := r.withContext(ctx).
Model(Account{}).
Where("id = ?", accountID).
Updates(map[string]interface{}{"last_authenticated": &now})
if tx.Error != nil {
return tx.Error
}
return nil
}
+128 -235
View File
@@ -8,6 +8,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/schema"
"net/netip"
"slices"
"sort"
"strconv"
"strings"
@@ -16,7 +17,9 @@ import (
const (
AutoGroupSelf = "autogroup:self"
AutoGroupMember = "autogroup:member"
AutoGroupMembers = "autogroup:members"
AutoGroupTagged = "autogroup:tagged"
AutoGroupInternet = "autogroup:internet"
)
@@ -32,31 +35,35 @@ type ACLPolicy struct {
TagOwners map[string][]string `json:"tagowners,omitempty"`
AutoApprovers *AutoApprovers `json:"autoApprovers,omitempty"`
SSHRules []SSHRule `json:"ssh,omitempty"`
NodeAttrs []NodeAttr `json:"nodeAttrs,omitempty"`
Grants []Grant `json:"grants,omitempty"`
}
type ACL struct {
Action string `json:"action"`
Proto string `json:"proto"`
Src []string `json:"src"`
Dst []string `json:"dst"`
}
type SSHRule struct {
Action string `json:"action"`
Src []string `json:"src"`
Dst []string `json:"dst"`
Users []string `json:"users"`
Action string `json:"action"`
Src []string `json:"src"`
Dst []string `json:"dst"`
Users []string `json:"users"`
CheckPeriod string `json:"checkPeriod,omitempty"`
}
func DefaultACLPolicy() ACLPolicy {
return ACLPolicy{
ACLs: []ACL{
{
Action: "accept",
Src: []string{"*"},
Dst: []string{"*:*"},
},
},
}
type NodeAttr struct {
Target []string `json:"target"`
Attr []string `json:"attr"`
}
type Grant struct {
Src []string `json:"src"`
Dst []string `json:"dst"`
IP []tailcfg.ProtoPortRange `json:"ip"`
App tailcfg.PeerCapMap `json:"app"`
}
func (a ACLPolicy) FindAutoApprovedIPs(routableIPs []netip.Prefix, tags []string, u *User) []netip.Prefix {
@@ -99,7 +106,7 @@ func (a ACLPolicy) FindAutoApprovedIPs(routableIPs []netip.Prefix, tags []string
return false
}
autoApprovedIPs := []netip.Prefix{}
var autoApprovedIPs []netip.Prefix
for route, autoApprovers := range a.AutoApprovers.Routes {
candidate, err := netip.ParsePrefix(route)
if err != nil {
@@ -111,7 +118,7 @@ func (a ACLPolicy) FindAutoApprovedIPs(routableIPs []netip.Prefix, tags []string
}
}
result := []netip.Prefix{}
var result []netip.Prefix
for _, c := range routableIPs {
if c.Bits() == 0 && matches(a.AutoApprovers.ExitNode) {
result = append(result, c)
@@ -124,15 +131,6 @@ func (a ACLPolicy) FindAutoApprovedIPs(routableIPs []netip.Prefix, tags []string
return result
}
func (a ACLPolicy) IsTagOwner(tags []string, p *User) bool {
for _, t := range tags {
if a.isTagOwner(t, p) {
return true
}
}
return false
}
func (a ACLPolicy) CheckTagOwners(tags []string, p *User) error {
var result *multierror.Error
for _, t := range tags {
@@ -148,231 +146,71 @@ func (a ACLPolicy) isTagOwner(tag string, p *User) bool {
return true
}
if tagOwners, ok := a.TagOwners[tag]; ok {
return a.validateTagOwners(tagOwners, p)
for _, alias := range tagOwners {
if strings.HasPrefix(alias, "group:") {
if group, ok := a.Groups[alias]; ok {
return slices.Contains(group, p.Name)
}
} else {
if alias == p.Name {
return true
}
}
}
}
return false
}
func (a ACLPolicy) validateTagOwners(tagOwners []string, p *User) bool {
for _, alias := range tagOwners {
if strings.HasPrefix(alias, "group:") {
if group, ok := a.Groups[alias]; ok {
for _, groupMember := range group {
if groupMember == p.Name {
func (a ACLPolicy) NodeCapabilities(m *Machine) []tailcfg.NodeCapability {
var result = &StringSet{}
matches := func(targets []string) bool {
for _, alias := range targets {
if alias == "*" {
return true
}
if strings.Contains(alias, "@") && !m.HasTags() && m.HasUser(alias) {
return true
}
if strings.HasPrefix(alias, "tag:") && m.HasTag(alias) {
return true
}
if strings.HasPrefix(alias, "group:") && !m.HasTags() {
for _, u := range a.Groups[alias] {
if m.HasUser(u) {
return true
}
}
}
} else {
if alias == p.Name {
return true
}
}
return false
}
for _, nodeAddr := range a.NodeAttrs {
if matches(nodeAddr.Target) {
result.Add(nodeAddr.Attr...)
}
}
return false
items := result.Items()
caps := make([]tailcfg.NodeCapability, len(items))
for i, c := range items {
caps[i] = tailcfg.NodeCapability(c)
}
return caps
}
func (a ACLPolicy) IsValidPeer(src *Machine, dest *Machine) bool {
if !src.HasTags() && !dest.HasTags() && dest.HasUser(src.User.Name) {
return true
}
for _, acl := range a.ACLs {
selfDestPorts, allDestPorts := a.expandMachineToDstPorts(dest, acl.Dst)
if len(selfDestPorts) != 0 {
for _, alias := range acl.Src {
if len(a.expandMachineAlias(src, alias, true, &dest.User)) != 0 {
return true
}
}
}
if len(allDestPorts) != 0 {
for _, alias := range acl.Src {
if len(a.expandMachineAlias(src, alias, true, nil)) != 0 {
return true
}
}
}
}
return false
}
func (a ACLPolicy) BuildFilterRules(srcs []Machine, dst *Machine) []tailcfg.FilterRule {
var rules []tailcfg.FilterRule
transform := func(src []string, destPorts []tailcfg.NetPortRange, u *User) tailcfg.FilterRule {
var allSrcIPsSet = &StringSet{}
for _, alias := range src {
for _, src := range srcs {
srcIPs := a.expandMachineAlias(&src, alias, true, u)
allSrcIPsSet.Add(srcIPs...)
}
}
allSrcIPs := allSrcIPsSet.Items()
if len(allSrcIPs) == 0 {
allSrcIPs = nil
}
return tailcfg.FilterRule{
SrcIPs: allSrcIPs,
DstPorts: destPorts,
}
}
for _, acl := range a.ACLs {
selfDestPorts, allDestPorts := a.expandMachineToDstPorts(dst, acl.Dst)
if len(selfDestPorts) != 0 {
rules = append(rules, transform(acl.Src, selfDestPorts, &dst.User))
}
if len(allDestPorts) != 0 {
rules = append(rules, transform(acl.Src, allDestPorts, nil))
}
}
if len(rules) == 0 {
return []tailcfg.FilterRule{{}}
}
return rules
}
func (a ACLPolicy) expandMachineToDstPorts(m *Machine, ports []string) ([]tailcfg.NetPortRange, []tailcfg.NetPortRange) {
selfDestRanges := []tailcfg.NetPortRange{}
otherDestRanges := []tailcfg.NetPortRange{}
for _, d := range ports {
self, ranges := a.expandMachineDestToNetPortRanges(m, d)
if self {
selfDestRanges = append(selfDestRanges, ranges...)
} else {
otherDestRanges = append(otherDestRanges, ranges...)
}
}
return selfDestRanges, otherDestRanges
}
func (a ACLPolicy) expandMachineDestToNetPortRanges(m *Machine, dest string) (bool, []tailcfg.NetPortRange) {
tokens := strings.Split(dest, ":")
if len(tokens) < 2 || len(tokens) > 3 {
return false, nil
}
var alias string
if len(tokens) == 2 {
alias = tokens[0]
} else {
alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
}
ports, err := a.expandValuePortToPortRange(tokens[len(tokens)-1])
if err != nil {
return false, nil
}
ips := a.expandMachineAlias(m, alias, false, nil)
if len(ips) == 0 {
return false, nil
}
dests := []tailcfg.NetPortRange{}
for _, d := range ips {
for _, p := range ports {
pr := tailcfg.NetPortRange{
IP: d,
Ports: p,
}
dests = append(dests, pr)
}
}
return alias == AutoGroupSelf, dests
}
func (a ACLPolicy) expandMachineAlias(m *Machine, alias string, src bool, u *User) []string {
if u != nil && m.HasTags() {
return []string{}
}
if u != nil && !m.HasUser(u.Name) {
return []string{}
}
if alias == "*" && u != nil {
return m.IPs()
}
if alias == "*" {
return []string{"*"}
}
if alias == AutoGroupMembers || alias == AutoGroupSelf {
if !m.HasTags() {
return m.IPs()
} else {
return []string{}
}
}
if alias == AutoGroupInternet && m.IsExitNode() {
return autogroupInternetRanges()
}
if strings.Contains(alias, "@") && !m.HasTags() && m.HasUser(alias) {
return m.IPs()
}
if strings.HasPrefix(alias, "group:") && !m.HasTags() {
users, ok := a.Groups[alias]
if !ok {
return []string{}
}
for _, u := range users {
if m.HasUser(u) {
return m.IPs()
}
}
return []string{}
}
if strings.HasPrefix(alias, "tag:") && m.HasTag(alias) {
return m.IPs()
}
if h, ok := a.Hosts[alias]; ok {
alias = h
}
if src {
ip, err := netip.ParseAddr(alias)
if err == nil && m.HasIP(ip) {
return []string{ip.String()}
}
} else {
ip, err := netip.ParseAddr(alias)
if err == nil && m.IsAllowedIP(ip) {
return []string{ip.String()}
}
prefix, err := netip.ParsePrefix(alias)
if err == nil && m.IsAllowedIPPrefix(prefix) {
return []string{prefix.String()}
}
}
return []string{}
}
func (a ACLPolicy) expandValuePortToPortRange(s string) ([]tailcfg.PortRange, error) {
func (a ACLPolicy) parsePortRanges(s string) ([]tailcfg.PortRange, error) {
if s == "*" {
return []tailcfg.PortRange{{First: 0, Last: 65535}}, nil
return []tailcfg.PortRange{tailcfg.PortRangeAny}, nil
}
ports := []tailcfg.PortRange{}
var ports []tailcfg.PortRange
for _, p := range strings.Split(s, ",") {
rang := strings.Split(p, "-")
if len(rang) == 1 {
@@ -451,6 +289,57 @@ func (ACLPolicy) GormDBDataType(db *gorm.DB, field *schema.Field) string {
return ""
}
const (
protocolICMP = 1 // Internet Control Message
protocolIGMP = 2 // Internet Group Management
protocolIPv4 = 4 // IPv4 encapsulation
protocolTCP = 6 // Transmission Control
protocolEGP = 8 // Exterior Gateway Protocol
protocolIGP = 9 // any private interior gateway (used by Cisco for their IGRP)
protocolUDP = 17 // User Datagram
protocolGRE = 47 // Generic Routing Encapsulation
protocolESP = 50 // Encap Security Payload
protocolAH = 51 // Authentication Header
protocolIPv6ICMP = 58 // ICMP for IPv6
protocolSCTP = 132 // Stream Control Transmission Protocol
)
func parseProtocol(protocol string) []int {
switch protocol {
case "":
return nil
case "igmp":
return []int{protocolIGMP}
case "ipv4", "ip-in-ip":
return []int{protocolIPv4}
case "tcp":
return []int{protocolTCP}
case "egp":
return []int{protocolEGP}
case "igp":
return []int{protocolIGP}
case "udp":
return []int{protocolUDP}
case "gre":
return []int{protocolGRE}
case "esp":
return []int{protocolESP}
case "ah":
return []int{protocolAH}
case "sctp":
return []int{protocolSCTP}
case "icmp":
return []int{protocolICMP, protocolIPv6ICMP}
default:
n, err := strconv.Atoi(protocol)
if err != nil {
return nil
}
return []int{n}
}
}
type StringSet struct {
items map[string]bool
}
@@ -476,6 +365,10 @@ func (s *StringSet) Items() []string {
return items
}
func (s *StringSet) Empty() bool {
return len(s.items) == 0
}
func autogroupInternetRanges() []string {
return []string{
"0.0.0.0/5",
+346
View File
@@ -0,0 +1,346 @@
package domain
import (
"net/netip"
"strings"
"tailscale.com/tailcfg"
)
func (a ACLPolicy) IsValidPeer(src *Machine, dest *Machine) bool {
if !src.HasTags() && !dest.HasTags() && dest.HasUser(src.User.Name) {
return true
}
for _, acl := range a.ACLs {
selfDestPorts, allDestPorts := a.translateDestinationAliasesToMachineNetPortRanges(acl.Dst, dest)
if len(selfDestPorts) != 0 {
for _, alias := range acl.Src {
if len(a.translateSourceAliasToMachineIPs(alias, src, &dest.User)) != 0 {
return true
}
}
}
if len(allDestPorts) != 0 {
for _, alias := range acl.Src {
if len(a.translateSourceAliasToMachineIPs(alias, src, nil)) != 0 {
return true
}
}
}
}
for _, grant := range a.Grants {
selfIps, otherIps := a.translateDestinationAliasesToMachineIPs(grant.Dst, dest)
if len(selfIps) != 0 {
for _, alias := range grant.Src {
if len(a.translateSourceAliasToMachineIPs(alias, src, &dest.User)) != 0 {
return true
}
}
}
if len(otherIps) != 0 {
for _, alias := range grant.Src {
if len(a.translateSourceAliasToMachineIPs(alias, src, nil)) != 0 {
return true
}
}
}
}
return false
}
func (a ACLPolicy) BuildFilterRules(peers []Machine, dst *Machine) []tailcfg.FilterRule {
var rules = make([]tailcfg.FilterRule, 0)
matchSourceAndAppendRule := func(rules []tailcfg.FilterRule, aliases []string, preparedRules []tailcfg.FilterRule, u *User) []tailcfg.FilterRule {
if len(preparedRules) == 0 {
return rules
}
var allSrcIPsSet = &StringSet{}
for _, alias := range aliases {
for _, peer := range peers {
allSrcIPsSet.Add(a.translateSourceAliasToMachineIPs(alias, &peer, u)...)
}
}
if allSrcIPsSet.Empty() {
return rules
}
allSrcIPs := allSrcIPsSet.Items()
if len(allSrcIPs) == 0 {
return rules
}
for _, pr := range preparedRules {
rules = append(rules, tailcfg.FilterRule{
SrcIPs: allSrcIPs,
DstPorts: pr.DstPorts,
IPProto: pr.IPProto,
CapGrant: pr.CapGrant,
})
}
return rules
}
for _, acl := range a.ACLs {
self, other := a.prepareFilterRulesFromACL(dst, acl)
rules = matchSourceAndAppendRule(rules, acl.Src, self, &dst.User)
rules = matchSourceAndAppendRule(rules, acl.Src, other, nil)
}
for _, acl := range a.Grants {
self, other := a.prepareFilterRulesFromGrant(dst, acl)
rules = matchSourceAndAppendRule(rules, acl.Src, self, &dst.User)
rules = matchSourceAndAppendRule(rules, acl.Src, other, nil)
}
return rules
}
func (a ACLPolicy) prepareFilterRulesFromACL(candidate *Machine, acl ACL) ([]tailcfg.FilterRule, []tailcfg.FilterRule) {
proto := parseProtocol(acl.Proto)
selfDstPorts, otherDstPorts := a.translateDestinationAliasesToMachineNetPortRanges(acl.Dst, candidate)
var selfFilterRules []tailcfg.FilterRule
var otherFilterRules []tailcfg.FilterRule
if len(selfDstPorts) != 0 {
selfFilterRules = append(selfFilterRules, tailcfg.FilterRule{IPProto: proto, DstPorts: selfDstPorts})
}
if len(otherDstPorts) != 0 {
otherFilterRules = append(otherFilterRules, tailcfg.FilterRule{IPProto: proto, DstPorts: otherDstPorts})
}
return selfFilterRules, otherFilterRules
}
func (a ACLPolicy) prepareFilterRulesFromGrant(candidate *Machine, grant Grant) ([]tailcfg.FilterRule, []tailcfg.FilterRule) {
selfIPs, otherIPs := a.translateDestinationAliasesToMachineIPs(grant.Dst, candidate)
var selfFilterRules []tailcfg.FilterRule
var otherFilterRules []tailcfg.FilterRule
for _, ip := range grant.IP {
if len(selfIPs) != 0 {
ranges := make([]tailcfg.NetPortRange, len(selfIPs))
for i, s := range selfIPs {
ranges[i] = tailcfg.NetPortRange{IP: s, Ports: ip.Ports}
}
rule := tailcfg.FilterRule{DstPorts: ranges}
if ip.Proto != 0 {
rule.IPProto = []int{ip.Proto}
}
selfFilterRules = append(selfFilterRules, rule)
}
if len(otherIPs) != 0 {
ranges := make([]tailcfg.NetPortRange, len(otherIPs))
for i, s := range otherIPs {
ranges[i] = tailcfg.NetPortRange{IP: s, Ports: ip.Ports}
}
rule := tailcfg.FilterRule{DstPorts: ranges}
if ip.Proto != 0 {
rule.IPProto = []int{ip.Proto}
}
otherFilterRules = append(otherFilterRules, rule)
}
}
if len(grant.App) != 0 {
selfPrefixes, otherPrefixes := appGrantDstIpsToPrefixes(candidate, selfIPs, otherIPs)
if len(selfPrefixes) != 0 {
rule := tailcfg.FilterRule{CapGrant: []tailcfg.CapGrant{{Dsts: selfPrefixes, CapMap: grant.App}}}
selfFilterRules = append(selfFilterRules, rule)
}
if len(otherPrefixes) != 0 {
rule := tailcfg.FilterRule{CapGrant: []tailcfg.CapGrant{{Dsts: otherPrefixes, CapMap: grant.App}}}
otherFilterRules = append(otherFilterRules, rule)
}
}
return selfFilterRules, otherFilterRules
}
func appGrantDstIpsToPrefixes(m *Machine, self []string, other []string) ([]netip.Prefix, []netip.Prefix) {
translate := func(ips []string) []netip.Prefix {
var prefixes []netip.Prefix
for _, ip := range ips {
if ip == "*" {
prefixes = append(prefixes, netip.PrefixFrom(*m.IPv4.Addr, 32))
prefixes = append(prefixes, netip.PrefixFrom(*m.IPv6.Addr, 128))
} else {
addr, err := netip.ParseAddr(ip)
if err == nil && m.HasIP(addr) {
if addr.Is4() {
prefixes = append(prefixes, netip.PrefixFrom(addr, 32))
} else {
prefixes = append(prefixes, netip.PrefixFrom(addr, 128))
}
}
}
}
return prefixes
}
return translate(self), translate(other)
}
func (a ACLPolicy) translateDestinationAliasesToMachineIPs(aliases []string, m *Machine) ([]string, []string) {
var self = &StringSet{}
var other = &StringSet{}
for _, alias := range aliases {
ips := a.translateDestinationAliasToMachineIPs(alias, m)
if alias == AutoGroupSelf {
self.Add(ips...)
} else {
other.Add(ips...)
}
}
return self.Items(), other.Items()
}
func (a ACLPolicy) translateDestinationAliasesToMachineNetPortRanges(aliases []string, m *Machine) ([]tailcfg.NetPortRange, []tailcfg.NetPortRange) {
var self []tailcfg.NetPortRange
var other []tailcfg.NetPortRange
for _, alias := range aliases {
ranges := a.translationDestinationAliasToMachineNetPortRanges(alias, m)
if strings.HasPrefix(alias, AutoGroupSelf) {
self = append(self, ranges...)
} else {
other = append(other, ranges...)
}
}
return self, other
}
func (a ACLPolicy) translationDestinationAliasToMachineNetPortRanges(alias string, m *Machine) []tailcfg.NetPortRange {
lastInd := strings.LastIndex(alias, ":")
if lastInd == -1 {
return nil
}
ports := alias[lastInd+1:]
alias = alias[:lastInd]
portRanges, err := a.parsePortRanges(ports)
if err != nil {
return nil
}
ips := a.translateDestinationAliasToMachineIPs(alias, m)
if len(ips) == 0 {
return nil
}
var netPortRanges []tailcfg.NetPortRange
for _, d := range ips {
for _, p := range portRanges {
pr := tailcfg.NetPortRange{
IP: d,
Ports: p,
}
netPortRanges = append(netPortRanges, pr)
}
}
return netPortRanges
}
func (a ACLPolicy) translateDestinationAliasToMachineIPs(alias string, m *Machine) []string {
f := func(alias string, m *Machine) []string {
ip, err := netip.ParseAddr(alias)
if err == nil && m.IsAllowedIP(ip) {
return []string{ip.String()}
}
prefix, err := netip.ParsePrefix(alias)
if err == nil && m.IsAllowedIPPrefix(prefix) {
return []string{prefix.String()}
}
return make([]string, 0)
}
return a.translateAliasToMachineIPs(alias, m, nil, f)
}
func (a ACLPolicy) translateSourceAliasToMachineIPs(alias string, m *Machine, u *User) []string {
f := func(alias string, m *Machine) []string {
ip, err := netip.ParseAddr(alias)
if err == nil && m.HasIP(ip) {
return []string{ip.String()}
}
return make([]string, 0)
}
return a.translateAliasToMachineIPs(alias, m, u, f)
}
func (a ACLPolicy) translateAliasToMachineIPs(alias string, m *Machine, u *User, f func(string, *Machine) []string) []string {
if u != nil && m.HasTags() {
return []string{}
}
if u != nil && !m.HasUser(u.Name) {
return []string{}
}
if alias == "*" && u != nil {
return m.IPs()
}
if alias == "*" {
return []string{"*"}
}
if alias == AutoGroupMember || alias == AutoGroupMembers || alias == AutoGroupSelf {
if !m.HasTags() {
return m.IPs()
} else {
return []string{}
}
}
if alias == AutoGroupTagged {
if m.HasTags() {
return m.IPs()
} else {
return []string{}
}
}
if alias == AutoGroupInternet && m.IsExitNode() {
return autogroupInternetRanges()
}
if strings.Contains(alias, "@") && !m.HasTags() && m.HasUser(alias) {
return m.IPs()
}
if strings.HasPrefix(alias, "group:") && !m.HasTags() && a.isGroupMember(alias, m) {
return m.IPs()
}
if strings.HasPrefix(alias, "tag:") && m.HasTag(alias) {
return m.IPs()
}
if h, ok := a.Hosts[alias]; ok {
alias = h
}
return f(alias, m)
}
+10 -3
View File
@@ -41,7 +41,7 @@ func (a ACLPolicy) BuildSSHPolicy(srcs []Machine, dst *Machine) *tailcfg.SSHPoli
if rule.Action == "check" {
action = &tailcfg.SSHAction{
HoldAndDelegate: "https://unused/machine/ssh/action/$SRC_NODE_ID/to/$DST_NODE_ID",
HoldAndDelegate: "https://unused/machine/ssh/action/$SRC_NODE_ID/to/$DST_NODE_ID/" + safeCheckPeriod(rule.CheckPeriod),
}
}
@@ -79,7 +79,7 @@ func (a ACLPolicy) expandSSHSrcAlias(m *Machine, alias string, dstUser *User) []
return []string{}
}
if alias == AutoGroupMembers {
if alias == AutoGroupMember || alias == AutoGroupMembers {
return m.IPs()
}
@@ -94,7 +94,7 @@ func (a ACLPolicy) expandSSHSrcAlias(m *Machine, alias string, dstUser *User) []
return []string{}
}
if alias == AutoGroupMembers && !m.HasTags() {
if (alias == AutoGroupMember || alias == AutoGroupMembers) && !m.HasTags() {
return m.IPs()
}
@@ -151,3 +151,10 @@ func buildSSHUsers(users []string) map[string]string {
return m
}
func safeCheckPeriod(period string) string {
if period == "" {
return "always"
}
return period
}
+375 -40
View File
@@ -1,14 +1,127 @@
package domain
import (
"encoding/json"
"github.com/jsiebens/ionscale/internal/addr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/netip"
"sort"
"tailscale.com/tailcfg"
"testing"
)
func TestACLPolicy_NodeAttributesWithWildcards(t *testing.T) {
p1 := createMachine("john@example.com")
policy := ACLPolicy{
NodeAttrs: []NodeAttr{
{
Target: []string{"*"},
Attr: []string{
"attr1",
"attr2",
},
},
{
Target: []string{"*"},
Attr: []string{
"attr3",
},
},
},
}
actualAttrs := policy.NodeCapabilities(p1)
expectedAttrs := []tailcfg.NodeCapability{
tailcfg.NodeCapability("attr1"),
tailcfg.NodeCapability("attr2"),
tailcfg.NodeCapability("attr3"),
}
assert.Equal(t, expectedAttrs, actualAttrs)
}
func TestACLPolicy_NodeAttributesWithUserAndGroups(t *testing.T) {
p1 := createMachine("john@example.com")
policy := ACLPolicy{
Groups: map[string][]string{
"group:admins": []string{"john@example.com"},
},
NodeAttrs: []NodeAttr{
{
Target: []string{"john@example.com"},
Attr: []string{
"attr1",
"attr2",
},
},
{
Target: []string{"jane@example.com", "group:analytics", "group:admins"},
Attr: []string{
"attr3",
},
},
},
}
actualAttrs := policy.NodeCapabilities(p1)
expectedAttrs := []tailcfg.NodeCapability{
tailcfg.NodeCapability("attr1"),
tailcfg.NodeCapability("attr2"),
tailcfg.NodeCapability("attr3"),
}
assert.Equal(t, expectedAttrs, actualAttrs)
}
func TestACLPolicy_NodeAttributesWithUserAndTags(t *testing.T) {
p1 := createMachine("john@example.com", "tag:web")
policy := ACLPolicy{
Groups: map[string][]string{
"group:admins": []string{"john@example.com"},
},
NodeAttrs: []NodeAttr{
{
Target: []string{"john@example.com"},
Attr: []string{
"attr1",
"attr2",
},
},
{
Target: []string{"jane@example.com", "tag:web"},
Attr: []string{
"attr3",
},
},
},
}
actualAttrs := policy.NodeCapabilities(p1)
expectedAttrs := []tailcfg.NodeCapability{tailcfg.NodeCapability("attr3")}
assert.Equal(t, expectedAttrs, actualAttrs)
}
func TestACLPolicy_BuildFilterRulesEmptyACL(t *testing.T) {
p1 := createMachine("john@example.com")
p2 := createMachine("jane@example.com")
policy := ACLPolicy{
ACLs: []ACL{},
}
dst := createMachine("john@example.com")
actualRules := policy.BuildFilterRules([]Machine{*p1, *p2}, dst)
expectedRules := []tailcfg.FilterRule{}
assert.Equal(t, expectedRules, actualRules)
}
func TestACLPolicy_BuildFilterRulesWildcards(t *testing.T) {
p1 := createMachine("john@example.com")
p2 := createMachine("jane@example.com")
@@ -44,6 +157,60 @@ func TestACLPolicy_BuildFilterRulesWildcards(t *testing.T) {
assert.Equal(t, expectedRules, actualRules)
}
func TestACLPolicy_BuildFilterRulesProto(t *testing.T) {
p1 := createMachine("john@example.com")
p2 := createMachine("jane@example.com")
policy := ACLPolicy{
ACLs: []ACL{
{
Action: "accept",
Src: []string{"*"},
Dst: []string{"*:22"},
},
{
Action: "accept",
Src: []string{"*"},
Dst: []string{"*:*"},
Proto: "igmp",
},
},
}
dst := createMachine("john@example.com")
actualRules := policy.BuildFilterRules([]Machine{*p1, *p2}, dst)
expectedRules := []tailcfg.FilterRule{
{
SrcIPs: []string{"*"},
DstPorts: []tailcfg.NetPortRange{
{
IP: "*",
Ports: tailcfg.PortRange{
First: 22,
Last: 22,
},
},
},
},
{
SrcIPs: []string{"*"},
DstPorts: []tailcfg.NetPortRange{
{
IP: "*",
Ports: tailcfg.PortRange{
First: 0,
Last: 65535,
},
},
},
IPProto: []int{protocolIGMP},
},
}
assert.Equal(t, expectedRules, actualRules)
}
func TestACLPolicy_BuildFilterRulesWithGroups(t *testing.T) {
p1 := createMachine("jane@example.com")
p2 := createMachine("nick@example.com")
@@ -150,6 +317,92 @@ func TestACLPolicy_BuildFilterRulesWithAutoGroupMembers(t *testing.T) {
assert.Equal(t, expectedRules, actualRules)
}
func TestACLPolicy_BuildFilterRulesWithAutoGroupMember(t *testing.T) {
p1 := createMachine("jane@example.com")
p2 := createMachine("nick@example.com")
p3 := createMachine("joe@example.com", "tag:web")
policy := ACLPolicy{
ACLs: []ACL{
{
Action: "accept",
Src: []string{"autogroup:member"},
Dst: []string{"*:22"},
},
},
}
dst := createMachine("john@example.com")
actualRules := policy.BuildFilterRules([]Machine{*p1, *p2, *p3}, dst)
expectedSrcIPs := []string{
p1.IPv4.String(), p1.IPv6.String(),
p2.IPv4.String(), p2.IPv6.String(),
}
sort.Strings(expectedSrcIPs)
expectedRules := []tailcfg.FilterRule{
{
SrcIPs: expectedSrcIPs,
DstPorts: []tailcfg.NetPortRange{
{
IP: "*",
Ports: tailcfg.PortRange{
First: 22,
Last: 22,
},
},
},
},
}
assert.Equal(t, expectedRules, actualRules)
}
func TestACLPolicy_BuildFilterRulesWithAutoGroupTagged(t *testing.T) {
p1 := createMachine("jane@example.com")
p2 := createMachine("nick@example.com")
p3 := createMachine("joe@example.com", "tag:web")
policy := ACLPolicy{
ACLs: []ACL{
{
Action: "accept",
Src: []string{"autogroup:tagged"},
Dst: []string{"*:22"},
},
},
}
dst := createMachine("john@example.com")
actualRules := policy.BuildFilterRules([]Machine{*p1, *p2, *p3}, dst)
expectedSrcIPs := []string{
p3.IPv4.String(), p3.IPv6.String(),
}
sort.Strings(expectedSrcIPs)
expectedRules := []tailcfg.FilterRule{
{
SrcIPs: expectedSrcIPs,
DstPorts: []tailcfg.NetPortRange{
{
IP: "*",
Ports: tailcfg.PortRange{
First: 22,
Last: 22,
},
},
},
},
}
assert.Equal(t, expectedRules, actualRules)
}
func TestACLPolicy_BuildFilterRulesAutogroupSelf(t *testing.T) {
p1 := createMachine("john@example.com")
p2 := createMachine("jane@example.com")
@@ -302,44 +555,6 @@ func TestACLPolicy_BuildFilterRulesAutogroupSelfAndOtherDestinations(t *testing.
assert.Equal(t, expectedRules, actualRules)
}
func TestACLPolicy_BuildFilterRulesAutogroupMember(t *testing.T) {
p1 := createMachine("jane@example.com")
p2 := createMachine("jane@example.com", "tag:web")
policy := ACLPolicy{
ACLs: []ACL{
{
Action: "accept",
Src: []string{"autogroup:members"},
Dst: []string{"*:*"},
},
},
}
dst := createMachine("john@example.com")
actualRules := policy.BuildFilterRules([]Machine{*p1, *p2}, dst)
expectedRules := []tailcfg.FilterRule{
{
SrcIPs: []string{
p1.IPv4.String(),
p1.IPv6.String(),
},
DstPorts: []tailcfg.NetPortRange{
{
IP: "*",
Ports: tailcfg.PortRange{
First: 0,
Last: 65535,
},
},
},
},
}
assert.Equal(t, expectedRules, actualRules)
}
func TestACLPolicy_BuildFilterRulesAutogroupInternet(t *testing.T) {
p1 := createMachine("nick@example.com")
p2 := createMachine("jane@example.com")
@@ -628,7 +843,7 @@ func TestACLPolicy_FindAutoApprovedIPs(t *testing.T) {
name: "no match",
userName: "nick@example.com",
routableIPs: []netip.Prefix{route1, route2, route3},
expected: []netip.Prefix{},
expected: nil,
},
{
name: "exit",
@@ -640,7 +855,7 @@ func TestACLPolicy_FindAutoApprovedIPs(t *testing.T) {
name: "exit no match",
userName: "john@example.com",
routableIPs: []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")},
expected: []netip.Prefix{},
expected: nil,
},
}
@@ -651,3 +866,123 @@ func TestACLPolicy_FindAutoApprovedIPs(t *testing.T) {
})
}
}
func TestACLPolicy_BuildFilterRulesWithAdvertisedRoutes(t *testing.T) {
route1 := netip.MustParsePrefix("fd7a:115c:a1e0:b1a:0:1:a3c:0/120")
p1 := createMachine("john@example.com", "tag:trusted")
policy := ACLPolicy{
ACLs: []ACL{
{
Action: "accept",
Src: []string{"tag:trusted"},
Dst: []string{"fd7a:115c:a1e0:b1a:0:1:a3c:0/120:*"},
},
},
}
dst := createMachine("john@example.com")
dst.AllowIPs = []netip.Prefix{route1}
actualRules := policy.BuildFilterRules([]Machine{*p1}, dst)
expectedRules := []tailcfg.FilterRule{
{
SrcIPs: p1.IPs(),
DstPorts: []tailcfg.NetPortRange{
{
IP: route1.String(),
Ports: tailcfg.PortRange{
First: 0,
Last: 65535,
},
},
},
},
}
assert.Equal(t, expectedRules, actualRules)
}
func TestACLPolicy_BuildFilterRulesWildcardGrants(t *testing.T) {
ranges, err := tailcfg.ParseProtoPortRanges([]string{"*"})
require.NoError(t, err)
p1 := createMachine("john@example.com")
p2 := createMachine("jane@example.com")
policy := ACLPolicy{
Grants: []Grant{
{
Src: []string{"*"},
Dst: []string{"*"},
IP: ranges,
},
},
}
dst := createMachine("john@example.com")
actualRules := policy.BuildFilterRules([]Machine{*p1, *p2}, dst)
expectedRules := []tailcfg.FilterRule{
{
SrcIPs: []string{"*"},
DstPorts: []tailcfg.NetPortRange{
{
IP: "*",
Ports: tailcfg.PortRange{
First: 0,
Last: 65535,
},
},
},
},
}
assert.Equal(t, expectedRules, actualRules)
}
func TestACLPolicy_BuildFilterRulesWithAppGrants(t *testing.T) {
p1 := createMachine("john@example.com")
p2 := createMachine("jane@example.com")
dst := createMachine("john@example.com")
mycap := map[string]interface{}{
"channel": "alpha",
"ids": []string{"1", "2", "3"},
}
marshal, _ := json.Marshal(mycap)
policy := ACLPolicy{
Grants: []Grant{
{
Src: []string{"*"},
Dst: []string{"*"},
App: map[tailcfg.PeerCapability][]tailcfg.RawMessage{
tailcfg.PeerCapability("localtest.me/cap/test"): {tailcfg.RawMessage(marshal)},
},
},
},
}
actualRules := policy.BuildFilterRules([]Machine{*p1, *p2}, dst)
expectedRules := []tailcfg.FilterRule{
{
SrcIPs: []string{"*"},
CapGrant: []tailcfg.CapGrant{
{
Dsts: []netip.Prefix{
netip.PrefixFrom(*dst.IPv4.Addr, 32),
netip.PrefixFrom(*dst.IPv6.Addr, 128),
},
CapMap: map[tailcfg.PeerCapability][]tailcfg.RawMessage{
tailcfg.PeerCapability("localtest.me/cap/test"): {tailcfg.RawMessage(marshal)},
},
},
},
},
}
assert.Equal(t, expectedRules, actualRules)
}
+1
View File
@@ -14,6 +14,7 @@ type DNSConfig struct {
OverrideLocalDNS bool `json:"override_local_dns"`
Nameservers []string `json:"nameservers"`
Routes map[string][]string `json:"routes"`
SearchDomains []string `json:"search_domains"`
}
func (i *DNSConfig) Scan(destination interface{}) error {
-4
View File
@@ -11,10 +11,6 @@ import (
"gorm.io/gorm/schema"
)
func DefaultIAMPolicy() IAMPolicy {
return IAMPolicy{}
}
type Identity struct {
UserID string
Username string
+10 -8
View File
@@ -310,7 +310,7 @@ func (HostInfo) GormDBDataType(db *gorm.DB, field *schema.Field) string {
return ""
}
type Endpoints []string
type Endpoints []netip.AddrPort
func (hi *Endpoints) Scan(destination interface{}) error {
switch value := destination.(type) {
@@ -357,7 +357,7 @@ func (r *repository) DeleteMachine(ctx context.Context, id uint64) (bool, error)
func (r *repository) GetMachine(ctx context.Context, machineID uint64) (*Machine, error) {
var m Machine
tx := r.withContext(ctx).Preload("Tailnet").Preload("User").Take(&m, machineID)
tx := r.withContext(ctx).Preload("Tailnet").Preload("User").Preload("User.Account").Take(&m, machineID)
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
return nil, nil
@@ -458,9 +458,10 @@ func (r *repository) ListMachineByTailnet(ctx context.Context, tailnetID uint64)
tx := r.withContext(ctx).
Preload("Tailnet").
Preload("User").
Where("tailnet_id = ?", tailnetID).
Order("name asc, name_idx asc").
Joins("User").
Joins("User.Account").
Where("machines.tailnet_id = ?", tailnetID).
Order("machines.name asc, machines.name_idx asc").
Find(&machines)
if tx.Error != nil {
@@ -475,9 +476,10 @@ func (r *repository) ListMachinePeers(ctx context.Context, tailnetID uint64, key
tx := r.withContext(ctx).
Preload("Tailnet").
Preload("User").
Where("tailnet_id = ? AND machine_key <> ?", tailnetID, key).
Order("id asc").
Joins("User").
Joins("User.Account").
Where("machines.tailnet_id = ? AND machines.machine_key <> ?", tailnetID, key).
Order("machines.id asc").
Find(&machines)
if tx.Error != nil {
-4
View File
@@ -22,10 +22,6 @@ type RegistrationRequest struct {
UserID uint64
}
func (r *RegistrationRequest) IsFinished() bool {
return r.Authenticated || len(r.Error) != 0
}
type RegistrationRequestData tailcfg.RegisterRequest
func (hi *RegistrationRequestData) Scan(destination interface{}) error {
+2 -1
View File
@@ -23,10 +23,11 @@ type Repository interface {
GetAccount(ctx context.Context, accountID uint64) (*Account, error)
GetOrCreateAccount(ctx context.Context, externalID, loginName string) (*Account, bool, error)
SetAccountLastAuthenticated(ctx context.Context, accountID uint64) error
SaveTailnet(ctx context.Context, tailnet *Tailnet) error
GetTailnet(ctx context.Context, id uint64) (*Tailnet, error)
GetTailnetByAlias(ctx context.Context, alias string) (*Tailnet, error)
GetTailnetByName(ctx context.Context, name string) (*Tailnet, error)
ListTailnets(ctx context.Context) ([]Tailnet, error)
DeleteTailnet(ctx context.Context, id uint64) error
+2 -2
View File
@@ -72,9 +72,9 @@ func (r *repository) GetTailnet(ctx context.Context, id uint64) (*Tailnet, error
return &t, nil
}
func (r *repository) GetTailnetByAlias(ctx context.Context, alias string) (*Tailnet, error) {
func (r *repository) GetTailnetByName(ctx context.Context, name string) (*Tailnet, error) {
var t Tailnet
tx := r.withContext(ctx).Take(&t, "alias = ?", alias)
tx := r.withContext(ctx).Take(&t, "name = ?", name)
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
return nil, nil
+127 -101
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/jsiebens/ionscale/internal/addr"
"github.com/jsiebens/ionscale/internal/auth"
tpl "github.com/jsiebens/ionscale/internal/templates"
"github.com/labstack/echo/v4/middleware"
"github.com/mr-tron/base58"
"net/http"
@@ -40,48 +41,71 @@ type AuthenticationHandlers struct {
systemIAMPolicy *domain.IAMPolicy
}
type AuthFormData struct {
ProviderAvailable bool
Csrf string
type AuthInput struct {
Key string `param:"key"`
Flow AuthFlow `param:"flow"`
AuthKey string `query:"ak" form:"ak"`
Oidc bool `query:"oidc" form:"oidc"`
}
type TailnetSelectionData struct {
AccountID uint64
Tailnets []domain.Tailnet
SystemAdmin bool
Csrf string
type EndAuthForm struct {
AccountID uint64 `form:"aid"`
TailnetID uint64 `form:"tid"`
AsSystemAdmin bool `form:"sad"`
AuthKey string `form:"ak"`
State string `form:"state"`
}
type oauthState struct {
Key string
Flow string
Flow AuthFlow
}
type AuthFlow string
const (
AuthFlowMachineRegistration = "r"
AuthFlowClient = "c"
AuthFlowSSHCheckFlow = "s"
)
func (h *AuthenticationHandlers) StartAuth(c echo.Context) error {
ctx := c.Request().Context()
flow := c.Param("flow")
key := c.Param("key")
var input AuthInput
if err := c.Bind(&input); err != nil {
return logError(err)
}
// machine registration auth flow
if flow == "r" || flow == "" {
if req, err := h.repository.GetRegistrationRequestByKey(ctx, key); err != nil || req == nil {
if input.Flow == AuthFlowMachineRegistration {
req, err := h.repository.GetRegistrationRequestByKey(ctx, input.Key)
if err != nil || req == nil {
return logError(err)
}
if input.Oidc && h.authProvider != nil {
goto startOidc
}
if input.AuthKey != "" {
return h.endMachineRegistrationFlow(c, EndAuthForm{AuthKey: input.AuthKey}, req)
}
csrf := c.Get(middleware.DefaultCSRFConfig.ContextKey).(string)
return c.Render(http.StatusOK, "auth.html", &AuthFormData{ProviderAvailable: h.authProvider != nil, Csrf: csrf})
return c.Render(http.StatusOK, "", tpl.Auth(h.authProvider != nil, csrf))
}
// cli auth flow
if flow == "c" {
if s, err := h.repository.GetAuthenticationRequest(ctx, key); err != nil || s == nil {
if input.Flow == AuthFlowClient {
if s, err := h.repository.GetAuthenticationRequest(ctx, input.Key); err != nil || s == nil {
return logError(err)
}
}
// ssh check auth flow
if flow == "s" {
if s, err := h.repository.GetSSHActionRequest(ctx, key); err != nil || s == nil {
if input.Flow == AuthFlowSSHCheckFlow {
if s, err := h.repository.GetSSHActionRequest(ctx, input.Key); err != nil || s == nil {
return logError(err)
}
}
@@ -90,7 +114,9 @@ func (h *AuthenticationHandlers) StartAuth(c echo.Context) error {
return logError(fmt.Errorf("unable to start auth flow as no auth provider is configured"))
}
state, err := h.createState(flow, key)
startOidc:
state, err := h.createState(input.Flow, input.Key)
if err != nil {
return logError(err)
}
@@ -103,21 +129,22 @@ func (h *AuthenticationHandlers) StartAuth(c echo.Context) error {
func (h *AuthenticationHandlers) ProcessAuth(c echo.Context) error {
ctx := c.Request().Context()
key := c.Param("key")
authKey := c.FormValue("ak")
interactive := c.FormValue("s")
var input AuthInput
if err := c.Bind(&input); err != nil {
return logError(err)
}
req, err := h.repository.GetRegistrationRequestByKey(ctx, key)
req, err := h.repository.GetRegistrationRequestByKey(ctx, input.Key)
if err != nil || req == nil {
return logError(err)
}
if authKey != "" {
return h.endMachineRegistrationFlow(c, req, &oauthState{Key: key})
if input.AuthKey != "" {
return h.endMachineRegistrationFlow(c, EndAuthForm{AuthKey: input.AuthKey}, req)
}
if interactive != "" {
state, err := h.createState("r", key)
if input.Oidc {
state, err := h.createState(input.Flow, input.Key)
if err != nil {
return logError(err)
}
@@ -127,7 +154,7 @@ func (h *AuthenticationHandlers) ProcessAuth(c echo.Context) error {
return c.Redirect(http.StatusFound, redirectUrl)
}
return c.Redirect(http.StatusFound, "/a/"+key)
return c.Redirect(http.StatusFound, fmt.Sprintf("/a/%s/%s", input.Flow, input.Key))
}
func (h *AuthenticationHandlers) Callback(c echo.Context) error {
@@ -149,7 +176,11 @@ func (h *AuthenticationHandlers) Callback(c echo.Context) error {
return logError(err)
}
if state.Flow == "s" {
if err := h.repository.SetAccountLastAuthenticated(ctx, account.ID); err != nil {
return logError(err)
}
if state.Flow == AuthFlowSSHCheckFlow {
sshActionReq, err := h.repository.GetSSHActionRequest(ctx, state.Key)
if err != nil || sshActionReq == nil {
return c.Redirect(http.StatusFound, "/a/error?e=ua")
@@ -182,7 +213,7 @@ func (h *AuthenticationHandlers) Callback(c echo.Context) error {
csrf := c.Get(middleware.DefaultCSRFConfig.ContextKey).(string)
if state.Flow == "r" {
if state.Flow == AuthFlowMachineRegistration {
if len(tailnets) == 0 {
registrationRequest, err := h.repository.GetRegistrationRequestByKey(ctx, state.Key)
if err == nil && registrationRequest != nil {
@@ -191,16 +222,23 @@ func (h *AuthenticationHandlers) Callback(c echo.Context) error {
}
return c.Redirect(http.StatusFound, "/a/error?e=ua")
}
return c.Render(http.StatusOK, "tailnets.html", &TailnetSelectionData{
Csrf: csrf,
Tailnets: tailnets,
SystemAdmin: false,
AccountID: account.ID,
})
if len(tailnets) == 1 {
req, err := h.repository.GetRegistrationRequestByKey(ctx, state.Key)
if err != nil {
return logError(err)
}
if req == nil {
return logError(fmt.Errorf("invalid registration key"))
}
return h.endMachineRegistrationFlow(c, EndAuthForm{AccountID: account.ID, TailnetID: tailnets[0].ID}, req)
}
return c.Render(http.StatusOK, "", tpl.Tailnets(account.ID, false, tailnets, csrf))
}
if state.Flow == "c" {
isSystemAdmin, err := h.isSystemAdmin(ctx, user)
if state.Flow == AuthFlowClient {
isSystemAdmin, err := h.isSystemAdmin(user)
if err != nil {
return logError(err)
}
@@ -213,103 +251,74 @@ func (h *AuthenticationHandlers) Callback(c echo.Context) error {
}
return c.Redirect(http.StatusFound, "/a/error?e=ua")
}
return c.Render(http.StatusOK, "tailnets.html", &TailnetSelectionData{
Csrf: csrf,
Tailnets: tailnets,
SystemAdmin: isSystemAdmin,
AccountID: account.ID,
})
return c.Render(http.StatusOK, "", tpl.Tailnets(account.ID, isSystemAdmin, tailnets, csrf))
}
return echo.NewHTTPError(http.StatusNotFound)
}
func (h *AuthenticationHandlers) isSystemAdmin(ctx context.Context, u *auth.User) (bool, error) {
return h.systemIAMPolicy.EvaluatePolicy(&domain.Identity{UserID: u.ID, Email: u.Name, Attr: u.Attr})
}
func (h *AuthenticationHandlers) listAvailableTailnets(ctx context.Context, u *auth.User) ([]domain.Tailnet, error) {
var result = []domain.Tailnet{}
tailnets, err := h.repository.ListTailnets(ctx)
if err != nil {
return nil, err
}
for _, t := range tailnets {
approved, err := t.IAMPolicy.EvaluatePolicy(&domain.Identity{UserID: u.ID, Email: u.Name, Attr: u.Attr})
if err != nil {
return nil, err
}
if approved {
result = append(result, t)
}
}
return result, nil
}
func (h *AuthenticationHandlers) EndOAuth(c echo.Context) error {
func (h *AuthenticationHandlers) EndAuth(c echo.Context) error {
ctx := c.Request().Context()
state, err := h.readState(c.QueryParam("state"))
var form EndAuthForm
if err := c.Bind(&form); err != nil {
return logError(err)
}
state, err := h.readState(form.State)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid state parameter")
}
if state.Flow == "r" {
if state.Flow == AuthFlowMachineRegistration {
req, err := h.repository.GetRegistrationRequestByKey(ctx, state.Key)
if err != nil || req == nil {
return logError(err)
}
return h.endMachineRegistrationFlow(c, req, state)
return h.endMachineRegistrationFlow(c, form, req)
}
req, err := h.repository.GetAuthenticationRequest(ctx, state.Key)
if err != nil || req == nil {
return logError(err)
if state.Flow == AuthFlowClient {
req, err := h.repository.GetAuthenticationRequest(ctx, state.Key)
if err != nil || req == nil {
return logError(err)
}
return h.endCliAuthenticationFlow(c, form, req)
}
return h.endCliAuthenticationFlow(c, req, state)
return echo.NewHTTPError(http.StatusBadRequest, "Invalid state parameter")
}
func (h *AuthenticationHandlers) Success(c echo.Context) error {
s := c.QueryParam("s")
switch s {
case "nma":
return c.Render(http.StatusOK, "newmachine.html", nil)
return c.Render(http.StatusOK, "", tpl.NewMachine())
}
return c.Render(http.StatusOK, "success.html", nil)
return c.Render(http.StatusOK, "", tpl.Success())
}
func (h *AuthenticationHandlers) Error(c echo.Context) error {
e := c.QueryParam("e")
switch e {
case "iak":
return c.Render(http.StatusForbidden, "invalidauthkey.html", nil)
return c.Render(http.StatusForbidden, "", tpl.InvalidAuthKey())
case "ua":
return c.Render(http.StatusForbidden, "unauthorized.html", nil)
return c.Render(http.StatusForbidden, "", tpl.Unauthorized())
case "nto":
return c.Render(http.StatusForbidden, "notagowner.html", nil)
return c.Render(http.StatusForbidden, "", tpl.NotTagOwner())
case "nmo":
return c.Render(http.StatusForbidden, "notmachineowner.html", nil)
return c.Render(http.StatusForbidden, "", tpl.NotMachineOwner())
}
return c.Render(http.StatusOK, "error.html", nil)
return c.Render(http.StatusOK, "", tpl.Error())
}
type TailnetSelectionForm struct {
AccountID uint64 `form:"aid"`
TailnetID uint64 `form:"tid"`
AsSystemAdmin bool `form:"sad"`
AuthKey string `form:"ak"`
}
func (h *AuthenticationHandlers) endCliAuthenticationFlow(c echo.Context, req *domain.AuthenticationRequest, state *oauthState) error {
func (h *AuthenticationHandlers) endCliAuthenticationFlow(c echo.Context, form EndAuthForm, req *domain.AuthenticationRequest) error {
ctx := c.Request().Context()
var form TailnetSelectionForm
if err := c.Bind(&form); err != nil {
return logError(err)
}
account, err := h.repository.GetAccount(ctx, form.AccountID)
if err != nil {
return logError(err)
@@ -367,14 +376,9 @@ func (h *AuthenticationHandlers) endCliAuthenticationFlow(c echo.Context, req *d
return c.Redirect(http.StatusFound, "/a/success")
}
func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, registrationRequest *domain.RegistrationRequest, state *oauthState) error {
func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, form EndAuthForm, registrationRequest *domain.RegistrationRequest) error {
ctx := c.Request().Context()
var form TailnetSelectionForm
if err := c.Bind(&form); err != nil {
return logError(err)
}
req := tailcfg.RegisterRequest(registrationRequest.Data)
machineKey := registrationRequest.MachineKey
nodeKey := req.NodeKey.String()
@@ -538,6 +542,28 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
}
}
func (h *AuthenticationHandlers) isSystemAdmin(u *auth.User) (bool, error) {
return h.systemIAMPolicy.EvaluatePolicy(&domain.Identity{UserID: u.ID, Email: u.Name, Attr: u.Attr})
}
func (h *AuthenticationHandlers) listAvailableTailnets(ctx context.Context, u *auth.User) ([]domain.Tailnet, error) {
var result = []domain.Tailnet{}
tailnets, err := h.repository.ListTailnets(ctx)
if err != nil {
return nil, err
}
for _, t := range tailnets {
approved, err := t.IAMPolicy.EvaluatePolicy(&domain.Identity{UserID: u.ID, Email: u.Name, Attr: u.Attr})
if err != nil {
return nil, err
}
if approved {
result = append(result, t)
}
}
return result, nil
}
func (h *AuthenticationHandlers) exchangeUser(code string) (*auth.User, error) {
redirectUrl := h.config.CreateUrl("/a/callback")
@@ -549,7 +575,7 @@ func (h *AuthenticationHandlers) exchangeUser(code string) (*auth.User, error) {
return user, nil
}
func (h *AuthenticationHandlers) createState(flow string, key string) (string, error) {
func (h *AuthenticationHandlers) createState(flow AuthFlow, key string) (string, error) {
stateMap := oauthState{Key: key, Flow: flow}
marshal, err := json.Marshal(&stateMap)
if err != nil {
+8 -15
View File
@@ -1,38 +1,31 @@
package handlers
import (
"github.com/jsiebens/ionscale/internal/bind"
"github.com/jsiebens/ionscale/internal/dns"
"github.com/labstack/echo/v4"
"net"
"net/http"
"strings"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"time"
)
func NewDNSHandlers(createBinder bind.Factory, provider dns.Provider) *DNSHandlers {
func NewDNSHandlers(_ key.MachinePublic, provider dns.Provider) *DNSHandlers {
return &DNSHandlers{
createBinder: createBinder,
provider: provider,
provider: provider,
}
}
type DNSHandlers struct {
createBinder bind.Factory
provider dns.Provider
provider dns.Provider
}
func (h *DNSHandlers) SetDNS(c echo.Context) error {
ctx := c.Request().Context()
binder, err := h.createBinder(c)
if err != nil {
return logError(err)
}
req := &tailcfg.SetDNSRequest{}
if err := binder.BindRequest(c, req); err != nil {
if err := c.Bind(req); err != nil {
return logError(err)
}
@@ -58,16 +51,16 @@ func (h *DNSHandlers) SetDNS(c echo.Context) error {
txtrecords, _ := net.LookupTXT(req.Name)
for _, txt := range txtrecords {
if txt == req.Value {
return binder.WriteResponse(c, http.StatusOK, tailcfg.SetDNSResponse{})
return c.JSON(http.StatusOK, tailcfg.SetDNSResponse{})
}
}
case <-timeout:
return binder.WriteResponse(c, http.StatusOK, tailcfg.SetDNSResponse{})
return c.JSON(http.StatusOK, tailcfg.SetDNSResponse{})
case <-notify:
return nil
}
}
}
return binder.WriteResponse(c, http.StatusOK, tailcfg.SetDNSResponse{})
return c.JSON(http.StatusOK, tailcfg.SetDNSResponse{})
}
+59 -52
View File
@@ -2,65 +2,38 @@ package handlers
import (
"fmt"
"github.com/go-jose/go-jose/v3"
"github.com/golang-jwt/jwt/v4"
"github.com/jsiebens/ionscale/internal/bind"
"github.com/jsiebens/ionscale/internal/config"
"github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/util"
"github.com/labstack/echo/v4"
"gopkg.in/square/go-jose.v2"
"net/http"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"time"
)
func NewIDTokenHandlers(createBinder bind.Factory, config *config.Config, repository domain.Repository) *IDTokenHandlers {
func NewIDTokenHandlers(machineKey key.MachinePublic, config *config.Config, repository domain.Repository) *IDTokenHandlers {
return &IDTokenHandlers{
issuer: config.ServerUrl,
jwksUri: config.CreateUrl("/.well-known/jwks"),
createBinder: createBinder,
repository: repository,
machineKey: machineKey,
issuer: config.ServerUrl,
repository: repository,
}
}
func NewOIDCConfigHandlers(config *config.Config, repository domain.Repository) *OIDCConfigHandlers {
return &OIDCConfigHandlers{
issuer: config.ServerUrl,
jwksUri: config.CreateUrl("/.well-known/jwks"),
repository: repository,
}
}
type IDTokenHandlers struct {
issuer string
jwksUri string
createBinder bind.Factory
repository domain.Repository
}
func (h *IDTokenHandlers) OpenIDConfig(c echo.Context) error {
v := map[string]interface{}{}
v["issuer"] = h.issuer
v["jwks_uri"] = h.jwksUri
v["subject_types_supported"] = []string{"public"}
v["response_types_supported"] = []string{"id_token"}
v["scopes_supported"] = []string{"openid"}
v["id_token_signing_alg_values_supported"] = []string{"RS256"}
v["claims_supported"] = []string{
"sub",
"aud",
"exp",
"iat",
"iss",
"jti",
"nbf",
}
return c.JSON(http.StatusOK, v)
}
func (h *IDTokenHandlers) Jwks(c echo.Context) error {
keySet, err := h.repository.GetJSONWebKeySet(c.Request().Context())
if err != nil {
return logError(err)
}
pub := jose.JSONWebKey{Key: keySet.Key.Public(), KeyID: keySet.Key.Id, Algorithm: "RS256", Use: "sig"}
set := jose.JSONWebKeySet{Keys: []jose.JSONWebKey{pub}}
return c.JSON(http.StatusOK, set)
machineKey key.MachinePublic
issuer string
repository domain.Repository
}
func (h *IDTokenHandlers) FetchToken(c echo.Context) error {
@@ -71,17 +44,12 @@ func (h *IDTokenHandlers) FetchToken(c echo.Context) error {
return logError(err)
}
binder, err := h.createBinder(c)
if err != nil {
return logError(err)
}
req := &tailcfg.TokenRequest{}
if err := binder.BindRequest(c, req); err != nil {
if err := c.Bind(req); err != nil {
return logError(err)
}
machineKey := binder.Peer().String()
machineKey := h.machineKey.String()
nodeKey := req.NodeKey.String()
var m *domain.Machine
@@ -134,7 +102,46 @@ func (h *IDTokenHandlers) FetchToken(c echo.Context) error {
}
resp := tailcfg.TokenResponse{IDToken: jwtB64}
return binder.WriteResponse(c, http.StatusOK, resp)
return c.JSON(http.StatusOK, resp)
}
type OIDCConfigHandlers struct {
issuer string
jwksUri string
repository domain.Repository
}
func (h *OIDCConfigHandlers) OpenIDConfig(c echo.Context) error {
v := map[string]interface{}{}
v["issuer"] = h.issuer
v["jwks_uri"] = h.jwksUri
v["subject_types_supported"] = []string{"public"}
v["response_types_supported"] = []string{"id_token"}
v["scopes_supported"] = []string{"openid"}
v["id_token_signing_alg_values_supported"] = []string{"RS256"}
v["claims_supported"] = []string{
"sub",
"aud",
"exp",
"iat",
"iss",
"jti",
"nbf",
}
return c.JSON(http.StatusOK, v)
}
func (h *OIDCConfigHandlers) Jwks(c echo.Context) error {
keySet, err := h.repository.GetJSONWebKeySet(c.Request().Context())
if err != nil {
return logError(err)
}
pub := jose.JSONWebKey{Key: keySet.Key.Public(), KeyID: keySet.Key.Id, Algorithm: "RS256", Use: "sig"}
set := jose.JSONWebKeySet{Keys: []jose.JSONWebKey{pub}}
return c.JSON(http.StatusOK, set)
}
func (h *IDTokenHandlers) names(m *domain.Machine) (string, string, string) {
+2 -6
View File
@@ -1,17 +1,13 @@
package handlers
import (
tpl "github.com/jsiebens/ionscale/internal/templates"
"github.com/jsiebens/ionscale/internal/version"
"github.com/labstack/echo/v4"
)
func IndexHandler(code int) echo.HandlerFunc {
return func(c echo.Context) error {
info, s := version.GetReleaseInfo()
data := map[string]interface{}{
"Version": info,
"Revision": s,
}
return c.Render(code, "index.html", data)
return c.Render(code, "", tpl.Index(version.GetReleaseInfo()))
}
}
+32
View File
@@ -41,3 +41,35 @@ func (h *NoiseHandlers) Upgrade(c echo.Context) error {
}
return nil
}
type JsonBinder struct {
echo.DefaultBinder
}
func (b JsonBinder) Bind(i interface{}, c echo.Context) error {
if err := b.BindPathParams(c, i); err != nil {
return err
}
method := c.Request().Method
if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead {
if err := b.BindQueryParams(c, i); err != nil {
return err
}
}
if c.Request().ContentLength == 0 {
return nil
}
if err := c.Echo().JSONSerializer.Deserialize(c, i); err != nil {
switch err.(type) {
case *echo.HTTPError:
return err
default:
return echo.NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
}
return nil
}
+56 -158
View File
@@ -2,26 +2,29 @@ package handlers
import (
"context"
"github.com/jsiebens/ionscale/internal/bind"
"encoding/binary"
"encoding/json"
"github.com/jsiebens/ionscale/internal/config"
"github.com/jsiebens/ionscale/internal/core"
"github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/mapping"
"github.com/klauspost/compress/zstd"
"github.com/labstack/echo/v4"
"net/http"
"net/netip"
"sync"
"tailscale.com/smallzstd"
"tailscale.com/tailcfg"
"tailscale.com/types/opt"
"tailscale.com/types/key"
"time"
)
func NewPollNetMapHandler(
createBinder bind.Factory,
machineKey key.MachinePublic,
sessionManager core.PollMapSessionManager,
repository domain.Repository) *PollNetMapHandler {
handler := &PollNetMapHandler{
createBinder: createBinder,
machineKey: machineKey,
sessionManager: sessionManager,
repository: repository,
}
@@ -30,28 +33,24 @@ func NewPollNetMapHandler(
}
type PollNetMapHandler struct {
createBinder bind.Factory
machineKey key.MachinePublic
repository domain.Repository
sessionManager core.PollMapSessionManager
}
func (h *PollNetMapHandler) PollNetMap(c echo.Context) error {
ctx := c.Request().Context()
binder, err := h.createBinder(c)
if err != nil {
return logError(err)
}
req := &tailcfg.MapRequest{}
if err := binder.BindRequest(c, req); err != nil {
if err := c.Bind(req); err != nil {
return logError(err)
}
machineKey := binder.Peer().String()
machineKey := h.machineKey.String()
nodeKey := req.NodeKey.String()
var m *domain.Machine
m, err = h.repository.GetMachineByKeys(ctx, machineKey, nodeKey)
m, err := h.repository.GetMachineByKeys(ctx, machineKey, nodeKey)
if err != nil {
return logError(err)
}
@@ -61,13 +60,13 @@ func (h *PollNetMapHandler) PollNetMap(c echo.Context) error {
}
if req.ReadOnly {
return h.handleReadOnly(c, binder, m, req)
return h.handleReadOnly(c, m, req)
} else {
return h.handleUpdate(c, binder, m, req)
return h.handleUpdate(c, m, req)
}
}
func (h *PollNetMapHandler) handleUpdate(c echo.Context, binder bind.Binder, m *domain.Machine, mapRequest *tailcfg.MapRequest) error {
func (h *PollNetMapHandler) handleUpdate(c echo.Context, m *domain.Machine, mapRequest *tailcfg.MapRequest) error {
ctx := c.Request().Context()
now := time.Now().UTC()
@@ -84,16 +83,15 @@ func (h *PollNetMapHandler) handleUpdate(c echo.Context, binder bind.Binder, m *
tailnetID := m.TailnetID
machineID := m.ID
h.sessionManager.NotifyAll(tailnetID)
h.sessionManager.NotifyAll(tailnetID, m.ID)
if !mapRequest.Stream {
return c.String(http.StatusOK, "")
}
var syncedPeers = make(map[uint64]bool)
var derpMapChecksum = ""
mapper := mapping.NewPollNetMapper(mapRequest, m.ID, h.repository, h.sessionManager)
response, syncedPeers, derpMapChecksum, err := h.createMapResponse(m, binder, mapRequest, false, make(map[uint64]bool), derpMapChecksum)
response, err := h.createMapResponse(mapper, false, mapRequest.Compress)
if err != nil {
return logError(err)
}
@@ -104,7 +102,7 @@ func (h *PollNetMapHandler) handleUpdate(c echo.Context, binder bind.Binder, m *
// Listen to connection close
notify := c.Request().Context().Done()
keepAliveResponse, err := h.createKeepAliveResponse(binder, mapRequest)
keepAliveResponse, err := h.createKeepAliveResponse(mapRequest)
if err != nil {
return logError(err)
}
@@ -157,7 +155,7 @@ func (h *PollNetMapHandler) handleUpdate(c echo.Context, binder bind.Binder, m *
var payload []byte
var payloadErr error
payload, syncedPeers, derpMapChecksum, payloadErr = h.createMapResponse(machine, binder, mapRequest, true, syncedPeers, derpMapChecksum)
payload, payloadErr = h.createMapResponse(mapper, true, mapRequest.Compress)
if payloadErr != nil {
return payloadErr
@@ -176,7 +174,7 @@ func (h *PollNetMapHandler) handleUpdate(c echo.Context, binder bind.Binder, m *
}
}
func (h *PollNetMapHandler) handleReadOnly(c echo.Context, binder bind.Binder, m *domain.Machine, request *tailcfg.MapRequest) error {
func (h *PollNetMapHandler) handleReadOnly(c echo.Context, m *domain.Machine, request *tailcfg.MapRequest) error {
ctx := c.Request().Context()
m.HostInfo = domain.HostInfo(*request.Hostinfo)
@@ -186,167 +184,67 @@ func (h *PollNetMapHandler) handleReadOnly(c echo.Context, binder bind.Binder, m
return logError(err)
}
response, _, _, err := h.createMapResponse(m, binder, request, false, map[uint64]bool{}, "")
mapper := mapping.NewPollNetMapper(request, m.ID, h.repository, h.sessionManager)
payload, err := h.createMapResponse(mapper, false, request.Compress)
if err != nil {
return logError(err)
}
_, err = c.Response().Write(response)
_, err = c.Response().Write(payload)
return logError(err)
}
func (h *PollNetMapHandler) createKeepAliveResponse(binder bind.Binder, request *tailcfg.MapRequest) ([]byte, error) {
func (h *PollNetMapHandler) createKeepAliveResponse(request *tailcfg.MapRequest) ([]byte, error) {
mapResponse := &tailcfg.MapResponse{
KeepAlive: true,
}
return binder.Marshal(request.Compress, mapResponse)
return h.marshalResponse(request.Compress, mapResponse)
}
func (h *PollNetMapHandler) createMapResponse(m *domain.Machine, binder bind.Binder, request *tailcfg.MapRequest, delta bool, prevSyncedPeerIDs map[uint64]bool, prevDerpMapChecksum string) ([]byte, map[uint64]bool, string, error) {
ctx := context.TODO()
prc := &primaryRoutesCollector{flagged: map[netip.Prefix]bool{}}
tailnet, err := h.repository.GetTailnet(ctx, m.TailnetID)
func (h *PollNetMapHandler) createMapResponse(m *mapping.PollNetMapper, delta bool, compress string) ([]byte, error) {
response, err := m.CreateMapResponse(context.Background(), delta)
if err != nil {
return nil, nil, "", err
return nil, err
}
return h.marshalResponse(compress, response)
}
serviceUser, _, err := h.repository.GetOrCreateServiceUser(ctx, tailnet)
func (h *PollNetMapHandler) marshalResponse(compress string, v interface{}) ([]byte, error) {
var payload []byte
marshalled, err := json.Marshal(v)
if err != nil {
return nil, nil, "", err
return nil, err
}
hostinfo := tailcfg.Hostinfo(m.HostInfo)
node, user, err := mapping.ToNode(m, tailnet, serviceUser, false, true, prc.filter)
if err != nil {
return nil, nil, "", err
}
policies := tailnet.ACLPolicy
var users = []tailcfg.UserProfile{*user}
var changedPeers []*tailcfg.Node
var removedPeers []tailcfg.NodeID
candidatePeers, err := h.repository.ListMachinePeers(ctx, m.TailnetID, m.MachineKey)
if err != nil {
return nil, nil, "", err
}
syncedPeerIDs := map[uint64]bool{}
syncedUserIDs := map[tailcfg.UserID]bool{user.ID: true}
for _, peer := range candidatePeers {
if peer.IsExpired() {
continue
}
if policies.IsValidPeer(m, &peer) || policies.IsValidPeer(&peer, m) {
isConnected := h.sessionManager.HasSession(peer.TailnetID, peer.ID)
n, u, err := mapping.ToNode(&peer, tailnet, serviceUser, true, isConnected, prc.filter)
if err != nil {
return nil, nil, "", err
}
changedPeers = append(changedPeers, n)
syncedPeerIDs[peer.ID] = true
delete(prevSyncedPeerIDs, peer.ID)
if _, ok := syncedUserIDs[u.ID]; !ok {
users = append(users, *u)
syncedUserIDs[u.ID] = true
}
}
}
for p, _ := range prevSyncedPeerIDs {
removedPeers = append(removedPeers, tailcfg.NodeID(p))
}
dnsConfig := tailnet.DNSConfig
derpMap, err := m.Tailnet.GetDERPMap(ctx, h.repository)
if err != nil {
return nil, nil, "", err
}
filterRules := policies.BuildFilterRules(candidatePeers, m)
controlTime := time.Now().UTC()
var mapResponse *tailcfg.MapResponse
if !delta {
mapResponse = &tailcfg.MapResponse{
KeepAlive: false,
Node: node,
DNSConfig: mapping.ToDNSConfig(m, &m.Tailnet, &dnsConfig),
PacketFilter: filterRules,
DERPMap: &derpMap.DERPMap,
Domain: domain.SanitizeTailnetName(m.Tailnet.Name),
Peers: changedPeers,
UserProfiles: users,
ControlTime: &controlTime,
CollectServices: optBool(tailnet.ServiceCollectionEnabled),
Debug: &tailcfg.Debug{
DisableLogTail: true,
},
}
if compress == "zstd" {
payload = zstdEncode(marshalled)
} else {
mapResponse = &tailcfg.MapResponse{
Node: node,
DNSConfig: mapping.ToDNSConfig(m, &m.Tailnet, &dnsConfig),
PacketFilter: filterRules,
Domain: domain.SanitizeTailnetName(m.Tailnet.Name),
PeersChanged: changedPeers,
PeersRemoved: removedPeers,
UserProfiles: users,
ControlTime: &controlTime,
CollectServices: optBool(tailnet.ServiceCollectionEnabled),
}
if prevDerpMapChecksum != derpMap.Checksum {
mapResponse.DERPMap = &derpMap.DERPMap
}
payload = marshalled
}
if tailnet.SSHEnabled && hostinfo.TailscaleSSHEnabled() {
mapResponse.SSHPolicy = policies.BuildSSHPolicy(candidatePeers, m)
}
data := make([]byte, 4)
binary.LittleEndian.PutUint32(data, uint32(len(payload)))
data = append(data, payload...)
if request.OmitPeers {
mapResponse.PeersChanged = nil
mapResponse.PeersRemoved = nil
mapResponse.Peers = nil
}
payload, err := binder.Marshal(request.Compress, mapResponse)
return payload, syncedPeerIDs, derpMap.Checksum, nil
return data, nil
}
func optBool(v bool) opt.Bool {
b := opt.Bool("")
b.Set(v)
return b
func zstdEncode(in []byte) []byte {
encoder := zstdEncoderPool.Get().(*zstd.Encoder)
out := encoder.EncodeAll(in, nil)
_ = encoder.Close()
zstdEncoderPool.Put(encoder)
return out
}
type primaryRoutesCollector struct {
flagged map[netip.Prefix]bool
}
func (p *primaryRoutesCollector) filter(m *domain.Machine) []netip.Prefix {
var result = []netip.Prefix{}
for _, r := range m.AllowIPs {
if _, ok := p.flagged[r]; r.Bits() != 0 && !ok {
result = append(result, r)
p.flagged[r] = true
var zstdEncoderPool = &sync.Pool{
New: func() any {
encoder, err := smallzstd.NewEncoder(nil, zstd.WithEncoderLevel(zstd.SpeedFastest))
if err != nil {
panic(err)
}
}
for _, r := range m.AutoAllowIPs {
if _, ok := p.flagged[r]; r.Bits() != 0 && !ok {
result = append(result, r)
p.flagged[r] = true
}
}
return result
return encoder
},
}
+69
View File
@@ -0,0 +1,69 @@
package handlers
import (
"fmt"
"github.com/jsiebens/ionscale/internal/dns"
"github.com/jsiebens/ionscale/internal/domain"
"github.com/labstack/echo/v4"
"net/http"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)
func NewQueryFeatureHandlers(machineKey key.MachinePublic, dnsProvider dns.Provider, repository domain.Repository) *QueryFeatureHandlers {
return &QueryFeatureHandlers{
machineKey: machineKey,
dnsProvider: dnsProvider,
repository: repository,
}
}
type QueryFeatureHandlers struct {
machineKey key.MachinePublic
dnsProvider dns.Provider
repository domain.Repository
}
func (h *QueryFeatureHandlers) QueryFeature(c echo.Context) error {
ctx := c.Request().Context()
req := new(tailcfg.QueryFeatureRequest)
if err := c.Bind(req); err != nil {
return logError(err)
}
machineKey := h.machineKey.String()
nodeKey := req.NodeKey.String()
resp := tailcfg.QueryFeatureResponse{Complete: true}
switch req.Feature {
case "serve":
machine, err := h.repository.GetMachineByKeys(ctx, machineKey, nodeKey)
if err != nil {
return err
}
if machine == nil {
return echo.NewHTTPError(http.StatusBadRequest)
}
if h.dnsProvider == nil || !machine.Tailnet.DNSConfig.HttpsCertsEnabled {
resp.Text = fmt.Sprintf(serverMessage, machine.Tailnet.Name)
resp.Complete = false
}
case "funnel":
resp.Text = fmt.Sprintf("Sorry, ionscale has no support for feature '%s'\n", req.Feature)
resp.Complete = false
default:
resp.Text = fmt.Sprintf("Unknown feature request '%s'\n", req.Feature)
resp.Complete = false
}
return c.JSON(http.StatusOK, resp)
}
const serverMessage = `Enabling HTTPS is required to use Serve:
ionscale tailnets set-dns --tailnet %s --https-certs=true --magic-dns
`
+33 -30
View File
@@ -3,7 +3,6 @@ package handlers
import (
"context"
"github.com/jsiebens/ionscale/internal/addr"
"github.com/jsiebens/ionscale/internal/bind"
"github.com/jsiebens/ionscale/internal/config"
"github.com/jsiebens/ionscale/internal/core"
"github.com/jsiebens/ionscale/internal/domain"
@@ -13,17 +12,18 @@ import (
"net/http"
"net/netip"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/util/dnsname"
"time"
)
func NewRegistrationHandlers(
createBinder bind.Factory,
machineKey key.MachinePublic,
config *config.Config,
sessionManager core.PollMapSessionManager,
repository domain.Repository) *RegistrationHandlers {
return &RegistrationHandlers{
createBinder: createBinder,
machineKey: machineKey,
sessionManager: sessionManager,
repository: repository,
config: config,
@@ -31,7 +31,7 @@ func NewRegistrationHandlers(
}
type RegistrationHandlers struct {
createBinder bind.Factory
machineKey key.MachinePublic
repository domain.Repository
sessionManager core.PollMapSessionManager
config *config.Config
@@ -40,21 +40,16 @@ type RegistrationHandlers struct {
func (h *RegistrationHandlers) Register(c echo.Context) error {
ctx := c.Request().Context()
binder, err := h.createBinder(c)
if err != nil {
return logError(err)
}
req := &tailcfg.RegisterRequest{}
if err := binder.BindRequest(c, req); err != nil {
if err := c.Bind(req); err != nil {
return logError(err)
}
machineKey := binder.Peer().String()
machineKey := h.machineKey.String()
nodeKey := req.NodeKey.String()
var m *domain.Machine
m, err = h.repository.GetMachineByKeys(ctx, machineKey, nodeKey)
m, err := h.repository.GetMachineByKeys(ctx, machineKey, nodeKey)
if err != nil {
return logError(err)
@@ -63,7 +58,7 @@ func (h *RegistrationHandlers) Register(c echo.Context) error {
if m != nil {
if m.IsExpired() {
response := tailcfg.RegisterResponse{NodeKeyExpired: true}
return binder.WriteResponse(c, http.StatusOK, response)
return c.JSON(http.StatusOK, response)
}
if !req.Expiry.IsZero() && req.Expiry.Before(time.Now()) {
@@ -82,7 +77,7 @@ func (h *RegistrationHandlers) Register(c echo.Context) error {
}
response := tailcfg.RegisterResponse{NodeKeyExpired: true}
return binder.WriteResponse(c, http.StatusOK, response)
return c.JSON(http.StatusOK, response)
}
sanitizeHostname := dnsname.SanitizeHostname(req.Hostinfo.Hostname)
@@ -111,17 +106,17 @@ func (h *RegistrationHandlers) Register(c echo.Context) error {
Login: tLogin,
}
return binder.WriteResponse(c, http.StatusOK, response)
return c.JSON(http.StatusOK, response)
}
return h.authenticateMachine(c, binder, machineKey, req)
return h.authenticateMachine(c, machineKey, req)
}
func (h *RegistrationHandlers) authenticateMachine(c echo.Context, binder bind.Binder, machineKey string, req *tailcfg.RegisterRequest) error {
func (h *RegistrationHandlers) authenticateMachine(c echo.Context, machineKey string, req *tailcfg.RegisterRequest) error {
ctx := c.Request().Context()
if req.Followup != "" {
return h.followup(c, binder, req)
return h.followup(c, req)
}
if req.Auth.AuthKey == "" {
@@ -138,17 +133,17 @@ func (h *RegistrationHandlers) authenticateMachine(c echo.Context, binder bind.B
err := h.repository.SaveRegistrationRequest(ctx, &request)
if err != nil {
response := tailcfg.RegisterResponse{MachineAuthorized: false, Error: "something went wrong"}
return binder.WriteResponse(c, http.StatusOK, response)
return c.JSON(http.StatusOK, response)
}
response := tailcfg.RegisterResponse{AuthURL: authUrl}
return binder.WriteResponse(c, http.StatusOK, response)
return c.JSON(http.StatusOK, response)
} else {
return h.authenticateMachineWithAuthKey(c, binder, machineKey, req)
return h.authenticateMachineWithAuthKey(c, machineKey, req)
}
}
func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, binder bind.Binder, machineKey string, req *tailcfg.RegisterRequest) error {
func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, machineKey string, req *tailcfg.RegisterRequest) error {
ctx := c.Request().Context()
nodeKey := req.NodeKey.String()
@@ -159,7 +154,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, bi
if authKey == nil {
response := tailcfg.RegisterResponse{MachineAuthorized: false, Error: "invalid auth key"}
return binder.WriteResponse(c, http.StatusOK, response)
return c.JSON(http.StatusOK, response)
}
tailnet := authKey.Tailnet
@@ -167,7 +162,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, bi
if err := tailnet.ACLPolicy.CheckTagOwners(req.Hostinfo.RequestTags, &user); err != nil {
response := tailcfg.RegisterResponse{MachineAuthorized: false, Error: err.Error()}
return binder.WriteResponse(c, http.StatusOK, response)
return c.JSON(http.StatusOK, response)
}
registeredTags := authKey.Tags
@@ -254,10 +249,10 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, bi
Login: tLogin,
}
return binder.WriteResponse(c, http.StatusOK, response)
return c.JSON(http.StatusOK, response)
}
func (h *RegistrationHandlers) followup(c echo.Context, binder bind.Binder, req *tailcfg.RegisterRequest) error {
func (h *RegistrationHandlers) followup(c echo.Context, req *tailcfg.RegisterRequest) error {
// Listen to connection close
ctx := c.Request().Context()
notify := ctx.Done()
@@ -265,7 +260,7 @@ func (h *RegistrationHandlers) followup(c echo.Context, binder bind.Binder, req
defer func() { tick.Stop() }()
machineKey := binder.Peer().String()
machineKey := h.machineKey.String()
for {
select {
@@ -274,10 +269,10 @@ func (h *RegistrationHandlers) followup(c echo.Context, binder bind.Binder, req
if err != nil || m == nil {
response := tailcfg.RegisterResponse{MachineAuthorized: false, Error: "something went wrong"}
return binder.WriteResponse(c, http.StatusOK, response)
return c.JSON(http.StatusOK, response)
}
if m != nil && m.IsFinished() {
if m != nil && m.Authenticated {
user, err := h.repository.GetUser(ctx, m.UserID)
if err != nil {
return err
@@ -291,7 +286,15 @@ func (h *RegistrationHandlers) followup(c echo.Context, binder bind.Binder, req
User: u,
Login: l,
}
return binder.WriteResponse(c, http.StatusOK, response)
return c.JSON(http.StatusOK, response)
}
if m != nil && len(m.Error) != 0 {
response := tailcfg.RegisterResponse{
MachineAuthorized: len(m.Error) != 0,
Error: m.Error,
}
return c.JSON(http.StatusOK, response)
}
case <-notify:
return nil
+42 -23
View File
@@ -2,48 +2,72 @@ package handlers
import (
"fmt"
"github.com/jsiebens/ionscale/internal/bind"
"github.com/jsiebens/ionscale/internal/config"
"github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/util"
"github.com/labstack/echo/v4"
"net/http"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"time"
)
func NewSSHActionHandlers(createBinder bind.Factory, config *config.Config, repository domain.Repository) *SSHActionHandlers {
func NewSSHActionHandlers(machineKey key.MachinePublic, config *config.Config, repository domain.Repository) *SSHActionHandlers {
return &SSHActionHandlers{
createBinder: createBinder,
repository: repository,
config: config,
machineKey: machineKey,
repository: repository,
config: config,
}
}
type SSHActionHandlers struct {
createBinder bind.Factory
repository domain.Repository
config *config.Config
machineKey key.MachinePublic
repository domain.Repository
config *config.Config
}
type sshActionRequestData struct {
SrcMachineID uint64 `param:"src_machine_id"`
DstMachineID uint64 `param:"dst_machine_id"`
CheckPeriod string `param:"check_period"`
}
func (h *SSHActionHandlers) StartAuth(c echo.Context) error {
ctx := c.Request().Context()
binder, err := h.createBinder(c)
if err != nil {
return logError(err)
}
data := new(sshActionRequestData)
if err = c.Bind(data); err != nil {
if err := c.Bind(data); err != nil {
return logError(err)
}
if data.CheckPeriod != "" && data.CheckPeriod != "always" {
checkPeriod, err := time.ParseDuration(data.CheckPeriod)
if err != nil {
_ = logError(err)
goto check
}
machine, err := h.repository.GetMachine(ctx, data.SrcMachineID)
if err != nil {
return logError(err)
}
if machine.User.Account != nil && machine.User.Account.LastAuthenticated != nil {
sinceLastAuthentication := time.Since(*machine.User.Account.LastAuthenticated)
if sinceLastAuthentication < checkPeriod {
resp := &tailcfg.SSHAction{
Accept: true,
AllowAgentForwarding: true,
AllowLocalPortForwarding: true,
}
return c.JSON(http.StatusOK, resp)
}
}
}
check:
key := util.RandStringBytes(8)
request := &domain.SSHActionRequest{
Key: key,
@@ -63,7 +87,7 @@ func (h *SSHActionHandlers) StartAuth(c echo.Context) error {
HoldAndDelegate: fmt.Sprintf("https://unused/machine/ssh/action/check/%s", key),
}
return binder.WriteResponse(c, http.StatusOK, resp)
return c.JSON(http.StatusOK, resp)
}
func (h *SSHActionHandlers) CheckAuth(c echo.Context) error {
@@ -71,11 +95,6 @@ func (h *SSHActionHandlers) CheckAuth(c echo.Context) error {
ctx := c.Request().Context()
notify := ctx.Done()
binder, err := h.createBinder(c)
if err != nil {
return logError(err)
}
tick := time.NewTicker(2 * time.Second)
defer func() { tick.Stop() }()
@@ -88,7 +107,7 @@ func (h *SSHActionHandlers) CheckAuth(c echo.Context) error {
m, err := h.repository.GetSSHActionRequest(ctx, key)
if err != nil || m == nil {
return binder.WriteResponse(c, http.StatusOK, &tailcfg.SSHAction{Reject: true})
return c.JSON(http.StatusOK, &tailcfg.SSHAction{Reject: true})
}
if m.Action == "accept" {
@@ -98,13 +117,13 @@ func (h *SSHActionHandlers) CheckAuth(c echo.Context) error {
AllowLocalPortForwarding: true,
}
_ = h.repository.DeleteSSHActionRequest(ctx, key)
return binder.WriteResponse(c, http.StatusOK, action)
return c.JSON(http.StatusOK, action)
}
if m.Action == "reject" {
action := &tailcfg.SSHAction{Reject: true}
_ = h.repository.DeleteSSHActionRequest(ctx, key)
return binder.WriteResponse(c, http.StatusOK, action)
return c.JSON(http.StatusOK, action)
}
case <-notify:
return nil
+63 -38
View File
@@ -7,6 +7,7 @@ import (
"github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/util"
"net/netip"
"slices"
"strconv"
"tailscale.com/tailcfg"
"tailscale.com/types/dnstype"
@@ -33,20 +34,20 @@ func ToDNSConfig(m *domain.Machine, tailnet *domain.Tailnet, c *domain.DNSConfig
sanitizeTailnetName := domain.SanitizeTailnetName(tailnet.Name)
tailnetDomain := fmt.Sprintf("%s.%s", sanitizeTailnetName, config.MagicDNSSuffix())
resolvers := []*dnstype.Resolver{}
resolvers := make([]*dnstype.Resolver, 0)
for _, r := range c.Nameservers {
resolver := &dnstype.Resolver{
Addr: r,
}
resolvers = append(resolvers, resolver)
resolvers = append(resolvers, &dnstype.Resolver{Addr: r})
}
dnsConfig := &tailcfg.DNSConfig{}
var routes = make(map[string][]*dnstype.Resolver)
var domains []string
var certDomains []string
if c.MagicDNS {
routes[tailnetDomain] = nil
domains = append(domains, tailnetDomain)
dnsConfig.Proxied = true
@@ -62,45 +63,30 @@ func ToDNSConfig(m *domain.Machine, tailnet *domain.Tailnet, c *domain.DNSConfig
}
if len(c.Routes) != 0 || certsEnabled {
routes := make(map[string][]*dnstype.Resolver)
for r, s := range c.Routes {
routeResolver := []*dnstype.Resolver{}
routeResolver := make([]*dnstype.Resolver, 0)
for _, addr := range s {
resolver := &dnstype.Resolver{Addr: addr}
routeResolver = append(routeResolver, resolver)
routeResolver = append(routeResolver, &dnstype.Resolver{Addr: addr})
}
routes[r] = routeResolver
domains = append(domains, r)
}
dnsConfig.Routes = routes
}
dnsConfig.Domains = domains
dnsConfig.Domains = append(domains, c.SearchDomains...)
dnsConfig.CertDomains = certDomains
dnsConfig.ExitNodeFilteredSet = []string{
fmt.Sprintf(".%s", config.MagicDNSSuffix()),
}
return dnsConfig
}
func ToNode(m *domain.Machine, tailnet *domain.Tailnet, taggedDevicesUser *domain.User, peer bool, connected bool, routeFilter func(m *domain.Machine) []netip.Prefix) (*tailcfg.Node, *tailcfg.UserProfile, error) {
func ToNode(capVer tailcfg.CapabilityVersion, m *domain.Machine, tailnet *domain.Tailnet, taggedDevicesUser *domain.User, peer bool, connected bool, routeFilter func(m *domain.Machine) []netip.Prefix) (*tailcfg.Node, *tailcfg.UserProfile, error) {
role := tailnet.IAMPolicy.GetRole(m.User)
var capabilities []string
if !peer {
if !m.HasTags() && role == domain.UserRoleAdmin {
capabilities = append(capabilities, tailcfg.CapabilityAdmin)
}
if tailnet.FileSharingEnabled {
capabilities = append(capabilities, tailcfg.CapabilityFileSharing)
}
if tailnet.SSHEnabled {
capabilities = append(capabilities, tailcfg.CapabilitySSH)
}
}
nKey, err := util.ParseNodePublicKey(m.NodeKey)
if err != nil {
return nil, nil, err
@@ -164,9 +150,10 @@ func ToNode(m *domain.Machine, tailnet *domain.Tailnet, taggedDevicesUser *domai
sanitizedTailnetName := domain.SanitizeTailnetName(m.Tailnet.Name)
hostInfo := tailcfg.Hostinfo{
OS: hostinfo.OS,
Hostname: hostinfo.Hostname,
Services: filterServices(hostinfo.Services),
OS: hostinfo.OS,
Hostname: hostinfo.Hostname,
Services: filterServices(hostinfo.Services),
SSH_HostKeys: hostinfo.SSH_HostKeys,
}
n := tailcfg.Node{
@@ -181,15 +168,55 @@ func ToNode(m *domain.Machine, tailnet *domain.Tailnet, taggedDevicesUser *domai
Endpoints: endpoints,
DERP: derp,
Hostinfo: hostInfo.View(),
Capabilities: capabilities,
Created: m.CreatedAt.UTC(),
Hostinfo: hostInfo.View(),
Created: m.CreatedAt.UTC(),
MachineAuthorized: m.Authorized,
User: tailcfg.UserID(m.UserID),
}
if !peer {
var capabilities []tailcfg.NodeCapability
capMap := make(tailcfg.NodeCapMap)
for _, c := range tailnet.ACLPolicy.NodeCapabilities(m) {
capabilities = append(capabilities, c)
capMap[c] = []tailcfg.RawMessage{}
}
if !m.HasTags() && role == domain.UserRoleAdmin {
capabilities = append(capabilities, tailcfg.CapabilityAdmin)
capMap[tailcfg.CapabilityAdmin] = []tailcfg.RawMessage{}
}
if tailnet.FileSharingEnabled {
capabilities = append(capabilities, tailcfg.CapabilityFileSharing)
capMap[tailcfg.CapabilityFileSharing] = []tailcfg.RawMessage{}
}
if tailnet.SSHEnabled {
capabilities = append(capabilities, tailcfg.CapabilitySSH)
capMap[tailcfg.CapabilitySSH] = []tailcfg.RawMessage{}
}
if tailnet.DNSConfig.HttpsCertsEnabled {
capabilities = append(capabilities, tailcfg.CapabilityHTTPS)
capMap[tailcfg.CapabilityHTTPS] = []tailcfg.RawMessage{}
}
// ionscale has no support for Funnel yet, so remove Funnel attribute if set via ACL policy
{
slices.DeleteFunc(capabilities, func(c tailcfg.NodeCapability) bool { return c == tailcfg.NodeAttrFunnel })
delete(capMap, tailcfg.NodeAttrFunnel)
}
if capVer >= 74 {
n.CapMap = capMap
} else {
n.Capabilities = capabilities
}
}
if !m.ExpiresAt.IsZero() {
e := m.ExpiresAt.UTC()
n.KeyExpiry = e
@@ -233,13 +260,11 @@ func ToUser(u domain.User) (tailcfg.User, tailcfg.Login) {
LoginName: u.Name,
DisplayName: u.Name,
Logins: []tailcfg.LoginID{tailcfg.LoginID(u.ID)},
Domain: u.Tailnet.Name,
}
login := tailcfg.Login{
ID: tailcfg.LoginID(u.ID),
LoginName: u.Name,
DisplayName: u.Name,
Domain: u.Tailnet.Name,
}
return user, login
}
+200
View File
@@ -0,0 +1,200 @@
package mapping
import (
"context"
"github.com/jsiebens/ionscale/internal/core"
"github.com/jsiebens/ionscale/internal/domain"
"net/netip"
"sync"
"tailscale.com/tailcfg"
"tailscale.com/types/opt"
"time"
)
// MapResponse is a custom tailcfg.MapResponse
// for marshalling non-nil zero-length slices (meaning explicitly now empty)
// see tailcfg.MapResponse documentation
type MapResponse struct {
tailcfg.MapResponse
PacketFilter []tailcfg.FilterRule
}
func NewPollNetMapper(req *tailcfg.MapRequest, machineID uint64, repository domain.Repository, sessionManager core.PollMapSessionManager) *PollNetMapper {
return &PollNetMapper{
req: req,
machineID: machineID,
prevSyncedPeerIDs: make(map[uint64]bool),
prevDerpMapChecksum: "",
repository: repository,
sessionManager: sessionManager,
}
}
type PollNetMapper struct {
sync.Mutex
req *tailcfg.MapRequest
machineID uint64
prevSyncedPeerIDs map[uint64]bool
prevDerpMapChecksum string
repository domain.Repository
sessionManager core.PollMapSessionManager
}
func (h *PollNetMapper) CreateMapResponse(ctx context.Context, delta bool) (*MapResponse, error) {
h.Lock()
defer h.Unlock()
m, err := h.repository.GetMachine(ctx, h.machineID)
if err != nil {
return nil, err
}
hostinfo := tailcfg.Hostinfo(m.HostInfo)
tailnet := m.Tailnet
policies := tailnet.ACLPolicy
dnsConfig := tailnet.DNSConfig
serviceUser, _, err := h.repository.GetOrCreateServiceUser(ctx, &tailnet)
if err != nil {
return nil, err
}
derpMap, err := m.Tailnet.GetDERPMap(ctx, h.repository)
if err != nil {
return nil, err
}
prc := &primaryRoutesCollector{flagged: map[netip.Prefix]bool{}}
node, user, err := ToNode(h.req.Version, m, &tailnet, serviceUser, false, true, prc.filter)
if err != nil {
return nil, err
}
var users = []tailcfg.UserProfile{*user}
var changedPeers []*tailcfg.Node
var removedPeers []tailcfg.NodeID
var filterRules = make([]tailcfg.FilterRule, 0)
var sshPolicy *tailcfg.SSHPolicy
syncedPeerIDs := map[uint64]bool{}
if !h.req.OmitPeers {
candidatePeers, err := h.repository.ListMachinePeers(ctx, m.TailnetID, m.MachineKey)
if err != nil {
return nil, err
}
syncedUserIDs := map[tailcfg.UserID]bool{user.ID: true}
for _, peer := range candidatePeers {
if peer.IsExpired() {
continue
}
if policies.IsValidPeer(m, &peer) || policies.IsValidPeer(&peer, m) {
isConnected := h.sessionManager.HasSession(peer.TailnetID, peer.ID)
n, u, err := ToNode(h.req.Version, &peer, &tailnet, serviceUser, true, isConnected, prc.filter)
if err != nil {
return nil, err
}
changedPeers = append(changedPeers, n)
syncedPeerIDs[peer.ID] = true
delete(h.prevSyncedPeerIDs, peer.ID)
if _, ok := syncedUserIDs[u.ID]; !ok {
users = append(users, *u)
syncedUserIDs[u.ID] = true
}
}
}
for p, _ := range h.prevSyncedPeerIDs {
removedPeers = append(removedPeers, tailcfg.NodeID(p))
}
filterRules = policies.BuildFilterRules(candidatePeers, m)
if tailnet.SSHEnabled && hostinfo.TailscaleSSHEnabled() {
sshPolicy = policies.BuildSSHPolicy(candidatePeers, m)
}
}
controlTime := time.Now().UTC()
var mapResponse tailcfg.MapResponse
if !delta {
mapResponse = tailcfg.MapResponse{
KeepAlive: false,
Node: node,
DNSConfig: ToDNSConfig(m, &m.Tailnet, &dnsConfig),
PacketFilter: filterRules,
SSHPolicy: sshPolicy,
DERPMap: &derpMap.DERPMap,
Domain: domain.SanitizeTailnetName(m.Tailnet.Name),
Peers: changedPeers,
UserProfiles: users,
ControlTime: &controlTime,
CollectServices: optBool(tailnet.ServiceCollectionEnabled),
Debug: &tailcfg.Debug{
DisableLogTail: true,
},
}
} else {
mapResponse = tailcfg.MapResponse{
Node: node,
DNSConfig: ToDNSConfig(m, &m.Tailnet, &dnsConfig),
PacketFilter: filterRules,
SSHPolicy: sshPolicy,
Domain: domain.SanitizeTailnetName(m.Tailnet.Name),
PeersChanged: changedPeers,
PeersRemoved: removedPeers,
UserProfiles: users,
ControlTime: &controlTime,
CollectServices: optBool(tailnet.ServiceCollectionEnabled),
}
if h.prevDerpMapChecksum != derpMap.Checksum {
mapResponse.DERPMap = &derpMap.DERPMap
}
}
if h.req.OmitPeers {
mapResponse.PeersChanged = nil
mapResponse.PeersRemoved = nil
mapResponse.Peers = nil
}
h.prevSyncedPeerIDs = syncedPeerIDs
h.prevDerpMapChecksum = derpMap.Checksum
return &MapResponse{MapResponse: mapResponse, PacketFilter: filterRules}, nil
}
type primaryRoutesCollector struct {
flagged map[netip.Prefix]bool
}
func (p *primaryRoutesCollector) filter(m *domain.Machine) []netip.Prefix {
var result []netip.Prefix
for _, r := range m.AllowIPs {
if _, ok := p.flagged[r]; r.Bits() != 0 && !ok {
result = append(result, r)
p.flagged[r] = true
}
}
for _, r := range m.AutoAllowIPs {
if _, ok := p.flagged[r]; r.Bits() != 0 && !ok {
result = append(result, r)
p.flagged[r] = true
}
}
return result
}
func optBool(v bool) opt.Bool {
b := opt.Bool("")
b.Set(v)
return b
}
+55 -50
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"github.com/caddyserver/certmagic"
"github.com/jsiebens/ionscale/internal/auth"
"github.com/jsiebens/ionscale/internal/bind"
"github.com/jsiebens/ionscale/internal/config"
"github.com/jsiebens/ionscale/internal/core"
"github.com/jsiebens/ionscale/internal/database"
@@ -18,6 +17,7 @@ import (
echo_prometheus "github.com/labstack/echo-contrib/prometheus"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
certmagicsql "github.com/travisjeffery/certmagic-sqlstorage"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/net/http2"
@@ -30,7 +30,7 @@ import (
"tailscale.com/types/key"
)
func Start(c *config.Config) error {
func Start(ctx context.Context, c *config.Config) error {
logger, err := setupLogging(c.Logging)
if err != nil {
return err
@@ -38,46 +38,56 @@ func Start(c *config.Config) error {
logger.Info("Starting ionscale server")
logError := func(err error) error {
if err != nil {
zap.L().WithOptions(zap.AddCallerSkip(1)).Error("Unable to start server", zap.Error(err))
}
return err
}
httpLogger := logger.Named("http")
dbLogger := logger.Named("db")
repository, err := database.OpenDB(&c.Database, dbLogger)
db, repository, err := database.OpenDB(&c.Database, dbLogger)
if err != nil {
return err
return logError(err)
}
sessionManager := core.NewPollMapSessionManager()
defaultControlKeys, err := repository.GetControlKeys(context.Background())
defaultControlKeys, err := repository.GetControlKeys(ctx)
if err != nil {
return err
return logError(err)
}
serverKey, err := c.ReadServerKeys(defaultControlKeys)
if err != nil {
return err
return logError(err)
}
core.StartReaper(repository, sessionManager)
core.StartWorker(repository, sessionManager)
serverUrl, err := url.Parse(c.ServerUrl)
if err != nil {
return err
return logError(err)
}
// prepare CertMagic
if c.Tls.AcmeEnabled {
storage, err := certmagicsql.NewStorage(ctx, db, certmagicsql.Options{})
if err != nil {
return err
}
certmagic.DefaultACME.Agreed = true
certmagic.DefaultACME.Email = c.Tls.AcmeEmail
certmagic.DefaultACME.CA = c.Tls.AcmeCA
certmagic.Default.Logger = logger.Named("certmagic")
if c.Tls.AcmePath != "" {
certmagic.Default.Storage = &certmagic.FileStorage{Path: c.Tls.AcmePath}
}
certmagic.Default.Storage = storage
cfg := certmagic.NewDefault()
if err := cfg.ManageAsync(context.Background(), []string{serverUrl.Host}); err != nil {
return err
if err := cfg.ManageAsync(ctx, []string{serverUrl.Host}); err != nil {
return logError(err)
}
c.HttpListenAddr = fmt.Sprintf(":%d", certmagic.HTTPPort)
@@ -86,12 +96,12 @@ func Start(c *config.Config) error {
authProvider, systemIAMPolicy, err := setupAuthProvider(c.Auth)
if err != nil {
return fmt.Errorf("error configuring OIDC provider: %v", err)
return logError(fmt.Errorf("error configuring OIDC provider: %v", err))
}
dnsProvider, err := dns.NewProvider(c.DNS)
if err != nil {
return err
return logError(err)
}
p := echo_prometheus.NewPrometheus("http", nil)
@@ -100,31 +110,31 @@ func Start(c *config.Config) error {
p.SetMetricsPath(metricsHandler)
createPeerHandler := func(machinePublicKey key.MachinePublic) http.Handler {
binder := bind.DefaultBinder(machinePublicKey)
registrationHandlers := handlers.NewRegistrationHandlers(binder, c, sessionManager, repository)
pollNetMapHandler := handlers.NewPollNetMapHandler(binder, sessionManager, repository)
dnsHandlers := handlers.NewDNSHandlers(binder, dnsProvider)
idTokenHandlers := handlers.NewIDTokenHandlers(binder, c, repository)
sshActionHandlers := handlers.NewSSHActionHandlers(binder, c, repository)
registrationHandlers := handlers.NewRegistrationHandlers(machinePublicKey, c, sessionManager, repository)
pollNetMapHandler := handlers.NewPollNetMapHandler(machinePublicKey, sessionManager, repository)
dnsHandlers := handlers.NewDNSHandlers(machinePublicKey, dnsProvider)
idTokenHandlers := handlers.NewIDTokenHandlers(machinePublicKey, c, repository)
sshActionHandlers := handlers.NewSSHActionHandlers(machinePublicKey, c, repository)
queryFeatureHandlers := handlers.NewQueryFeatureHandlers(machinePublicKey, dnsProvider, repository)
e := echo.New()
e.Binder = handlers.JsonBinder{}
e.Use(EchoMetrics(p), EchoLogger(httpLogger), EchoErrorHandler(), EchoRecover())
e.POST("/machine/register", registrationHandlers.Register)
e.POST("/machine/map", pollNetMapHandler.PollNetMap)
e.POST("/machine/set-dns", dnsHandlers.SetDNS)
e.POST("/machine/id-token", idTokenHandlers.FetchToken)
e.GET("/machine/ssh/action/:src_machine_id/to/:dst_machine_id", sshActionHandlers.StartAuth)
e.GET("/machine/ssh/action/:src_machine_id/to/:dst_machine_id/:check_period", sshActionHandlers.StartAuth)
e.GET("/machine/ssh/action/check/:key", sshActionHandlers.CheckAuth)
e.POST("/machine/feature/query", queryFeatureHandlers.QueryFeature)
return e
}
noiseHandlers := handlers.NewNoiseHandlers(serverKey.ControlKey, createPeerHandler)
registrationHandlers := handlers.NewRegistrationHandlers(bind.BoxBinder(serverKey.LegacyControlKey), c, sessionManager, repository)
pollNetMapHandler := handlers.NewPollNetMapHandler(bind.BoxBinder(serverKey.LegacyControlKey), sessionManager, repository)
dnsHandlers := handlers.NewDNSHandlers(bind.BoxBinder(serverKey.LegacyControlKey), dnsProvider)
idTokenHandlers := handlers.NewIDTokenHandlers(bind.BoxBinder(serverKey.LegacyControlKey), c, repository)
oidcConfigHandlers := handlers.NewOIDCConfigHandlers(c, repository)
authenticationHandlers := handlers.NewAuthenticationHandlers(
c,
authProvider,
@@ -132,7 +142,7 @@ func Start(c *config.Config) error {
repository,
)
rpcService := service.NewService(c, authProvider, repository, sessionManager)
rpcService := service.NewService(c, authProvider, dnsProvider, repository, sessionManager)
rpcPath, rpcHandler := NewRpcHandler(serverKey.SystemAdminKey, repository, rpcService)
nonTlsAppHandler := echo.New()
@@ -141,9 +151,9 @@ func Start(c *config.Config) error {
nonTlsAppHandler.Any("/*", handlers.HttpRedirectHandler(c.Tls))
tlsAppHandler := echo.New()
tlsAppHandler.Renderer = templates.NewTemplates()
tlsAppHandler.Renderer = &templates.Renderer{}
tlsAppHandler.Pre(handlers.HttpsRedirect(c.Tls))
tlsAppHandler.Use(EchoMetrics(p), EchoLogger(logger), EchoErrorHandler(), EchoRecover())
tlsAppHandler.Use(EchoMetrics(p), EchoLogger(httpLogger), EchoErrorHandler(), EchoRecover())
tlsAppHandler.Any("/*", handlers.IndexHandler(http.StatusNotFound))
tlsAppHandler.Any("/", handlers.IndexHandler(http.StatusOK))
@@ -151,36 +161,30 @@ func Start(c *config.Config) error {
tlsAppHandler.GET("/version", handlers.Version)
tlsAppHandler.GET("/key", handlers.KeyHandler(serverKey))
tlsAppHandler.POST("/ts2021", noiseHandlers.Upgrade)
tlsAppHandler.POST("/machine/:id", registrationHandlers.Register)
tlsAppHandler.POST("/machine/:id/map", pollNetMapHandler.PollNetMap)
tlsAppHandler.POST("/machine/:id/set-dns", dnsHandlers.SetDNS)
tlsAppHandler.GET("/.well-known/jwks", idTokenHandlers.Jwks)
tlsAppHandler.GET("/.well-known/openid-configuration", idTokenHandlers.OpenIDConfig)
tlsAppHandler.GET("/.well-known/jwks", oidcConfigHandlers.Jwks)
tlsAppHandler.GET("/.well-known/openid-configuration", oidcConfigHandlers.OpenIDConfig)
auth := tlsAppHandler.Group("/a")
auth.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "form:_csrf",
}))
auth.GET("/:flow/:key", authenticationHandlers.StartAuth)
auth.POST("/:flow/:key", authenticationHandlers.ProcessAuth)
auth.GET("/callback", authenticationHandlers.Callback)
auth.POST("/callback", authenticationHandlers.EndOAuth)
auth.GET("/success", authenticationHandlers.Success)
auth.GET("/error", authenticationHandlers.Error)
csrf := middleware.CSRFWithConfig(middleware.CSRFConfig{TokenLookup: "form:_csrf"})
tlsAppHandler.GET("/a/:flow/:key", authenticationHandlers.StartAuth, csrf)
tlsAppHandler.POST("/a/:flow/:key", authenticationHandlers.ProcessAuth, csrf)
tlsAppHandler.GET("/a/callback", authenticationHandlers.Callback, csrf)
tlsAppHandler.POST("/a/callback", authenticationHandlers.EndAuth, csrf)
tlsAppHandler.GET("/a/success", authenticationHandlers.Success, csrf)
tlsAppHandler.GET("/a/error", authenticationHandlers.Error, csrf)
tlsL, err := tlsListener(c)
if err != nil {
return err
return logError(err)
}
nonTlsL, err := nonTlsListener(c)
if err != nil {
return err
return logError(err)
}
metricsL, err := metricsListener(c)
if err != nil {
return err
return logError(err)
}
httpL := selectListener(tlsL, nonTlsL)
@@ -298,7 +302,8 @@ func setupLogging(config config.Logging) (*zap.Logger, error) {
return nil, err
}
zap.ReplaceGlobals(logger)
globalLogger := logger.Named("ionscale")
zap.ReplaceGlobals(globalLogger)
return logger, nil
return globalLogger, nil
}
+14 -8
View File
@@ -42,6 +42,10 @@ func (s *Service) SetDNSConfig(ctx context.Context, req *connect.Request[api.Set
return nil, connect.NewError(connect.CodeFailedPrecondition, fmt.Errorf("MagicDNS must be enabled when enabling HTTPS Certs"))
}
if dnsConfig.HttpsCerts && s.dnsProvider != nil {
return nil, connect.NewError(connect.CodeFailedPrecondition, fmt.Errorf("A DNS provider must be configured when enabling HTTPS Certs"))
}
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
if err != nil {
return nil, logError(err)
@@ -50,13 +54,7 @@ func (s *Service) SetDNSConfig(ctx context.Context, req *connect.Request[api.Set
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
}
tailnet.DNSConfig = domain.DNSConfig{
MagicDNS: dnsConfig.MagicDns,
HttpsCertsEnabled: dnsConfig.HttpsCerts,
OverrideLocalDNS: dnsConfig.OverrideLocalDns,
Nameservers: dnsConfig.Nameservers,
Routes: apiRoutesToDomainRoutes(dnsConfig.Routes),
}
tailnet.DNSConfig = apiDNSConfigToDomainDNSConfig(req.Msg.Config)
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
return nil, logError(err)
@@ -64,7 +62,13 @@ func (s *Service) SetDNSConfig(ctx context.Context, req *connect.Request[api.Set
s.sessionManager.NotifyAll(tailnet.ID)
resp := &api.SetDNSConfigResponse{Config: dnsConfig}
resp := &api.SetDNSConfigResponse{
Config: domainDNSConfigToApiDNSConfig(tailnet),
}
if dnsConfig.HttpsCerts && s.dnsProvider == nil {
resp.Message = "# HTTPS Certs cannot be enabled because a DNS provider is not properly configured"
}
return connect.NewResponse(resp), nil
}
@@ -96,6 +100,7 @@ func apiDNSConfigToDomainDNSConfig(dnsConfig *api.DNSConfig) domain.DNSConfig {
OverrideLocalDNS: dnsConfig.OverrideLocalDns,
Nameservers: dnsConfig.Nameservers,
Routes: apiRoutesToDomainRoutes(dnsConfig.Routes),
SearchDomains: dnsConfig.SearchDomains,
}
}
@@ -109,5 +114,6 @@ func domainDNSConfigToApiDNSConfig(tailnet *domain.Tailnet) *api.DNSConfig {
OverrideLocalDns: dnsConfig.OverrideLocalDNS,
Nameservers: dnsConfig.Nameservers,
Routes: domainRoutesToApiRoutes(dnsConfig.Routes),
SearchDomains: dnsConfig.SearchDomains,
}
}
+4
View File
@@ -46,6 +46,10 @@ func (s *Service) SetIAMPolicy(ctx context.Context, req *connect.Request[api.Set
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet does not exist"))
}
if err := validateIamPolicy(req.Msg.Policy); err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err))
}
tailnet.IAMPolicy = domain.IAMPolicy{
Subs: req.Msg.Policy.Subs,
Emails: req.Msg.Policy.Emails,
+6 -1
View File
@@ -24,6 +24,11 @@ func (s *Service) machineToApi(m *domain.Machine) *api.Machine {
lastSeen = timestamppb.New(*m.LastSeen)
}
var endpoints []string
for _, e := range m.Endpoints {
endpoints = append(endpoints, e.String())
}
return &api.Machine{
Id: m.ID,
Name: name,
@@ -47,7 +52,7 @@ func (s *Service) machineToApi(m *domain.Machine) *api.Machine {
Name: m.User.Name,
},
ClientConnectivity: &api.ClientConnectivity{
Endpoints: m.Endpoints,
Endpoints: endpoints,
},
AdvertisedRoutes: m.AdvertisedPrefixes(),
EnabledRoutes: m.AllowedPrefixes(),
+17 -1
View File
@@ -2,19 +2,24 @@ package service
import (
"context"
"fmt"
"github.com/bufbuild/connect-go"
"github.com/hashicorp/go-bexpr/grammar"
"github.com/hashicorp/go-multierror"
"github.com/jsiebens/ionscale/internal/auth"
"github.com/jsiebens/ionscale/internal/config"
"github.com/jsiebens/ionscale/internal/core"
"github.com/jsiebens/ionscale/internal/dns"
"github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/version"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
)
func NewService(config *config.Config, authProvider auth.Provider, repository domain.Repository, sessionManager core.PollMapSessionManager) *Service {
func NewService(config *config.Config, authProvider auth.Provider, dnsProvider dns.Provider, repository domain.Repository, sessionManager core.PollMapSessionManager) *Service {
return &Service{
config: config,
authProvider: authProvider,
dnsProvider: dnsProvider,
repository: repository,
sessionManager: sessionManager,
}
@@ -23,6 +28,7 @@ func NewService(config *config.Config, authProvider auth.Provider, repository do
type Service struct {
config *config.Config
authProvider auth.Provider
dnsProvider dns.Provider
repository domain.Repository
sessionManager core.PollMapSessionManager
}
@@ -34,3 +40,13 @@ func (s *Service) GetVersion(_ context.Context, _ *connect.Request[api.GetVersio
Revision: revision,
}), nil
}
func validateIamPolicy(p *api.IAMPolicy) error {
var mErr *multierror.Error
for i, exp := range p.Filters {
if _, err := grammar.Parse(fmt.Sprintf("filter %d", i), []byte(exp)); err != nil {
mErr = multierror.Append(mErr, err)
}
}
return mErr.ErrorOrNil()
}
+33 -12
View File
@@ -8,6 +8,7 @@ import (
"github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/mapping"
"github.com/jsiebens/ionscale/internal/util"
"github.com/jsiebens/ionscale/pkg/defaults"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
"tailscale.com/tailcfg"
)
@@ -42,6 +43,26 @@ func (s *Service) CreateTailnet(ctx context.Context, req *connect.Request[api.Cr
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
}
check, err := s.repository.GetTailnetByName(ctx, req.Msg.Name)
if err != nil {
return nil, logError(err)
}
if check != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("tailnet with name '%s' already exists", req.Msg.Name))
}
if req.Msg.IamPolicy == nil {
req.Msg.IamPolicy = defaults.DefaultIAMPolicy()
}
if req.Msg.AclPolicy == nil {
req.Msg.AclPolicy = defaults.DefaultACLPolicy()
}
if req.Msg.DnsConfig == nil {
req.Msg.DnsConfig = defaults.DefaultDNSConfig()
}
tailnet := &domain.Tailnet{
ID: util.NextID(),
Name: req.Msg.Name,
@@ -54,20 +75,16 @@ func (s *Service) CreateTailnet(ctx context.Context, req *connect.Request[api.Cr
MachineAuthorizationEnabled: req.Msg.MachineAuthorizationEnabled,
}
if req.Msg.IamPolicy != nil {
if err := mapping.CopyViaJson(req.Msg.IamPolicy, &tailnet.IAMPolicy); err != nil {
return nil, logError(err)
}
} else {
tailnet.IAMPolicy = domain.DefaultIAMPolicy()
if err := validateIamPolicy(req.Msg.IamPolicy); err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err))
}
if req.Msg.AclPolicy != nil {
if err := mapping.CopyViaJson(req.Msg.AclPolicy, &tailnet.ACLPolicy); err != nil {
return nil, logError(err)
}
} else {
tailnet.ACLPolicy = domain.DefaultACLPolicy()
if err := mapping.CopyViaJson(req.Msg.IamPolicy, &tailnet.IAMPolicy); err != nil {
return nil, logError(err)
}
if err := mapping.CopyViaJson(req.Msg.AclPolicy, &tailnet.ACLPolicy); err != nil {
return nil, logError(err)
}
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
@@ -100,6 +117,10 @@ func (s *Service) UpdateTailnet(ctx context.Context, req *connect.Request[api.Up
}
if req.Msg.IamPolicy != nil {
if err := validateIamPolicy(req.Msg.IamPolicy); err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid iam policy: %w", err))
}
tailnet.IAMPolicy = domain.IAMPolicy{}
if err := mapping.CopyViaJson(req.Msg.IamPolicy, &tailnet.IAMPolicy); err != nil {
return nil, logError(err)
-107
View File
@@ -1,107 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList {
padding-top: 5px
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
input {
display: block;
width: 100%;
height: 100%;
padding: 10px;
}
button {
padding-top: 10px;
padding-bottom: 10px;
padding-left: 20px;
padding-right: 20px;
height: 45px;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
{{if .ProviderAvailable}}
<div style="text-align: left; padding-bottom: 10px">
<p><b>Authentication required</b></p>
<small>Login with:</small>
</div>
<form method="post">
<input type="hidden" name="_csrf" value="{{.Csrf}}">
<ul class="selectionList">
<li><button type="submit" name="s" value="true">OpenID</button></li>
</ul>
</form>
<div style="text-align: left; padding-bottom: 10px; padding-top: 20px">
<small>Or enter an <label for="ak">auth key</label> here:</small>
</div>
{{end}}
{{if not .ProviderAvailable}}
<div style="text-align: left; padding-bottom: 10px">
<p><b>Authentication required</b></p>
<small>Enter an <label for="ak">auth key</label> here:</small>
</div>
{{end}}
<form method="post" style="text-align: right">
<input type="hidden" name="_csrf" value="{{.Csrf}}">
<p><input id="ak" name="ak" type="text"/></p>
<div style="padding-top: 10px">
<button type="submit">submit</button>
</div>
</form>
</div>
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
package templates
templ Auth(oidc bool, csrf string) {
if oidc {
<div style="text-align: left; padding-bottom: 10px">
<p><b>Authentication required</b></p>
<small>Login with:</small>
</div>
<form method="post">
<input type="hidden" name="_csrf" value={ csrf } />
<ul class="selectionList">
<li><button type="submit" name="oidc" value="true">OpenID</button></li>
</ul>
</form>
<div style="text-align: left; padding-bottom: 10px; padding-top: 20px">
<small>Or enter an <label for="ak">auth key</label> here:</small>
</div>
} else {
<div style="text-align: left; padding-bottom: 10px">
<p><b>Authentication required</b></p>
<small>Enter an <label for="ak">auth key</label> here:</small>
</div>
}
<form method="post" style="text-align: right">
<input type="hidden" name="_csrf" value={ csrf } />
<p><input id="ak" name="ak" type="text"/></p>
<div style="padding-top: 10px">
<button type="submit">submit</button>
</div>
</form>
}
+62
View File
@@ -0,0 +1,62 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.543
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func Auth(oidc bool, csrf string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if oidc {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: left; padding-bottom: 10px\"><p><b>Authentication required</b></p><small>Login with:</small></div><form method=\"post\"><input type=\"hidden\" name=\"_csrf\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(csrf))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><ul class=\"selectionList\"><li><button type=\"submit\" name=\"oidc\" value=\"true\">OpenID</button></li></ul></form><div style=\"text-align: left; padding-bottom: 10px; padding-top: 20px\"><small>Or enter an <label for=\"ak\">auth key</label> here:</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: left; padding-bottom: 10px\"><p><b>Authentication required</b></p><small>Enter an <label for=\"ak\">auth key</label> here:</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" style=\"text-align: right\"><input type=\"hidden\" name=\"_csrf\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(csrf))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><p><input id=\"ak\" name=\"ak\" type=\"text\"></p><div style=\"padding-top: 10px\"><button type=\"submit\">submit</button></div></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
-96
View File
@@ -1,96 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList {
padding-top: 5px
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
input {
display: block;
width: 100%;
height: 100%;
padding: 10px;
}
button {
padding-top: 10px;
padding-bottom: 10px;
padding-left: 20px;
padding-right: 20px;
height: 45px;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
{{if .ProviderAvailable}}
<div style="text-align: left; padding-bottom: 10px">
<p><b>Authentication required</b></p>
<small>Login with:</small>
</div>
<form method="post">
<ul class="selectionList">
<li><button type="submit" name="s" value="true">OpenID</button></li>
</ul>
</form>
{{end}}
{{if not .ProviderAvailable}}
<div style="text-align: center">
<p><b>No authentication method available.</b></p>
<small>contact your ionscale administrator for more information</small>
</div>
{{end}}
</div>
</body>
</html>
-62
View File
@@ -1,62 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
<div style="text-align: center">
<p><b>An error occurred</b></p>
</div>
</div>
</body>
</html>
-64
View File
@@ -1,64 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
<div style="text-align: center">
<p><b>ionscale</b></p>
<p><small>{{.Version}}</small></p>
<p><small>{{.Revision}}</small></p>
</div>
</div>
</body>
</html>
-63
View File
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
<div style="text-align: center">
<p><b>Authorization failed</b></p>
<small>the provided auth key is <b style="color: red">invalid</b></small>
</div>
</div>
</body>
</html>
+140
View File
@@ -0,0 +1,140 @@
package templates
templ Index(version string, revision string) {
<div style="text-align: center">
<p><b>ionscale</b></p>
<p><small>{ version }</small></p>
<p><small>{ revision }</small></p>
</div>
}
templ Success() {
<div style="text-align: center">
<p><b>Authorization successful</b></p>
<small>You can now close this window</small>
</div>
}
templ NewMachine() {
<div style="text-align: center">
<p><b>Authentication successful</b></p>
<small>but this is a <b style="color: blue">new machine</b> and needs to be authorized by your Tailnet admin.</small>
</div>
}
templ Error() {
<div style="text-align: center">
<p><b>An error occurred</b></p>
</div>
}
templ Unauthorized() {
<div style="text-align: center">
<p><b>Authentication successful</b></p>
<small>but you're <b style="color: red">not</b> authorized to use any network</small>
</div>
}
templ InvalidAuthKey() {
<div style="text-align: center">
<p><b>Authorization failed</b></p>
<small>the provided auth key is <b style="color: red">invalid</b></small>
</div>
}
templ NotTagOwner() {
<div style="text-align: center">
<p><b>Authentication successful</b></p>
<small>but you're <b style="color: red">not</b> a valid tag owner for the requested tags</small>
</div>
}
templ NotMachineOwner() {
<div style="text-align: center">
<p><b>Authentication successful</b></p>
<small>but you're <b style="color: red">not</b> a valid owner of the machine</small>
</div>
}
templ layout(contents templ.Component) {
<!DOCTYPE html>
<html lang="en">
<head>
@heading()
</head>
<body>
<div class="wrapper">
@contents
</div>
</body>
</html>
}
templ heading() {
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
input {
display: block;
width: 100%;
height: 100%;
padding: 10px;
}
button {
padding-top: 10px;
padding-bottom: 10px;
padding-left: 20px;
padding-right: 20px;
height: 45px;
border: none;
}
</style>
<title>ionscale</title>
}
+293
View File
@@ -0,0 +1,293 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.543
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func Index(version string, revision string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: center\"><p><b>ionscale</b></p><p><small>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(version)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/layout.templ`, Line: 5, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</small></p><p><small>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(revision)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/layout.templ`, Line: 6, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</small></p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func Success() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: center\"><p><b>Authorization successful</b></p><small>You can now close this window</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func NewMachine() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: center\"><p><b>Authentication successful</b></p><small>but this is a <b style=\"color: blue\">new machine</b> and needs to be authorized by your Tailnet admin.</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func Error() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: center\"><p><b>An error occurred</b></p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func Unauthorized() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
if templ_7745c5c3_Var7 == nil {
templ_7745c5c3_Var7 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: center\"><p><b>Authentication successful</b></p><small>but you're <b style=\"color: red\">not</b> authorized to use any network</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func InvalidAuthKey() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: center\"><p><b>Authorization failed</b></p><small>the provided auth key is <b style=\"color: red\">invalid</b></small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func NotTagOwner() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var9 := templ.GetChildren(ctx)
if templ_7745c5c3_Var9 == nil {
templ_7745c5c3_Var9 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: center\"><p><b>Authentication successful</b></p><small>but you're <b style=\"color: red\">not</b> a valid tag owner for the requested tags</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func NotMachineOwner() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: center\"><p><b>Authentication successful</b></p><small>but you're <b style=\"color: red\">not</b> a valid owner of the machine</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func layout(contents templ.Component) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = heading().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</head><body><div class=\"wrapper\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = contents.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func heading() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
if templ_7745c5c3_Var12 == nil {
templ_7745c5c3_Var12 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><style>\n @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');\n\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n font-family: 'Poppins', sans-serif;\n }\n\n body {\n width: 100%;\n height: 100vh;\n padding: 10px;\n background: #379683;\n }\n\n .wrapper {\n background: #fff;\n max-width: 400px;\n width: 100%;\n margin: 120px auto;\n padding: 25px;\n border-radius: 5px;\n box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);\n }\n\n .selectionList li {\n position: relative;\n list-style: none;\n height: 45px;\n line-height: 45px;\n margin-bottom: 8px;\n background: #f2f2f2;\n border-radius: 3px;\n overflow: hidden;\n box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);\n }\n\n .selectionList li button {\n margin: 0;\n display: block;\n width: 100%;\n height: 100%;\n border: none;\n }\n\n input {\n display: block;\n width: 100%;\n height: 100%;\n padding: 10px;\n }\n\n button {\n padding-top: 10px;\n padding-bottom: 10px;\n padding-left: 20px;\n padding-right: 20px;\n height: 45px;\n border: none;\n }\n </style><title>ionscale</title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
-63
View File
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
<div style="text-align: center">
<p><b>Authentication successful</b></p>
<small>but this is a <b style="color: blue">new machine</b> and needs to be authorized by your Tailnet admin.</small>
</div>
</div>
</body>
</html>
-63
View File
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
<div style="text-align: center">
<p><b>Authentication successful</b></p>
<small>but you're <b style="color: red">not</b> a valid tag owner for the requested tags</small>
</div>
</div>
</body>
</html>
-63
View File
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
<div style="text-align: center">
<p><b>Authentication successful</b></p>
<small>but you're <b style="color: red">not</b> a valid owner of the machine</small>
</div>
</div>
</body>
</html>
-63
View File
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
<div style="text-align: center">
<p><b>Authorization successful</b></p>
<small>You can now close this window</small>
</div>
</div>
</body>
</html>
-114
View File
@@ -1,114 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList {
padding-top: 5px
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
input {
display: block;
width: 100%;
height: 100%;
padding: 10px;
}
button {
padding-top: 10px;
padding-bottom: 10px;
padding-left: 20px;
padding-right: 20px;
height: 45px;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
{{if .SystemAdmin}}
<div style="text-align: left; padding-bottom: 10px">
<p><b>System Admin</b></p>
<small>You are a member of the System Admin group:</small>
</div>
<form method="post">
<input type="hidden" name="aid" value="{{.AccountID}}">
<input type="hidden" name="_csrf" value="{{.Csrf}}">
<ul class="selectionList">
<li><button type="submit" name="sad" value="true">OK, continue as System Admin</button></li>
</ul>
</form>
{{end}}
{{if .Tailnets}}
{{if .SystemAdmin}}
<div style="text-align: left; padding-bottom: 10px; padding-top: 20px">
<small>Or select your <b>tailnet</b>:</small>
</div>
{{end}}
{{if not .SystemAdmin}}
<div style="text-align: left; padding-bottom: 10px;">
<p><b>Tailnets</b></p>
<small>Select your tailnet:</small>
</div>
{{end}}
<form method="post">
<input type="hidden" name="aid" value="{{.AccountID}}">
<input type="hidden" name="_csrf" value="{{.Csrf}}">
<ul class="selectionList">
{{range .Tailnets}}
<li><button type="submit" name="tid" value="{{.ID}}">{{.Name}}</button></li>
{{end}}
</ul>
</form>
{{end}}
</div>
</body>
</html>
+43
View File
@@ -0,0 +1,43 @@
package templates
import "strconv"
import "github.com/jsiebens/ionscale/internal/domain"
templ Tailnets(accountID uint64, isSystemAdmin bool, tailnets []domain.Tailnet, csrf string) {
if isSystemAdmin {
<div style="text-align: left; padding-bottom: 10px">
<p><b>System Admin</b></p>
<small>You are a member of the System Admin group:</small>
</div>
<form method="post">
<input type="hidden" name="aid" value={ strconv.FormatUint(accountID, 10) } />
<input type="hidden" name="_csrf" value={ csrf } />
<ul class="selectionList">
<li><button type="submit" name="sad" value="true">OK, continue as System Admin</button></li>
</ul>
</form>
}
if len(tailnets) != 0 {
if isSystemAdmin {
<div style="text-align: left; padding-bottom: 10px; padding-top: 20px">
<small>Or select your <b>tailnet</b>:</small>
</div>
} else {
<div style="text-align: left; padding-bottom: 10px;">
<p><b>Tailnets</b></p>
<small>Select your tailnet:</small>
</div>
}
<form method="post">
<input type="hidden" name="aid" value={ strconv.FormatUint(accountID, 10) }/>
<input type="hidden" name="_csrf" value={ csrf }/>
<ul class="selectionList">
for _, t := range tailnets {
<li><button type="submit" name="tid" value={ strconv.FormatUint(t.ID, 10) }>{ t.Name }</button></li>
}
</ul>
</form>
}
}
+120
View File
@@ -0,0 +1,120 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.543
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "strconv"
import "github.com/jsiebens/ionscale/internal/domain"
func Tailnets(accountID uint64, isSystemAdmin bool, tailnets []domain.Tailnet, csrf string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if isSystemAdmin {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: left; padding-bottom: 10px\"><p><b>System Admin</b></p><small>You are a member of the System Admin group:</small></div><form method=\"post\"><input type=\"hidden\" name=\"aid\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(strconv.FormatUint(accountID, 10)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <input type=\"hidden\" name=\"_csrf\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(csrf))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><ul class=\"selectionList\"><li><button type=\"submit\" name=\"sad\" value=\"true\">OK, continue as System Admin</button></li></ul></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(tailnets) != 0 {
if isSystemAdmin {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: left; padding-bottom: 10px; padding-top: 20px\"><small>Or select your <b>tailnet</b>:</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"text-align: left; padding-bottom: 10px;\"><p><b>Tailnets</b></p><small>Select your tailnet:</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <form method=\"post\"><input type=\"hidden\" name=\"aid\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(strconv.FormatUint(accountID, 10)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <input type=\"hidden\" name=\"_csrf\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(csrf))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><ul class=\"selectionList\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, t := range tailnets {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><button type=\"submit\" name=\"tid\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(strconv.FormatUint(t.ID, 10)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(t.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/tailnets.templ`, Line: 37, Col: 100}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
+8 -12
View File
@@ -1,23 +1,19 @@
package templates
import (
"embed"
"fmt"
"github.com/a-h/templ"
"github.com/labstack/echo/v4"
"html/template"
"io"
)
//go:embed *.html
var fs embed.FS
func NewTemplates() *Template {
return &Template{templates: template.Must(template.ParseFS(fs, "*.html"))}
type Renderer struct {
}
type Template struct {
templates *template.Template
}
func (t *Renderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
if x, ok := data.(templ.Component); ok {
return layout(x).Render(c.Request().Context(), w)
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
return fmt.Errorf("invalid data")
}
-63
View File
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
width: 100%;
height: 100vh;
padding: 10px;
background: #379683;
}
.wrapper {
background: #fff;
max-width: 400px;
width: 100%;
margin: 120px auto;
padding: 25px;
border-radius: 5px;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.selectionList li {
position: relative;
list-style: none;
height: 45px;
line-height: 45px;
margin-bottom: 8px;
background: #f2f2f2;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.selectionList li button {
margin: 0;
display: block;
width: 100%;
height: 100%;
border: none;
}
</style>
<title>ionscale</title>
</head>
<body>
<div class="wrapper">
<div style="text-align: center">
<p><b>Authentication successful</b></p>
<small>but you're <b style="color: red">not</b> authorized to use any network</small>
</div>
</div>
</body>
</html>
+22 -13
View File
@@ -6,19 +6,33 @@ import (
"net"
"os"
"strconv"
"sync"
"time"
)
var sf *sonyflake.Sonyflake
var (
sf *sonyflake.Sonyflake
sfOnce sync.Once
)
func init() {
sf = sonyflake.NewSonyflake(sonyflake.Settings{
MachineID: machineID(),
StartTime: time.Date(2022, 05, 01, 00, 0, 0, 0, time.UTC),
func NextID() uint64 {
ensureProvider()
id, _ := sf.NextID()
return id
}
func ensureProvider() {
sfOnce.Do(func() {
sfInstance, err := sonyflake.New(sonyflake.Settings{
MachineID: machineID(),
StartTime: time.Date(2022, 05, 01, 00, 0, 0, 0, time.UTC),
})
if err != nil {
panic("unable to initialize sonyflake: " + err.Error())
}
sf = sfInstance
})
if sf == nil {
panic("unable to initialize sonyflake")
}
}
func machineID() func() (uint16, error) {
@@ -46,8 +60,3 @@ func machineID() func() (uint16, error) {
return nil
}
func NextID() uint64 {
id, _ := sf.NextID()
return id
}
+88 -15
View File
@@ -1,55 +1,128 @@
package ionscale
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/99designs/keyring"
"github.com/jsiebens/ionscale/internal/key"
"github.com/jsiebens/ionscale/internal/token"
)
func LoadClientAuth(systemAdminKey string) (ClientAuth, error) {
const (
defaultDir string = "~/.ionscale"
)
func LoadClientAuth(addr string, systemAdminKey string) (ClientAuth, error) {
if systemAdminKey != "" {
k, err := key.ParsePrivateKey(systemAdminKey)
if err != nil {
return nil, fmt.Errorf("invalid system admin key")
}
return &systemAdminTokenAuth{key: *k}, nil
return systemAdminTokenSession{key: *k}, nil
}
apiToken, err := TokenFromFile()
ring, err := openKeyring()
if err != nil {
return nil, err
}
if len(apiToken) != 0 {
return &apiTokenAuth{token: apiToken}, nil
data, err := ring.Get(createKeyName(addr))
if err != nil && !errors.Is(err, keyring.ErrKeyNotFound) {
return nil, err
}
return &anonymous{}, nil
if errors.Is(err, keyring.ErrKeyNotFound) {
return Anonymous{}, nil
}
var ds defaultSession
if err := json.Unmarshal(data.Data, &ds); err != nil {
return nil, err
}
return ds, nil
}
func StoreAuthToken(addr, token string, tailnetID uint64) error {
ring, err := openKeyring()
if err != nil {
return err
}
ds := defaultSession{
TK: token,
TID: tailnetID,
}
data, err := json.Marshal(&ds)
if err != nil {
return err
}
return ring.Set(keyring.Item{
Key: createKeyName(addr),
Data: data,
})
}
type ClientAuth interface {
GetToken() (string, error)
TailnetID() uint64
}
type anonymous struct {
type defaultSession struct {
TK string
TID uint64
}
func (m *anonymous) GetToken() (string, error) {
return "", nil
func (m defaultSession) GetToken() (string, error) {
return m.TK, nil
}
type systemAdminTokenAuth struct {
func (m defaultSession) TailnetID() uint64 {
return m.TID
}
type systemAdminTokenSession struct {
key key.ServerPrivate
}
func (m *systemAdminTokenAuth) GetToken() (string, error) {
func (m systemAdminTokenSession) GetToken() (string, error) {
return token.GenerateSystemAdminToken(m.key)
}
type apiTokenAuth struct {
token string
func (m systemAdminTokenSession) TailnetID() uint64 {
return 0
}
func (m *apiTokenAuth) GetToken() (string, error) {
return m.token, nil
type Anonymous struct {
}
func (m Anonymous) GetToken() (string, error) {
return "", nil
}
func (m Anonymous) TailnetID() uint64 {
return 0
}
func openKeyring() (keyring.Keyring, error) {
return keyring.Open(keyring.Config{
LibSecretCollectionName: "login",
PassPrefix: "ionscale",
FileDir: defaultDir,
FilePasswordFunc: keyring.FixedStringPrompt(""),
AllowedBackends: []keyring.BackendType{
keyring.FileBackend,
},
})
}
func createKeyName(addr string) string {
sum := md5.Sum([]byte(addr))
x := hex.EncodeToString(sum[:])
return fmt.Sprintf("ionscale:%s", x)
}
-104
View File
@@ -1,104 +0,0 @@
package ionscale
import (
"github.com/mitchellh/go-homedir"
"io/ioutil"
"os"
"path"
"path/filepath"
"strconv"
)
const (
DefaultDir string = "~/.ionscale"
DefaultPermissions os.FileMode = 0700
)
func TokenFromFile() (string, error) {
return valueFromFile("token")
}
func TailnetFromFile() (uint64, error) {
v, err := valueFromFile("tailnet_id")
if v == "" {
return 0, nil
}
p, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return 0, err
}
return p, err
}
func valueFromFile(name string) (string, error) {
file, err := EnsureFile(name)
if err != nil {
return "", err
}
token, err := ioutil.ReadFile(file)
if err != nil {
return "", err
}
return string(token), nil
}
func SessionToFile(token string, tailnetID *uint64) error {
if err := TokenToFile(token); err != nil {
return err
}
if err := TailnetToFile(tailnetID); err != nil {
return err
}
return nil
}
func TokenToFile(token string) error {
file, err := EnsureFile("token")
if err != nil {
return err
}
return ioutil.WriteFile(file, []byte(token), 0600)
}
func TailnetToFile(id *uint64) error {
file, err := EnsureFile("tailnet_id")
if err != nil {
return err
}
var v = ""
if id != nil {
v = strconv.FormatUint(*id, 10)
}
return ioutil.WriteFile(file, []byte(v), 0600)
}
func ConfigDir() string {
return DefaultDir
}
func EnsureFile(file string) (string, error) {
permission := DefaultPermissions
dir := ConfigDir()
dirPath, err := homedir.Expand(dir)
if err != nil {
return "", err
}
filePath := path.Clean(filepath.Join(dirPath, file))
if err := os.MkdirAll(filepath.Dir(filePath), permission); err != nil {
return "", err
}
if _, err := os.Stat(filePath); os.IsNotExist(err) {
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return "", err
}
defer file.Close()
}
return filePath, nil
}
+12
View File
@@ -11,9 +11,21 @@ func DefaultACLPolicy() *ionscalev1.ACLPolicy {
Dst: []string{"*:*"},
},
},
Ssh: []*ionscalev1.SSHRule{
{
Action: "check",
Src: []string{"autogroup:member"},
Dst: []string{"autogroup:self"},
Users: []string{"autogroup:nonroot", "root"},
},
},
}
}
func DefaultIAMPolicy() *ionscalev1.IAMPolicy {
return &ionscalev1.IAMPolicy{}
}
func DefaultDNSConfig() *ionscalev1.DNSConfig {
return &ionscalev1.DNSConfig{
MagicDns: true,
+280 -66
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/acl.proto
@@ -219,6 +219,8 @@ type ACLPolicy struct {
Tagowners map[string]*structpb.ListValue `protobuf:"bytes,4,rep,name=tagowners,proto3" json:"tagowners,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Autoapprovers *AutoApprovers `protobuf:"bytes,5,opt,name=autoapprovers,proto3,oneof" json:"autoapprovers,omitempty"`
Ssh []*SSHRule `protobuf:"bytes,6,rep,name=ssh,proto3" json:"ssh,omitempty"`
Nodeattrs []*NodeAttr `protobuf:"bytes,7,rep,name=nodeattrs,proto3" json:"nodeattrs,omitempty"`
Grants []*ACLGrant `protobuf:"bytes,8,rep,name=grants,proto3" json:"grants,omitempty"`
}
func (x *ACLPolicy) Reset() {
@@ -295,6 +297,20 @@ func (x *ACLPolicy) GetSsh() []*SSHRule {
return nil
}
func (x *ACLPolicy) GetNodeattrs() []*NodeAttr {
if x != nil {
return x.Nodeattrs
}
return nil
}
func (x *ACLPolicy) GetGrants() []*ACLGrant {
if x != nil {
return x.Grants
}
return nil
}
type ACL struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -303,6 +319,7 @@ type ACL struct {
Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"`
Src []string `protobuf:"bytes,2,rep,name=src,proto3" json:"src,omitempty"`
Dst []string `protobuf:"bytes,3,rep,name=dst,proto3" json:"dst,omitempty"`
Proto string `protobuf:"bytes,4,opt,name=proto,proto3" json:"proto,omitempty"`
}
func (x *ACL) Reset() {
@@ -358,6 +375,13 @@ func (x *ACL) GetDst() []string {
return nil
}
func (x *ACL) GetProto() string {
if x != nil {
return x.Proto
}
return ""
}
type AutoApprovers struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -418,10 +442,11 @@ type SSHRule struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"`
Src []string `protobuf:"bytes,2,rep,name=src,proto3" json:"src,omitempty"`
Dst []string `protobuf:"bytes,3,rep,name=dst,proto3" json:"dst,omitempty"`
Users []string `protobuf:"bytes,4,rep,name=users,proto3" json:"users,omitempty"`
Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"`
Src []string `protobuf:"bytes,2,rep,name=src,proto3" json:"src,omitempty"`
Dst []string `protobuf:"bytes,3,rep,name=dst,proto3" json:"dst,omitempty"`
Users []string `protobuf:"bytes,4,rep,name=users,proto3" json:"users,omitempty"`
Checkperiod string `protobuf:"bytes,5,opt,name=checkperiod,proto3" json:"checkperiod,omitempty"`
}
func (x *SSHRule) Reset() {
@@ -484,6 +509,139 @@ func (x *SSHRule) GetUsers() []string {
return nil
}
func (x *SSHRule) GetCheckperiod() string {
if x != nil {
return x.Checkperiod
}
return ""
}
type NodeAttr struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Target []string `protobuf:"bytes,1,rep,name=target,proto3" json:"target,omitempty"`
Attr []string `protobuf:"bytes,2,rep,name=attr,proto3" json:"attr,omitempty"`
}
func (x *NodeAttr) Reset() {
*x = NodeAttr{}
if protoimpl.UnsafeEnabled {
mi := &file_ionscale_v1_acl_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NodeAttr) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeAttr) ProtoMessage() {}
func (x *NodeAttr) ProtoReflect() protoreflect.Message {
mi := &file_ionscale_v1_acl_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeAttr.ProtoReflect.Descriptor instead.
func (*NodeAttr) Descriptor() ([]byte, []int) {
return file_ionscale_v1_acl_proto_rawDescGZIP(), []int{8}
}
func (x *NodeAttr) GetTarget() []string {
if x != nil {
return x.Target
}
return nil
}
func (x *NodeAttr) GetAttr() []string {
if x != nil {
return x.Attr
}
return nil
}
type ACLGrant struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Src []string `protobuf:"bytes,1,rep,name=src,proto3" json:"src,omitempty"`
Dst []string `protobuf:"bytes,2,rep,name=dst,proto3" json:"dst,omitempty"`
Ip []string `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"`
App map[string]*structpb.ListValue `protobuf:"bytes,4,rep,name=app,proto3" json:"app,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *ACLGrant) Reset() {
*x = ACLGrant{}
if protoimpl.UnsafeEnabled {
mi := &file_ionscale_v1_acl_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ACLGrant) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ACLGrant) ProtoMessage() {}
func (x *ACLGrant) ProtoReflect() protoreflect.Message {
mi := &file_ionscale_v1_acl_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ACLGrant.ProtoReflect.Descriptor instead.
func (*ACLGrant) Descriptor() ([]byte, []int) {
return file_ionscale_v1_acl_proto_rawDescGZIP(), []int{9}
}
func (x *ACLGrant) GetSrc() []string {
if x != nil {
return x.Src
}
return nil
}
func (x *ACLGrant) GetDst() []string {
if x != nil {
return x.Dst
}
return nil
}
func (x *ACLGrant) GetIp() []string {
if x != nil {
return x.Ip
}
return nil
}
func (x *ACLGrant) GetApp() map[string]*structpb.ListValue {
if x != nil {
return x.App
}
return nil
}
var File_ionscale_v1_acl_proto protoreflect.FileDescriptor
var file_ionscale_v1_acl_proto_rawDesc = []byte{
@@ -506,8 +664,8 @@ var file_ionscale_v1_acl_proto_rawDesc = []byte{
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06,
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x41, 0x43, 0x4c,
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd7,
0x04, 0x0a, 0x09, 0x41, 0x43, 0x4c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x05,
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbb,
0x05, 0x0a, 0x09, 0x41, 0x43, 0x4c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x05,
0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x69, 0x6f,
0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x50, 0x6f, 0x6c,
0x69, 0x63, 0x79, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05,
@@ -528,50 +686,75 @@ var file_ionscale_v1_acl_proto_rawDesc = []byte{
0x00, 0x52, 0x0d, 0x61, 0x75, 0x74, 0x6f, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x73,
0x88, 0x01, 0x01, 0x12, 0x26, 0x0a, 0x03, 0x73, 0x73, 0x68, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x14, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53,
0x53, 0x48, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x03, 0x73, 0x73, 0x68, 0x1a, 0x38, 0x0a, 0x0a, 0x48,
0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x55, 0x0a, 0x0b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75,
0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x58, 0x0a, 0x0e,
0x54, 0x61, 0x67, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x61,
0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x22, 0x41, 0x0a, 0x03, 0x41, 0x43, 0x4c, 0x12,
0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x72, 0x63, 0x18, 0x02,
0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x73, 0x72, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x73, 0x74,
0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x64, 0x73, 0x74, 0x22, 0xc2, 0x01, 0x0a, 0x0d,
0x41, 0x75, 0x74, 0x6f, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x12, 0x3e, 0x0a,
0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e,
0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x6f,
0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a,
0x08, 0x65, 0x78, 0x69, 0x74, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
0x08, 0x65, 0x78, 0x69, 0x74, 0x6e, 0x6f, 0x64, 0x65, 0x1a, 0x55, 0x0a, 0x0b, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
0x53, 0x48, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x03, 0x73, 0x73, 0x68, 0x12, 0x33, 0x0a, 0x09, 0x6e,
0x6f, 0x64, 0x65, 0x61, 0x74, 0x74, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,
0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64,
0x65, 0x41, 0x74, 0x74, 0x72, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x61, 0x74, 0x74, 0x72, 0x73,
0x12, 0x2d, 0x0a, 0x06, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x15, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41,
0x43, 0x4c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x06, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x1a,
0x38, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x55, 0x0a, 0x0b, 0x47, 0x72, 0x6f,
0x75, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x22, 0x5b, 0x0a, 0x07, 0x53, 0x53, 0x48, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x72, 0x63, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,
0x52, 0x03, 0x73, 0x72, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x73, 0x74, 0x18, 0x03, 0x20, 0x03,
0x28, 0x09, 0x52, 0x03, 0x64, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73,
0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x42, 0x3d, 0x5a,
0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x73, 0x69, 0x65,
0x62, 0x65, 0x6e, 0x73, 0x2f, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x70, 0x6b,
0x67, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76,
0x31, 0x3b, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x1a, 0x58, 0x0a, 0x0e, 0x54, 0x61, 0x67, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x61,
0x75, 0x74, 0x6f, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x22, 0x57, 0x0a, 0x03,
0x41, 0x43, 0x4c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x73,
0x72, 0x63, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x73, 0x72, 0x63, 0x12, 0x10, 0x0a,
0x03, 0x64, 0x73, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x64, 0x73, 0x74, 0x12,
0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc2, 0x01, 0x0a, 0x0d, 0x41, 0x75, 0x74, 0x6f, 0x41, 0x70,
0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x12, 0x3e, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61,
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76,
0x65, 0x72, 0x73, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x69, 0x74, 0x6e,
0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x6e,
0x6f, 0x64, 0x65, 0x1a, 0x55, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7d, 0x0a, 0x07, 0x53, 0x53,
0x48, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a,
0x03, 0x73, 0x72, 0x63, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x73, 0x72, 0x63, 0x12,
0x10, 0x0a, 0x03, 0x64, 0x73, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x64, 0x73,
0x74, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09,
0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x68, 0x65, 0x63, 0x6b,
0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x68,
0x65, 0x63, 0x6b, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x22, 0x36, 0x0a, 0x08, 0x4e, 0x6f, 0x64,
0x65, 0x41, 0x74, 0x74, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18,
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x0a,
0x04, 0x61, 0x74, 0x74, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x61, 0x74, 0x74,
0x72, 0x22, 0xc4, 0x01, 0x0a, 0x08, 0x41, 0x43, 0x4c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x12, 0x10,
0x0a, 0x03, 0x73, 0x72, 0x63, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x73, 0x72, 0x63,
0x12, 0x10, 0x0a, 0x03, 0x64, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x64,
0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x02,
0x69, 0x70, 0x12, 0x30, 0x0a, 0x03, 0x61, 0x70, 0x70, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x1e, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x43,
0x4c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
0x03, 0x61, 0x70, 0x70, 0x1a, 0x52, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x73, 0x69, 0x65, 0x62, 0x65, 0x6e, 0x73, 0x2f,
0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, 0x65, 0x6e,
0x2f, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x69, 0x6f, 0x6e,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -586,7 +769,7 @@ func file_ionscale_v1_acl_proto_rawDescGZIP() []byte {
return file_ionscale_v1_acl_proto_rawDescData
}
var file_ionscale_v1_acl_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_ionscale_v1_acl_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
var file_ionscale_v1_acl_proto_goTypes = []interface{}{
(*GetACLPolicyRequest)(nil), // 0: ionscale.v1.GetACLPolicyRequest
(*GetACLPolicyResponse)(nil), // 1: ionscale.v1.GetACLPolicyResponse
@@ -596,30 +779,37 @@ var file_ionscale_v1_acl_proto_goTypes = []interface{}{
(*ACL)(nil), // 5: ionscale.v1.ACL
(*AutoApprovers)(nil), // 6: ionscale.v1.AutoApprovers
(*SSHRule)(nil), // 7: ionscale.v1.SSHRule
nil, // 8: ionscale.v1.ACLPolicy.HostsEntry
nil, // 9: ionscale.v1.ACLPolicy.GroupsEntry
nil, // 10: ionscale.v1.ACLPolicy.TagownersEntry
nil, // 11: ionscale.v1.AutoApprovers.RoutesEntry
(*structpb.ListValue)(nil), // 12: google.protobuf.ListValue
(*NodeAttr)(nil), // 8: ionscale.v1.NodeAttr
(*ACLGrant)(nil), // 9: ionscale.v1.ACLGrant
nil, // 10: ionscale.v1.ACLPolicy.HostsEntry
nil, // 11: ionscale.v1.ACLPolicy.GroupsEntry
nil, // 12: ionscale.v1.ACLPolicy.TagownersEntry
nil, // 13: ionscale.v1.AutoApprovers.RoutesEntry
nil, // 14: ionscale.v1.ACLGrant.AppEntry
(*structpb.ListValue)(nil), // 15: google.protobuf.ListValue
}
var file_ionscale_v1_acl_proto_depIdxs = []int32{
4, // 0: ionscale.v1.GetACLPolicyResponse.policy:type_name -> ionscale.v1.ACLPolicy
4, // 1: ionscale.v1.SetACLPolicyRequest.policy:type_name -> ionscale.v1.ACLPolicy
8, // 2: ionscale.v1.ACLPolicy.hosts:type_name -> ionscale.v1.ACLPolicy.HostsEntry
9, // 3: ionscale.v1.ACLPolicy.groups:type_name -> ionscale.v1.ACLPolicy.GroupsEntry
10, // 2: ionscale.v1.ACLPolicy.hosts:type_name -> ionscale.v1.ACLPolicy.HostsEntry
11, // 3: ionscale.v1.ACLPolicy.groups:type_name -> ionscale.v1.ACLPolicy.GroupsEntry
5, // 4: ionscale.v1.ACLPolicy.acls:type_name -> ionscale.v1.ACL
10, // 5: ionscale.v1.ACLPolicy.tagowners:type_name -> ionscale.v1.ACLPolicy.TagownersEntry
12, // 5: ionscale.v1.ACLPolicy.tagowners:type_name -> ionscale.v1.ACLPolicy.TagownersEntry
6, // 6: ionscale.v1.ACLPolicy.autoapprovers:type_name -> ionscale.v1.AutoApprovers
7, // 7: ionscale.v1.ACLPolicy.ssh:type_name -> ionscale.v1.SSHRule
11, // 8: ionscale.v1.AutoApprovers.routes:type_name -> ionscale.v1.AutoApprovers.RoutesEntry
12, // 9: ionscale.v1.ACLPolicy.GroupsEntry.value:type_name -> google.protobuf.ListValue
12, // 10: ionscale.v1.ACLPolicy.TagownersEntry.value:type_name -> google.protobuf.ListValue
12, // 11: ionscale.v1.AutoApprovers.RoutesEntry.value:type_name -> google.protobuf.ListValue
12, // [12:12] is the sub-list for method output_type
12, // [12:12] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
8, // 8: ionscale.v1.ACLPolicy.nodeattrs:type_name -> ionscale.v1.NodeAttr
9, // 9: ionscale.v1.ACLPolicy.grants:type_name -> ionscale.v1.ACLGrant
13, // 10: ionscale.v1.AutoApprovers.routes:type_name -> ionscale.v1.AutoApprovers.RoutesEntry
14, // 11: ionscale.v1.ACLGrant.app:type_name -> ionscale.v1.ACLGrant.AppEntry
15, // 12: ionscale.v1.ACLPolicy.GroupsEntry.value:type_name -> google.protobuf.ListValue
15, // 13: ionscale.v1.ACLPolicy.TagownersEntry.value:type_name -> google.protobuf.ListValue
15, // 14: ionscale.v1.AutoApprovers.RoutesEntry.value:type_name -> google.protobuf.ListValue
15, // 15: ionscale.v1.ACLGrant.AppEntry.value:type_name -> google.protobuf.ListValue
16, // [16:16] is the sub-list for method output_type
16, // [16:16] is the sub-list for method input_type
16, // [16:16] is the sub-list for extension type_name
16, // [16:16] is the sub-list for extension extendee
0, // [0:16] is the sub-list for field type_name
}
func init() { file_ionscale_v1_acl_proto_init() }
@@ -724,6 +914,30 @@ func file_ionscale_v1_acl_proto_init() {
return nil
}
}
file_ionscale_v1_acl_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NodeAttr); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ionscale_v1_acl_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ACLGrant); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_ionscale_v1_acl_proto_msgTypes[4].OneofWrappers = []interface{}{}
type x struct{}
@@ -732,7 +946,7 @@ func file_ionscale_v1_acl_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_ionscale_v1_acl_proto_rawDesc,
NumEnums: 0,
NumMessages: 12,
NumMessages: 15,
NumExtensions: 0,
NumServices: 0,
},
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/auth.proto
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/auth_keys.proto
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/derp.proto
+51 -31
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/dns.proto
@@ -174,7 +174,8 @@ type SetDNSConfigResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Config *DNSConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
Config *DNSConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *SetDNSConfigResponse) Reset() {
@@ -216,6 +217,13 @@ func (x *SetDNSConfigResponse) GetConfig() *DNSConfig {
return nil
}
func (x *SetDNSConfigResponse) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
type DNSConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -227,6 +235,7 @@ type DNSConfig struct {
Routes map[string]*Routes `protobuf:"bytes,4,rep,name=routes,proto3" json:"routes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
MagicDnsSuffix string `protobuf:"bytes,5,opt,name=magic_dns_suffix,json=magicDnsSuffix,proto3" json:"magic_dns_suffix,omitempty"`
HttpsCerts bool `protobuf:"varint,6,opt,name=https_certs,json=httpsCerts,proto3" json:"https_certs,omitempty"`
SearchDomains []string `protobuf:"bytes,7,rep,name=search_domains,json=searchDomains,proto3" json:"search_domains,omitempty"`
}
func (x *DNSConfig) Reset() {
@@ -303,6 +312,13 @@ func (x *DNSConfig) GetHttpsCerts() bool {
return false
}
func (x *DNSConfig) GetSearchDomains() []string {
if x != nil {
return x.SearchDomains
}
return nil
}
type Routes struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -369,39 +385,43 @@ var file_ionscale_v1_dns_proto_rawDesc = []byte{
0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x46, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x44,
0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x60, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x44,
0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x2e, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x16, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44,
0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x22, 0xcf, 0x02, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b,
0x0a, 0x09, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x5f, 0x64, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
0x08, 0x52, 0x08, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x44, 0x6e, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6f,
0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x64, 0x6e,
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x6e, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x61, 0x6d,
0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b,
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x3a, 0x0a, 0x06, 0x72,
0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x69, 0x6f,
0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x67, 0x69, 0x63,
0x5f, 0x64, 0x6e, 0x73, 0x5f, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0e, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x44, 0x6e, 0x73, 0x53, 0x75, 0x66, 0x66, 0x69,
0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x73,
0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x43, 0x65, 0x72,
0x74, 0x73, 0x1a, 0x4e, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0x20, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06,
0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f,
0x75, 0x74, 0x65, 0x73, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x6a, 0x73, 0x69, 0x65, 0x62, 0x65, 0x6e, 0x73, 0x2f, 0x69, 0x6f, 0x6e, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x69, 0x6f, 0x6e,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xf6, 0x02, 0x0a, 0x09, 0x44,
0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x67, 0x69,
0x63, 0x5f, 0x64, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x61, 0x67,
0x69, 0x63, 0x44, 0x6e, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x08, 0x52, 0x10, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c,
0x44, 0x6e, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x3a, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18,
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65,
0x2e, 0x76, 0x31, 0x2e, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x6f,
0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65,
0x73, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x5f, 0x64, 0x6e, 0x73, 0x5f, 0x73,
0x75, 0x66, 0x66, 0x69, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x61, 0x67,
0x69, 0x63, 0x44, 0x6e, 0x73, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x68,
0x74, 0x74, 0x70, 0x73, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08,
0x52, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x43, 0x65, 0x72, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x07,
0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61,
0x69, 0x6e, 0x73, 0x1a, 0x4e, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
0x02, 0x38, 0x01, 0x22, 0x20, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x16, 0x0a,
0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72,
0x6f, 0x75, 0x74, 0x65, 0x73, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x73, 0x69, 0x65, 0x62, 0x65, 0x6e, 0x73, 0x2f, 0x69, 0x6f, 0x6e,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x69, 0x6f,
0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61,
0x6c, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/iam.proto
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/ionscale.proto
@@ -25,6 +25,148 @@ const (
IonscaleServiceName = "ionscale.v1.IonscaleService"
)
// These constants are the fully-qualified names of the RPCs defined in this package. They're
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
//
// Note that these are different from the fully-qualified method names used by
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
// period.
const (
// IonscaleServiceGetVersionProcedure is the fully-qualified name of the IonscaleService's
// GetVersion RPC.
IonscaleServiceGetVersionProcedure = "/ionscale.v1.IonscaleService/GetVersion"
// IonscaleServiceAuthenticateProcedure is the fully-qualified name of the IonscaleService's
// Authenticate RPC.
IonscaleServiceAuthenticateProcedure = "/ionscale.v1.IonscaleService/Authenticate"
// IonscaleServiceGetDefaultDERPMapProcedure is the fully-qualified name of the IonscaleService's
// GetDefaultDERPMap RPC.
IonscaleServiceGetDefaultDERPMapProcedure = "/ionscale.v1.IonscaleService/GetDefaultDERPMap"
// IonscaleServiceSetDefaultDERPMapProcedure is the fully-qualified name of the IonscaleService's
// SetDefaultDERPMap RPC.
IonscaleServiceSetDefaultDERPMapProcedure = "/ionscale.v1.IonscaleService/SetDefaultDERPMap"
// IonscaleServiceResetDefaultDERPMapProcedure is the fully-qualified name of the IonscaleService's
// ResetDefaultDERPMap RPC.
IonscaleServiceResetDefaultDERPMapProcedure = "/ionscale.v1.IonscaleService/ResetDefaultDERPMap"
// IonscaleServiceCreateTailnetProcedure is the fully-qualified name of the IonscaleService's
// CreateTailnet RPC.
IonscaleServiceCreateTailnetProcedure = "/ionscale.v1.IonscaleService/CreateTailnet"
// IonscaleServiceUpdateTailnetProcedure is the fully-qualified name of the IonscaleService's
// UpdateTailnet RPC.
IonscaleServiceUpdateTailnetProcedure = "/ionscale.v1.IonscaleService/UpdateTailnet"
// IonscaleServiceGetTailnetProcedure is the fully-qualified name of the IonscaleService's
// GetTailnet RPC.
IonscaleServiceGetTailnetProcedure = "/ionscale.v1.IonscaleService/GetTailnet"
// IonscaleServiceListTailnetsProcedure is the fully-qualified name of the IonscaleService's
// ListTailnets RPC.
IonscaleServiceListTailnetsProcedure = "/ionscale.v1.IonscaleService/ListTailnets"
// IonscaleServiceDeleteTailnetProcedure is the fully-qualified name of the IonscaleService's
// DeleteTailnet RPC.
IonscaleServiceDeleteTailnetProcedure = "/ionscale.v1.IonscaleService/DeleteTailnet"
// IonscaleServiceGetDERPMapProcedure is the fully-qualified name of the IonscaleService's
// GetDERPMap RPC.
IonscaleServiceGetDERPMapProcedure = "/ionscale.v1.IonscaleService/GetDERPMap"
// IonscaleServiceSetDERPMapProcedure is the fully-qualified name of the IonscaleService's
// SetDERPMap RPC.
IonscaleServiceSetDERPMapProcedure = "/ionscale.v1.IonscaleService/SetDERPMap"
// IonscaleServiceResetDERPMapProcedure is the fully-qualified name of the IonscaleService's
// ResetDERPMap RPC.
IonscaleServiceResetDERPMapProcedure = "/ionscale.v1.IonscaleService/ResetDERPMap"
// IonscaleServiceEnableFileSharingProcedure is the fully-qualified name of the IonscaleService's
// EnableFileSharing RPC.
IonscaleServiceEnableFileSharingProcedure = "/ionscale.v1.IonscaleService/EnableFileSharing"
// IonscaleServiceDisableFileSharingProcedure is the fully-qualified name of the IonscaleService's
// DisableFileSharing RPC.
IonscaleServiceDisableFileSharingProcedure = "/ionscale.v1.IonscaleService/DisableFileSharing"
// IonscaleServiceEnableServiceCollectionProcedure is the fully-qualified name of the
// IonscaleService's EnableServiceCollection RPC.
IonscaleServiceEnableServiceCollectionProcedure = "/ionscale.v1.IonscaleService/EnableServiceCollection"
// IonscaleServiceDisableServiceCollectionProcedure is the fully-qualified name of the
// IonscaleService's DisableServiceCollection RPC.
IonscaleServiceDisableServiceCollectionProcedure = "/ionscale.v1.IonscaleService/DisableServiceCollection"
// IonscaleServiceEnableSSHProcedure is the fully-qualified name of the IonscaleService's EnableSSH
// RPC.
IonscaleServiceEnableSSHProcedure = "/ionscale.v1.IonscaleService/EnableSSH"
// IonscaleServiceDisableSSHProcedure is the fully-qualified name of the IonscaleService's
// DisableSSH RPC.
IonscaleServiceDisableSSHProcedure = "/ionscale.v1.IonscaleService/DisableSSH"
// IonscaleServiceEnableMachineAuthorizationProcedure is the fully-qualified name of the
// IonscaleService's EnableMachineAuthorization RPC.
IonscaleServiceEnableMachineAuthorizationProcedure = "/ionscale.v1.IonscaleService/EnableMachineAuthorization"
// IonscaleServiceDisableMachineAuthorizationProcedure is the fully-qualified name of the
// IonscaleService's DisableMachineAuthorization RPC.
IonscaleServiceDisableMachineAuthorizationProcedure = "/ionscale.v1.IonscaleService/DisableMachineAuthorization"
// IonscaleServiceGetDNSConfigProcedure is the fully-qualified name of the IonscaleService's
// GetDNSConfig RPC.
IonscaleServiceGetDNSConfigProcedure = "/ionscale.v1.IonscaleService/GetDNSConfig"
// IonscaleServiceSetDNSConfigProcedure is the fully-qualified name of the IonscaleService's
// SetDNSConfig RPC.
IonscaleServiceSetDNSConfigProcedure = "/ionscale.v1.IonscaleService/SetDNSConfig"
// IonscaleServiceGetIAMPolicyProcedure is the fully-qualified name of the IonscaleService's
// GetIAMPolicy RPC.
IonscaleServiceGetIAMPolicyProcedure = "/ionscale.v1.IonscaleService/GetIAMPolicy"
// IonscaleServiceSetIAMPolicyProcedure is the fully-qualified name of the IonscaleService's
// SetIAMPolicy RPC.
IonscaleServiceSetIAMPolicyProcedure = "/ionscale.v1.IonscaleService/SetIAMPolicy"
// IonscaleServiceGetACLPolicyProcedure is the fully-qualified name of the IonscaleService's
// GetACLPolicy RPC.
IonscaleServiceGetACLPolicyProcedure = "/ionscale.v1.IonscaleService/GetACLPolicy"
// IonscaleServiceSetACLPolicyProcedure is the fully-qualified name of the IonscaleService's
// SetACLPolicy RPC.
IonscaleServiceSetACLPolicyProcedure = "/ionscale.v1.IonscaleService/SetACLPolicy"
// IonscaleServiceGetAuthKeyProcedure is the fully-qualified name of the IonscaleService's
// GetAuthKey RPC.
IonscaleServiceGetAuthKeyProcedure = "/ionscale.v1.IonscaleService/GetAuthKey"
// IonscaleServiceCreateAuthKeyProcedure is the fully-qualified name of the IonscaleService's
// CreateAuthKey RPC.
IonscaleServiceCreateAuthKeyProcedure = "/ionscale.v1.IonscaleService/CreateAuthKey"
// IonscaleServiceDeleteAuthKeyProcedure is the fully-qualified name of the IonscaleService's
// DeleteAuthKey RPC.
IonscaleServiceDeleteAuthKeyProcedure = "/ionscale.v1.IonscaleService/DeleteAuthKey"
// IonscaleServiceListAuthKeysProcedure is the fully-qualified name of the IonscaleService's
// ListAuthKeys RPC.
IonscaleServiceListAuthKeysProcedure = "/ionscale.v1.IonscaleService/ListAuthKeys"
// IonscaleServiceListUsersProcedure is the fully-qualified name of the IonscaleService's ListUsers
// RPC.
IonscaleServiceListUsersProcedure = "/ionscale.v1.IonscaleService/ListUsers"
// IonscaleServiceDeleteUserProcedure is the fully-qualified name of the IonscaleService's
// DeleteUser RPC.
IonscaleServiceDeleteUserProcedure = "/ionscale.v1.IonscaleService/DeleteUser"
// IonscaleServiceGetMachineProcedure is the fully-qualified name of the IonscaleService's
// GetMachine RPC.
IonscaleServiceGetMachineProcedure = "/ionscale.v1.IonscaleService/GetMachine"
// IonscaleServiceListMachinesProcedure is the fully-qualified name of the IonscaleService's
// ListMachines RPC.
IonscaleServiceListMachinesProcedure = "/ionscale.v1.IonscaleService/ListMachines"
// IonscaleServiceAuthorizeMachineProcedure is the fully-qualified name of the IonscaleService's
// AuthorizeMachine RPC.
IonscaleServiceAuthorizeMachineProcedure = "/ionscale.v1.IonscaleService/AuthorizeMachine"
// IonscaleServiceExpireMachineProcedure is the fully-qualified name of the IonscaleService's
// ExpireMachine RPC.
IonscaleServiceExpireMachineProcedure = "/ionscale.v1.IonscaleService/ExpireMachine"
// IonscaleServiceDeleteMachineProcedure is the fully-qualified name of the IonscaleService's
// DeleteMachine RPC.
IonscaleServiceDeleteMachineProcedure = "/ionscale.v1.IonscaleService/DeleteMachine"
// IonscaleServiceSetMachineKeyExpiryProcedure is the fully-qualified name of the IonscaleService's
// SetMachineKeyExpiry RPC.
IonscaleServiceSetMachineKeyExpiryProcedure = "/ionscale.v1.IonscaleService/SetMachineKeyExpiry"
// IonscaleServiceGetMachineRoutesProcedure is the fully-qualified name of the IonscaleService's
// GetMachineRoutes RPC.
IonscaleServiceGetMachineRoutesProcedure = "/ionscale.v1.IonscaleService/GetMachineRoutes"
// IonscaleServiceEnableMachineRoutesProcedure is the fully-qualified name of the IonscaleService's
// EnableMachineRoutes RPC.
IonscaleServiceEnableMachineRoutesProcedure = "/ionscale.v1.IonscaleService/EnableMachineRoutes"
// IonscaleServiceDisableMachineRoutesProcedure is the fully-qualified name of the IonscaleService's
// DisableMachineRoutes RPC.
IonscaleServiceDisableMachineRoutesProcedure = "/ionscale.v1.IonscaleService/DisableMachineRoutes"
// IonscaleServiceEnableExitNodeProcedure is the fully-qualified name of the IonscaleService's
// EnableExitNode RPC.
IonscaleServiceEnableExitNodeProcedure = "/ionscale.v1.IonscaleService/EnableExitNode"
// IonscaleServiceDisableExitNodeProcedure is the fully-qualified name of the IonscaleService's
// DisableExitNode RPC.
IonscaleServiceDisableExitNodeProcedure = "/ionscale.v1.IonscaleService/DisableExitNode"
)
// IonscaleServiceClient is a client for the ionscale.v1.IonscaleService service.
type IonscaleServiceClient interface {
GetVersion(context.Context, *connect_go.Request[v1.GetVersionRequest]) (*connect_go.Response[v1.GetVersionResponse], error)
@@ -85,222 +227,222 @@ func NewIonscaleServiceClient(httpClient connect_go.HTTPClient, baseURL string,
return &ionscaleServiceClient{
getVersion: connect_go.NewClient[v1.GetVersionRequest, v1.GetVersionResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/GetVersion",
baseURL+IonscaleServiceGetVersionProcedure,
opts...,
),
authenticate: connect_go.NewClient[v1.AuthenticateRequest, v1.AuthenticateResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/Authenticate",
baseURL+IonscaleServiceAuthenticateProcedure,
opts...,
),
getDefaultDERPMap: connect_go.NewClient[v1.GetDefaultDERPMapRequest, v1.GetDefaultDERPMapResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/GetDefaultDERPMap",
baseURL+IonscaleServiceGetDefaultDERPMapProcedure,
opts...,
),
setDefaultDERPMap: connect_go.NewClient[v1.SetDefaultDERPMapRequest, v1.SetDefaultDERPMapResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/SetDefaultDERPMap",
baseURL+IonscaleServiceSetDefaultDERPMapProcedure,
opts...,
),
resetDefaultDERPMap: connect_go.NewClient[v1.ResetDefaultDERPMapRequest, v1.ResetDefaultDERPMapResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/ResetDefaultDERPMap",
baseURL+IonscaleServiceResetDefaultDERPMapProcedure,
opts...,
),
createTailnet: connect_go.NewClient[v1.CreateTailnetRequest, v1.CreateTailnetResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/CreateTailnet",
baseURL+IonscaleServiceCreateTailnetProcedure,
opts...,
),
updateTailnet: connect_go.NewClient[v1.UpdateTailnetRequest, v1.UpdateTailnetResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/UpdateTailnet",
baseURL+IonscaleServiceUpdateTailnetProcedure,
opts...,
),
getTailnet: connect_go.NewClient[v1.GetTailnetRequest, v1.GetTailnetResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/GetTailnet",
baseURL+IonscaleServiceGetTailnetProcedure,
opts...,
),
listTailnets: connect_go.NewClient[v1.ListTailnetsRequest, v1.ListTailnetsResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/ListTailnets",
baseURL+IonscaleServiceListTailnetsProcedure,
opts...,
),
deleteTailnet: connect_go.NewClient[v1.DeleteTailnetRequest, v1.DeleteTailnetResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/DeleteTailnet",
baseURL+IonscaleServiceDeleteTailnetProcedure,
opts...,
),
getDERPMap: connect_go.NewClient[v1.GetDERPMapRequest, v1.GetDERPMapResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/GetDERPMap",
baseURL+IonscaleServiceGetDERPMapProcedure,
opts...,
),
setDERPMap: connect_go.NewClient[v1.SetDERPMapRequest, v1.SetDERPMapResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/SetDERPMap",
baseURL+IonscaleServiceSetDERPMapProcedure,
opts...,
),
resetDERPMap: connect_go.NewClient[v1.ResetDERPMapRequest, v1.ResetDERPMapResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/ResetDERPMap",
baseURL+IonscaleServiceResetDERPMapProcedure,
opts...,
),
enableFileSharing: connect_go.NewClient[v1.EnableFileSharingRequest, v1.EnableFileSharingResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/EnableFileSharing",
baseURL+IonscaleServiceEnableFileSharingProcedure,
opts...,
),
disableFileSharing: connect_go.NewClient[v1.DisableFileSharingRequest, v1.DisableFileSharingResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/DisableFileSharing",
baseURL+IonscaleServiceDisableFileSharingProcedure,
opts...,
),
enableServiceCollection: connect_go.NewClient[v1.EnableServiceCollectionRequest, v1.EnableServiceCollectionResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/EnableServiceCollection",
baseURL+IonscaleServiceEnableServiceCollectionProcedure,
opts...,
),
disableServiceCollection: connect_go.NewClient[v1.DisableServiceCollectionRequest, v1.DisableServiceCollectionResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/DisableServiceCollection",
baseURL+IonscaleServiceDisableServiceCollectionProcedure,
opts...,
),
enableSSH: connect_go.NewClient[v1.EnableSSHRequest, v1.EnableSSHResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/EnableSSH",
baseURL+IonscaleServiceEnableSSHProcedure,
opts...,
),
disableSSH: connect_go.NewClient[v1.DisableSSHRequest, v1.DisableSSHResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/DisableSSH",
baseURL+IonscaleServiceDisableSSHProcedure,
opts...,
),
enableMachineAuthorization: connect_go.NewClient[v1.EnableMachineAuthorizationRequest, v1.EnableMachineAuthorizationResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/EnableMachineAuthorization",
baseURL+IonscaleServiceEnableMachineAuthorizationProcedure,
opts...,
),
disableMachineAuthorization: connect_go.NewClient[v1.DisableMachineAuthorizationRequest, v1.DisableMachineAuthorizationResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/DisableMachineAuthorization",
baseURL+IonscaleServiceDisableMachineAuthorizationProcedure,
opts...,
),
getDNSConfig: connect_go.NewClient[v1.GetDNSConfigRequest, v1.GetDNSConfigResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/GetDNSConfig",
baseURL+IonscaleServiceGetDNSConfigProcedure,
opts...,
),
setDNSConfig: connect_go.NewClient[v1.SetDNSConfigRequest, v1.SetDNSConfigResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/SetDNSConfig",
baseURL+IonscaleServiceSetDNSConfigProcedure,
opts...,
),
getIAMPolicy: connect_go.NewClient[v1.GetIAMPolicyRequest, v1.GetIAMPolicyResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/GetIAMPolicy",
baseURL+IonscaleServiceGetIAMPolicyProcedure,
opts...,
),
setIAMPolicy: connect_go.NewClient[v1.SetIAMPolicyRequest, v1.SetIAMPolicyResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/SetIAMPolicy",
baseURL+IonscaleServiceSetIAMPolicyProcedure,
opts...,
),
getACLPolicy: connect_go.NewClient[v1.GetACLPolicyRequest, v1.GetACLPolicyResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/GetACLPolicy",
baseURL+IonscaleServiceGetACLPolicyProcedure,
opts...,
),
setACLPolicy: connect_go.NewClient[v1.SetACLPolicyRequest, v1.SetACLPolicyResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/SetACLPolicy",
baseURL+IonscaleServiceSetACLPolicyProcedure,
opts...,
),
getAuthKey: connect_go.NewClient[v1.GetAuthKeyRequest, v1.GetAuthKeyResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/GetAuthKey",
baseURL+IonscaleServiceGetAuthKeyProcedure,
opts...,
),
createAuthKey: connect_go.NewClient[v1.CreateAuthKeyRequest, v1.CreateAuthKeyResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/CreateAuthKey",
baseURL+IonscaleServiceCreateAuthKeyProcedure,
opts...,
),
deleteAuthKey: connect_go.NewClient[v1.DeleteAuthKeyRequest, v1.DeleteAuthKeyResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/DeleteAuthKey",
baseURL+IonscaleServiceDeleteAuthKeyProcedure,
opts...,
),
listAuthKeys: connect_go.NewClient[v1.ListAuthKeysRequest, v1.ListAuthKeysResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/ListAuthKeys",
baseURL+IonscaleServiceListAuthKeysProcedure,
opts...,
),
listUsers: connect_go.NewClient[v1.ListUsersRequest, v1.ListUsersResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/ListUsers",
baseURL+IonscaleServiceListUsersProcedure,
opts...,
),
deleteUser: connect_go.NewClient[v1.DeleteUserRequest, v1.DeleteUserResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/DeleteUser",
baseURL+IonscaleServiceDeleteUserProcedure,
opts...,
),
getMachine: connect_go.NewClient[v1.GetMachineRequest, v1.GetMachineResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/GetMachine",
baseURL+IonscaleServiceGetMachineProcedure,
opts...,
),
listMachines: connect_go.NewClient[v1.ListMachinesRequest, v1.ListMachinesResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/ListMachines",
baseURL+IonscaleServiceListMachinesProcedure,
opts...,
),
authorizeMachine: connect_go.NewClient[v1.AuthorizeMachineRequest, v1.AuthorizeMachineResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/AuthorizeMachine",
baseURL+IonscaleServiceAuthorizeMachineProcedure,
opts...,
),
expireMachine: connect_go.NewClient[v1.ExpireMachineRequest, v1.ExpireMachineResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/ExpireMachine",
baseURL+IonscaleServiceExpireMachineProcedure,
opts...,
),
deleteMachine: connect_go.NewClient[v1.DeleteMachineRequest, v1.DeleteMachineResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/DeleteMachine",
baseURL+IonscaleServiceDeleteMachineProcedure,
opts...,
),
setMachineKeyExpiry: connect_go.NewClient[v1.SetMachineKeyExpiryRequest, v1.SetMachineKeyExpiryResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/SetMachineKeyExpiry",
baseURL+IonscaleServiceSetMachineKeyExpiryProcedure,
opts...,
),
getMachineRoutes: connect_go.NewClient[v1.GetMachineRoutesRequest, v1.GetMachineRoutesResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/GetMachineRoutes",
baseURL+IonscaleServiceGetMachineRoutesProcedure,
opts...,
),
enableMachineRoutes: connect_go.NewClient[v1.EnableMachineRoutesRequest, v1.EnableMachineRoutesResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/EnableMachineRoutes",
baseURL+IonscaleServiceEnableMachineRoutesProcedure,
opts...,
),
disableMachineRoutes: connect_go.NewClient[v1.DisableMachineRoutesRequest, v1.DisableMachineRoutesResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/DisableMachineRoutes",
baseURL+IonscaleServiceDisableMachineRoutesProcedure,
opts...,
),
enableExitNode: connect_go.NewClient[v1.EnableExitNodeRequest, v1.EnableExitNodeResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/EnableExitNode",
baseURL+IonscaleServiceEnableExitNodeProcedure,
opts...,
),
disableExitNode: connect_go.NewClient[v1.DisableExitNodeRequest, v1.DisableExitNodeResponse](
httpClient,
baseURL+"/ionscale.v1.IonscaleService/DisableExitNode",
baseURL+IonscaleServiceDisableExitNodeProcedure,
opts...,
),
}
@@ -628,228 +770,320 @@ type IonscaleServiceHandler interface {
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression.
func NewIonscaleServiceHandler(svc IonscaleServiceHandler, opts ...connect_go.HandlerOption) (string, http.Handler) {
mux := http.NewServeMux()
mux.Handle("/ionscale.v1.IonscaleService/GetVersion", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/GetVersion",
ionscaleServiceGetVersionHandler := connect_go.NewUnaryHandler(
IonscaleServiceGetVersionProcedure,
svc.GetVersion,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/Authenticate", connect_go.NewServerStreamHandler(
"/ionscale.v1.IonscaleService/Authenticate",
)
ionscaleServiceAuthenticateHandler := connect_go.NewServerStreamHandler(
IonscaleServiceAuthenticateProcedure,
svc.Authenticate,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/GetDefaultDERPMap", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/GetDefaultDERPMap",
)
ionscaleServiceGetDefaultDERPMapHandler := connect_go.NewUnaryHandler(
IonscaleServiceGetDefaultDERPMapProcedure,
svc.GetDefaultDERPMap,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/SetDefaultDERPMap", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/SetDefaultDERPMap",
)
ionscaleServiceSetDefaultDERPMapHandler := connect_go.NewUnaryHandler(
IonscaleServiceSetDefaultDERPMapProcedure,
svc.SetDefaultDERPMap,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/ResetDefaultDERPMap", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/ResetDefaultDERPMap",
)
ionscaleServiceResetDefaultDERPMapHandler := connect_go.NewUnaryHandler(
IonscaleServiceResetDefaultDERPMapProcedure,
svc.ResetDefaultDERPMap,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/CreateTailnet", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/CreateTailnet",
)
ionscaleServiceCreateTailnetHandler := connect_go.NewUnaryHandler(
IonscaleServiceCreateTailnetProcedure,
svc.CreateTailnet,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/UpdateTailnet", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/UpdateTailnet",
)
ionscaleServiceUpdateTailnetHandler := connect_go.NewUnaryHandler(
IonscaleServiceUpdateTailnetProcedure,
svc.UpdateTailnet,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/GetTailnet", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/GetTailnet",
)
ionscaleServiceGetTailnetHandler := connect_go.NewUnaryHandler(
IonscaleServiceGetTailnetProcedure,
svc.GetTailnet,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/ListTailnets", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/ListTailnets",
)
ionscaleServiceListTailnetsHandler := connect_go.NewUnaryHandler(
IonscaleServiceListTailnetsProcedure,
svc.ListTailnets,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/DeleteTailnet", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/DeleteTailnet",
)
ionscaleServiceDeleteTailnetHandler := connect_go.NewUnaryHandler(
IonscaleServiceDeleteTailnetProcedure,
svc.DeleteTailnet,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/GetDERPMap", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/GetDERPMap",
)
ionscaleServiceGetDERPMapHandler := connect_go.NewUnaryHandler(
IonscaleServiceGetDERPMapProcedure,
svc.GetDERPMap,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/SetDERPMap", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/SetDERPMap",
)
ionscaleServiceSetDERPMapHandler := connect_go.NewUnaryHandler(
IonscaleServiceSetDERPMapProcedure,
svc.SetDERPMap,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/ResetDERPMap", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/ResetDERPMap",
)
ionscaleServiceResetDERPMapHandler := connect_go.NewUnaryHandler(
IonscaleServiceResetDERPMapProcedure,
svc.ResetDERPMap,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/EnableFileSharing", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/EnableFileSharing",
)
ionscaleServiceEnableFileSharingHandler := connect_go.NewUnaryHandler(
IonscaleServiceEnableFileSharingProcedure,
svc.EnableFileSharing,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/DisableFileSharing", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/DisableFileSharing",
)
ionscaleServiceDisableFileSharingHandler := connect_go.NewUnaryHandler(
IonscaleServiceDisableFileSharingProcedure,
svc.DisableFileSharing,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/EnableServiceCollection", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/EnableServiceCollection",
)
ionscaleServiceEnableServiceCollectionHandler := connect_go.NewUnaryHandler(
IonscaleServiceEnableServiceCollectionProcedure,
svc.EnableServiceCollection,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/DisableServiceCollection", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/DisableServiceCollection",
)
ionscaleServiceDisableServiceCollectionHandler := connect_go.NewUnaryHandler(
IonscaleServiceDisableServiceCollectionProcedure,
svc.DisableServiceCollection,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/EnableSSH", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/EnableSSH",
)
ionscaleServiceEnableSSHHandler := connect_go.NewUnaryHandler(
IonscaleServiceEnableSSHProcedure,
svc.EnableSSH,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/DisableSSH", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/DisableSSH",
)
ionscaleServiceDisableSSHHandler := connect_go.NewUnaryHandler(
IonscaleServiceDisableSSHProcedure,
svc.DisableSSH,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/EnableMachineAuthorization", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/EnableMachineAuthorization",
)
ionscaleServiceEnableMachineAuthorizationHandler := connect_go.NewUnaryHandler(
IonscaleServiceEnableMachineAuthorizationProcedure,
svc.EnableMachineAuthorization,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/DisableMachineAuthorization", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/DisableMachineAuthorization",
)
ionscaleServiceDisableMachineAuthorizationHandler := connect_go.NewUnaryHandler(
IonscaleServiceDisableMachineAuthorizationProcedure,
svc.DisableMachineAuthorization,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/GetDNSConfig", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/GetDNSConfig",
)
ionscaleServiceGetDNSConfigHandler := connect_go.NewUnaryHandler(
IonscaleServiceGetDNSConfigProcedure,
svc.GetDNSConfig,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/SetDNSConfig", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/SetDNSConfig",
)
ionscaleServiceSetDNSConfigHandler := connect_go.NewUnaryHandler(
IonscaleServiceSetDNSConfigProcedure,
svc.SetDNSConfig,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/GetIAMPolicy", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/GetIAMPolicy",
)
ionscaleServiceGetIAMPolicyHandler := connect_go.NewUnaryHandler(
IonscaleServiceGetIAMPolicyProcedure,
svc.GetIAMPolicy,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/SetIAMPolicy", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/SetIAMPolicy",
)
ionscaleServiceSetIAMPolicyHandler := connect_go.NewUnaryHandler(
IonscaleServiceSetIAMPolicyProcedure,
svc.SetIAMPolicy,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/GetACLPolicy", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/GetACLPolicy",
)
ionscaleServiceGetACLPolicyHandler := connect_go.NewUnaryHandler(
IonscaleServiceGetACLPolicyProcedure,
svc.GetACLPolicy,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/SetACLPolicy", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/SetACLPolicy",
)
ionscaleServiceSetACLPolicyHandler := connect_go.NewUnaryHandler(
IonscaleServiceSetACLPolicyProcedure,
svc.SetACLPolicy,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/GetAuthKey", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/GetAuthKey",
)
ionscaleServiceGetAuthKeyHandler := connect_go.NewUnaryHandler(
IonscaleServiceGetAuthKeyProcedure,
svc.GetAuthKey,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/CreateAuthKey", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/CreateAuthKey",
)
ionscaleServiceCreateAuthKeyHandler := connect_go.NewUnaryHandler(
IonscaleServiceCreateAuthKeyProcedure,
svc.CreateAuthKey,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/DeleteAuthKey", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/DeleteAuthKey",
)
ionscaleServiceDeleteAuthKeyHandler := connect_go.NewUnaryHandler(
IonscaleServiceDeleteAuthKeyProcedure,
svc.DeleteAuthKey,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/ListAuthKeys", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/ListAuthKeys",
)
ionscaleServiceListAuthKeysHandler := connect_go.NewUnaryHandler(
IonscaleServiceListAuthKeysProcedure,
svc.ListAuthKeys,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/ListUsers", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/ListUsers",
)
ionscaleServiceListUsersHandler := connect_go.NewUnaryHandler(
IonscaleServiceListUsersProcedure,
svc.ListUsers,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/DeleteUser", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/DeleteUser",
)
ionscaleServiceDeleteUserHandler := connect_go.NewUnaryHandler(
IonscaleServiceDeleteUserProcedure,
svc.DeleteUser,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/GetMachine", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/GetMachine",
)
ionscaleServiceGetMachineHandler := connect_go.NewUnaryHandler(
IonscaleServiceGetMachineProcedure,
svc.GetMachine,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/ListMachines", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/ListMachines",
)
ionscaleServiceListMachinesHandler := connect_go.NewUnaryHandler(
IonscaleServiceListMachinesProcedure,
svc.ListMachines,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/AuthorizeMachine", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/AuthorizeMachine",
)
ionscaleServiceAuthorizeMachineHandler := connect_go.NewUnaryHandler(
IonscaleServiceAuthorizeMachineProcedure,
svc.AuthorizeMachine,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/ExpireMachine", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/ExpireMachine",
)
ionscaleServiceExpireMachineHandler := connect_go.NewUnaryHandler(
IonscaleServiceExpireMachineProcedure,
svc.ExpireMachine,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/DeleteMachine", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/DeleteMachine",
)
ionscaleServiceDeleteMachineHandler := connect_go.NewUnaryHandler(
IonscaleServiceDeleteMachineProcedure,
svc.DeleteMachine,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/SetMachineKeyExpiry", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/SetMachineKeyExpiry",
)
ionscaleServiceSetMachineKeyExpiryHandler := connect_go.NewUnaryHandler(
IonscaleServiceSetMachineKeyExpiryProcedure,
svc.SetMachineKeyExpiry,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/GetMachineRoutes", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/GetMachineRoutes",
)
ionscaleServiceGetMachineRoutesHandler := connect_go.NewUnaryHandler(
IonscaleServiceGetMachineRoutesProcedure,
svc.GetMachineRoutes,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/EnableMachineRoutes", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/EnableMachineRoutes",
)
ionscaleServiceEnableMachineRoutesHandler := connect_go.NewUnaryHandler(
IonscaleServiceEnableMachineRoutesProcedure,
svc.EnableMachineRoutes,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/DisableMachineRoutes", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/DisableMachineRoutes",
)
ionscaleServiceDisableMachineRoutesHandler := connect_go.NewUnaryHandler(
IonscaleServiceDisableMachineRoutesProcedure,
svc.DisableMachineRoutes,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/EnableExitNode", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/EnableExitNode",
)
ionscaleServiceEnableExitNodeHandler := connect_go.NewUnaryHandler(
IonscaleServiceEnableExitNodeProcedure,
svc.EnableExitNode,
opts...,
))
mux.Handle("/ionscale.v1.IonscaleService/DisableExitNode", connect_go.NewUnaryHandler(
"/ionscale.v1.IonscaleService/DisableExitNode",
)
ionscaleServiceDisableExitNodeHandler := connect_go.NewUnaryHandler(
IonscaleServiceDisableExitNodeProcedure,
svc.DisableExitNode,
opts...,
))
return "/ionscale.v1.IonscaleService/", mux
)
return "/ionscale.v1.IonscaleService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case IonscaleServiceGetVersionProcedure:
ionscaleServiceGetVersionHandler.ServeHTTP(w, r)
case IonscaleServiceAuthenticateProcedure:
ionscaleServiceAuthenticateHandler.ServeHTTP(w, r)
case IonscaleServiceGetDefaultDERPMapProcedure:
ionscaleServiceGetDefaultDERPMapHandler.ServeHTTP(w, r)
case IonscaleServiceSetDefaultDERPMapProcedure:
ionscaleServiceSetDefaultDERPMapHandler.ServeHTTP(w, r)
case IonscaleServiceResetDefaultDERPMapProcedure:
ionscaleServiceResetDefaultDERPMapHandler.ServeHTTP(w, r)
case IonscaleServiceCreateTailnetProcedure:
ionscaleServiceCreateTailnetHandler.ServeHTTP(w, r)
case IonscaleServiceUpdateTailnetProcedure:
ionscaleServiceUpdateTailnetHandler.ServeHTTP(w, r)
case IonscaleServiceGetTailnetProcedure:
ionscaleServiceGetTailnetHandler.ServeHTTP(w, r)
case IonscaleServiceListTailnetsProcedure:
ionscaleServiceListTailnetsHandler.ServeHTTP(w, r)
case IonscaleServiceDeleteTailnetProcedure:
ionscaleServiceDeleteTailnetHandler.ServeHTTP(w, r)
case IonscaleServiceGetDERPMapProcedure:
ionscaleServiceGetDERPMapHandler.ServeHTTP(w, r)
case IonscaleServiceSetDERPMapProcedure:
ionscaleServiceSetDERPMapHandler.ServeHTTP(w, r)
case IonscaleServiceResetDERPMapProcedure:
ionscaleServiceResetDERPMapHandler.ServeHTTP(w, r)
case IonscaleServiceEnableFileSharingProcedure:
ionscaleServiceEnableFileSharingHandler.ServeHTTP(w, r)
case IonscaleServiceDisableFileSharingProcedure:
ionscaleServiceDisableFileSharingHandler.ServeHTTP(w, r)
case IonscaleServiceEnableServiceCollectionProcedure:
ionscaleServiceEnableServiceCollectionHandler.ServeHTTP(w, r)
case IonscaleServiceDisableServiceCollectionProcedure:
ionscaleServiceDisableServiceCollectionHandler.ServeHTTP(w, r)
case IonscaleServiceEnableSSHProcedure:
ionscaleServiceEnableSSHHandler.ServeHTTP(w, r)
case IonscaleServiceDisableSSHProcedure:
ionscaleServiceDisableSSHHandler.ServeHTTP(w, r)
case IonscaleServiceEnableMachineAuthorizationProcedure:
ionscaleServiceEnableMachineAuthorizationHandler.ServeHTTP(w, r)
case IonscaleServiceDisableMachineAuthorizationProcedure:
ionscaleServiceDisableMachineAuthorizationHandler.ServeHTTP(w, r)
case IonscaleServiceGetDNSConfigProcedure:
ionscaleServiceGetDNSConfigHandler.ServeHTTP(w, r)
case IonscaleServiceSetDNSConfigProcedure:
ionscaleServiceSetDNSConfigHandler.ServeHTTP(w, r)
case IonscaleServiceGetIAMPolicyProcedure:
ionscaleServiceGetIAMPolicyHandler.ServeHTTP(w, r)
case IonscaleServiceSetIAMPolicyProcedure:
ionscaleServiceSetIAMPolicyHandler.ServeHTTP(w, r)
case IonscaleServiceGetACLPolicyProcedure:
ionscaleServiceGetACLPolicyHandler.ServeHTTP(w, r)
case IonscaleServiceSetACLPolicyProcedure:
ionscaleServiceSetACLPolicyHandler.ServeHTTP(w, r)
case IonscaleServiceGetAuthKeyProcedure:
ionscaleServiceGetAuthKeyHandler.ServeHTTP(w, r)
case IonscaleServiceCreateAuthKeyProcedure:
ionscaleServiceCreateAuthKeyHandler.ServeHTTP(w, r)
case IonscaleServiceDeleteAuthKeyProcedure:
ionscaleServiceDeleteAuthKeyHandler.ServeHTTP(w, r)
case IonscaleServiceListAuthKeysProcedure:
ionscaleServiceListAuthKeysHandler.ServeHTTP(w, r)
case IonscaleServiceListUsersProcedure:
ionscaleServiceListUsersHandler.ServeHTTP(w, r)
case IonscaleServiceDeleteUserProcedure:
ionscaleServiceDeleteUserHandler.ServeHTTP(w, r)
case IonscaleServiceGetMachineProcedure:
ionscaleServiceGetMachineHandler.ServeHTTP(w, r)
case IonscaleServiceListMachinesProcedure:
ionscaleServiceListMachinesHandler.ServeHTTP(w, r)
case IonscaleServiceAuthorizeMachineProcedure:
ionscaleServiceAuthorizeMachineHandler.ServeHTTP(w, r)
case IonscaleServiceExpireMachineProcedure:
ionscaleServiceExpireMachineHandler.ServeHTTP(w, r)
case IonscaleServiceDeleteMachineProcedure:
ionscaleServiceDeleteMachineHandler.ServeHTTP(w, r)
case IonscaleServiceSetMachineKeyExpiryProcedure:
ionscaleServiceSetMachineKeyExpiryHandler.ServeHTTP(w, r)
case IonscaleServiceGetMachineRoutesProcedure:
ionscaleServiceGetMachineRoutesHandler.ServeHTTP(w, r)
case IonscaleServiceEnableMachineRoutesProcedure:
ionscaleServiceEnableMachineRoutesHandler.ServeHTTP(w, r)
case IonscaleServiceDisableMachineRoutesProcedure:
ionscaleServiceDisableMachineRoutesHandler.ServeHTTP(w, r)
case IonscaleServiceEnableExitNodeProcedure:
ionscaleServiceEnableExitNodeHandler.ServeHTTP(w, r)
case IonscaleServiceDisableExitNodeProcedure:
ionscaleServiceDisableExitNodeHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
// UnimplementedIonscaleServiceHandler returns CodeUnimplemented from all methods.
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/machines.proto
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/ref.proto
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/routes.proto
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/tailnets.proto
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/users.proto
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: ionscale/v1/version.proto

Some files were not shown because too many files have changed in this diff Show More