You've already forked ionscale
mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-04-05 12:32:58 +01:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44b6b20361 | |||
| cbde00c9f5 | |||
| 8f2c198bfe | |||
| 8f998b05f7 | |||
| 3fccde2932 | |||
| 7fa31bdf1f | |||
| 980ab1bc46 | |||
| 123ca99665 | |||
| 0c5e586cf9 | |||
| 79bc3bffb1 | |||
| 452c5ee516 | |||
| c1ea283e6d | |||
| 6a5d44882a | |||
| cbcbd61c3e | |||
| b083e2631a | |||
| 4587ed8eaa | |||
| 3118d2e573 | |||
| 7e1d90590d | |||
| 1b66b1e9be | |||
| 35e13a0698 | |||
| 951d0f299e | |||
| d10a022f29 | |||
| 9b5f045849 | |||
| 8a3f47490e | |||
| c76c2f16dd | |||
| dd2e783d8e | |||
| 473c3370ce | |||
| d6cc55cf5b | |||
| 9808860412 | |||
| 2bc03b895b | |||
| 54fa423acd | |||
| a303de71ee | |||
| cdbecf04fc | |||
| 75b58d0784 | |||
| 038c0afa8b | |||
| d9fafdcfd2 | |||
| 9b8782cccf | |||
| ea658a0e81 | |||
| e31ce67f84 | |||
| d5ca503318 | |||
| 4cab4dfb9a | |||
| 515f441dae | |||
| 9ac4c85c99 | |||
| 60a2faec4a | |||
| 339b9cfd37 | |||
| d0eac84271 | |||
| f193afa146 | |||
| cf67f6cf64 | |||
| 1ac3aa36ba | |||
| 9fd4e5fee4 | |||
| 326860c941 | |||
| 4ba540cb2c | |||
| 3577b8b46e | |||
| f24f0973fe | |||
| 12cad15a4e | |||
| d5c3c699dd | |||
| b3b21be50d | |||
| 051650ae4e | |||
| 2fc79ee0a1 | |||
| b7b3796ae6 | |||
| b0074152d1 | |||
| 4550bdbf2a | |||
| d32ece6304 | |||
| ef325dd936 | |||
| 9a55d67c7e |
@@ -0,0 +1,3 @@
|
||||
.git
|
||||
.idea
|
||||
tests
|
||||
@@ -27,9 +27,10 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version-file: 'go.mod'
|
||||
cache: true
|
||||
- name: Build
|
||||
run: |
|
||||
go test -v -short ./...
|
||||
|
||||
@@ -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.34.1"
|
||||
- "1.32.3"
|
||||
- "1.30.2"
|
||||
- "1.28.0"
|
||||
- "1.26.2"
|
||||
- "1.24.2"
|
||||
- "1.22.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@v2
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version-file: 'go.mod'
|
||||
cache: true
|
||||
- name: Run Integration Tests
|
||||
run: |
|
||||
go test -v ./tests
|
||||
@@ -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@v2
|
||||
with:
|
||||
go-version: 1.19
|
||||
- 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 }}
|
||||
@@ -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@v2
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.19
|
||||
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 }}
|
||||
@@ -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'
|
||||
@@ -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
@@ -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
@@ -1,4 +1,4 @@
|
||||
FROM --platform=${BUILDPLATFORM:-linux/amd64} alpine:3.16.2
|
||||
FROM --platform=${BUILDPLATFORM:-linux/amd64} alpine:3.19.0
|
||||
|
||||
COPY ionscale /usr/local/bin/ionscale
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,173 +1,189 @@
|
||||
module github.com/jsiebens/ionscale
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.2.543
|
||||
github.com/apparentlymart/go-cidr v1.1.0
|
||||
github.com/bufbuild/connect-go v1.0.0
|
||||
github.com/bufbuild/connect-go v1.10.0
|
||||
github.com/caarlos0/env/v6 v6.10.1
|
||||
github.com/caddyserver/certmagic v0.17.1
|
||||
github.com/coreos/go-oidc/v3 v3.3.0
|
||||
github.com/glebarez/sqlite v1.5.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.0.2
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/hashicorp/go-bexpr v0.1.11
|
||||
github.com/hashicorp/go-hclog v1.3.0
|
||||
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.13
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/imdario/mergo v0.3.16
|
||||
github.com/jsiebens/go-edit v0.1.0
|
||||
github.com/klauspost/compress v1.15.9
|
||||
github.com/labstack/echo-contrib v0.13.0
|
||||
github.com/labstack/echo/v4 v4.9.0
|
||||
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/googleclouddns v1.0.2
|
||||
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.2.2
|
||||
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/muesli/coral v1.0.0
|
||||
github.com/nleeper/goment v1.4.4
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/rodaine/table v1.0.1
|
||||
github.com/sony/sonyflake v1.1.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/xhit/go-str2duration/v2 v2.0.0
|
||||
golang.org/x/crypto v0.1.0
|
||||
golang.org/x/net v0.4.0
|
||||
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/square/go-jose.v2 v2.6.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.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/xhit/go-str2duration/v2 v2.1.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.4
|
||||
gorm.io/gorm v1.24.0
|
||||
gorm.io/plugin/prometheus v0.0.0-20221204031128-799a96c40bf9
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
|
||||
tailscale.com v1.32.0
|
||||
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.7.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v52.4.0+incompatible // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.17 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.11 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.7 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // 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.0 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // 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/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/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.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.11.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.6.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.12.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 // indirect
|
||||
github.com/aws/smithy-go v1.9.0 // 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.1.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/continuity v0.3.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.4.3 // indirect
|
||||
github.com/coreos/go-iptables v0.7.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/digitalocean/godo v1.41.0 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.16+incompatible // indirect
|
||||
github.com/docker/docker v20.10.16+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
|
||||
github.com/glebarez/go-sqlite v1.19.2 // indirect
|
||||
github.com/dblohm7/wingoes v0.0.0-20240108175832-49174e152ce1 // indirect
|
||||
github.com/digitalocean/godo v1.107.0 // indirect
|
||||
github.com/docker/cli v24.0.7+incompatible // indirect
|
||||
github.com/docker/docker v24.0.7+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/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/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.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // 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.0.0-20220520183353-fd19c99a87aa // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.4.0 // 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/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.13.0 // indirect
|
||||
github.com/jackc/pgio v1.0.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/pgproto3/v2 v2.3.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.12.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.17.2 // 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.0.0 // indirect
|
||||
github.com/jsimonetti/rtnetlink v1.2.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.1.1 // 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.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mdlayher/netlink v1.6.0 // indirect
|
||||
github.com/mdlayher/socket v0.2.3 // indirect
|
||||
github.com/mholt/acmez v1.0.4 // indirect
|
||||
github.com/miekg/dns v1.1.50 // 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-20220808134915-39b0c02b01ae // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||
github.com/opencontainers/runc v1.1.2 // indirect
|
||||
github.com/ory/dockertest/v3 v3.9.1 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/opencontainers/runc v1.1.11 // 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.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // 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.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/travisjeffery/certmagic-sqlstorage v1.1.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // 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
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
go4.org/intern v0.0.0-20220617035311-6925f38cc365 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.opencensus.io v0.24.0 // 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-20220617031537-928513b29760 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
|
||||
golang.org/x/tools v0.1.12 // 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/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.16.1 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
google.golang.org/api v0.84.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
modernc.org/libc v1.21.1 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.4.0 // indirect
|
||||
modernc.org/sqlite v1.19.2 // 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
|
||||
)
|
||||
|
||||
@@ -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
-12
@@ -8,13 +8,13 @@ import (
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/go-edit/editor"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/muesli/coral"
|
||||
"io/ioutil"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tailscale/hujson"
|
||||
"os"
|
||||
)
|
||||
|
||||
func getACLConfigCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func getACLConfigCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "get-acl-policy",
|
||||
Short: "Get the ACL policy",
|
||||
SilenceUsage: true,
|
||||
@@ -29,7 +29,7 @@ func getACLConfigCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(cmd *coral.Command, args []string) error {
|
||||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -58,8 +58,8 @@ func getACLConfigCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func editACLConfigCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func editACLConfigCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "edit-acl-policy",
|
||||
Short: "Edit the ACL policy",
|
||||
SilenceUsage: true,
|
||||
@@ -74,7 +74,7 @@ func editACLConfigCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(cmd *coral.Command, args []string) error {
|
||||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
edit := editor.NewDefaultEditor([]string{"IONSCALE_EDITOR", "EDITOR"})
|
||||
|
||||
client, err := target.createGRPCClient()
|
||||
@@ -104,6 +104,11 @@ func editACLConfigCommand() *coral.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
|
||||
@@ -122,8 +127,8 @@ func editACLConfigCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func setACLConfigCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func setACLConfigCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "set-acl-policy",
|
||||
Short: "Set ACL policy",
|
||||
SilenceUsage: true,
|
||||
@@ -140,8 +145,13 @@ func setACLConfigCommand() *coral.Command {
|
||||
command.Flags().StringVar(&file, "file", "", "Path to json file with the acl configuration")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(cmd *coral.Command, args []string) error {
|
||||
rawJson, err := ioutil.ReadFile(file)
|
||||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawJson, err := hujson.Standardize(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/pkg/client/ionscale"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func authCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func authCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "auth",
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ func authCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func authLoginCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func authLoginCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "login",
|
||||
SilenceUsage: true,
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func authLoginCommand() *coral.Command {
|
||||
|
||||
target.prepareCommand(command)
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
|
||||
+12
-12
@@ -5,16 +5,16 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/rodaine/table"
|
||||
"github.com/spf13/cobra"
|
||||
str2dur "github.com/xhit/go-str2duration/v2"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func authkeysCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func authkeysCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "auth-keys",
|
||||
Aliases: []string{"auth-key"},
|
||||
Short: "Manage ionscale auth keys",
|
||||
@@ -27,8 +27,8 @@ func authkeysCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func createAuthkeysCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func createAuthkeysCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Creates a new auth key in the specified tailnet",
|
||||
SilenceUsage: true,
|
||||
@@ -51,7 +51,7 @@ func createAuthkeysCommand() *coral.Command {
|
||||
command.Flags().BoolVar(&preAuthorized, "pre-authorized", false, "Generate an auth key which is pre-authorized.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -98,8 +98,8 @@ func createAuthkeysCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func deleteAuthKeyCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func deleteAuthKeyCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete a specified auth key",
|
||||
SilenceUsage: true,
|
||||
@@ -110,7 +110,7 @@ func deleteAuthKeyCommand() *coral.Command {
|
||||
target.prepareCommand(command)
|
||||
command.Flags().Uint64Var(&authKeyId, "id", 0, "Auth Key ID")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
grpcClient, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -129,8 +129,8 @@ func deleteAuthKeyCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func listAuthkeysCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func listAuthkeysCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all auth keys for a given tailnet",
|
||||
SilenceUsage: true,
|
||||
@@ -145,7 +145,7 @@ func listAuthkeysCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"fmt"
|
||||
"github.com/jsiebens/ionscale/internal/config"
|
||||
"github.com/jsiebens/ionscale/internal/key"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v2"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func configureCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func configureCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "configure",
|
||||
Short: "Generate a simple config file to get started.",
|
||||
SilenceUsage: true,
|
||||
@@ -33,7 +33,7 @@ func configureCommand() *coral.Command {
|
||||
|
||||
command.MarkFlagRequired("domain")
|
||||
|
||||
command.PreRunE = func(cmd *coral.Command, args []string) error {
|
||||
command.PreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
if domain == "" {
|
||||
return errors.New("required flag 'domain' is missing")
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func configureCommand() *coral.Command {
|
||||
return nil
|
||||
}
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
c := &config.Config{}
|
||||
|
||||
c.HttpListenAddr = "0.0.0.0:80"
|
||||
@@ -67,7 +67,6 @@ func configureCommand() *coral.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
|
||||
|
||||
+19
-13
@@ -6,14 +6,15 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tailscale/hujson"
|
||||
"gopkg.in/yaml.v2"
|
||||
"os"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
func systemCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func systemCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "system",
|
||||
Short: "Manage global system configurations",
|
||||
}
|
||||
@@ -25,8 +26,8 @@ func systemCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func getDefaultDERPMap() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func getDefaultDERPMap() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "get-derp-map",
|
||||
Short: "Get the DERP Map configuration",
|
||||
SilenceUsage: true,
|
||||
@@ -38,7 +39,7 @@ func getDefaultDERPMap() *coral.Command {
|
||||
target.prepareCommand(command)
|
||||
command.Flags().BoolVar(&asJson, "json", false, "When enabled, render output as json otherwise yaml")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -80,8 +81,8 @@ func getDefaultDERPMap() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func setDefaultDERPMap() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func setDefaultDERPMap() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "set-derp-map",
|
||||
Short: "Set the DERP Map configuration",
|
||||
SilenceUsage: true,
|
||||
@@ -92,13 +93,18 @@ func setDefaultDERPMap() *coral.Command {
|
||||
target.prepareCommand(command)
|
||||
command.Flags().StringVar(&file, "file", "", "Path to json file with the DERP Map configuration")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
grpcClient, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawJson, err := os.ReadFile(file)
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawJson, err := hujson.Standardize(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -121,8 +127,8 @@ func setDefaultDERPMap() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func resetDefaultDERPMap() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func resetDefaultDERPMap() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "reset-derp-map",
|
||||
Short: "Reset the DERP Map to the default configuration",
|
||||
SilenceUsage: true,
|
||||
@@ -131,7 +137,7 @@ func resetDefaultDERPMap() *coral.Command {
|
||||
var target = Target{}
|
||||
target.prepareCommand(command)
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
grpcClient, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+40
-15
@@ -5,14 +5,14 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
func getDNSConfigCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func getDNSConfigCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "get-dns",
|
||||
Short: "Get DNS configuration",
|
||||
SilenceUsage: true,
|
||||
@@ -27,7 +27,7 @@ func getDNSConfigCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -82,8 +82,8 @@ func getDNSConfigCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func setDNSConfigCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func setDNSConfigCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "set-dns",
|
||||
Short: "Set DNS config",
|
||||
SilenceUsage: true,
|
||||
@@ -91,6 +91,7 @@ func setDNSConfigCommand() *coral.Command {
|
||||
|
||||
var nameservers []string
|
||||
var magicDNS bool
|
||||
var httpsCerts bool
|
||||
var overrideLocalDNS bool
|
||||
var tailnetID uint64
|
||||
var tailnetName string
|
||||
@@ -101,11 +102,11 @@ func setDNSConfigCommand() *coral.Command {
|
||||
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.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -140,6 +141,7 @@ func setDNSConfigCommand() *coral.Command {
|
||||
OverrideLocalDns: overrideLocalDNS,
|
||||
Nameservers: globalNameservers,
|
||||
Routes: routes,
|
||||
HttpsCerts: httpsCerts,
|
||||
},
|
||||
}
|
||||
resp, err := client.SetDNSConfig(context.Background(), connect.NewRequest(&req))
|
||||
@@ -150,17 +152,40 @@ func setDNSConfigCommand() *coral.Command {
|
||||
|
||||
config := resp.Msg.Config
|
||||
|
||||
var allNameservers = config.Nameservers
|
||||
if resp.Msg.Message != "" {
|
||||
fmt.Println(resp.Msg.Message)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
for i, j := range config.Routes {
|
||||
for _, n := range j.Routes {
|
||||
allNameservers = append(allNameservers, fmt.Sprintf("%s:%s", i, n))
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, ","))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"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/muesli/coral"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func checkRequiredTailnetAndTailnetIdFlags(cmd *coral.Command, args []string) error {
|
||||
func checkRequiredTailnetAndTailnetIdFlags(cmd *cobra.Command, args []string) error {
|
||||
savedTailnetID, err := ionscale.TailnetFromFile()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+22
-12
@@ -8,13 +8,13 @@ import (
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/go-edit/editor"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/muesli/coral"
|
||||
"io/ioutil"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tailscale/hujson"
|
||||
"os"
|
||||
)
|
||||
|
||||
func getIAMPolicyCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func getIAMPolicyCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "get-iam-policy",
|
||||
Short: "Get the IAM policy",
|
||||
SilenceUsage: true,
|
||||
@@ -29,7 +29,7 @@ func getIAMPolicyCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(cmd *coral.Command, args []string) error {
|
||||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -58,8 +58,8 @@ func getIAMPolicyCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func editIAMPolicyCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func editIAMPolicyCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "edit-iam-policy",
|
||||
Short: "Edit the IAM policy",
|
||||
SilenceUsage: true,
|
||||
@@ -74,7 +74,7 @@ func editIAMPolicyCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(cmd *coral.Command, args []string) error {
|
||||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
edit := editor.NewDefaultEditor([]string{"IONSCALE_EDITOR", "EDITOR"})
|
||||
|
||||
client, err := target.createGRPCClient()
|
||||
@@ -102,6 +102,11 @@ func editIAMPolicyCommand() *coral.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
next, err = hujson.Standardize(next)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer os.Remove(s)
|
||||
|
||||
var policy = &api.IAMPolicy{}
|
||||
@@ -122,8 +127,8 @@ func editIAMPolicyCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func setIAMPolicyCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func setIAMPolicyCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "set-iam-policy",
|
||||
Short: "Set IAM policy",
|
||||
SilenceUsage: true,
|
||||
@@ -140,8 +145,13 @@ func setIAMPolicyCommand() *coral.Command {
|
||||
command.Flags().StringVar(&file, "file", "", "Path to json file with the acl configuration")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(cmd *coral.Command, args []string) error {
|
||||
rawJson, err := ioutil.ReadFile(file)
|
||||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawJson, err := hujson.Standardize(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+4
-4
@@ -3,11 +3,11 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jsiebens/ionscale/internal/key"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func keyCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func keyCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "genkey",
|
||||
SilenceUsage: true,
|
||||
}
|
||||
@@ -16,7 +16,7 @@ func keyCommand() *coral.Command {
|
||||
|
||||
command.Flags().BoolVarP(&disableNewLine, "no-newline", "n", false, "do not output a trailing newline")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
serverKey := key.NewServerKey()
|
||||
if disableNewLine {
|
||||
fmt.Print(serverKey.String())
|
||||
|
||||
+41
-41
@@ -5,17 +5,17 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/nleeper/goment"
|
||||
"github.com/rodaine/table"
|
||||
"github.com/spf13/cobra"
|
||||
"inet.af/netaddr"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
func machineCommands() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func machineCommands() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "machines",
|
||||
Aliases: []string{"machine"},
|
||||
Short: "Manage ionscale machines",
|
||||
@@ -38,8 +38,8 @@ func machineCommands() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func getMachineCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func getMachineCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Retrieve detailed information for a machine",
|
||||
SilenceUsage: true,
|
||||
@@ -52,7 +52,7 @@ func getMachineCommand() *coral.Command {
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -150,8 +150,8 @@ func getMachineCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func deleteMachineCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func deleteMachineCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Deletes a machine",
|
||||
SilenceUsage: true,
|
||||
@@ -164,7 +164,7 @@ func deleteMachineCommand() *coral.Command {
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -183,8 +183,8 @@ func deleteMachineCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func expireMachineCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func expireMachineCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "expire",
|
||||
Short: "Expires a machine",
|
||||
SilenceUsage: true,
|
||||
@@ -197,7 +197,7 @@ func expireMachineCommand() *coral.Command {
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -216,8 +216,8 @@ func expireMachineCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func authorizeMachineCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func authorizeMachineCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "authorize",
|
||||
Short: "Authorizes a machine",
|
||||
SilenceUsage: true,
|
||||
@@ -230,7 +230,7 @@ func authorizeMachineCommand() *coral.Command {
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -249,8 +249,8 @@ func authorizeMachineCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func listMachinesCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func listMachinesCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List machines",
|
||||
SilenceUsage: true,
|
||||
@@ -265,7 +265,7 @@ func listMachinesCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -283,7 +283,7 @@ func listMachinesCommand() *coral.Command {
|
||||
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 +294,7 @@ func listMachinesCommand() *coral.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()
|
||||
|
||||
@@ -304,8 +304,8 @@ func listMachinesCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func getMachineRoutesCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func getMachineRoutesCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "get-routes",
|
||||
Short: "Show routes advertised and enabled by a given machine",
|
||||
SilenceUsage: true,
|
||||
@@ -318,7 +318,7 @@ func getMachineRoutesCommand() *coral.Command {
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
grpcClient, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -338,8 +338,8 @@ func getMachineRoutesCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func enableMachineRoutesCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func enableMachineRoutesCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "enable-routes",
|
||||
Short: "Enable routes for a given machine",
|
||||
SilenceUsage: true,
|
||||
@@ -356,7 +356,7 @@ func enableMachineRoutesCommand() *coral.Command {
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -382,8 +382,8 @@ func enableMachineRoutesCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func disableMachineRoutesCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func disableMachineRoutesCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "disable-routes",
|
||||
Short: "Disable routes for a given machine",
|
||||
SilenceUsage: true,
|
||||
@@ -398,7 +398,7 @@ func disableMachineRoutesCommand() *coral.Command {
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -424,8 +424,8 @@ func disableMachineRoutesCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func enableExitNodeCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func enableExitNodeCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "enable-exit-node",
|
||||
Short: "Enable given machine as an exit node",
|
||||
SilenceUsage: true,
|
||||
@@ -438,7 +438,7 @@ func enableExitNodeCommand() *coral.Command {
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -458,8 +458,8 @@ func enableExitNodeCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func disableExitNodeCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func disableExitNodeCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "disable-exit-node",
|
||||
Short: "Disable given machine as an exit node",
|
||||
SilenceUsage: true,
|
||||
@@ -472,7 +472,7 @@ func disableExitNodeCommand() *coral.Command {
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -492,8 +492,8 @@ func disableExitNodeCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func enableMachineKeyExpiryCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func enableMachineKeyExpiryCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "enable-key-expiry",
|
||||
Short: "Enable machine key expiry",
|
||||
SilenceUsage: true,
|
||||
@@ -502,8 +502,8 @@ func enableMachineKeyExpiryCommand() *coral.Command {
|
||||
return configureSetMachineKeyExpiryCommand(command, false)
|
||||
}
|
||||
|
||||
func disableMachineKeyExpiryCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func disableMachineKeyExpiryCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "disable-key-expiry",
|
||||
Short: "Disable machine key expiry",
|
||||
SilenceUsage: true,
|
||||
@@ -512,7 +512,7 @@ func disableMachineKeyExpiryCommand() *coral.Command {
|
||||
return configureSetMachineKeyExpiryCommand(command, true)
|
||||
}
|
||||
|
||||
func configureSetMachineKeyExpiryCommand(command *coral.Command, v bool) *coral.Command {
|
||||
func configureSetMachineKeyExpiryCommand(command *cobra.Command, v bool) *cobra.Command {
|
||||
var machineID uint64
|
||||
var target = Target{}
|
||||
target.prepareCommand(command)
|
||||
@@ -520,7 +520,7 @@ func configureSetMachineKeyExpiryCommand(command *coral.Command, v bool) *coral.
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/muesli/coral"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func Command() *coral.Command {
|
||||
func Command() *cobra.Command {
|
||||
rootCmd := rootCommand()
|
||||
rootCmd.AddCommand(configureCommand())
|
||||
rootCmd.AddCommand(keyCommand())
|
||||
@@ -24,8 +24,8 @@ func Execute() error {
|
||||
return Command().Execute()
|
||||
}
|
||||
|
||||
func rootCommand() *coral.Command {
|
||||
return &coral.Command{
|
||||
func rootCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "ionscale",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package cmd
|
||||
import (
|
||||
"github.com/jsiebens/ionscale/internal/config"
|
||||
"github.com/jsiebens/ionscale/internal/server"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func serverCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func serverCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "Start an ionscale server",
|
||||
SilenceUsage: true,
|
||||
@@ -17,14 +17,14 @@ func serverCommand() *coral.Command {
|
||||
|
||||
command.Flags().StringVarP(&configFile, "config", "c", "", "Path to the configuration file.")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
|
||||
c, err := config.LoadConfig(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return server.Start(c)
|
||||
return server.Start(command.Context(), c)
|
||||
}
|
||||
|
||||
return command
|
||||
|
||||
+55
-50
@@ -6,17 +6,18 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
idomain "github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/pkg/defaults"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/rodaine/table"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"strings"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
func tailnetCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func tailnetCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "tailnets",
|
||||
Aliases: []string{"tailnet"},
|
||||
Short: "Manage ionscale tailnets",
|
||||
@@ -48,8 +49,8 @@ func tailnetCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func listTailnetsCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func listTailnetsCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List available Tailnets",
|
||||
SilenceUsage: true,
|
||||
@@ -58,7 +59,7 @@ func listTailnetsCommand() *coral.Command {
|
||||
var target = Target{}
|
||||
target.prepareCommand(command)
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
@@ -83,8 +84,8 @@ func listTailnetsCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func createTailnetsCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func createTailnetsCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new Tailnet",
|
||||
SilenceUsage: true,
|
||||
@@ -100,7 +101,7 @@ func createTailnetsCommand() *coral.Command {
|
||||
command.Flags().StringVar(&domain, "domain", "", "")
|
||||
command.Flags().StringVar(&email, "email", "", "")
|
||||
|
||||
command.PreRunE = func(cmd *coral.Command, args []string) error {
|
||||
command.PreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("flag --name is required")
|
||||
}
|
||||
@@ -110,20 +111,22 @@ func createTailnetsCommand() *coral.Command {
|
||||
return nil
|
||||
}
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
|
||||
var iamPolicy = api.IAMPolicy{}
|
||||
dnsConfig := defaults.DefaultDNSConfig()
|
||||
aclPolicy := defaults.DefaultACLPolicy()
|
||||
iamPolicy := &api.IAMPolicy{}
|
||||
|
||||
if len(domain) != 0 {
|
||||
domainToLower := strings.ToLower(domain)
|
||||
iamPolicy = api.IAMPolicy{
|
||||
iamPolicy = &api.IAMPolicy{
|
||||
Filters: []string{fmt.Sprintf("domain == %s", domainToLower)},
|
||||
}
|
||||
}
|
||||
|
||||
if len(email) != 0 {
|
||||
emailToLower := strings.ToLower(email)
|
||||
iamPolicy = api.IAMPolicy{
|
||||
iamPolicy = &api.IAMPolicy{
|
||||
Emails: []string{emailToLower},
|
||||
Roles: map[string]string{
|
||||
emailToLower: string(idomain.UserRoleAdmin),
|
||||
@@ -138,7 +141,9 @@ func createTailnetsCommand() *coral.Command {
|
||||
|
||||
resp, err := client.CreateTailnet(context.Background(), connect.NewRequest(&api.CreateTailnetRequest{
|
||||
Name: name,
|
||||
IamPolicy: &iamPolicy,
|
||||
IamPolicy: iamPolicy,
|
||||
AclPolicy: aclPolicy,
|
||||
DnsConfig: dnsConfig,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
@@ -155,8 +160,8 @@ func createTailnetsCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func deleteTailnetCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func deleteTailnetCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete a tailnet",
|
||||
SilenceUsage: true,
|
||||
@@ -173,7 +178,7 @@ func deleteTailnetCommand() *coral.Command {
|
||||
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 *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
@@ -199,8 +204,8 @@ func deleteTailnetCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func getDERPMap() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func getDERPMap() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "get-derp-map",
|
||||
Short: "Get the DERP Map configuration",
|
||||
SilenceUsage: true,
|
||||
@@ -218,7 +223,7 @@ func getDERPMap() *coral.Command {
|
||||
command.Flags().BoolVar(&asJson, "json", false, "When enabled, render output as json otherwise yaml")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -265,8 +270,8 @@ func getDERPMap() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func setDERPMap() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func setDERPMap() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "set-derp-map",
|
||||
Short: "Set the DERP Map configuration",
|
||||
SilenceUsage: true,
|
||||
@@ -283,7 +288,7 @@ func setDERPMap() *coral.Command {
|
||||
command.Flags().StringVar(&file, "file", "", "Path to json file with the DERP Map configuration")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -317,8 +322,8 @@ func setDERPMap() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func resetDERPMap() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func resetDERPMap() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "reset-derp-map",
|
||||
Short: "Reset the DERP Map to the default configuration",
|
||||
SilenceUsage: true,
|
||||
@@ -333,7 +338,7 @@ func resetDERPMap() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -356,8 +361,8 @@ func resetDERPMap() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func enableFileSharingCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func enableFileSharingCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "enable-file-sharing",
|
||||
Aliases: []string{"enable-taildrop"},
|
||||
Short: "Enable Taildrop, the file sharing feature",
|
||||
@@ -373,7 +378,7 @@ func enableFileSharingCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -398,8 +403,8 @@ func enableFileSharingCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func disableFileSharingCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func disableFileSharingCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "disable-file-sharing",
|
||||
Aliases: []string{"disable-taildrop"},
|
||||
Short: "Disable Taildrop, the file sharing feature",
|
||||
@@ -415,7 +420,7 @@ func disableFileSharingCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -440,8 +445,8 @@ func disableFileSharingCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func enableServiceCollectionCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func enableServiceCollectionCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "enable-service-collection",
|
||||
Short: "Enable monitoring live services running on your network’s machines.",
|
||||
SilenceUsage: true,
|
||||
@@ -456,7 +461,7 @@ func enableServiceCollectionCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -481,8 +486,8 @@ func enableServiceCollectionCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func disableServiceCollectionCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func disableServiceCollectionCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "disable-service-collection",
|
||||
Short: "Disable monitoring live services running on your network’s machines.",
|
||||
SilenceUsage: true,
|
||||
@@ -497,7 +502,7 @@ func disableServiceCollectionCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -522,8 +527,8 @@ func disableServiceCollectionCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func enableSSHCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func enableSSHCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "enable-ssh",
|
||||
Short: "Enable ssh access using tailnet and ACLs.",
|
||||
SilenceUsage: true,
|
||||
@@ -538,7 +543,7 @@ func enableSSHCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -563,8 +568,8 @@ func enableSSHCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func disableSSHCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func disableSSHCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "disable-ssh",
|
||||
Short: "Disable ssh access using tailnet and ACLs.",
|
||||
SilenceUsage: true,
|
||||
@@ -579,7 +584,7 @@ func disableSSHCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -604,8 +609,8 @@ func disableSSHCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func enableMachineAuthorizationCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func enableMachineAuthorizationCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "enable-machine-authorization",
|
||||
Short: "Enable machine authorization.",
|
||||
SilenceUsage: true,
|
||||
@@ -620,7 +625,7 @@ func enableMachineAuthorizationCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -645,8 +650,8 @@ func enableMachineAuthorizationCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func disableMachineAuthorizationCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func disableMachineAuthorizationCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "disable-machine-authorization",
|
||||
Short: "Disable machine authorization.",
|
||||
SilenceUsage: true,
|
||||
@@ -661,7 +666,7 @@ func disableMachineAuthorizationCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/jsiebens/ionscale/internal/config"
|
||||
"github.com/jsiebens/ionscale/pkg/client/ionscale"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1/ionscalev1connect"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -19,7 +19,7 @@ type Target struct {
|
||||
systemAdminKey string
|
||||
}
|
||||
|
||||
func (t *Target) prepareCommand(cmd *coral.Command) {
|
||||
func (t *Target) prepareCommand(cmd *cobra.Command) {
|
||||
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.")
|
||||
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/rodaine/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func userCommands() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func userCommands() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "users",
|
||||
Aliases: []string{"user"},
|
||||
Short: "Manage ionscale users",
|
||||
@@ -23,8 +23,8 @@ func userCommands() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func listUsersCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func listUsersCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List users",
|
||||
SilenceUsage: true,
|
||||
@@ -39,7 +39,7 @@ func listUsersCommand() *coral.Command {
|
||||
command.Flags().Uint64Var(&tailnetID, "tailnet-id", 0, "Tailnet ID. Mutually exclusive with --tailnet.")
|
||||
|
||||
command.PreRunE = checkRequiredTailnetAndTailnetIdFlags
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -69,8 +69,8 @@ func listUsersCommand() *coral.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func deleteUserCommand() *coral.Command {
|
||||
command := &coral.Command{
|
||||
func deleteUserCommand() *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Deletes a user",
|
||||
SilenceUsage: true,
|
||||
@@ -83,7 +83,7 @@ func deleteUserCommand() *coral.Command {
|
||||
|
||||
_ = command.MarkFlagRequired("user-id")
|
||||
|
||||
command.RunE = func(command *coral.Command, args []string) error {
|
||||
command.RunE = func(command *cobra.Command, args []string) error {
|
||||
client, err := target.createGRPCClient()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/version"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"github.com/muesli/coral"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func versionCommand() *coral.Command {
|
||||
var command = &coral.Command{
|
||||
func versionCommand() *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Display version information",
|
||||
SilenceUsage: true,
|
||||
@@ -19,7 +19,7 @@ func versionCommand() *coral.Command {
|
||||
var target = Target{}
|
||||
target.prepareCommand(command)
|
||||
|
||||
command.Run = func(cmd *coral.Command, args []string) {
|
||||
command.Run = func(cmd *cobra.Command, args []string) {
|
||||
clientVersion, clientRevision := version.GetReleaseInfo()
|
||||
fmt.Printf(`
|
||||
Client:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/jsiebens/ionscale/internal/database/migration"
|
||||
"github.com/jsiebens/ionscale/internal/util"
|
||||
"go.uber.org/zap"
|
||||
"tailscale.com/types/key"
|
||||
"time"
|
||||
|
||||
@@ -23,17 +24,17 @@ type dbLock interface {
|
||||
UnlockErr(error) error
|
||||
}
|
||||
|
||||
func OpenDB(config *config.Database, logger hclog.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,19 +45,19 @@ func OpenDB(config *config.Database, logger hclog.Logger) (domain.Repository, er
|
||||
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 hclog.Logger) (*gorm.DB, dbLock, error) {
|
||||
func createDB(config *config.Database, logger *zap.Logger) (*gorm.DB, dbLock, error) {
|
||||
gormConfig := &gorm.Config{
|
||||
Logger: &GormLoggerAdapter{logger: logger.Named("db")},
|
||||
Logger: &GormLoggerAdapter{logger: logger.Sugar()},
|
||||
}
|
||||
|
||||
switch config.Type {
|
||||
@@ -134,7 +135,7 @@ func createJSONWebKeySet(ctx context.Context, repository domain.Repository) erro
|
||||
}
|
||||
|
||||
type GormLoggerAdapter struct {
|
||||
logger hclog.Logger
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func (g *GormLoggerAdapter) LogMode(level logger.LogLevel) logger.Interface {
|
||||
@@ -142,11 +143,11 @@ func (g *GormLoggerAdapter) LogMode(level logger.LogLevel) logger.Interface {
|
||||
}
|
||||
|
||||
func (g *GormLoggerAdapter) Info(ctx context.Context, s string, i ...interface{}) {
|
||||
g.logger.Info(s, i)
|
||||
g.logger.Infow(s, i)
|
||||
}
|
||||
|
||||
func (g *GormLoggerAdapter) Warn(ctx context.Context, s string, i ...interface{}) {
|
||||
g.logger.Warn(s, i)
|
||||
g.logger.Warnw(s, i)
|
||||
}
|
||||
|
||||
func (g *GormLoggerAdapter) Error(ctx context.Context, s string, i ...interface{}) {
|
||||
@@ -154,22 +155,22 @@ func (g *GormLoggerAdapter) Error(ctx context.Context, s string, i ...interface{
|
||||
}
|
||||
|
||||
func (g *GormLoggerAdapter) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
||||
if g.logger.IsTrace() {
|
||||
if g.logger.Level().Enabled(zap.DebugLevel) {
|
||||
elapsed := time.Since(begin)
|
||||
switch {
|
||||
case err != nil && !errors.Is(err, gorm.ErrRecordNotFound):
|
||||
sql, rows := fc()
|
||||
if rows == -1 {
|
||||
g.logger.Trace("Error executing query", "sql", sql, "start_time", begin.Format(time.RFC3339), "duration", elapsed, "err", err)
|
||||
g.logger.Debugw("Error executing query", "sql", sql, "start_time", begin.Format(time.RFC3339), "duration", elapsed, "err", err)
|
||||
} else {
|
||||
g.logger.Trace("Error executing query", "sql", sql, "start_time", begin.Format(time.RFC3339), "duration", elapsed, "rows", rows, "err", err)
|
||||
g.logger.Debugw("Error executing query", "sql", sql, "start_time", begin.Format(time.RFC3339), "duration", elapsed, "rows", rows, "err", err)
|
||||
}
|
||||
default:
|
||||
sql, rows := fc()
|
||||
if rows == -1 {
|
||||
g.logger.Trace("Statement executed", "sql", sql, "start_time", begin.Format(time.RFC3339), "duration", elapsed)
|
||||
g.logger.Debugw("Statement executed", "sql", sql, "start_time", begin.Format(time.RFC3339), "duration", elapsed)
|
||||
} else {
|
||||
g.logger.Trace("Statement executed", "sql", sql, "start_time", begin.Format(time.RFC3339), "duration", elapsed, "rows", rows)
|
||||
g.logger.Debugw("Statement executed", "sql", sql, "start_time", begin.Format(time.RFC3339), "duration", elapsed, "rows", rows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+137
-232
@@ -8,6 +8,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -16,38 +17,56 @@ import (
|
||||
|
||||
const (
|
||||
AutoGroupSelf = "autogroup:self"
|
||||
AutoGroupMember = "autogroup:member"
|
||||
AutoGroupMembers = "autogroup:members"
|
||||
AutoGroupTagged = "autogroup:tagged"
|
||||
AutoGroupInternet = "autogroup:internet"
|
||||
)
|
||||
|
||||
type AutoApprovers struct {
|
||||
Routes map[string][]string `json:"routes"`
|
||||
ExitNode []string `json:"exitNode"`
|
||||
Routes map[string][]string `json:"routes,omitempty"`
|
||||
ExitNode []string `json:"exitNode,omitempty"`
|
||||
}
|
||||
|
||||
type ACLPolicy struct {
|
||||
Groups map[string][]string `json:"groups,omitempty"`
|
||||
Hosts map[string]string `json:"hosts,omitempty"`
|
||||
ACLs []ACL `json:"acls"`
|
||||
TagOwners map[string][]string `json:"tagowners"`
|
||||
AutoApprovers *AutoApprovers `json:"autoApprovers"`
|
||||
SSHRules []SSHRule `json:"ssh"`
|
||||
ACLs []ACL `json:"acls,omitempty"`
|
||||
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 DefaultPolicy() ACLPolicy {
|
||||
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 DefaultACLPolicy() ACLPolicy {
|
||||
return ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
{
|
||||
@@ -99,7 +118,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 +130,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 +143,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 +158,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 +301,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 +377,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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import (
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func DefaultIAMPolicy() IAMPolicy {
|
||||
return IAMPolicy{}
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
UserID string
|
||||
Username string
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -23,11 +23,10 @@ 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
|
||||
GetOrCreateTailnet(ctx context.Context, name string, iamPolicy IAMPolicy) (*Tailnet, bool, error)
|
||||
GetTailnet(ctx context.Context, id uint64) (*Tailnet, error)
|
||||
GetTailnetByAlias(ctx context.Context, alias string) (*Tailnet, error)
|
||||
ListTailnets(ctx context.Context) ([]Tailnet, error)
|
||||
DeleteTailnet(ctx context.Context, id uint64) error
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package domain
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/jsiebens/ionscale/internal/util"
|
||||
"gorm.io/gorm"
|
||||
"net/mail"
|
||||
"strings"
|
||||
@@ -58,22 +57,6 @@ func (r *repository) SaveTailnet(ctx context.Context, tailnet *Tailnet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *repository) GetOrCreateTailnet(ctx context.Context, name string, iamPolicy IAMPolicy) (*Tailnet, bool, error) {
|
||||
tailnet := &Tailnet{}
|
||||
id := util.NextID()
|
||||
|
||||
tx := r.withContext(ctx).
|
||||
Where(Tailnet{Name: name}).
|
||||
Attrs(Tailnet{ID: id, ACLPolicy: DefaultPolicy(), IAMPolicy: iamPolicy}).
|
||||
FirstOrCreate(tailnet)
|
||||
|
||||
if tx.Error != nil {
|
||||
return nil, false, tx.Error
|
||||
}
|
||||
|
||||
return tailnet, tailnet.ID == id, nil
|
||||
}
|
||||
|
||||
func (r *repository) GetTailnet(ctx context.Context, id uint64) (*Tailnet, error) {
|
||||
var t Tailnet
|
||||
tx := r.withContext(ctx).Take(&t, "id = ?", id)
|
||||
@@ -89,21 +72,6 @@ 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) {
|
||||
var t Tailnet
|
||||
tx := r.withContext(ctx).Take(&t, "alias = ?", alias)
|
||||
|
||||
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func (r *repository) ListTailnets(ctx context.Context) ([]Tailnet, error) {
|
||||
var tailnets = []Tailnet{}
|
||||
tx := r.withContext(ctx).Find(&tailnets)
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Cause error
|
||||
Location string
|
||||
}
|
||||
|
||||
func Wrap(err error, skip int) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := &Error{
|
||||
Cause: err,
|
||||
Location: getLocation(skip),
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (w *Error) Error() string {
|
||||
return w.Cause.Error()
|
||||
}
|
||||
|
||||
func (f *Error) Unwrap() error {
|
||||
return f.Cause
|
||||
}
|
||||
|
||||
func (f *Error) Format(s fmt.State, verb rune) {
|
||||
fmt.Fprintf(s, "%s\n", f.Cause.Error())
|
||||
fmt.Fprintf(s, "\t%s\n", f.Location)
|
||||
}
|
||||
|
||||
func getLocation(skip int) string {
|
||||
_, file, line, _ := runtime.Caller(2 + skip)
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
+160
-135
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/jsiebens/ionscale/internal/addr"
|
||||
"github.com/jsiebens/ionscale/internal/auth"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
tpl "github.com/jsiebens/ionscale/internal/templates"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"github.com/mr-tron/base58"
|
||||
"net/http"
|
||||
@@ -41,59 +41,84 @@ 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 {
|
||||
return errors.Wrap(err, 0)
|
||||
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 {
|
||||
return errors.Wrap(err, 0)
|
||||
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 {
|
||||
return errors.Wrap(err, 0)
|
||||
if input.Flow == AuthFlowSSHCheckFlow {
|
||||
if s, err := h.repository.GetSSHActionRequest(ctx, input.Key); err != nil || s == nil {
|
||||
return logError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if h.authProvider == nil {
|
||||
return errors.Wrap(fmt.Errorf("unable to start auth flow as no auth provider is configured"), 0)
|
||||
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 errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
redirectUrl := h.authProvider.GetLoginURL(h.config.CreateUrl("/a/callback"), state)
|
||||
@@ -104,23 +129,24 @@ 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 errors.Wrap(err, 0)
|
||||
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 errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
redirectUrl := h.authProvider.GetLoginURL(h.config.CreateUrl("/a/callback"), state)
|
||||
@@ -128,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 {
|
||||
@@ -142,15 +168,19 @@ func (h *AuthenticationHandlers) Callback(c echo.Context) error {
|
||||
|
||||
user, err := h.exchangeUser(code)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
account, _, err := h.repository.GetOrCreateAccount(ctx, user.ID, user.Name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
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")
|
||||
@@ -158,32 +188,32 @@ func (h *AuthenticationHandlers) Callback(c echo.Context) error {
|
||||
|
||||
machine, err := h.repository.GetMachine(ctx, sshActionReq.SrcMachineID)
|
||||
if err != nil || sshActionReq == nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
if !machine.HasTags() && machine.User.AccountID != nil && *machine.User.AccountID == account.ID {
|
||||
sshActionReq.Action = "accept"
|
||||
if err := h.repository.SaveSSHActionRequest(ctx, sshActionReq); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
return c.Redirect(http.StatusFound, "/a/success")
|
||||
}
|
||||
|
||||
sshActionReq.Action = "reject"
|
||||
if err := h.repository.SaveSSHActionRequest(ctx, sshActionReq); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
return c.Redirect(http.StatusFound, "/a/error?e=nmo")
|
||||
}
|
||||
|
||||
tailnets, err := h.listAvailableTailnets(ctx, user)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -192,18 +222,25 @@ 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 errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
if !isSystemAdmin && len(tailnets) == 0 {
|
||||
@@ -214,106 +251,77 @@ 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 errors.Wrap(err, 0)
|
||||
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 errors.Wrap(err, 0)
|
||||
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 errors.Wrap(err, 0)
|
||||
}
|
||||
|
||||
account, err := h.repository.GetAccount(ctx, form.AccountID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
// continue as system admin?
|
||||
@@ -324,27 +332,27 @@ func (h *AuthenticationHandlers) endCliAuthenticationFlow(c echo.Context, req *d
|
||||
|
||||
err := h.repository.Transaction(func(rp domain.Repository) error {
|
||||
if err := rp.SaveSystemApiKey(ctx, apiKey); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
if err := rp.SaveAuthenticationRequest(ctx, req); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
return c.Redirect(http.StatusFound, "/a/success")
|
||||
}
|
||||
|
||||
tailnet, err := h.repository.GetTailnet(ctx, form.TailnetID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
user, _, err := h.repository.GetOrCreateUserWithAccount(ctx, tailnet, account)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
expiresAt := time.Now().Add(24 * time.Hour)
|
||||
@@ -362,20 +370,15 @@ func (h *AuthenticationHandlers) endCliAuthenticationFlow(c echo.Context, req *d
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
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 errors.Wrap(err, 0)
|
||||
}
|
||||
|
||||
req := tailcfg.RegisterRequest(registrationRequest.Data)
|
||||
machineKey := registrationRequest.MachineKey
|
||||
nodeKey := req.NodeKey.String()
|
||||
@@ -389,7 +392,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
|
||||
if form.AuthKey != "" {
|
||||
authKey, err := h.repository.LoadAuthKey(ctx, form.AuthKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
if authKey == nil {
|
||||
@@ -398,7 +401,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
|
||||
registrationRequest.Error = "invalid auth key"
|
||||
|
||||
if err := h.repository.SaveRegistrationRequest(ctx, registrationRequest); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
return c.Redirect(http.StatusFound, "/a/error?e=iak")
|
||||
@@ -412,17 +415,17 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
|
||||
} else {
|
||||
selectedTailnet, err := h.repository.GetTailnet(ctx, form.TailnetID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
account, err := h.repository.GetAccount(ctx, form.AccountID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
selectedUser, _, err := h.repository.GetOrCreateUserWithAccount(ctx, selectedTailnet, account)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
user = selectedUser
|
||||
@@ -434,7 +437,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
|
||||
registrationRequest.Authenticated = false
|
||||
registrationRequest.Error = err.Error()
|
||||
if err := h.repository.SaveRegistrationRequest(ctx, registrationRequest); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
return c.Redirect(http.StatusFound, "/a/error?e=nto")
|
||||
}
|
||||
@@ -445,7 +448,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
|
||||
|
||||
m, err := h.repository.GetMachineByKey(ctx, tailnet.ID, machineKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
@@ -458,7 +461,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
|
||||
sanitizeHostname := dnsname.SanitizeHostname(req.Hostinfo.Hostname)
|
||||
nameIdx, err := h.repository.GetNextMachineNameIndex(ctx, tailnet.ID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
m = &domain.Machine{
|
||||
@@ -482,7 +485,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
|
||||
|
||||
ipv4, ipv6, err := addr.SelectIP(checkIP(ctx, h.repository.CountMachinesWithIPv4))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
m.IPv4 = domain.IP{Addr: ipv4}
|
||||
m.IPv6 = domain.IP{Addr: ipv6}
|
||||
@@ -495,7 +498,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
|
||||
if m.Name != sanitizeHostname {
|
||||
nameIdx, err := h.repository.GetNextMachineNameIndex(ctx, tailnet.ID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
m.Name = sanitizeHostname
|
||||
m.NameIdx = nameIdx
|
||||
@@ -529,7 +532,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
if m.Authorized {
|
||||
@@ -539,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")
|
||||
|
||||
@@ -550,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 {
|
||||
|
||||
+10
-18
@@ -1,40 +1,32 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/jsiebens/ionscale/internal/bind"
|
||||
"github.com/jsiebens/ionscale/internal/dns"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
"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 errors.Wrap(err, 0)
|
||||
}
|
||||
|
||||
req := &tailcfg.SetDNSRequest{}
|
||||
if err := binder.BindRequest(c, req); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
if err := c.Bind(req); err != nil {
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
if h.provider == nil {
|
||||
@@ -42,7 +34,7 @@ func (h *DNSHandlers) SetDNS(c echo.Context) error {
|
||||
}
|
||||
|
||||
if err := h.provider.SetRecord(ctx, req.Type, req.Name, req.Value); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(req.Name, "_acme-challenge") && req.Type == "TXT" {
|
||||
@@ -59,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{})
|
||||
}
|
||||
|
||||
@@ -2,66 +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/errors"
|
||||
"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 errors.Wrap(err, 0)
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -69,26 +41,21 @@ func (h *IDTokenHandlers) FetchToken(c echo.Context) error {
|
||||
|
||||
keySet, err := h.repository.GetJSONWebKeySet(c.Request().Context())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
}
|
||||
|
||||
binder, err := h.createBinder(c)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
req := &tailcfg.TokenRequest{}
|
||||
if err := binder.BindRequest(c, req); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
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)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -131,11 +98,50 @@ func (h *IDTokenHandlers) FetchToken(c echo.Context) error {
|
||||
|
||||
jwtB64, err := unsignedToken.SignedString(&keySet.Key.PrivateKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package handlers
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
"github.com/labstack/echo/v4"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
@@ -28,9 +27,9 @@ func NewNoiseHandlers(controlKey key.MachinePrivate, createPeerHandler CreatePee
|
||||
}
|
||||
|
||||
func (h *NoiseHandlers) Upgrade(c echo.Context) error {
|
||||
conn, err := controlhttp.AcceptHTTP(c.Request().Context(), c.Response(), c.Request(), h.controlKey)
|
||||
conn, err := controlhttp.AcceptHTTP(c.Request().Context(), c.Response(), c.Request(), h.controlKey, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
handler := h.createPeerHandler(conn.Peer())
|
||||
@@ -42,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
|
||||
}
|
||||
|
||||
@@ -2,27 +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/errors"
|
||||
"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,
|
||||
}
|
||||
@@ -31,30 +33,26 @@ 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 errors.Wrap(err, 0)
|
||||
}
|
||||
|
||||
req := &tailcfg.MapRequest{}
|
||||
if err := binder.BindRequest(c, req); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
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 errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -62,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()
|
||||
@@ -79,24 +77,23 @@ func (h *PollNetMapHandler) handleUpdate(c echo.Context, binder bind.Binder, m *
|
||||
m.LastSeen = &now
|
||||
|
||||
if err := h.repository.SaveMachine(ctx, m); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
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 errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
updateChan := make(chan *core.Ping, 20)
|
||||
@@ -105,15 +102,15 @@ 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 errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
c.Response().WriteHeader(http.StatusOK)
|
||||
|
||||
if _, err := c.Response().Write(response); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
c.Response().Flush()
|
||||
|
||||
@@ -140,7 +137,7 @@ func (h *PollNetMapHandler) handleUpdate(c echo.Context, binder bind.Binder, m *
|
||||
case <-keepAliveTicker.C:
|
||||
if mapRequest.KeepAlive {
|
||||
if _, err := c.Response().Write(keepAliveResponse); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
_ = h.repository.SetMachineLastSeen(ctx, machineID)
|
||||
c.Response().Flush()
|
||||
@@ -149,7 +146,7 @@ func (h *PollNetMapHandler) handleUpdate(c echo.Context, binder bind.Binder, m *
|
||||
if latestSync.Before(latestUpdate) {
|
||||
machine, err := h.repository.GetMachine(ctx, machineID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
if machine == nil {
|
||||
return nil
|
||||
@@ -158,14 +155,14 @@ 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
|
||||
}
|
||||
|
||||
if _, err := c.Response().Write(payload); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
c.Response().Flush()
|
||||
|
||||
@@ -177,177 +174,77 @@ 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)
|
||||
m.DiscoKey = request.DiscoKey.String()
|
||||
|
||||
if err := h.repository.SaveMachine(ctx, m); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
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 errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
_, err = c.Response().Write(response)
|
||||
return errors.Wrap(err, 0)
|
||||
_, 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
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
`
|
||||
@@ -3,28 +3,27 @@ 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"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
"github.com/jsiebens/ionscale/internal/mapping"
|
||||
"github.com/jsiebens/ionscale/internal/util"
|
||||
"github.com/labstack/echo/v4"
|
||||
"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,
|
||||
@@ -32,7 +31,7 @@ func NewRegistrationHandlers(
|
||||
}
|
||||
|
||||
type RegistrationHandlers struct {
|
||||
createBinder bind.Factory
|
||||
machineKey key.MachinePublic
|
||||
repository domain.Repository
|
||||
sessionManager core.PollMapSessionManager
|
||||
config *config.Config
|
||||
@@ -41,30 +40,25 @@ type RegistrationHandlers struct {
|
||||
func (h *RegistrationHandlers) Register(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
|
||||
binder, err := h.createBinder(c)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
}
|
||||
|
||||
req := &tailcfg.RegisterRequest{}
|
||||
if err := binder.BindRequest(c, req); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
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 errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
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()) {
|
||||
@@ -72,25 +66,25 @@ func (h *RegistrationHandlers) Register(c echo.Context) error {
|
||||
|
||||
if m.Ephemeral {
|
||||
if _, err := h.repository.DeleteMachine(ctx, m.ID); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
h.sessionManager.NotifyAll(m.TailnetID)
|
||||
} else {
|
||||
if err := h.repository.SaveMachine(ctx, m); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
h.sessionManager.NotifyAll(m.TailnetID)
|
||||
}
|
||||
|
||||
response := tailcfg.RegisterResponse{NodeKeyExpired: true}
|
||||
return binder.WriteResponse(c, http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
sanitizeHostname := dnsname.SanitizeHostname(req.Hostinfo.Hostname)
|
||||
if m.Name != sanitizeHostname {
|
||||
nameIdx, err := h.repository.GetNextMachineNameIndex(ctx, m.TailnetID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
m.Name = sanitizeHostname
|
||||
m.NameIdx = nameIdx
|
||||
@@ -101,7 +95,7 @@ func (h *RegistrationHandlers) Register(c echo.Context) error {
|
||||
m.Tags = append(m.RegisteredTags, advertisedTags...)
|
||||
|
||||
if err := h.repository.SaveMachine(ctx, m); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
tUser, tLogin := mapping.ToUser(m.User)
|
||||
@@ -112,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 == "" {
|
||||
@@ -139,28 +133,28 @@ 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()
|
||||
|
||||
authKey, err := h.repository.LoadAuthKey(ctx, req.Auth.AuthKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -168,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
|
||||
@@ -181,7 +175,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, bi
|
||||
|
||||
m, err = h.repository.GetMachineByKey(ctx, tailnet.ID, machineKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
@@ -190,7 +184,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, bi
|
||||
sanitizeHostname := dnsname.SanitizeHostname(req.Hostinfo.Hostname)
|
||||
nameIdx, err := h.repository.GetNextMachineNameIndex(ctx, tailnet.ID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
m = &domain.Machine{
|
||||
@@ -218,7 +212,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, bi
|
||||
|
||||
ipv4, ipv6, err := addr.SelectIP(checkIP(ctx, h.repository.CountMachinesWithIPv4))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
m.IPv4 = domain.IP{Addr: ipv4}
|
||||
m.IPv6 = domain.IP{Addr: ipv6}
|
||||
@@ -227,7 +221,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, bi
|
||||
if m.Name != sanitizeHostname {
|
||||
nameIdx, err := h.repository.GetNextMachineNameIndex(ctx, tailnet.ID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
m.Name = sanitizeHostname
|
||||
m.NameIdx = nameIdx
|
||||
@@ -245,7 +239,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, bi
|
||||
}
|
||||
|
||||
if err := h.repository.SaveMachine(ctx, m); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
tUser, tLogin := mapping.ToUser(m.User)
|
||||
@@ -255,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()
|
||||
@@ -266,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 {
|
||||
@@ -275,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
|
||||
@@ -292,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
|
||||
|
||||
@@ -2,49 +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/errors"
|
||||
"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 errors.Wrap(err, 0)
|
||||
}
|
||||
|
||||
data := new(sshActionRequestData)
|
||||
if err = c.Bind(data); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
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,
|
||||
@@ -56,7 +79,7 @@ func (h *SSHActionHandlers) StartAuth(c echo.Context) error {
|
||||
authUrl := h.config.CreateUrl("/a/s/%s", key)
|
||||
|
||||
if err := h.repository.SaveSSHActionRequest(ctx, request); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
resp := &tailcfg.SSHAction{
|
||||
@@ -64,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 {
|
||||
@@ -72,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 errors.Wrap(err, 0)
|
||||
}
|
||||
|
||||
tick := time.NewTicker(2 * time.Second)
|
||||
|
||||
defer func() { tick.Stop() }()
|
||||
@@ -89,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" {
|
||||
@@ -99,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
|
||||
|
||||
@@ -3,6 +3,7 @@ package handlers
|
||||
import (
|
||||
"github.com/jsiebens/ionscale/internal/version"
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -14,3 +15,8 @@ func Version(c echo.Context) error {
|
||||
}
|
||||
return c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func logError(err error) error {
|
||||
zap.L().WithOptions(zap.AddCallerSkip(1)).Error("error processing request", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
+50
-26
@@ -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"
|
||||
@@ -82,25 +83,9 @@ func ToDNSConfig(m *domain.Machine, tailnet *domain.Tailnet, c *domain.DNSConfig
|
||||
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 +149,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 +167,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 +259,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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+12
-39
@@ -2,54 +2,35 @@ package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
"github.com/labstack/echo-contrib/prometheus"
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func EchoErrorHandler(logger hclog.Logger) echo.MiddlewareFunc {
|
||||
func EchoErrorHandler() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
request := c.Request()
|
||||
|
||||
if err := next(c); err != nil {
|
||||
switch t := err.(type) {
|
||||
case *echo.HTTPError:
|
||||
return err
|
||||
case *errors.Error:
|
||||
logger.Error("error processing request",
|
||||
"err", t.Cause,
|
||||
"location", t.Location,
|
||||
"http.method", request.Method,
|
||||
"http.uri", request.RequestURI,
|
||||
)
|
||||
default:
|
||||
logger.Error("error processing request",
|
||||
"err", err,
|
||||
"http.method", request.Method,
|
||||
"http.uri", request.RequestURI,
|
||||
)
|
||||
}
|
||||
err := next(c)
|
||||
|
||||
if strings.HasPrefix(request.RequestURI, "/a/") {
|
||||
return c.Render(http.StatusInternalServerError, "error.html", nil)
|
||||
}
|
||||
if err != nil && strings.HasPrefix(request.RequestURI, "/a/") {
|
||||
return c.Render(http.StatusInternalServerError, "error.html", nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func EchoLogger(logger hclog.Logger) echo.MiddlewareFunc {
|
||||
httpLogger := logger.Named("http")
|
||||
func EchoLogger(logger *zap.Logger) echo.MiddlewareFunc {
|
||||
httpLogger := logger.Sugar()
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if !httpLogger.IsTrace() {
|
||||
if !httpLogger.Level().Enabled(zap.DebugLevel) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
@@ -60,7 +41,7 @@ func EchoLogger(logger hclog.Logger) echo.MiddlewareFunc {
|
||||
c.Error(err)
|
||||
}
|
||||
|
||||
httpLogger.Trace("finished server http call",
|
||||
httpLogger.Debugw("finished server http call",
|
||||
"http.code", response.Status,
|
||||
"http.method", request.Method,
|
||||
"http.uri", request.RequestURI,
|
||||
@@ -72,7 +53,7 @@ func EchoLogger(logger hclog.Logger) echo.MiddlewareFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func EchoRecover(logger hclog.Logger) echo.MiddlewareFunc {
|
||||
func EchoRecover() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
apply := func() (topErr error) {
|
||||
@@ -82,6 +63,7 @@ func EchoRecover(logger hclog.Logger) echo.MiddlewareFunc {
|
||||
if !ok {
|
||||
err = fmt.Errorf("%v", r)
|
||||
}
|
||||
zap.L().Error("panic when processing request", zap.Error(err))
|
||||
topErr = err
|
||||
}
|
||||
}()
|
||||
@@ -92,15 +74,6 @@ func EchoRecover(logger hclog.Logger) echo.MiddlewareFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorRedirect() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
c.Set("redirect_on_error", true)
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func EchoMetrics(p *prometheus.Prometheus) echo.MiddlewareFunc {
|
||||
return p.HandlerFunc
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package server
|
||||
|
||||
import (
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/key"
|
||||
"github.com/jsiebens/ionscale/internal/service"
|
||||
@@ -10,7 +9,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func NewRpcHandler(systemAdminKey *key.ServerPrivate, repository domain.Repository, logger hclog.Logger, handler apiconnect.IonscaleServiceHandler) (string, http.Handler) {
|
||||
interceptors := connect.WithInterceptors(service.NewErrorInterceptor(logger), service.AuthenticationInterceptor(systemAdminKey, repository))
|
||||
func NewRpcHandler(systemAdminKey *key.ServerPrivate, repository domain.Repository, handler apiconnect.IonscaleServiceHandler) (string, http.Handler) {
|
||||
interceptors := connect.WithInterceptors(service.NewErrorInterceptor(), service.AuthenticationInterceptor(systemAdminKey, repository))
|
||||
return apiconnect.NewIonscaleServiceHandler(handler, interceptors)
|
||||
}
|
||||
|
||||
+89
-80
@@ -5,9 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"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"
|
||||
@@ -19,19 +17,20 @@ 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"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"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
|
||||
@@ -39,42 +38,56 @@ func Start(c *config.Config) error {
|
||||
|
||||
logger.Info("Starting ionscale server")
|
||||
|
||||
repository, err := database.OpenDB(&c.Database, logger)
|
||||
if err != nil {
|
||||
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")
|
||||
|
||||
db, repository, err := database.OpenDB(&c.Database, dbLogger)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
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
|
||||
if c.Tls.AcmePath != "" {
|
||||
certmagic.Default.Storage = &certmagic.FileStorage{Path: c.Tls.AcmePath}
|
||||
}
|
||||
certmagic.Default.Logger = logger.Named("certmagic")
|
||||
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)
|
||||
@@ -83,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)
|
||||
@@ -97,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.Use(EchoMetrics(p), EchoLogger(logger), EchoErrorHandler(logger), EchoRecover(logger))
|
||||
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,
|
||||
@@ -129,18 +142,18 @@ func Start(c *config.Config) error {
|
||||
repository,
|
||||
)
|
||||
|
||||
rpcService := service.NewService(c, authProvider, repository, sessionManager)
|
||||
rpcPath, rpcHandler := NewRpcHandler(serverKey.SystemAdminKey, repository, logger, rpcService)
|
||||
rpcService := service.NewService(c, authProvider, dnsProvider, repository, sessionManager)
|
||||
rpcPath, rpcHandler := NewRpcHandler(serverKey.SystemAdminKey, repository, rpcService)
|
||||
|
||||
nonTlsAppHandler := echo.New()
|
||||
nonTlsAppHandler.Use(EchoMetrics(p), EchoLogger(logger), EchoErrorHandler(logger), EchoRecover(logger))
|
||||
nonTlsAppHandler.Use(EchoMetrics(p), EchoLogger(httpLogger), EchoErrorHandler(), EchoRecover())
|
||||
nonTlsAppHandler.POST("/ts2021", noiseHandlers.Upgrade)
|
||||
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(logger), EchoRecover(logger))
|
||||
tlsAppHandler.Use(EchoMetrics(p), EchoLogger(logger), EchoErrorHandler(), EchoRecover())
|
||||
|
||||
tlsAppHandler.Any("/*", handlers.IndexHandler(http.StatusNotFound))
|
||||
tlsAppHandler.Any("/", handlers.IndexHandler(http.StatusOK))
|
||||
@@ -148,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)
|
||||
@@ -192,14 +199,14 @@ func Start(c *config.Config) error {
|
||||
}
|
||||
|
||||
if c.Tls.AcmeEnabled {
|
||||
logger.Info("TLS is enabled with ACME", "domain", serverUrl.Host)
|
||||
logger.Info("Server is running", "http_addr", c.HttpListenAddr, "https_addr", c.HttpsListenAddr, "metrics_addr", c.MetricsListenAddr)
|
||||
logger.Sugar().Infow("TLS is enabled with ACME", "domain", serverUrl.Host)
|
||||
logger.Sugar().Infow("Server is running", "http_addr", c.HttpListenAddr, "https_addr", c.HttpsListenAddr, "metrics_addr", c.MetricsListenAddr)
|
||||
} else if !c.Tls.Disable {
|
||||
logger.Info("TLS is enabled", "cert", c.Tls.CertFile)
|
||||
logger.Info("Server is running", "http_addr", c.HttpListenAddr, "https_addr", c.HttpsListenAddr, "metrics_addr", c.MetricsListenAddr)
|
||||
logger.Sugar().Infow("TLS is enabled", "cert", c.Tls.CertFile)
|
||||
logger.Sugar().Infow("Server is running", "http_addr", c.HttpListenAddr, "https_addr", c.HttpsListenAddr, "metrics_addr", c.MetricsListenAddr)
|
||||
} else {
|
||||
logger.Warn("TLS is disabled")
|
||||
logger.Info("Server is running", "http_addr", c.HttpListenAddr, "metrics_addr", c.MetricsListenAddr)
|
||||
logger.Sugar().Warnw("TLS is disabled")
|
||||
logger.Sugar().Infow("Server is running", "http_addr", c.HttpListenAddr, "metrics_addr", c.MetricsListenAddr)
|
||||
}
|
||||
|
||||
return g.Wait()
|
||||
@@ -268,32 +275,34 @@ func selectListener(a net.Listener, b net.Listener) net.Listener {
|
||||
return b
|
||||
}
|
||||
|
||||
func setupLogging(config config.Logging) (hclog.Logger, error) {
|
||||
file, err := createLogFile(config)
|
||||
func setupLogging(config config.Logging) (*zap.Logger, error) {
|
||||
level, err := zap.ParseAtomicLevel(config.Level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appLogger := hclog.New(&hclog.LoggerOptions{
|
||||
Name: "ionscale",
|
||||
Level: hclog.LevelFromString(config.Level),
|
||||
JSONFormat: strings.ToLower(config.Format) == "json",
|
||||
Output: file,
|
||||
})
|
||||
|
||||
log.SetOutput(appLogger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true}))
|
||||
log.SetPrefix("")
|
||||
log.SetFlags(0)
|
||||
pc := zap.NewProductionConfig()
|
||||
pc.Level = level
|
||||
pc.DisableStacktrace = true
|
||||
pc.OutputPaths = []string{"stdout"}
|
||||
pc.Encoding = "console"
|
||||
pc.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
pc.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
|
||||
return appLogger, nil
|
||||
}
|
||||
|
||||
func createLogFile(config config.Logging) (*os.File, error) {
|
||||
if config.File != "" {
|
||||
f, err := os.OpenFile(config.File, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
pc.OutputPaths = []string{config.File}
|
||||
}
|
||||
return os.Stdout, nil
|
||||
|
||||
if config.Format == "json" {
|
||||
pc.Encoding = "json"
|
||||
}
|
||||
|
||||
logger, err := pc.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.ReplaceGlobals(logger)
|
||||
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
"github.com/jsiebens/ionscale/internal/mapping"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
)
|
||||
@@ -18,7 +17,7 @@ func (s *Service) GetACLPolicy(ctx context.Context, req *connect.Request[api.Get
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet does not exist"))
|
||||
@@ -26,7 +25,7 @@ func (s *Service) GetACLPolicy(ctx context.Context, req *connect.Request[api.Get
|
||||
|
||||
var policy api.ACLPolicy
|
||||
if err := mapping.CopyViaJson(&tailnet.ACLPolicy, &policy); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&api.GetACLPolicyResponse{Policy: &policy}), nil
|
||||
@@ -40,7 +39,7 @@ func (s *Service) SetACLPolicy(ctx context.Context, req *connect.Request[api.Set
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet does not exist"))
|
||||
@@ -48,12 +47,12 @@ func (s *Service) SetACLPolicy(ctx context.Context, req *connect.Request[api.Set
|
||||
|
||||
var policy domain.ACLPolicy
|
||||
if err := mapping.CopyViaJson(req.Msg.Policy, &policy); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
tailnet.ACLPolicy = policy
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(tailnet.ID)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
"github.com/jsiebens/ionscale/internal/util"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"time"
|
||||
@@ -25,11 +24,11 @@ func (s *Service) Authenticate(ctx context.Context, req *connect.Request[api.Aut
|
||||
}
|
||||
|
||||
if err := s.repository.SaveAuthenticationRequest(ctx, session); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
if err := stream.Send(&api.AuthenticateResponse{AuthUrl: authUrl}); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
notify := ctx.Done()
|
||||
@@ -45,7 +44,7 @@ func (s *Service) Authenticate(ctx context.Context, req *connect.Request[api.Aut
|
||||
case <-tick.C:
|
||||
m, err := s.repository.GetAuthenticationRequest(ctx, key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -54,7 +53,7 @@ func (s *Service) Authenticate(ctx context.Context, req *connect.Request[api.Aut
|
||||
|
||||
if len(m.Token) != 0 {
|
||||
if err := stream.Send(&api.AuthenticateResponse{Token: m.Token, TailnetId: m.TailnetID}); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -64,7 +63,7 @@ func (s *Service) Authenticate(ctx context.Context, req *connect.Request[api.Aut
|
||||
}
|
||||
|
||||
if err := stream.Send(&api.AuthenticateResponse{AuthUrl: authUrl}); err != nil {
|
||||
return errors.Wrap(err, 0)
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
case <-notify:
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"time"
|
||||
@@ -16,7 +15,7 @@ func (s *Service) GetAuthKey(ctx context.Context, req *connect.Request[api.GetAu
|
||||
|
||||
key, err := s.repository.GetAuthKey(ctx, req.Msg.AuthKeyId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if key == nil {
|
||||
@@ -80,7 +79,7 @@ func (s *Service) ListAuthKeys(ctx context.Context, req *connect.Request[api.Lis
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if tailnet == nil {
|
||||
@@ -92,7 +91,7 @@ func (s *Service) ListAuthKeys(ctx context.Context, req *connect.Request[api.Lis
|
||||
if principal.IsSystemAdmin() {
|
||||
authKeys, err := s.repository.ListAuthKeys(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
response.AuthKeys = mapAuthKeysToApi(authKeys)
|
||||
@@ -102,7 +101,7 @@ func (s *Service) ListAuthKeys(ctx context.Context, req *connect.Request[api.Lis
|
||||
if principal.User != nil {
|
||||
authKeys, err := s.repository.ListAuthKeysByTailnetAndUser(ctx, req.Msg.TailnetId, principal.User.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
response.AuthKeys = mapAuthKeysToApi(authKeys)
|
||||
@@ -128,7 +127,7 @@ func (s *Service) CreateAuthKey(ctx context.Context, req *connect.Request[api.Cr
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if tailnet == nil {
|
||||
@@ -155,7 +154,7 @@ func (s *Service) CreateAuthKey(ctx context.Context, req *connect.Request[api.Cr
|
||||
if user == nil {
|
||||
u, _, err := s.repository.GetOrCreateServiceUser(ctx, tailnet)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
user = u
|
||||
}
|
||||
@@ -165,7 +164,7 @@ func (s *Service) CreateAuthKey(ctx context.Context, req *connect.Request[api.Cr
|
||||
v, authKey := domain.CreateAuthKey(tailnet, user, req.Msg.Ephemeral, req.Msg.PreAuthorized, tags, expiresAt)
|
||||
|
||||
if err := s.repository.SaveAuthKey(ctx, authKey); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
response := api.CreateAuthKeyResponse{
|
||||
@@ -191,7 +190,7 @@ func (s *Service) DeleteAuthKey(ctx context.Context, req *connect.Request[api.De
|
||||
|
||||
key, err := s.repository.GetAuthKey(ctx, req.Msg.AuthKeyId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if key == nil {
|
||||
@@ -203,7 +202,7 @@ func (s *Service) DeleteAuthKey(ctx context.Context, req *connect.Request[api.De
|
||||
}
|
||||
|
||||
if _, err := s.repository.DeleteAuthKey(ctx, req.Msg.AuthKeyId); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
return connect.NewResponse(&api.DeleteAuthKeyResponse{}), nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
"github.com/jsiebens/ionscale/internal/util"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -20,12 +19,12 @@ func (s *Service) GetDefaultDERPMap(ctx context.Context, _ *connect.Request[api.
|
||||
|
||||
dm, err := s.repository.GetDERPMap(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
raw, err := json.Marshal(dm.DERPMap)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&api.GetDefaultDERPMapResponse{Value: raw}), nil
|
||||
@@ -39,7 +38,7 @@ func (s *Service) SetDefaultDERPMap(ctx context.Context, req *connect.Request[ap
|
||||
|
||||
var derpMap tailcfg.DERPMap
|
||||
if err := json.Unmarshal(req.Msg.Value, &derpMap); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
dp := domain.DERPMap{
|
||||
@@ -48,12 +47,12 @@ func (s *Service) SetDefaultDERPMap(ctx context.Context, req *connect.Request[ap
|
||||
}
|
||||
|
||||
if err := s.repository.SetDERPMap(ctx, &dp); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
tailnets, err := s.repository.ListTailnets(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
for _, t := range tailnets {
|
||||
@@ -72,12 +71,12 @@ func (s *Service) ResetDefaultDERPMap(ctx context.Context, req *connect.Request[
|
||||
dp := domain.DERPMap{}
|
||||
|
||||
if err := s.repository.SetDERPMap(ctx, &dp); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
tailnets, err := s.repository.ListTailnets(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
for _, t := range tailnets {
|
||||
|
||||
+39
-17
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/config"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
)
|
||||
|
||||
@@ -18,24 +17,14 @@ func (s *Service) GetDNSConfig(ctx context.Context, req *connect.Request[api.Get
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
}
|
||||
|
||||
dnsConfig := tailnet.DNSConfig
|
||||
tailnetDomain := domain.SanitizeTailnetName(tailnet.Name)
|
||||
|
||||
resp := &api.GetDNSConfigResponse{
|
||||
Config: &api.DNSConfig{
|
||||
MagicDns: dnsConfig.MagicDNS,
|
||||
HttpsCerts: dnsConfig.HttpsCertsEnabled,
|
||||
MagicDnsSuffix: fmt.Sprintf("%s.%s", tailnetDomain, config.MagicDNSSuffix()),
|
||||
OverrideLocalDns: dnsConfig.OverrideLocalDNS,
|
||||
Nameservers: dnsConfig.Nameservers,
|
||||
Routes: domainRoutesToApiRoutes(dnsConfig.Routes),
|
||||
},
|
||||
Config: domainDNSConfigToApiDNSConfig(tailnet),
|
||||
}
|
||||
|
||||
return connect.NewResponse(resp), nil
|
||||
@@ -55,7 +44,7 @@ func (s *Service) SetDNSConfig(ctx context.Context, req *connect.Request[api.Set
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -63,19 +52,25 @@ func (s *Service) SetDNSConfig(ctx context.Context, req *connect.Request[api.Set
|
||||
|
||||
tailnet.DNSConfig = domain.DNSConfig{
|
||||
MagicDNS: dnsConfig.MagicDns,
|
||||
HttpsCertsEnabled: dnsConfig.HttpsCerts,
|
||||
HttpsCertsEnabled: s.dnsProvider != nil && dnsConfig.HttpsCerts,
|
||||
OverrideLocalDNS: dnsConfig.OverrideLocalDns,
|
||||
Nameservers: dnsConfig.Nameservers,
|
||||
Routes: apiRoutesToDomainRoutes(dnsConfig.Routes),
|
||||
}
|
||||
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -95,3 +90,30 @@ func apiRoutesToDomainRoutes(routes map[string]*api.Routes) map[string][]string
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func apiDNSConfigToDomainDNSConfig(dnsConfig *api.DNSConfig) domain.DNSConfig {
|
||||
if dnsConfig == nil {
|
||||
return domain.DNSConfig{}
|
||||
}
|
||||
|
||||
return domain.DNSConfig{
|
||||
MagicDNS: dnsConfig.MagicDns,
|
||||
HttpsCertsEnabled: dnsConfig.HttpsCerts,
|
||||
OverrideLocalDNS: dnsConfig.OverrideLocalDns,
|
||||
Nameservers: dnsConfig.Nameservers,
|
||||
Routes: apiRoutesToDomainRoutes(dnsConfig.Routes),
|
||||
}
|
||||
}
|
||||
|
||||
func domainDNSConfigToApiDNSConfig(tailnet *domain.Tailnet) *api.DNSConfig {
|
||||
tailnetDomain := domain.SanitizeTailnetName(tailnet.Name)
|
||||
dnsConfig := tailnet.DNSConfig
|
||||
return &api.DNSConfig{
|
||||
MagicDns: dnsConfig.MagicDNS,
|
||||
HttpsCerts: dnsConfig.HttpsCertsEnabled,
|
||||
MagicDnsSuffix: fmt.Sprintf("%s.%s", tailnetDomain, config.MagicDNSSuffix()),
|
||||
OverrideLocalDns: dnsConfig.OverrideLocalDNS,
|
||||
Nameservers: dnsConfig.Nameservers,
|
||||
Routes: domainRoutesToApiRoutes(dnsConfig.Routes),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
)
|
||||
|
||||
@@ -17,7 +16,7 @@ func (s *Service) GetIAMPolicy(ctx context.Context, req *connect.Request[api.Get
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet does not exist"))
|
||||
@@ -41,7 +40,7 @@ func (s *Service) SetIAMPolicy(ctx context.Context, req *connect.Request[api.Set
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet does not exist"))
|
||||
@@ -55,7 +54,7 @@ func (s *Service) SetIAMPolicy(ctx context.Context, req *connect.Request[api.Set
|
||||
}
|
||||
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&api.SetIAMPolicyResponse{}), nil
|
||||
|
||||
@@ -4,11 +4,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
"github.com/jsiebens/ionscale/internal/key"
|
||||
"github.com/jsiebens/ionscale/internal/token"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -78,14 +77,11 @@ func exchangeToken(ctx context.Context, systemAdminKey *key.ServerPrivate, repos
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewErrorInterceptor(logger hclog.Logger) *ErrorInterceptor {
|
||||
return &ErrorInterceptor{
|
||||
logger: logger,
|
||||
}
|
||||
func NewErrorInterceptor() *ErrorInterceptor {
|
||||
return &ErrorInterceptor{}
|
||||
}
|
||||
|
||||
type ErrorInterceptor struct {
|
||||
logger hclog.Logger
|
||||
}
|
||||
|
||||
func (e *ErrorInterceptor) handleError(err error) error {
|
||||
@@ -93,23 +89,14 @@ func (e *ErrorInterceptor) handleError(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
switch t := err.(type) {
|
||||
switch err.(type) {
|
||||
case *connect.Error:
|
||||
return err
|
||||
case *errors.Error:
|
||||
e.logger.Error("error processing grpc request",
|
||||
"err", t.Cause,
|
||||
"location", t.Location,
|
||||
)
|
||||
return connect.NewError(connect.CodeInternal, fmt.Errorf("internal server error"))
|
||||
default:
|
||||
e.logger.Error("error processing grpc request",
|
||||
"err", err,
|
||||
)
|
||||
return connect.NewError(connect.CodeInternal, fmt.Errorf("internal server error"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (e *ErrorInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc {
|
||||
return func(ctx context.Context, request connect.AnyRequest) (connect.AnyResponse, error) {
|
||||
response, err := next(ctx, request)
|
||||
@@ -129,3 +116,8 @@ func (e *ErrorInterceptor) WrapStreamingHandler(next connect.StreamingHandlerFun
|
||||
return e.handleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func logError(err error) error {
|
||||
zap.L().WithOptions(zap.AddCallerSkip(1)).Error("error processing request", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
+28
-24
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"net/netip"
|
||||
@@ -25,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,
|
||||
@@ -48,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(),
|
||||
@@ -66,7 +70,7 @@ func (s *Service) ListMachines(ctx context.Context, req *connect.Request[api.Lis
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -74,7 +78,7 @@ func (s *Service) ListMachines(ctx context.Context, req *connect.Request[api.Lis
|
||||
|
||||
machines, err := s.repository.ListMachineByTailnet(ctx, tailnet.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
response := &api.ListMachinesResponse{}
|
||||
@@ -90,7 +94,7 @@ func (s *Service) GetMachine(ctx context.Context, req *connect.Request[api.GetMa
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -109,7 +113,7 @@ func (s *Service) DeleteMachine(ctx context.Context, req *connect.Request[api.De
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -121,7 +125,7 @@ func (s *Service) DeleteMachine(ctx context.Context, req *connect.Request[api.De
|
||||
}
|
||||
|
||||
if _, err := s.repository.DeleteMachine(ctx, req.Msg.MachineId); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(m.TailnetID)
|
||||
@@ -134,7 +138,7 @@ func (s *Service) ExpireMachine(ctx context.Context, req *connect.Request[api.Ex
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -150,7 +154,7 @@ func (s *Service) ExpireMachine(ctx context.Context, req *connect.Request[api.Ex
|
||||
m.KeyExpiryDisabled = false
|
||||
|
||||
if err := s.repository.SaveMachine(ctx, m); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(m.TailnetID)
|
||||
@@ -163,7 +167,7 @@ func (s *Service) AuthorizeMachine(ctx context.Context, req *connect.Request[api
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -177,7 +181,7 @@ func (s *Service) AuthorizeMachine(ctx context.Context, req *connect.Request[api
|
||||
if !m.Authorized {
|
||||
m.Authorized = true
|
||||
if err := s.repository.SaveMachine(ctx, m); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +195,7 @@ func (s *Service) GetMachineRoutes(ctx context.Context, req *connect.Request[api
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -220,7 +224,7 @@ func (s *Service) EnableMachineRoutes(ctx context.Context, req *connect.Request[
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -242,7 +246,7 @@ func (s *Service) EnableMachineRoutes(ctx context.Context, req *connect.Request[
|
||||
for _, r := range req.Msg.Routes {
|
||||
prefix, err := netip.ParsePrefix(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
allowIPs.Add(prefix)
|
||||
}
|
||||
@@ -250,7 +254,7 @@ func (s *Service) EnableMachineRoutes(ctx context.Context, req *connect.Request[
|
||||
m.AllowIPs = allowIPs.Items()
|
||||
m.AutoAllowIPs = autoAllowIPs.Items()
|
||||
if err := s.repository.SaveMachine(ctx, m); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(m.TailnetID)
|
||||
@@ -273,7 +277,7 @@ func (s *Service) DisableMachineRoutes(ctx context.Context, req *connect.Request
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -290,7 +294,7 @@ func (s *Service) DisableMachineRoutes(ctx context.Context, req *connect.Request
|
||||
for _, r := range req.Msg.Routes {
|
||||
prefix, err := netip.ParsePrefix(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
allowIPs.Remove(prefix)
|
||||
autoAllowIPs.Remove(prefix)
|
||||
@@ -299,7 +303,7 @@ func (s *Service) DisableMachineRoutes(ctx context.Context, req *connect.Request
|
||||
m.AllowIPs = allowIPs.Items()
|
||||
m.AutoAllowIPs = autoAllowIPs.Items()
|
||||
if err := s.repository.SaveMachine(ctx, m); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(m.TailnetID)
|
||||
@@ -322,7 +326,7 @@ func (s *Service) EnableExitNode(ctx context.Context, req *connect.Request[api.E
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -346,7 +350,7 @@ func (s *Service) EnableExitNode(ctx context.Context, req *connect.Request[api.E
|
||||
m.AllowIPs = allowIPs.Items()
|
||||
|
||||
if err := s.repository.SaveMachine(ctx, m); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(m.TailnetID)
|
||||
@@ -369,7 +373,7 @@ func (s *Service) DisableExitNode(ctx context.Context, req *connect.Request[api.
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -397,7 +401,7 @@ func (s *Service) DisableExitNode(ctx context.Context, req *connect.Request[api.
|
||||
m.AutoAllowIPs = autoAllowIPs.Items()
|
||||
|
||||
if err := s.repository.SaveMachine(ctx, m); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(m.TailnetID)
|
||||
@@ -420,7 +424,7 @@ func (s *Service) SetMachineKeyExpiry(ctx context.Context, req *connect.Request[
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
@@ -434,7 +438,7 @@ func (s *Service) SetMachineKeyExpiry(ctx context.Context, req *connect.Request[
|
||||
m.KeyExpiryDisabled = req.Msg.Disabled
|
||||
|
||||
if err := s.repository.SaveMachine(ctx, m); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(m.TailnetID)
|
||||
|
||||
@@ -6,15 +6,17 @@ import (
|
||||
"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 +25,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
|
||||
}
|
||||
|
||||
+146
-54
@@ -6,44 +6,134 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
"github.com/jsiebens/ionscale/internal/mapping"
|
||||
"github.com/jsiebens/ionscale/internal/util"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
func domainTailnetToApiTailnet(tailnet *domain.Tailnet) (*api.Tailnet, error) {
|
||||
t := &api.Tailnet{
|
||||
Id: tailnet.ID,
|
||||
Name: tailnet.Name,
|
||||
IamPolicy: new(api.IAMPolicy),
|
||||
AclPolicy: new(api.ACLPolicy),
|
||||
DnsConfig: domainDNSConfigToApiDNSConfig(tailnet),
|
||||
ServiceCollectionEnabled: tailnet.ServiceCollectionEnabled,
|
||||
FileSharingEnabled: tailnet.FileSharingEnabled,
|
||||
SshEnabled: tailnet.SSHEnabled,
|
||||
MachineAuthorizationEnabled: tailnet.MachineAuthorizationEnabled,
|
||||
}
|
||||
|
||||
if err := mapping.CopyViaJson(tailnet.IAMPolicy, t.IamPolicy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mapping.CopyViaJson(tailnet.ACLPolicy, t.AclPolicy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (s *Service) CreateTailnet(ctx context.Context, req *connect.Request[api.CreateTailnetRequest]) (*connect.Response[api.CreateTailnetResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
|
||||
}
|
||||
|
||||
name := req.Msg.Name
|
||||
iamPolicy := domain.IAMPolicy{}
|
||||
|
||||
if req.Msg.IamPolicy != nil {
|
||||
iamPolicy.Subs = req.Msg.IamPolicy.Subs
|
||||
iamPolicy.Emails = req.Msg.IamPolicy.Emails
|
||||
iamPolicy.Filters = req.Msg.IamPolicy.Filters
|
||||
iamPolicy.Roles = apiRolesMapToDomainRolesMap(req.Msg.IamPolicy.Roles)
|
||||
tailnet := &domain.Tailnet{
|
||||
ID: util.NextID(),
|
||||
Name: req.Msg.Name,
|
||||
IAMPolicy: domain.IAMPolicy{},
|
||||
ACLPolicy: domain.ACLPolicy{},
|
||||
DNSConfig: apiDNSConfigToDomainDNSConfig(req.Msg.DnsConfig),
|
||||
ServiceCollectionEnabled: req.Msg.ServiceCollectionEnabled,
|
||||
FileSharingEnabled: req.Msg.FileSharingEnabled,
|
||||
SSHEnabled: req.Msg.SshEnabled,
|
||||
MachineAuthorizationEnabled: req.Msg.MachineAuthorizationEnabled,
|
||||
}
|
||||
|
||||
tailnet := &domain.Tailnet{
|
||||
ID: util.NextID(),
|
||||
Name: name,
|
||||
IAMPolicy: iamPolicy,
|
||||
ACLPolicy: domain.DefaultPolicy(),
|
||||
DNSConfig: domain.DNSConfig{MagicDNS: true},
|
||||
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 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 := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
resp := &api.CreateTailnetResponse{Tailnet: &api.Tailnet{
|
||||
Id: tailnet.ID,
|
||||
Name: tailnet.Name,
|
||||
}}
|
||||
t, err := domainTailnetToApiTailnet(tailnet)
|
||||
if err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
resp := &api.CreateTailnetResponse{Tailnet: t}
|
||||
|
||||
return connect.NewResponse(resp), nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateTailnet(ctx context.Context, req *connect.Request[api.UpdateTailnetRequest]) (*connect.Response[api.UpdateTailnetResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.TailnetId) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
|
||||
}
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
}
|
||||
|
||||
if req.Msg.IamPolicy != nil {
|
||||
tailnet.IAMPolicy = domain.IAMPolicy{}
|
||||
if err := mapping.CopyViaJson(req.Msg.IamPolicy, &tailnet.IAMPolicy); err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Msg.AclPolicy != nil {
|
||||
tailnet.ACLPolicy = domain.ACLPolicy{}
|
||||
if err := mapping.CopyViaJson(req.Msg.AclPolicy, &tailnet.ACLPolicy); err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Msg.DnsConfig != nil {
|
||||
tailnet.DNSConfig = apiDNSConfigToDomainDNSConfig(req.Msg.DnsConfig)
|
||||
}
|
||||
|
||||
tailnet.ServiceCollectionEnabled = req.Msg.ServiceCollectionEnabled
|
||||
tailnet.FileSharingEnabled = req.Msg.FileSharingEnabled
|
||||
tailnet.SSHEnabled = req.Msg.SshEnabled
|
||||
tailnet.MachineAuthorizationEnabled = req.Msg.MachineAuthorizationEnabled
|
||||
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(tailnet.ID)
|
||||
|
||||
t, err := domainTailnetToApiTailnet(tailnet)
|
||||
if err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
resp := &api.UpdateTailnetResponse{Tailnet: t}
|
||||
|
||||
return connect.NewResponse(resp), nil
|
||||
}
|
||||
@@ -56,17 +146,19 @@ func (s *Service) GetTailnet(ctx context.Context, req *connect.Request[api.GetTa
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
}
|
||||
|
||||
return connect.NewResponse(&api.GetTailnetResponse{Tailnet: &api.Tailnet{
|
||||
Id: tailnet.ID,
|
||||
Name: tailnet.Name,
|
||||
}}), nil
|
||||
t, err := domainTailnetToApiTailnet(tailnet)
|
||||
if err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&api.GetTailnetResponse{Tailnet: t}), nil
|
||||
}
|
||||
|
||||
func (s *Service) ListTailnets(ctx context.Context, req *connect.Request[api.ListTailnetsRequest]) (*connect.Response[api.ListTailnetsResponse], error) {
|
||||
@@ -77,7 +169,7 @@ func (s *Service) ListTailnets(ctx context.Context, req *connect.Request[api.Lis
|
||||
if principal.IsSystemAdmin() {
|
||||
tailnets, err := s.repository.ListTailnets(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
for _, t := range tailnets {
|
||||
gt := api.Tailnet{Id: t.ID, Name: t.Name}
|
||||
@@ -88,7 +180,7 @@ func (s *Service) ListTailnets(ctx context.Context, req *connect.Request[api.Lis
|
||||
if principal.User != nil {
|
||||
tailnet, err := s.repository.GetTailnet(ctx, principal.User.TailnetID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
gt := api.Tailnet{Id: tailnet.ID, Name: tailnet.Name}
|
||||
resp.Tailnet = append(resp.Tailnet, >)
|
||||
@@ -105,7 +197,7 @@ func (s *Service) DeleteTailnet(ctx context.Context, req *connect.Request[api.De
|
||||
|
||||
count, err := s.repository.CountMachineByTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if !req.Msg.Force && count > 0 {
|
||||
@@ -137,7 +229,7 @@ func (s *Service) DeleteTailnet(ctx context.Context, req *connect.Request[api.De
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(req.Msg.TailnetId)
|
||||
@@ -153,12 +245,12 @@ func (s *Service) SetDERPMap(ctx context.Context, req *connect.Request[api.SetDE
|
||||
|
||||
derpMap := tailcfg.DERPMap{}
|
||||
if err := json.Unmarshal(req.Msg.Value, &derpMap); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -170,14 +262,14 @@ func (s *Service) SetDERPMap(ctx context.Context, req *connect.Request[api.SetDE
|
||||
}
|
||||
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(tailnet.ID)
|
||||
|
||||
raw, err := json.Marshal(derpMap)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&api.SetDERPMapResponse{Value: raw}), nil
|
||||
@@ -191,7 +283,7 @@ func (s *Service) ResetDERPMap(ctx context.Context, req *connect.Request[api.Res
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -200,7 +292,7 @@ func (s *Service) ResetDERPMap(ctx context.Context, req *connect.Request[api.Res
|
||||
tailnet.DERPMap = domain.DERPMap{}
|
||||
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(tailnet.ID)
|
||||
@@ -216,7 +308,7 @@ func (s *Service) GetDERPMap(ctx context.Context, req *connect.Request[api.GetDE
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -224,12 +316,12 @@ func (s *Service) GetDERPMap(ctx context.Context, req *connect.Request[api.GetDE
|
||||
|
||||
derpMap, err := tailnet.GetDERPMap(ctx, s.repository)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
raw, err := json.Marshal(derpMap.DERPMap)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&api.GetDERPMapResponse{Value: raw}), nil
|
||||
@@ -243,7 +335,7 @@ func (s *Service) EnableFileSharing(ctx context.Context, req *connect.Request[ap
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -252,7 +344,7 @@ func (s *Service) EnableFileSharing(ctx context.Context, req *connect.Request[ap
|
||||
if !tailnet.FileSharingEnabled {
|
||||
tailnet.FileSharingEnabled = true
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(tailnet.ID)
|
||||
@@ -269,7 +361,7 @@ func (s *Service) DisableFileSharing(ctx context.Context, req *connect.Request[a
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -278,7 +370,7 @@ func (s *Service) DisableFileSharing(ctx context.Context, req *connect.Request[a
|
||||
if tailnet.FileSharingEnabled {
|
||||
tailnet.FileSharingEnabled = false
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(tailnet.ID)
|
||||
@@ -295,7 +387,7 @@ func (s *Service) EnableServiceCollection(ctx context.Context, req *connect.Requ
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -304,7 +396,7 @@ func (s *Service) EnableServiceCollection(ctx context.Context, req *connect.Requ
|
||||
if !tailnet.ServiceCollectionEnabled {
|
||||
tailnet.ServiceCollectionEnabled = true
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(tailnet.ID)
|
||||
@@ -321,7 +413,7 @@ func (s *Service) DisableServiceCollection(ctx context.Context, req *connect.Req
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -330,7 +422,7 @@ func (s *Service) DisableServiceCollection(ctx context.Context, req *connect.Req
|
||||
if tailnet.ServiceCollectionEnabled {
|
||||
tailnet.ServiceCollectionEnabled = false
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(tailnet.ID)
|
||||
@@ -347,7 +439,7 @@ func (s *Service) EnableSSH(ctx context.Context, req *connect.Request[api.Enable
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -356,7 +448,7 @@ func (s *Service) EnableSSH(ctx context.Context, req *connect.Request[api.Enable
|
||||
if !tailnet.SSHEnabled {
|
||||
tailnet.SSHEnabled = true
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(tailnet.ID)
|
||||
@@ -373,7 +465,7 @@ func (s *Service) DisableSSH(ctx context.Context, req *connect.Request[api.Disab
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -382,7 +474,7 @@ func (s *Service) DisableSSH(ctx context.Context, req *connect.Request[api.Disab
|
||||
if tailnet.SSHEnabled {
|
||||
tailnet.SSHEnabled = false
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(tailnet.ID)
|
||||
@@ -399,7 +491,7 @@ func (s *Service) EnableMachineAuthorization(ctx context.Context, req *connect.R
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -408,7 +500,7 @@ func (s *Service) EnableMachineAuthorization(ctx context.Context, req *connect.R
|
||||
if !tailnet.MachineAuthorizationEnabled {
|
||||
tailnet.MachineAuthorizationEnabled = true
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +515,7 @@ func (s *Service) DisableMachineAuthorization(ctx context.Context, req *connect.
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
if tailnet == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
|
||||
@@ -432,7 +524,7 @@ func (s *Service) DisableMachineAuthorization(ctx context.Context, req *connect.
|
||||
if tailnet.MachineAuthorizationEnabled {
|
||||
tailnet.MachineAuthorizationEnabled = false
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/bufbuild/connect-go"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/errors"
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
)
|
||||
|
||||
@@ -14,7 +13,7 @@ func (s *Service) ListUsers(ctx context.Context, req *connect.Request[api.ListUs
|
||||
|
||||
tailnet, err := s.repository.GetTailnet(ctx, req.Msg.TailnetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if tailnet == nil {
|
||||
@@ -27,7 +26,7 @@ func (s *Service) ListUsers(ctx context.Context, req *connect.Request[api.ListUs
|
||||
|
||||
users, err := s.repository.ListUsers(ctx, tailnet.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
resp := &api.ListUsersResponse{}
|
||||
@@ -51,7 +50,7 @@ func (s *Service) DeleteUser(ctx context.Context, req *connect.Request[api.Delet
|
||||
|
||||
user, err := s.repository.GetUser(ctx, req.Msg.UserId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
@@ -87,7 +86,7 @@ func (s *Service) DeleteUser(ctx context.Context, req *connect.Request[api.Delet
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, 0)
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(user.TailnetID)
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,124 @@
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
<title>ionscale</title>
|
||||
}
|
||||
@@ -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 </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
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -29,13 +29,13 @@ tls:
|
||||
# Required when TLS is enabled and ACME disabled
|
||||
key_file: ""
|
||||
# Enable automatic TLS certificates provisioning with Let's Encrypt
|
||||
acme_enabled: false
|
||||
acme: false
|
||||
# An email address, used when creating an ACME account and keeping you up-to-date regarding your certificates
|
||||
acme_email: ""
|
||||
# The URL to the ACME CA's directory.
|
||||
acme_ca: "https://acme-v02.api.letsencrypt.org/directory"
|
||||
# Path to store certificates and metadata needed by ACME
|
||||
amce_path: "./data"
|
||||
acme_path: "./data"
|
||||
|
||||
database:
|
||||
# Type of databas to use, supported values are sqlite or postgres
|
||||
|
||||
@@ -68,5 +68,5 @@ docker run \
|
||||
-v $(pwd)/data:/data \
|
||||
-p 80:80 \
|
||||
-p 443:443 \
|
||||
ghcr.io/jsiebens/ionscale:0.5.0 server --config /etc/ionscale/config.yaml
|
||||
ghcr.io/jsiebens/ionscale:0.8.2 server --config /etc/ionscale/config.yaml
|
||||
```
|
||||
@@ -41,7 +41,7 @@ Run the following commands to install the __ionscale__ binary on your Linux host
|
||||
``` bash
|
||||
sudo curl \
|
||||
-o "/usr/local/bin/ionscale" \
|
||||
-sfL "https://github.com/jsiebens/ionscale/releases/download/v0.5.0/ionscale_linux_amd64"
|
||||
-sfL "https://github.com/jsiebens/ionscale/releases/download/v0.8.2/ionscale_linux_amd64"
|
||||
|
||||
sudo chmod +x "/usr/local/bin/ionscale"
|
||||
```
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package defaults
|
||||
|
||||
import ionscalev1 "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
|
||||
func DefaultACLPolicy() *ionscalev1.ACLPolicy {
|
||||
return &ionscalev1.ACLPolicy{
|
||||
Acls: []*ionscalev1.ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"*:*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultDNSConfig() *ionscalev1.DNSConfig {
|
||||
return &ionscalev1.DNSConfig{
|
||||
MagicDns: true,
|
||||
}
|
||||
}
|
||||
+280
-66
@@ -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,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,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,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
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user