Compare commits

..

1 Commits

Author SHA1 Message Date
ee69644699 feat: authenticate users and agents requests 2023-03-07 23:10:42 +01:00
135 changed files with 1012 additions and 5855 deletions

5
.gitignore vendored
View File

@ -4,9 +4,8 @@ dist/
/tools /tools
/tmp /tmp
/state.json /state.json
/emissary.sqlite* /emissary.sqlite
/.gitea-release /.gitea-release
/agent-key.json /agent-key.json
/apps /apps
/server-key.json /server-key.json
/.emissary-token

View File

@ -2,7 +2,6 @@ project_name: emissary
before: before:
hooks: hooks:
- go mod tidy - go mod tidy
- go generate ./...
builds: builds:
- id: emissary-server - id: emissary-server
env: env:
@ -46,9 +45,6 @@ builds:
- arm64 - arm64
- arm - arm
- "386" - "386"
goarm:
- "6"
- "7"
main: ./cmd/agent main: ./cmd/agent
archives: archives:
- id: server - id: server
@ -67,7 +63,7 @@ archives:
checksum: checksum:
name_template: 'checksums.txt' name_template: 'checksums.txt'
snapshot: snapshot:
name_template: "{{ .Version }}" name_template: "{{ incpatch .Version }}-next"
changelog: changelog:
sort: asc sort: asc
filters: filters:

85
Jenkinsfile vendored
View File

@ -1,85 +0,0 @@
@Library('cadoles') _
pipeline {
agent {
dockerfile {
label 'docker'
filename 'Dockerfile'
dir 'misc/jenkins'
}
}
stages {
stage('Cancel older jobs') {
steps {
script {
def buildNumber = env.BUILD_NUMBER as int
if (buildNumber > 1) milestone(buildNumber - 1)
milestone(buildNumber)
}
}
}
stage('Run unit tests') {
steps {
script {
withCredentials([
usernamePassword([
credentialsId: 'forge-jenkins',
usernameVariable: 'GIT_USERNAME',
passwordVariable: 'GIT_PASSWORD'
])
]) {
sh '''
git config --global credential.https://forge.cadoles.com.username "$GIT_USERNAME"
git config --global credential.https://forge.cadoles.com.helper '!f() { test "$1" = get && echo "password=$GIT_PASSWORD"; }; f'
export GOPRIVATE=forge.cadoles.com/arcad/edge
make test
'''
}
}
}
}
stage('Release') {
when {
anyOf {
branch 'master'
branch 'develop'
}
}
steps {
script {
withCredentials([
usernamePassword([
credentialsId: 'forge-jenkins',
usernameVariable: 'GITEA_RELEASE_USERNAME',
passwordVariable: 'GITEA_RELEASE_PASSWORD'
])
]) {
sh 'make gitea-release'
}
def currentVersion = sh(returnStdout: true, script: 'make full-version').trim()
if (currentVersion.endsWith('-dirty')) {
unstable('Could not trigger emissary-firmware build, dirty version !')
} else {
build(
job: "../emissary-firmware/${env.GIT_BRANCH}",
parameters: [
[$class: 'StringParameterValue', name: 'emissaryRelease', value: currentVersion]
],
wait: false
)
}
}
}
}
}
post {
always {
cleanWs()
}
}
}

View File

@ -1,16 +1,14 @@
LINT_ARGS ?= --timeout 5m LINT_ARGS ?= --timeout 5m
GORELEASER_VERSION ?= v1.13.1 GORELEASER_VERSION ?= v1.13.1
GORELEASER_ARGS ?= release --snapshot --rm-dist GORELEASER_ARGS ?= release --auto-snapshot --snapshot --rm-dist
GITCHLOG_ARGS ?= GITCHLOG_ARGS ?=
SHELL := /bin/bash SHELL := /bin/bash
EMISSARY_VERSION ?= EMISSARY_VERSION ?=
GIT_VERSION := $(shell git describe --always) GIT_VERSION ?= $(shell git describe --always)
DATE_VERSION := $(shell date +%Y.%-m.%-d)
FULL_VERSION := v$(DATE_VERSION)-$(GIT_VERSION)$(if $(shell git diff --stat),-dirty,)
DOCKER_IMAGE_NAME ?= bornholm/emissary DOCKER_IMAGE_NAME ?= bornholm/emissary
DOCKER_IMAGE_TAG ?= $(FULL_VERSION) DOCKER_IMAGE_TAG ?= $(GIT_VERSION)$(if $(shell git diff --stat),-dirty,)
GOTEST_ARGS ?= -short GOTEST_ARGS ?= -short
@ -45,7 +43,7 @@ build-emissary-%: deps ## Build executable
-v \ -v \
-ldflags "\ -ldflags "\
-X 'main.GitRef=$(shell git rev-parse --short HEAD)' \ -X 'main.GitRef=$(shell git rev-parse --short HEAD)' \
-X 'main.ProjectVersion=$(FULL_VERSION)' \ -X 'main.ProjectVersion=$(shell git describe --always)' \
-X 'main.BuildDate=$(shell date --utc --rfc-3339=seconds)' \ -X 'main.BuildDate=$(shell date --utc --rfc-3339=seconds)' \
" \ " \
-o ./bin/$* \ -o ./bin/$* \
@ -75,7 +73,7 @@ dump-config: build-emissary
.PHONY: goreleaser .PHONY: goreleaser
goreleaser: deps goreleaser: deps
( set -o allexport && source .env && set +o allexport && VERSION=$(GORELEASER_VERSION) curl -sfL https://goreleaser.com/static/run | GORELEASER_CURRENT_TAG="$(FULL_VERSION)" bash /dev/stdin $(GORELEASER_ARGS) ) ( set -o allexport && source .env && set +o allexport && VERSION=$(GORELEASER_VERSION) curl -sfL https://goreleaser.com/static/run | bash /dev/stdin $(GORELEASER_ARGS) )
.PHONY: start-release .PHONY: start-release
start-release: start-release:
@ -124,43 +122,19 @@ gitea-release: tools/gitea-release/bin/gitea-release.sh goreleaser
rm -rf .gitea-release/* rm -rf .gitea-release/*
cp dist/*.tar.gz .gitea-release/ cp dist/*.tar.gz .gitea-release/
cp dist/*.apk .gitea-release/
cp dist/*.deb .gitea-release/
GITEA_RELEASE_PROJECT="emissary" \ GITEA_RELEASE_PROJECT="emissary" \
GITEA_RELEASE_ORG="arcad" \ GITEA_RELEASE_ORG="arcad" \
GITEA_RELEASE_BASE_URL="https://forge.cadoles.com" \ GITEA_RELEASE_BASE_URL="https://forge.cadoles.com" \
GITEA_RELEASE_VERSION="$(FULL_VERSION)" \ GITEA_RELEASE_VERSION="$(GIT_VERSION)" \
GITEA_RELEASE_NAME="$(FULL_VERSION)" \ GITEA_RELEASE_NAME="$(GIT_VERSION)" \
GITEA_RELEASE_COMMITISH_TARGET="$(GIT_VERSION)" \ GITEA_RELEASE_COMMITISH_TARGET="$(GIT_VERSION)" \
GITEA_RELEASE_IS_DRAFT="false" \ GITEA_RELEASE_IS_DRAFT="false" \
GITEA_RELEASE_BODY="" \ GITEA_RELEASE_BODY="" \
GITEA_RELEASE_ATTACHMENTS="$$(find .gitea-release/* -type f)" \ GITEA_RELEASE_ATTACHMENTS="$(shell find .gitea-release/* -type f)" \
tools/gitea-release/bin/gitea-release.sh tools/gitea-release/bin/gitea-release.sh
tools/gitea-release/bin/gitea-release.sh: tools/gitea-release/bin/gitea-release.sh:
mkdir -p tools/gitea-release/bin mkdir -p tools/gitea-release/bin
curl --output tools/gitea-release/bin/gitea-release.sh https://forge.cadoles.com/Cadoles/Jenkins/raw/branch/master/resources/com/cadoles/gitea/gitea-release.sh curl --output tools/gitea-release/bin/gitea-release.sh https://forge.cadoles.com/Cadoles/Jenkins/raw/branch/master/resources/com/cadoles/gitea/gitea-release.sh
chmod +x tools/gitea-release/bin/gitea-release.sh chmod +x tools/gitea-release/bin/gitea-release.sh
.emissary-token:
$(MAKE) run-emissary-server EMISSARY_CMD="--debug --config tmp/server.yml server auth create-token --role writer > .emissary-token"
AGENT_ID ?= 1
load-sample-specs:
cat misc/spec-samples/app.emissary.cadoles.com.json | ./bin/server api agent spec update -a $(AGENT_ID) --no-patch --spec-data - --spec-name app.emissary.cadoles.com
cat misc/spec-samples/proxy.emissary.cadoles.com.json | ./bin/server api agent spec update -a $(AGENT_ID) --no-patch --spec-data - --spec-name proxy.emissary.cadoles.com
cat misc/spec-samples/mdns.emissary.cadoles.com.json | ./bin/server api agent spec update -a $(AGENT_ID) --no-patch --spec-data - --spec-name mdns.emissary.cadoles.com
full-version:
@echo $(FULL_VERSION)
update-edge-lib:
git pull --rebase
GOPROXY=direct GOPRIVATE=forge.cadoles.com/arcad/edge go get -u forge.cadoles.com/arcad/edge
go mod tidy
$(MAKE) test
git add go.mod go.sum
git commit -m "feat: update arcad/edge dependency"
git push

View File

@ -5,10 +5,7 @@ import (
"forge.cadoles.com/Cadoles/emissary/internal/command" "forge.cadoles.com/Cadoles/emissary/internal/command"
"forge.cadoles.com/Cadoles/emissary/internal/command/agent" "forge.cadoles.com/Cadoles/emissary/internal/command/agent"
"forge.cadoles.com/Cadoles/emissary/internal/command/api" "forge.cadoles.com/Cadoles/emissary/internal/command/client"
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/format"
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/spec"
) )
// nolint: gochecknoglobals // nolint: gochecknoglobals
@ -20,5 +17,5 @@ var (
) )
func main() { func main() {
command.Main(BuildDate, ProjectVersion, GitRef, DefaultConfigPath, agent.Root(), api.Root()) command.Main(BuildDate, ProjectVersion, GitRef, DefaultConfigPath, agent.Root(), client.Root())
} }

View File

@ -4,12 +4,11 @@ import (
"time" "time"
"forge.cadoles.com/Cadoles/emissary/internal/command" "forge.cadoles.com/Cadoles/emissary/internal/command"
"forge.cadoles.com/Cadoles/emissary/internal/command/api" "forge.cadoles.com/Cadoles/emissary/internal/command/client"
"forge.cadoles.com/Cadoles/emissary/internal/command/server" "forge.cadoles.com/Cadoles/emissary/internal/command/server"
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/format" _ "github.com/jackc/pgx/v5/stdlib"
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/spec" _ "modernc.org/sqlite"
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/sql"
) )
// nolint: gochecknoglobals // nolint: gochecknoglobals
@ -21,5 +20,5 @@ var (
) )
func main() { func main() {
command.Main(BuildDate, ProjectVersion, GitRef, DefaultConfigPath, server.Root(), api.Root()) command.Main(BuildDate, ProjectVersion, GitRef, DefaultConfigPath, server.Root(), client.Root())
} }

View File

@ -1,7 +1,5 @@
# Documentation # Documentation
- (FR) - [Introduction](./fr/introduction.md)
## Tutorials ## Tutorials
- (FR) - [Premiers pas](./tutorials/fr/first-steps.md) - (FR) - [Premiers pas](./tutorials/fr/first-steps.md)
@ -12,12 +10,6 @@
[See `misc/rest/server.rest`](../misc/rest/server.rest) [See `misc/rest/server.rest`](../misc/rest/server.rest)
### Spécifications
- [Schéma `app.emissary.cadoles.com`](../internal/spec/app/schema.json)
- [Schéma `uci.emissary.cadoles.com`](../internal/spec/uci/schema.json)
- [Schéma `gateway.emissary.cadoles.com`](../internal/spec/gateway/schema.json)
### Configuration ### Configuration
See: See:

View File

@ -1,15 +0,0 @@
# Introduction
"Emissary" est un programme entrant dans la catégorie des outils de gestion et déploiement de configuration.
En utilisant un agent déployé sur chaque système cible, il permet aux administrateurs système de centraliser le contrôle et la supervision de la configuration. Grâce à ses fonctionnalités avancées, il est capable de faire converger la configuration d'une machine vers un modèle précis défini par une ou plusieurs spécifications centralisées sur un serveur de pilotage dédié.
Le principal atout d'"Emissary" réside dans sa capacité à activer des "contrôleurs" spécifiques pour chaque aspect de la configuration système. Ces contrôleurs sont des modules intelligents qui agissent comme des agents spécialisés, veillant à ce que les paramètres de configuration soient correctement appliqués et respectent les spécifications définies.
Grâce à cette approche modulaire, "Emissary" peut gérer diverses facettes de la configuration, telles que les paramètres réseau, les règles de sécurité, les options de performance et bien plus encore. Chaque contrôleur est conçu pour répondre à des besoins spécifiques, offrant ainsi une flexibilité et une granularité optimales dans la gestion de la configuration.
Certains contrôleurs permettent également l'exécution de services spécialisés comme des serveurs mandataires inverses ou des applications web autonomes.
L'utilisation d'un serveur de pilotage centralisé permet à "Emissary" de stocker et de mettre à jour les spécifications de configuration de manière cohérente. Les administrateurs peuvent définir des modèles de configuration précis, les affiner au fil du temps et les appliquer en un seul clic sur l'ensemble du parc de machines gérées. Cela garantit une uniformité et une conformité accrues, tout en facilitant la maintenance et les mises à jour à grande échelle.
À l'heure actuelle, Emissary est conçu pour cibler spécifiquement le système d'exploitation OpenWRT. L'activation des "contrôleurs" spécifiques à cet OS permet de converger la configuration de la machine OpenWRT vers un modèle correspondant aux spécifications centralisées sur le serveur de pilotage. Ces spécifications peuvent inclure des paramètres réseau, des configurations de sécurité, des règles de pare-feu, des options de routage, des services système, et bien d'autres éléments spécifiques à OpenWRT.

50
go.mod
View File

@ -3,71 +3,59 @@ module forge.cadoles.com/Cadoles/emissary
go 1.19 go 1.19
require ( require (
forge.cadoles.com/arcad/edge v0.0.0-20230426135323-17808d14c978 forge.cadoles.com/arcad/edge v0.0.0-20230303153719-6399196fe5c9
github.com/Masterminds/sprig/v3 v3.2.3
github.com/alecthomas/participle/v2 v2.0.0-beta.5 github.com/alecthomas/participle/v2 v2.0.0-beta.5
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
github.com/brutella/dnssd v1.2.6
github.com/btcsuite/btcd/btcutil v1.1.3 github.com/btcsuite/btcd/btcutil v1.1.3
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/denisbrodbeck/machineid v1.0.1 github.com/denisbrodbeck/machineid v1.0.1
github.com/dop251/goja v0.0.0-20230304130813-e2f543bf4b4c
github.com/evanphx/json-patch/v5 v5.6.0 github.com/evanphx/json-patch/v5 v5.6.0
github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/chi v4.1.2+incompatible
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/golang-migrate/migrate/v4 v4.15.2 github.com/golang-migrate/migrate/v4 v4.15.2
github.com/jackc/pgx/v5 v5.3.1 github.com/jackc/pgx/v5 v5.3.1
github.com/jedib0t/go-pretty/v6 v6.4.4 github.com/jedib0t/go-pretty/v6 v6.4.4
github.com/lestrrat-go/jwx/v2 v2.0.9 github.com/lestrrat-go/jwx/v2 v2.0.8
github.com/lithammer/shortuuid/v4 v4.0.0
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/qri-io/jsonschema v0.2.1 github.com/qri-io/jsonschema v0.2.1
github.com/santhosh-tekuri/jsonschema/v5 v5.1.1
github.com/urfave/cli/v2 v2.24.4 github.com/urfave/cli/v2 v2.24.4
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b gitlab.com/wpetit/goweb v0.0.0-20230227162855-a1f09bafccb3
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.21.0 modernc.org/sqlite v1.21.0
) )
require ( require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692 // indirect github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692 // indirect
github.com/dop251/goja_nodejs v0.0.0-20230320130059-dcf93ba651dd // indirect github.com/dop251/goja v0.0.0-20230226152633-7c93113e17ac // indirect
github.com/dop251/goja_nodejs v0.0.0-20230226152057-060fa99b809f // indirect
github.com/gabriel-vasile/mimetype v1.4.1 // indirect github.com/gabriel-vasile/mimetype v1.4.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/mdns v1.0.5 // indirect github.com/hashicorp/mdns v1.0.5 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/igm/sockjs-go/v3 v3.0.2 // indirect github.com/igm/sockjs-go/v3 v3.0.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect github.com/miekg/dns v1.1.51 // indirect
github.com/miekg/dns v1.1.53 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/oklog/ulid/v2 v2.1.0 // indirect github.com/oklog/ulid/v2 v2.1.0 // indirect
github.com/orcaman/concurrent-map v1.0.0 // indirect github.com/orcaman/concurrent-map v1.0.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect golang.org/x/net v0.7.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
golang.org/x/net v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 // indirect google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )
require ( require (
cdr.dev/slog v1.4.2 // indirect cdr.dev/slog v1.4.1 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect github.com/alecthomas/chroma v0.10.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dlclark/regexp2 v1.8.1 // indirect github.com/dlclark/regexp2 v1.8.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.15.0 // indirect github.com/fatih/color v1.14.1 // indirect
github.com/go-chi/chi/v5 v5.0.8 github.com/go-chi/chi/v5 v5.0.8
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.9.11 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
@ -81,7 +69,7 @@ require (
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.4 // indirect github.com/lestrrat-go/httprc v1.0.4 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/option v1.0.0 // indirect
github.com/lib/pq v1.10.7 // indirect github.com/lib/pq v1.10.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
@ -95,13 +83,13 @@ require (
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.10.0 // indirect go.uber.org/atomic v1.10.0 // indirect
golang.org/x/crypto v0.7.0 // indirect golang.org/x/crypto v0.6.0 // indirect
golang.org/x/mod v0.10.0 // indirect golang.org/x/mod v0.8.0 // indirect
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.7.0 // indirect golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.9.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.8.0 // indirect golang.org/x/tools v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
@ -115,5 +103,3 @@ require (
modernc.org/strutil v1.1.3 // indirect modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.1.0 // indirect modernc.org/token v1.1.0 // indirect
) )
// replace forge.cadoles.com/arcad/edge => ../edge

118
go.sum
View File

@ -1,8 +1,8 @@
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM=
cdr.dev/slog v1.4.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns= cdr.dev/slog v1.4.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns=
cdr.dev/slog v1.4.2 h1:fIfiqASYQFJBZiASwL825atyzeA96NsqSxx2aL61P8I= cdr.dev/slog v1.4.1 h1:Q8+X63m8/WB4geelMTDO8t4CTwVh1f7+5Cxi7kS/SZg=
cdr.dev/slog v1.4.2/go.mod h1:0EkH+GkFNxizNR+GAXUEdUHanxUH5t9zqPILmPM/Vn8= cdr.dev/slog v1.4.1/go.mod h1:O76C6gZJxa5HK1SXMrjd48V2kJxYZKFRTcFfn/V9OhA=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@ -54,8 +54,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
forge.cadoles.com/arcad/edge v0.0.0-20230426135323-17808d14c978 h1:fekSRSb8gYcVx8C0B9K6B7+KiFHVixIwvPUkxcnRFp4= forge.cadoles.com/arcad/edge v0.0.0-20230303153719-6399196fe5c9 h1:dleaaf/rV2GWtGvrPunRakjrKVDfXoANxAy8/1ctMVs=
forge.cadoles.com/arcad/edge v0.0.0-20230426135323-17808d14c978/go.mod h1:uv3wBa+UbcEUb7IiJCj1T96Xo3cmx1BwNxbBYRZhln8= forge.cadoles.com/arcad/edge v0.0.0-20230303153719-6399196fe5c9/go.mod h1:pl9EMtSLSVr4wbDgQBDjr4aizwtmwasE686dm5arYPw=
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
@ -84,12 +84,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
@ -129,11 +123,13 @@ github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJzKIkbg= github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJzKIkbg=
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY= github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
github.com/alecthomas/chroma v0.9.1/go.mod h1:eMuEnpA18XbG/WhOWtCzJHS7WqEtDAI+HxdwoW0nVSk=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA= github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo= github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo=
github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM= github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
@ -201,8 +197,6 @@ github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/brutella/dnssd v1.2.6 h1:/0P13JkHLRzeLQkWRPEn4hJCr4T3NfknIFw3aNPIC34=
github.com/brutella/dnssd v1.2.6/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
@ -242,11 +236,8 @@ github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOo
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
@ -452,12 +443,12 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20221118162653-d4bf6fde1b86/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs= github.com/dop251/goja v0.0.0-20221118162653-d4bf6fde1b86/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
github.com/dop251/goja v0.0.0-20230304130813-e2f543bf4b4c h1:/utv6nmTctV6OVgfk5+O6lEMEWL+6KJy4h9NZ5fnkQQ= github.com/dop251/goja v0.0.0-20230226152633-7c93113e17ac h1:NGu46Adk2oPN3tinGFItahy4W9l+9uhEf03ZxbwmdVE=
github.com/dop251/goja v0.0.0-20230304130813-e2f543bf4b4c/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja v0.0.0-20230226152633-7c93113e17ac/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
github.com/dop251/goja_nodejs v0.0.0-20230320130059-dcf93ba651dd h1:8FguYHL/davT0sAfVoi84iRI4MCVTVFtlnmZqIoAXDQ= github.com/dop251/goja_nodejs v0.0.0-20230226152057-060fa99b809f h1:mmnNidRg3cMfcgyeNtIBSDZgjf/85lA/2pplccwSxYg=
github.com/dop251/goja_nodejs v0.0.0-20230320130059-dcf93ba651dd/go.mod h1:0tlktQL7yHfYEtjcRGi/eiOkbDR5XF7gyFFvbC5//E0= github.com/dop251/goja_nodejs v0.0.0-20230226152057-060fa99b809f/go.mod h1:0tlktQL7yHfYEtjcRGi/eiOkbDR5XF7gyFFvbC5//E0=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@ -482,9 +473,9 @@ github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
@ -585,8 +576,8 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
@ -696,9 +687,7 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d h1:um9/pc7tKMINFfP1eE7Wv6PRGXlcCSJkVajF7KJw3uQ=
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -769,19 +758,15 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/igm/sockjs-go/v3 v3.0.2 h1:2m0k53w0DBiGozeQUIEPR6snZFmpFpYvVsGnfLPNXbE= github.com/igm/sockjs-go/v3 v3.0.2 h1:2m0k53w0DBiGozeQUIEPR6snZFmpFpYvVsGnfLPNXbE=
github.com/igm/sockjs-go/v3 v3.0.2/go.mod h1:UqchsOjeagIBFHvd+RZpLaVRbCwGilEC08EDHsD1jYE= github.com/igm/sockjs-go/v3 v3.0.2/go.mod h1:UqchsOjeagIBFHvd+RZpLaVRbCwGilEC08EDHsD1jYE=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ=
@ -915,11 +900,10 @@ github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJG
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.0.9 h1:TRX4Q630UXxPVLvP5vGaqVJO7S+0PE6msRZUsFSBoC8= github.com/lestrrat-go/jwx/v2 v2.0.8 h1:jCFT8oc0hEDVjgUgsBy1F9cbjsjAVZSXNi7JaU9HR/Q=
github.com/lestrrat-go/jwx/v2 v2.0.9/go.mod h1:K68euYaR95FnL0hIQB8VvzL70vB7pSifbJUydCTPmgM= github.com/lestrrat-go/jwx/v2 v2.0.8/go.mod h1:zLxnyv9rTlEvOUHbc48FAfIL8iYu2hHvIRaTFGc8mT0=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -929,8 +913,6 @@ github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -948,7 +930,7 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
@ -960,7 +942,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -980,20 +961,15 @@ github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88J
github.com/miekg/dns v0.0.0-20161006100029-fc4e1e2843d8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v0.0.0-20161006100029-fc4e1e2843d8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw= github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@ -1001,8 +977,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
@ -1185,6 +1159,8 @@ github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2u
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 h1:lEOLY2vyGIqKWUI9nzsOJRV3mb3WC9dXYORsLEUcoeY=
github.com/santhosh-tekuri/jsonschema/v5 v5.1.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
@ -1196,7 +1172,6 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
@ -1221,8 +1196,6 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
@ -1310,8 +1283,8 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPS
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b h1:nkvOl8TCj/mErADnwFFynjxBtC+hHsrESw6rw56JGmg= gitlab.com/wpetit/goweb v0.0.0-20230227162855-a1f09bafccb3 h1:ddXRTeqEr7LcHQEtkd6gogZOh9tI1Y6Gappr0a1oa2I=
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY= gitlab.com/wpetit/goweb v0.0.0-20230227162855-a1f09bafccb3/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
@ -1364,7 +1337,6 @@ go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
@ -1398,9 +1370,9 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1449,9 +1421,9 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20161013035702-8b4af36cd21a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20161013035702-8b4af36cd21a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1516,9 +1488,9 @@ golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -1527,10 +1499,8 @@ golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -1621,6 +1591,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1686,17 +1657,14 @@ golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -1704,10 +1672,8 @@ golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1717,13 +1683,10 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1811,11 +1774,10 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -30,6 +30,7 @@ func (a *Agent) Run(ctx context.Context) error {
ticker := time.NewTicker(a.interval) ticker := time.NewTicker(a.interval)
defer ticker.Stop() defer ticker.Stop()
logger.Info(ctx, "generating token")
token, err := agent.GenerateToken(a.privateKey, a.thumbprint) token, err := agent.GenerateToken(a.privateKey, a.thumbprint)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
@ -39,30 +40,28 @@ func (a *Agent) Run(ctx context.Context) error {
ctx = withClient(ctx, client) ctx = withClient(ctx, client)
tick := func() {
logger.Debug(ctx, "registering agent")
if err := a.registerAgent(ctx, client, state); err != nil {
logger.Error(ctx, "could not register agent", logger.E(errors.WithStack(err)))
}
logger.Debug(ctx, "state before reconciliation", logger.F("state", state))
if err := a.Reconcile(ctx, state); err != nil {
logger.Error(ctx, "could not reconcile node with state", logger.E(errors.WithStack(err)))
return
}
logger.Debug(ctx, "state after reconciliation", logger.F("state", state))
}
tick()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
tick()
logger.Debug(ctx, "registering agent")
if err := a.registerAgent(ctx, client, state); err != nil {
logger.Error(ctx, "could not register agent", logger.E(errors.WithStack(err)))
continue
}
logger.Debug(ctx, "state before reconciliation", logger.F("state", state))
if err := a.Reconcile(ctx, state); err != nil {
logger.Error(ctx, "could not reconcile node with state", logger.E(errors.WithStack(err)))
continue
}
logger.Debug(ctx, "state after reconciliation", logger.F("state", state))
case <-ctx.Done(): case <-ctx.Done():
return errors.WithStack(ctx.Err()) return errors.WithStack(ctx.Err())
} }
@ -79,7 +78,7 @@ func (a *Agent) Reconcile(ctx context.Context, state *State) error {
) )
if err := ctrl.Reconcile(ctrlCtx, state); err != nil { if err := ctrl.Reconcile(ctrlCtx, state); err != nil {
logger.Error(ctx, "could not reconcile", logger.E(errors.WithStack(err))) return errors.WithStack(err)
} }
} }

View File

@ -1,290 +0,0 @@
package app
import (
"bytes"
"context"
"net"
"path/filepath"
"text/template"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
"forge.cadoles.com/arcad/edge/pkg/app"
"forge.cadoles.com/arcad/edge/pkg/bus"
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http"
"forge.cadoles.com/arcad/edge/pkg/module"
appModule "forge.cadoles.com/arcad/edge/pkg/module/app"
"forge.cadoles.com/arcad/edge/pkg/module/blob"
"forge.cadoles.com/arcad/edge/pkg/module/cast"
fetchModule "forge.cadoles.com/arcad/edge/pkg/module/fetch"
netModule "forge.cadoles.com/arcad/edge/pkg/module/net"
shareModule "forge.cadoles.com/arcad/edge/pkg/module/share"
shareSqlite "forge.cadoles.com/arcad/edge/pkg/module/share/sqlite"
"forge.cadoles.com/arcad/edge/pkg/storage"
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
"github.com/Masterminds/sprig/v3"
"github.com/go-chi/chi/v5"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
type Dependencies struct {
Bus bus.Bus
DocumentStore storage.DocumentStore
BlobStore storage.BlobStore
KeySet jwk.Set
AppRepository appModule.Repository
AppID app.ID
ShareRepository shareModule.Repository
}
const defaultSQLiteParams = "?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000"
func (c *Controller) getHandlerOptions(ctx context.Context, appKey string, specs *spec.Spec) ([]edgeHTTP.HandlerOptionFunc, error) {
dataDir, err := c.ensureAppDataDir(ctx, appKey)
if err != nil {
return nil, errors.Wrap(err, "could not retrieve app data dir")
}
dbFile := filepath.Join(dataDir, appKey+".sqlite")
db, err := sqlite.Open(dbFile + defaultSQLiteParams)
if err != nil {
return nil, errors.Wrapf(err, "could not open database file '%s'", dbFile)
}
shareDBFile := filepath.Join(dataDir, "shared.sqlite")
shareDB, err := sqlite.Open(shareDBFile + defaultSQLiteParams)
if err != nil {
return nil, errors.Wrapf(err, "could not open database file '%s'", shareDBFile)
}
keySet, err := getAuthKeySet(specs.Config)
if err != nil {
return nil, errors.Wrap(err, "could not retrieve auth key set")
}
mounts := make([]func(r chi.Router), 0)
authMount, err := getAuthMount(specs.Config.Auth, keySet)
if err != nil {
return nil, errors.WithStack(err)
}
if authMount != nil {
mounts = append(mounts, authMount)
}
mounts = append(mounts, appModule.Mount(c.appRepository))
deps := Dependencies{
Bus: memory.NewBus(),
DocumentStore: sqlite.NewDocumentStoreWithDB(db),
BlobStore: sqlite.NewBlobStoreWithDB(db),
KeySet: keySet,
AppRepository: c.appRepository,
AppID: app.ID(appKey),
ShareRepository: shareSqlite.NewRepositoryWithDB(shareDB),
}
modules := c.getAppModules(deps)
options := []edgeHTTP.HandlerOptionFunc{
edgeHTTP.WithBus(deps.Bus),
edgeHTTP.WithServerModules(modules...),
edgeHTTP.WithHTTPMounts(mounts...),
}
return options, nil
}
func getAuthKeySet(config *spec.Config) (jwk.Set, error) {
keySet := jwk.NewSet()
if config == nil {
return nil, nil
}
auth := config.Auth
if auth == nil {
return nil, nil
}
switch {
case auth.Local != nil:
var (
key jwk.Key
err error
)
switch typedKey := auth.Local.Key.(type) {
case string:
key, err = jwk.FromRaw([]byte(typedKey))
if err != nil {
return nil, errors.Wrap(err, "could not parse local auth key")
}
if err := key.Set(jwk.AlgorithmKey, jwa.HS256); err != nil {
return nil, errors.WithStack(err)
}
default:
return nil, errors.Errorf("unexpected key type '%T'", auth.Local.Key)
}
if err := keySet.AddKey(key); err != nil {
return nil, errors.WithStack(err)
}
}
return keySet, nil
}
func createResolveAppURL(specs *spec.Spec) (ResolveAppURLFunc, error) {
rawIfaceMappings := make(map[string]string, 0)
if specs.Config != nil && specs.Config.AppURLResolving != nil && specs.Config.AppURLResolving.IfaceMappings != nil {
rawIfaceMappings = specs.Config.AppURLResolving.IfaceMappings
}
ifaceMappings := make(map[string]*template.Template, len(rawIfaceMappings))
for iface, rawTemplate := range rawIfaceMappings {
tmpl, err := template.New("").Funcs(sprig.TxtFuncMap()).Parse(rawTemplate)
if err != nil {
return nil, errors.Wrapf(err, "could not parse iface '%s' template", iface)
}
ifaceMappings[iface] = tmpl
}
defaultRawTemplate := `http://{{ .DeviceIP }}:{{ .AppPort }}`
if specs.Config != nil && specs.Config.AppURLResolving != nil && specs.Config.AppURLResolving.DefaultURLTemplate != "" {
defaultRawTemplate = specs.Config.AppURLResolving.DefaultURLTemplate
}
defaultTemplate, err := template.New("").Funcs(sprig.TxtFuncMap()).Parse(defaultRawTemplate)
if err != nil {
return nil, errors.WithStack(err)
}
return func(ctx context.Context, manifest *app.Manifest, from string) (string, error) {
var (
urlTemplate *template.Template
deviceIP net.IP
)
fromIP := net.ParseIP(from)
if fromIP != nil {
LOOP:
for ifaceName, ifaceTmpl := range ifaceMappings {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
logger.Error(
ctx, "could not find interface",
logger.E(errors.WithStack(err)), logger.F("iface", ifaceName),
)
continue
}
addresses, err := iface.Addrs()
if err != nil {
logger.Error(
ctx, "could not list interface addresses",
logger.E(errors.WithStack(err)),
logger.F("iface", iface.Name),
)
continue
}
for _, addr := range addresses {
ifaIP, network, err := net.ParseCIDR(addr.String())
if err != nil {
logger.Error(
ctx, "could not parse interface ip",
logger.E(errors.WithStack(err)),
logger.F("iface", iface.Name),
)
continue
}
if !network.Contains(fromIP) {
continue
}
deviceIP = ifaIP
urlTemplate = ifaceTmpl
break LOOP
}
}
}
if urlTemplate == nil {
urlTemplate = defaultTemplate
}
if deviceIP == nil {
deviceIP = net.ParseIP("127.0.0.1")
}
var appEntry *spec.AppEntry
for appID, entry := range specs.Apps {
if manifest.ID != app.ID(appID) {
continue
}
appEntry = &entry
break
}
if appEntry == nil {
return "", errors.Errorf("could not find app '%s' in specs", manifest.ID)
}
_, port, err := net.SplitHostPort(appEntry.Address)
if err != nil {
return "", errors.WithStack(err)
}
data := struct {
Manifest *app.Manifest
Specs *spec.Spec
DeviceIP string
AppPort string
}{
Manifest: manifest,
Specs: specs,
DeviceIP: deviceIP.String(),
AppPort: port,
}
var buf bytes.Buffer
if err := urlTemplate.Execute(&buf, data); err != nil {
return "", errors.WithStack(err)
}
return buf.String(), nil
}, nil
}
func (c *Controller) getAppModules(deps Dependencies) []app.ServerModuleFactory {
return []app.ServerModuleFactory{
module.ContextModuleFactory(),
module.ConsoleModuleFactory(),
cast.CastModuleFactory(),
module.LifecycleModuleFactory(),
netModule.ModuleFactory(deps.Bus),
module.RPCModuleFactory(deps.Bus),
module.StoreModuleFactory(deps.DocumentStore),
blob.ModuleFactory(deps.Bus, deps.BlobStore),
authModuleFactory(deps.KeySet),
appModule.ModuleFactory(deps.AppRepository),
fetchModule.ModuleFactory(deps.Bus),
shareModule.ModuleFactory(deps.AppID, deps.ShareRepository),
}
}

View File

@ -1,73 +0,0 @@
package app
import (
"context"
"testing"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
"forge.cadoles.com/arcad/edge/pkg/app"
"github.com/pkg/errors"
)
func TestCreateResolveAppURL(t *testing.T) {
specs := &spec.Spec{
Apps: map[string]spec.AppEntry{
"app.arcad.test": {
Address: ":8080",
},
"app.arcad.foo": {
Address: ":8081",
},
"app.arcad.bar": {
Address: ":8082",
},
},
Config: &spec.Config{
AppURLResolving: &spec.AppURLResolving{
IfaceMappings: map[string]string{
"lo": "http://{{ .DeviceIP }}:{{ .AppPort }}",
"does-not-exists": "http://{{ .DeviceIP }}:{{ .AppPort }}",
},
DefaultURLTemplate: `http://{{ last ( splitList "." ( toString .Manifest.ID ) ) }}.arcad.local`,
},
},
}
resolveAppURL, err := createResolveAppURL(specs)
if err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
manifest := &app.Manifest{
ID: "app.arcad.test",
}
ctx := context.Background()
url, err := resolveAppURL(ctx, manifest, "127.0.0.2")
if err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
if e, g := "http://127.0.0.1:8080", url; e != g {
t.Errorf("url: expected '%s', got '%s", e, g)
}
url, err = resolveAppURL(ctx, manifest, "")
if err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
if e, g := "http://test.arcad.local", url; e != g {
t.Errorf("url: expected '%s', got '%s", e, g)
}
url, err = resolveAppURL(ctx, manifest, "192.168.0.100")
if err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
if e, g := "http://test.arcad.local", url; e != g {
t.Errorf("url: expected '%s', got '%s", e, g)
}
}

View File

@ -1,137 +0,0 @@
package app
import (
"context"
"sort"
"sync"
"forge.cadoles.com/arcad/edge/pkg/app"
"forge.cadoles.com/arcad/edge/pkg/bundle"
appModule "forge.cadoles.com/arcad/edge/pkg/module/app"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
type ResolveAppURLFunc func(context.Context, *app.Manifest, string) (string, error)
type AppRepository struct {
resolveAppURL ResolveAppURLFunc
bundles []string
mutex sync.RWMutex
}
// Get implements app.Repository
func (r *AppRepository) Get(ctx context.Context, id app.ID) (*app.Manifest, error) {
r.mutex.RLock()
defer r.mutex.RUnlock()
manifest, err := r.findManifest(ctx, id)
if err != nil {
return nil, errors.WithStack(err)
}
return manifest, nil
}
// GetURL implements app.Repository
func (r *AppRepository) GetURL(ctx context.Context, id app.ID, from string) (string, error) {
r.mutex.RLock()
defer r.mutex.RUnlock()
manifest, err := r.findManifest(ctx, id)
if err != nil {
return "", errors.WithStack(err)
}
url, err := r.resolveAppURL(ctx, manifest, from)
if err != nil {
return "", errors.WithStack(err)
}
return url, nil
}
// List implements app.Repository
func (r *AppRepository) List(ctx context.Context) ([]*app.Manifest, error) {
r.mutex.RLock()
defer r.mutex.RUnlock()
manifests := make([]*app.Manifest, 0)
for _, path := range r.bundles {
bundleCtx := logger.With(ctx, logger.F("path", path))
bundle, err := bundle.FromPath(path)
if err != nil {
logger.Error(bundleCtx, "could not load bundle", logger.E(errors.WithStack(err)))
continue
}
manifest, err := app.LoadManifest(bundle)
if err != nil {
logger.Error(bundleCtx, "could not load manifest", logger.E(errors.WithStack(err)))
continue
}
manifests = append(manifests, manifest)
}
sort.Sort(ByID(manifests))
return manifests, nil
}
func (r *AppRepository) Update(resolveAppURL ResolveAppURLFunc, bundles []string) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.resolveAppURL = resolveAppURL
r.bundles = bundles
}
func (r *AppRepository) findManifest(ctx context.Context, id app.ID) (*app.Manifest, error) {
for _, path := range r.bundles {
bundleCtx := logger.With(ctx, logger.F("path", path))
bundle, err := bundle.FromPath(path)
if err != nil {
logger.Error(bundleCtx, "could not load bundle", logger.E(errors.WithStack(err)))
continue
}
manifest, err := app.LoadManifest(bundle)
if err != nil {
logger.Error(bundleCtx, "could not load manifest", logger.E(errors.WithStack(err)))
continue
}
if manifest.ID != id {
continue
}
return manifest, nil
}
return nil, errors.WithStack(appModule.ErrNotFound)
}
func NewAppRepository() *AppRepository {
return &AppRepository{
resolveAppURL: func(ctx context.Context, m *app.Manifest, from string) (string, error) {
return "", errors.New("unavailable")
},
bundles: []string{},
}
}
var _ appModule.Repository = &AppRepository{}
type ByID []*app.Manifest
func (a ByID) Len() int { return len(a) }
func (a ByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByID) Less(i, j int) bool { return a[i].ID > a[j].ID }

View File

@ -1,93 +0,0 @@
package app
import (
"time"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
"forge.cadoles.com/arcad/edge/pkg/app"
"forge.cadoles.com/arcad/edge/pkg/module"
"forge.cadoles.com/arcad/edge/pkg/module/auth"
authModule "forge.cadoles.com/arcad/edge/pkg/module/auth"
authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
"github.com/dop251/goja"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/pkg/errors"
)
const (
RoleVisitor string = "visitor"
RoleUser string = "user"
RoleSuperuser string = "superuser"
RoleAdmin string = "admin"
RoleSuperadmin string = "superadmin"
)
func authModuleFactory(keySet jwk.Set) app.ServerModuleFactory {
return module.Extends(
authModule.ModuleFactory(
authModule.WithJWT(func() (jwk.Set, error) {
return keySet, nil
}),
),
func(o *goja.Object) {
if err := o.Set("ROLE_VISITOR", RoleVisitor); err != nil {
panic(errors.New("could not set 'ROLE_VISITOR' property"))
}
if err := o.Set("ROLE_USER", RoleUser); err != nil {
panic(errors.New("could not set 'ROLE_USER' property"))
}
if err := o.Set("ROLE_SUPERUSER", RoleSuperuser); err != nil {
panic(errors.New("could not set 'ROLE_SUPERUSER' property"))
}
if err := o.Set("ROLE_ADMIN", RoleAdmin); err != nil {
panic(errors.New("could not set 'ROLE_ADMIN' property"))
}
if err := o.Set("ROLE_SUPERADMIN", RoleSuperadmin); err != nil {
panic(errors.New("could not set 'ROLE_SUPERADMIN' property"))
}
},
)
}
func getAuthMount(auth *spec.Auth, keySet jwk.Set) (auth.MountFunc, error) {
switch {
case auth.Local != nil:
var rawKey any = auth.Local.Key
if strKey, ok := rawKey.(string); ok {
rawKey = []byte(strKey)
}
key, err := jwk.FromRaw(rawKey)
if err != nil {
return nil, errors.WithStack(err)
}
cookieDuration := defaultCookieDuration
if auth.Local.CookieDuration != "" {
cookieDuration, err = time.ParseDuration(auth.Local.CookieDuration)
if err != nil {
return nil, errors.WithStack(err)
}
}
return authModule.Mount(
authHTTP.NewLocalHandler(
jwa.HS256, key,
authHTTP.WithRoutePrefix("/auth"),
authHTTP.WithAccounts(auth.Local.Accounts...),
authHTTP.WithCookieOptions(getCookieDomain, cookieDuration),
),
authModule.WithJWT(func() (jwk.Set, error) {
return keySet, nil
}),
), nil
default:
return nil, nil
}
}

View File

@ -8,25 +8,19 @@ import (
"path/filepath" "path/filepath"
"forge.cadoles.com/Cadoles/emissary/internal/agent" "forge.cadoles.com/Cadoles/emissary/internal/agent"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" "forge.cadoles.com/Cadoles/emissary/internal/spec/app"
"forge.cadoles.com/arcad/edge/pkg/app"
"forge.cadoles.com/arcad/edge/pkg/bundle" "forge.cadoles.com/arcad/edge/pkg/bundle"
"github.com/mitchellh/hashstructure/v2" "forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
"github.com/pkg/errors" "github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/logger"
) )
type serverEntry struct {
AppDefHash uint64
Server *Server
}
type Controller struct { type Controller struct {
client *http.Client currentSpecRevision int
downloadDir string client *http.Client
dataDir string downloadDir string
servers map[string]*serverEntry dataDir string
appRepository *AppRepository servers map[string]*Server
} }
// Name implements node.Controller. // Name implements node.Controller.
@ -36,13 +30,13 @@ func (c *Controller) Name() string {
// Reconcile implements node.Controller. // Reconcile implements node.Controller.
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error { func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
appSpec := spec.NewSpec() appSpec := app.NewSpec()
if err := state.GetSpec(spec.Name, appSpec); err != nil { if err := state.GetSpec(app.NameApp, appSpec); err != nil {
if errors.Is(err, agent.ErrSpecNotFound) { if errors.Is(err, agent.ErrSpecNotFound) {
logger.Info(ctx, "could not find app spec") logger.Info(ctx, "could not find app spec, stopping all remaining apps")
c.stopAllApps(ctx, appSpec) c.stopAllApps(ctx)
return nil return nil
} }
@ -52,20 +46,22 @@ func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
logger.Info(ctx, "retrieved spec", logger.F("spec", appSpec.SpecName()), logger.F("revision", appSpec.SpecRevision())) logger.Info(ctx, "retrieved spec", logger.F("spec", appSpec.SpecName()), logger.F("revision", appSpec.SpecRevision()))
if c.currentSpecRevision == appSpec.SpecRevision() {
logger.Info(ctx, "spec revision did not change, doing nothing")
return nil
}
c.updateApps(ctx, appSpec) c.updateApps(ctx, appSpec)
return nil return nil
} }
func (c *Controller) stopAllApps(ctx context.Context, spec *spec.Spec) { func (c *Controller) stopAllApps(ctx context.Context) {
if len(c.servers) > 0 { for appID, server := range c.servers {
logger.Info(ctx, "stopping all apps")
}
for appID, entry := range c.servers {
logger.Info(ctx, "stopping app", logger.F("appID", appID)) logger.Info(ctx, "stopping app", logger.F("appID", appID))
if err := entry.Server.Stop(); err != nil { if err := server.Stop(); err != nil {
logger.Error( logger.Error(
ctx, "error while stopping app", ctx, "error while stopping app",
logger.F("appID", appID), logger.F("appID", appID),
@ -77,169 +73,108 @@ func (c *Controller) stopAllApps(ctx context.Context, spec *spec.Spec) {
} }
} }
func (c *Controller) updateApps(ctx context.Context, specs *spec.Spec) { func (c *Controller) updateApps(ctx context.Context, spec *app.Spec) {
hadError := false
// Stop and remove obsolete apps // Stop and remove obsolete apps
for appKey, server := range c.servers { for appID, server := range c.servers {
if _, exists := specs.Apps[appKey]; exists { if _, exists := spec.Apps[appID]; exists {
continue continue
} }
logger.Info(ctx, "stopping app", logger.F("appKey", appKey)) logger.Info(ctx, "stopping app", logger.F("appID", appID))
if err := server.Server.Stop(); err != nil { if err := server.Stop(); err != nil {
logger.Error( logger.Error(
ctx, "error while stopping app", ctx, "error while stopping app",
logger.F("appKey", appKey), logger.F("gatewayID", appID),
logger.E(errors.WithStack(err)), logger.E(errors.WithStack(err)),
) )
delete(c.servers, appKey) delete(c.servers, appID)
hadError = true
} }
} }
if err := c.updateAppRepository(ctx, specs); err != nil { // (Re)start apps
logger.Error( for appID, appSpec := range spec.Apps {
ctx, "could not update app repository", appCtx := logger.With(ctx, logger.F("appID", appID))
logger.E(errors.WithStack(err)),
)
return if err := c.updateApp(ctx, appID, appSpec); err != nil {
}
// (Re)start apps if necessary
for appKey := range specs.Apps {
appCtx := logger.With(ctx, logger.F("appKey", appKey))
if err := c.updateApp(ctx, specs, appKey); err != nil {
logger.Error(appCtx, "could not update app", logger.E(errors.WithStack(err))) logger.Error(appCtx, "could not update app", logger.E(errors.WithStack(err)))
hadError = true
continue continue
} }
} }
if !hadError {
c.currentSpecRevision = spec.SpecRevision()
logger.Info(ctx, "updating current spec revision", logger.F("revision", c.currentSpecRevision))
}
} }
func (c *Controller) updateAppRepository(ctx context.Context, specs *spec.Spec) error { func (c *Controller) updateApp(ctx context.Context, appID string, appSpec app.AppEntry) error {
bundles := make([]string, 0, len(specs.Apps)) bundle, sha256sum, err := c.ensureAppBundle(ctx, appID, appSpec)
for appKey, app := range specs.Apps {
path := c.getAppBundlePath(appKey, app.Format)
bundles = append(bundles, path)
}
resolveAppURL, err := createResolveAppURL(specs)
if err != nil {
return errors.WithStack(err)
}
c.appRepository.Update(resolveAppURL, bundles)
return nil
}
func (c *Controller) updateApp(ctx context.Context, specs *spec.Spec, appKey string) (err error) {
appEntry := specs.Apps[appKey]
appDef := struct {
App spec.AppEntry
Config *spec.Config
}{
App: appEntry,
Config: specs.Config,
}
newAppDefHash, err := hashstructure.Hash(appDef, hashstructure.FormatV2, nil)
if err != nil {
return errors.WithStack(err)
}
bundle, sha256sum, err := c.ensureAppBundle(ctx, appKey, appEntry)
if err != nil { if err != nil {
return errors.Wrap(err, "could not download app bundle") return errors.Wrap(err, "could not download app bundle")
} }
server, exists := c.servers[appKey] dataDir, err := c.ensureAppDataDir(ctx, appID)
if err != nil {
return errors.Wrap(err, "could not retrieve app data dir")
}
server, exists := c.servers[appID]
if !exists { if !exists {
logger.Info(ctx, "app currently not running") logger.Info(ctx, "app currently not running")
} else if sha256sum != appEntry.SHA256Sum { } else if sha256sum != appSpec.SHA256Sum {
logger.Info( logger.Info(
ctx, "bundle hash mismatch, stopping app", ctx, "bundle hash mismatch, stopping app",
logger.F("currentHash", sha256sum), logger.F("currentHash", sha256sum),
logger.F("specHash", appEntry.SHA256Sum), logger.F("specHash", appSpec.SHA256Sum),
) )
if err := server.Server.Stop(); err != nil { if err := server.Stop(); err != nil {
return errors.Wrap(err, "could not stop app") return errors.Wrap(err, "could not stop app")
} }
server = nil server = nil
} }
newServerEntry := func() (*serverEntry, error) {
options, err := c.getHandlerOptions(ctx, appKey, specs)
if err != nil {
return nil, errors.Wrap(err, "could not create handler options")
}
server = &serverEntry{
Server: NewServer(bundle, specs.Config, options...),
AppDefHash: 0,
}
return server, nil
}
if server == nil { if server == nil {
serverEntry, err := newServerEntry() dbFile := filepath.Join(dataDir, appID+".sqlite")
db, err := sqlite.Open(dbFile)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.Wrapf(err, "could not opend database file '%s'", dbFile)
} }
c.servers[appKey] = serverEntry server = NewServer(bundle, db)
c.servers[appID] = server
} }
defChanged := newAppDefHash != server.AppDefHash logger.Info(
if server.Server.Running() && !defChanged { ctx, "starting app",
return nil logger.F("address", appSpec.Address),
} )
if defChanged && server.AppDefHash != 0 { if err := server.Start(ctx, appSpec.Address); err != nil {
logger.Info( delete(c.servers, appID)
ctx, "restarting app",
logger.F("address", appEntry.Address),
)
if err := server.Server.Stop(); err != nil {
return errors.WithStack(err)
}
serverEntry, err := newServerEntry()
if err != nil {
return errors.WithStack(err)
}
c.servers[appKey] = serverEntry
} else {
logger.Info(
ctx, "starting app",
logger.F("address", appEntry.Address),
)
}
if err := server.Server.Start(ctx, appEntry.Address); err != nil {
delete(c.servers, appKey)
return errors.Wrap(err, "could not start app") return errors.Wrap(err, "could not start app")
} }
server.AppDefHash = newAppDefHash
return nil return nil
} }
func (c *Controller) ensureAppBundle(ctx context.Context, appID string, spec spec.AppEntry) (bundle.Bundle, string, error) { func (c *Controller) ensureAppBundle(ctx context.Context, appID string, spec app.AppEntry) (bundle.Bundle, string, error) {
if err := os.MkdirAll(c.downloadDir, os.ModePerm); err != nil { if err := os.MkdirAll(c.downloadDir, os.ModePerm); err != nil {
return nil, "", errors.WithStack(err) return nil, "", errors.WithStack(err)
} }
bundlePath := c.getAppBundlePath(appID, spec.Format) bundlePath := filepath.Join(c.downloadDir, appID+"."+spec.Format)
_, err := os.Stat(bundlePath) _, err := os.Stat(bundlePath)
if err != nil && !errors.Is(err, os.ErrNotExist) { if err != nil && !errors.Is(err, os.ErrNotExist) {
@ -277,21 +212,7 @@ func (c *Controller) ensureAppBundle(ctx context.Context, appID string, spec spe
return nil, "", errors.WithStack(err) return nil, "", errors.WithStack(err)
} }
manifest, err := app.LoadManifest(bdle) return bdle, "", nil
if err != nil {
return nil, "", errors.WithStack(err)
}
valid, err := validateManifest(manifest)
if err != nil {
return nil, "", errors.WithStack(err)
}
if !valid {
return nil, "", errors.New("bundle's manifest is invalid")
}
return bdle, spec.SHA256Sum, nil
} }
func (c *Controller) downloadFile(url string, sha256sum string, dest string) error { func (c *Controller) downloadFile(url string, sha256sum string, dest string) error {
@ -347,10 +268,6 @@ func (c *Controller) ensureAppDataDir(ctx context.Context, appID string) (string
return dataDir, nil return dataDir, nil
} }
func (c *Controller) getAppBundlePath(appKey string, format string) string {
return filepath.Join(c.downloadDir, appKey+"."+format)
}
func NewController(funcs ...OptionFunc) *Controller { func NewController(funcs ...OptionFunc) *Controller {
opts := defaultOptions() opts := defaultOptions()
for _, fn := range funcs { for _, fn := range funcs {
@ -358,11 +275,11 @@ func NewController(funcs ...OptionFunc) *Controller {
} }
return &Controller{ return &Controller{
client: opts.Client, client: opts.Client,
downloadDir: opts.DownloadDir, downloadDir: opts.DownloadDir,
dataDir: opts.DataDir, dataDir: opts.DataDir,
servers: make(map[string]*serverEntry), currentSpecRevision: -1,
appRepository: NewAppRepository(), servers: make(map[string]*Server),
} }
} }

View File

@ -1,19 +0,0 @@
package app
import (
"forge.cadoles.com/arcad/edge/pkg/app"
"forge.cadoles.com/arcad/edge/pkg/app/metadata"
"github.com/pkg/errors"
)
func validateManifest(manifest *app.Manifest) (bool, error) {
valid, err := manifest.Validate(
metadata.WithMinimumRoleValidator(RoleVisitor, RoleUser, RoleSuperuser, RoleAdmin, RoleSuperadmin),
metadata.WithNamedPathsValidator(metadata.NamedPathAdmin, metadata.NamedPathIcon),
)
if err != nil {
return false, errors.WithStack(err)
}
return valid, nil
}

View File

@ -2,66 +2,54 @@ package app
import ( import (
"context" "context"
"net" "database/sql"
"net/http" "net/http"
"strings"
"sync"
"time"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" "forge.cadoles.com/arcad/edge/pkg/app"
appSpec "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" "forge.cadoles.com/arcad/edge/pkg/bus"
"forge.cadoles.com/Cadoles/emissary/internal/proxy/wildcard" "forge.cadoles.com/arcad/edge/pkg/bus/memory"
edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http" edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http"
"gitlab.com/wpetit/goweb/logger" "forge.cadoles.com/arcad/edge/pkg/module"
"forge.cadoles.com/arcad/edge/pkg/module/cast"
"forge.cadoles.com/arcad/edge/pkg/module/net"
"forge.cadoles.com/arcad/edge/pkg/storage"
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
"forge.cadoles.com/arcad/edge/pkg/bundle" "forge.cadoles.com/arcad/edge/pkg/bundle"
"github.com/go-chi/chi/middleware" "github.com/go-chi/chi/middleware"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/pkg/errors" "github.com/pkg/errors"
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/passwd"
) )
const defaultCookieDuration time.Duration = 24 * time.Hour
type Server struct { type Server struct {
bundle bundle.Bundle bundle bundle.Bundle
handlerOptions []edgeHTTP.HandlerOptionFunc db *sql.DB
server *http.Server server *http.Server
serverMutex sync.RWMutex
config *appSpec.Config
} }
func (s *Server) Start(ctx context.Context, addr string) (err error) { func (s *Server) Start(ctx context.Context, addr string) error {
if s.Running() { if s.server != nil {
if err := s.Stop(); err != nil { if err := s.Stop(); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
} }
s.serverMutex.Lock()
defer s.serverMutex.Unlock()
router := chi.NewRouter() router := chi.NewRouter()
router.Use(middleware.RealIP)
router.Use(middleware.Logger) router.Use(middleware.Logger)
router.Use(middleware.Compress(5))
handler := edgeHTTP.NewHandler(s.handlerOptions...) bus := memory.NewBus()
ds := sqlite.NewDocumentStoreWithDB(s.db)
bs := sqlite.NewBlobStoreWithDB(s.db)
handler := edgeHTTP.NewHandler(
edgeHTTP.WithBus(bus),
edgeHTTP.WithServerModules(s.getAppModules(bus, ds, bs)...),
)
if err := handler.Load(s.bundle); err != nil { if err := handler.Load(s.bundle); err != nil {
return errors.Wrap(err, "could not load app bundle") return errors.Wrap(err, "could not load app bundle")
} }
if s.config != nil {
if s.config.UnexpectedHostRedirect != nil {
router.Use(unexpectedHostRedirect(
s.config.UnexpectedHostRedirect.HostTarget,
s.config.UnexpectedHostRedirect.AcceptedHostPatterns...,
))
}
}
router.Handle("/*", handler) router.Handle("/*", handler)
server := &http.Server{ server := &http.Server{
@ -70,18 +58,6 @@ func (s *Server) Start(ctx context.Context, addr string) (err error) {
} }
go func() { go func() {
defer func() {
if recovered := recover(); recovered != nil {
if err, ok := recovered.(error); ok {
logger.Error(ctx, err.Error(), logger.E(errors.WithStack(err)))
return
}
panic(recovered)
}
}()
defer func() { defer func() {
if err := s.Stop(); err != nil { if err := s.Stop(); err != nil {
panic(errors.WithStack(err)) panic(errors.WithStack(err))
@ -98,92 +74,52 @@ func (s *Server) Start(ctx context.Context, addr string) (err error) {
return nil return nil
} }
func (s *Server) Running() bool {
s.serverMutex.RLock()
defer s.serverMutex.RUnlock()
return s.server != nil
}
func (s *Server) Stop() error { func (s *Server) Stop() error {
if !s.Running() {
return nil
}
s.serverMutex.Lock()
defer s.serverMutex.Unlock()
if s.server == nil { if s.server == nil {
return nil return nil
} }
if err := s.server.Close(); err != nil { defer func() {
s.server = nil s.server = nil
}()
return errors.WithStack(err) if err := s.server.Close(); err != nil {
panic(errors.WithStack(err))
} }
s.server = nil
return nil return nil
} }
func NewServer(bundle bundle.Bundle, config *spec.Config, handlerOptions ...edgeHTTP.HandlerOptionFunc) *Server { func (s *Server) getAppModules(bus bus.Bus, ds storage.DocumentStore, bs storage.BlobStore) []app.ServerModuleFactory {
return []app.ServerModuleFactory{
module.ContextModuleFactory(),
module.ConsoleModuleFactory(),
cast.CastModuleFactory(),
module.LifecycleModuleFactory(),
net.ModuleFactory(bus),
module.RPCModuleFactory(bus),
module.StoreModuleFactory(ds),
module.BlobModuleFactory(bus, bs),
// module.Extends(
// auth.ModuleFactory(
// auth.WithJWT(dummyKeyFunc),
// ),
// func(o *goja.Object) {
// if err := o.Set("CLAIM_ROLE", "role"); err != nil {
// panic(errors.New("could not set 'CLAIM_ROLE' property"))
// }
// if err := o.Set("CLAIM_PREFERRED_USERNAME", "preferred_username"); err != nil {
// panic(errors.New("could not set 'CLAIM_PREFERRED_USERNAME' property"))
// }
// },
// ),
}
}
func NewServer(bundle bundle.Bundle, db *sql.DB) *Server {
return &Server{ return &Server{
bundle: bundle, bundle: bundle,
config: config, db: db,
handlerOptions: handlerOptions,
}
}
func getCookieDomain(r *http.Request) (string, error) {
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
host = r.Host
}
// If host is an IP address
if ip := net.ParseIP(host); ip != nil {
return "", nil
}
// If host is an domain, return top level domain
domainParts := strings.Split(host, ".")
if len(domainParts) >= 2 {
topLevelDomain := strings.Join(domainParts[len(domainParts)-2:], ".")
return topLevelDomain, nil
}
// By default, return host
return host, nil
}
func unexpectedHostRedirect(hostTarget string, acceptedHostPatterns ...string) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
host, port, err := net.SplitHostPort(r.Host)
if err != nil {
host = r.Host
}
matched := wildcard.MatchAny(host, acceptedHostPatterns...)
if !matched {
url := r.URL
url.Host = hostTarget
if port != "" {
url.Host += ":" + port
}
http.Redirect(w, r, url.String(), http.StatusTemporaryRedirect)
return
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
} }
} }

View File

@ -1,135 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://app.edge.emissary.cadoles.com/spec.json",
"title": "AppSpec",
"description": "Emissary 'App' specification",
"type": "object",
"properties": {
"apps": {
"type": "object",
"patternProperties": {
".*": {
"type": "object",
"properties": {
"url": {
"type": "string"
},
"sha256sum": {
"type": "string"
},
"address": {
"type": "string"
},
"format": {
"type": "string",
"enum": [
"zip",
"tar.gz"
]
}
},
"required": [
"url",
"sha256sum",
"address",
"format"
],
"additionalProperties": false
}
}
},
"config": {
"type": "object",
"properties": {
"appUrlResolving": {
"type": "object",
"properties": {
"ifaceMappings": {
"type": "object",
"patternProperties": {
".*": {
"type": "string"
}
}
},
"defaultUrlTemplate": {
"type": "string"
}
},
"required": ["defaultUrlTemplate"],
"additionalProperties": false
},
"unexpectedHostRedirect": {
"type": "object",
"properties": {
"acceptedHostPatterns": {
"type": "array",
"items": {
"type": "string"
}
},
"hostTarget": {
"type": "string"
}
},
"required": ["acceptedHostPatterns", "hostTarget"],
"additionalProperties": false
},
"auth": {
"type": "object",
"properties": {
"local": {
"type": "object",
"properties": {
"key": {
"type": ["object", "string"]
},
"accounts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"algo": {
"type": "string"
},
"claims": {
"type": "object"
}
},
"required": [
"username",
"password",
"algo"
]
}
},
"cookieDomain": {
"type": "string"
},
"cookieDuration": {
"type": "string"
}
},
"required": [
"key"
],
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"required": [
"apps"
],
"additionalProperties": false
}

View File

@ -1,71 +0,0 @@
package spec
import (
"forge.cadoles.com/Cadoles/emissary/internal/spec"
edgeAuth "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
)
const Name spec.Name = "app.emissary.cadoles.com"
type Spec struct {
Revision int `json:"revision"`
Apps map[string]AppEntry `json:"apps"`
Config *Config `json:"config"`
}
type AppEntry struct {
URL string `json:"url"`
SHA256Sum string `json:"sha256sum"`
Address string `json:"address"`
Format string `json:"format"`
}
type Auth struct {
Local *LocalAuth `json:"local,omitempty"`
}
type LocalAuth struct {
Key any `json:"key"`
Accounts []edgeAuth.LocalAccount `json:"accounts"`
CookieDomain string `json:"cookieDomain"`
CookieDuration string `json:"cookieDuration"`
}
type Config struct {
Auth *Auth `json:"auth"`
UnexpectedHostRedirect *UnexpectedHostRedirect `json:"unexpectedHostRedirect"`
AppURLResolving *AppURLResolving `json:"appUrlResolving"`
}
type UnexpectedHostRedirect struct {
AcceptedHostPatterns []string `json:"acceptedHostPatterns"`
HostTarget string `json:"hostTarget"`
}
type AppURLResolving struct {
IfaceMappings map[string]string `json:"ifaceMappings"`
DefaultURLTemplate string `json:"defaultUrlTemplate"`
}
func (s *Spec) SpecName() spec.Name {
return Name
}
func (s *Spec) SpecRevision() int {
return s.Revision
}
func (s *Spec) SpecData() map[string]any {
return map[string]any{
"apps": s.Apps,
"config": s.Config,
}
}
func NewSpec() *Spec {
return &Spec{
Revision: -1,
}
}
var _ spec.Spec = &Spec{}

View File

@ -1,54 +0,0 @@
{
"name": "app.emissary.cadoles.com",
"data": {
"apps": {
"edge.sdk.client.test": {
"url": "http://example.com/edge.sdk.client.test_0.0.0.zip",
"sha256sum": "58019192dacdae17755707719707db007e26dac856102280583fbd18427dd352",
"address": ":8081",
"format": "zip"
}
},
"config": {
"auth": {
"local": {
"key": {
"d": "YOre0WZefGfUGFvDg42oL5Oad5Zsb1N_hqPyLVM5ajpTZzcHpB3wT6In9tFO_VshB6lxVtPA9ckPkpMTFY7ygt1Yomc1HkoOKRtmIaqdr4VgNQifU-4yiLiJkSbdYSeMV-KkkN8mGR1keJpJeS34W1X0W6CkU2nw7F5VueBCJfWJA0funRfuWdI68MTUgT9kRZFp-SfvptvRL6jVYHV_5hqxzHCvgEdBSF6QKwx4M6P6QBMt7ft6uMLmFx9abKFw2V51hX3PkxiSepVB3w5CYg4HtS3AHX6bILL4m0R2pdTIkap7i3tkH_xAOuKWt8D6JhadI8X1rEAwXmCS5KrRgQ",
"dp": "U0HfvBC6hk-SCpuotGIv3vbHCVt1aF3SHK0y32EYCOe8e_9G6YCEILfcvEJ5fiOCc2kvx6TasHQu4qj1uWRKenZlK1sJ6KDybGCkZL1D3jYnbeLZYBuWBL__YbZiST3ewbxzj_EDMWiZ8sUltahza_1weSgg8auSzTHS2LJBHIE",
"dq": "hVom4ScDxgqhCsQNVpZlN7M3v0tgWjl_gTOHjOyzKCHQJeC0QmJJaMKkQZPWJ8jjLqy7VwVpqC2nZU7QDuX1Cq5eJDQcXi9XtaAfIBico9WcYDre6mDyhL588YHpekyRke8HnZ810iesr0G3gU1h0QvZVVuW-pXTJOXhZTt6nFc",
"e": "AQAB",
"kty": "RSA",
"n": "vPnpkE3-HfNgJSru_K40LstkjiG2Bq_Tt-m0d_yUBBSbirFxF3qH4EXi7WrtZdeDahg2iV2BvpbVVj9GlmGo9OLol6jc7AP2yvZrkbABiiJhCbuPdkYbNpx6B7Itl8RT_bUSYAMZhmux5lpsn4weQ01fzjICi1rA-bIJpOfotdOjP4_lol-LxGZOGJQv9kndP8bgmssJb3Y_2s4gPtkmXySLrhpr5So-_6dVksyuBD9aLcnsMLDbywusjEMCdhqzQbvOjryomnmEXwyz_Ewb5HFK2PfgFtoHkdjqDz-mrEs3tw5g4TdYhCftzJxgbyNAEq4aEiOQrAncYyrXlotP_w",
"p": "8TNMF0WUe7CEeNVUTsuEcBAAXRguNtpvVifIjlwzFRGOYVGIpKuHsqQPKlZL07I9gPr9LifQnyQus3oEmTOrVs6LB9sfbukbg43ZRKoGVM40JYF5Xjs7R3mEZhgU0WaYOVe3iLtBGMfXNWFwlbfQP-zEb-dPCBX1jWT3LdgNBcE",
"q": "yJJLNc9w6O4y2icME8k99FugV9E7ObwUxF3v5JN3y1cmAT0h2njyE3iAGqaDZwcY1_jGCisjwoqX6i5E8xqhxX3Gcy3J7SmUAf8fhY8wU3zv9DK7skg2IdvanDb8Y1OM6GchbYZAOVPEg2IvVio8zI-Ih3DDwDk8Df0ufzoHRb8",
"qi": "zOE-4R3cjPesm3MX-4PdwmsaF9QZLUVRUvvHJ08pKs6kAXP18hzjctAoOjhQDxlTYqNYNePfKzKwost3OJoPgRIc9w9qwUCK1gNOS4Z_xozCIaXgMddNFhkoAfZ4JaKjNCiinzjGfqG99Lf-yzmmREuuhRv7SdS3ST4VQjiJQew"
},
"accounts": [
{
"username": "foo",
"algo": "plain",
"password": "bar",
"claims": {
"arcad_role": "user",
"arcad_tenant": "dev.cli",
"preferred_username": "Foo",
"sub": "foo"
}
}
]
}
},
"unexpectedHostRedirect": {
"acceptedHostPatterns": ["arcad.local", "*.arcad.local", "arcad-*.local", "*.*.*.*"],
"hostTarget": "arcad.local"
},
"appUrlResolving": {
"ifaceMappings": {
"eth0": "http://{{ .DeviceIP }}:{{ .AppHost }}"
},
"defaultUrlTemplate": "http://{{ last ( splitList \".\" ( toString .Manifest.ID ) ) }}.arcad.local"
}
}
},
"revision": 0
}

View File

@ -1,65 +0,0 @@
package spec
import (
"context"
"encoding/json"
"io/ioutil"
"testing"
"forge.cadoles.com/Cadoles/emissary/internal/spec"
"github.com/pkg/errors"
)
type validatorTestCase struct {
Name string
Source string
ShouldFail bool
}
var validatorTestCases = []validatorTestCase{
{
Name: "SpecOK",
Source: "testdata/spec-ok.json",
ShouldFail: false,
},
}
func TestValidator(t *testing.T) {
t.Parallel()
validator := spec.NewValidator()
if err := validator.Register(Name, schema); err != nil {
t.Fatalf("+%v", errors.WithStack(err))
}
for _, tc := range validatorTestCases {
func(tc validatorTestCase) {
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
rawSpec, err := ioutil.ReadFile(tc.Source)
if err != nil {
t.Fatalf("+%v", errors.WithStack(err))
}
var spec spec.RawSpec
if err := json.Unmarshal(rawSpec, &spec); err != nil {
t.Fatalf("+%v", errors.WithStack(err))
}
ctx := context.Background()
err = validator.Validate(ctx, &spec)
if !tc.ShouldFail && err != nil {
t.Errorf("+%v", errors.WithStack(err))
}
if tc.ShouldFail && err == nil {
t.Error("validation should have failed")
}
})
}(tc)
}
}

View File

@ -0,0 +1,124 @@
package gateway
import (
"context"
"forge.cadoles.com/Cadoles/emissary/internal/agent"
"forge.cadoles.com/Cadoles/emissary/internal/spec/gateway"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
type Controller struct {
proxies map[gateway.ID]*ReverseProxy
currentSpecRevision int
}
// Name implements node.Controller.
func (c *Controller) Name() string {
return "gateway-controller"
}
// Reconcile implements node.Controller.
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
gatewaySpec := gateway.NewSpec()
if err := state.GetSpec(gateway.NameGateway, gatewaySpec); err != nil {
if errors.Is(err, agent.ErrSpecNotFound) {
logger.Info(ctx, "could not find gateway spec, stopping all remaining proxies")
c.stopAllProxies(ctx)
return nil
}
return errors.WithStack(err)
}
logger.Info(ctx, "retrieved spec", logger.F("spec", gatewaySpec.SpecName()), logger.F("revision", gatewaySpec.SpecRevision()))
if c.currentSpecRevision == gatewaySpec.SpecRevision() {
logger.Info(ctx, "spec revision did not change, doing nothing")
return nil
}
c.updateProxies(ctx, gatewaySpec)
c.currentSpecRevision = gatewaySpec.SpecRevision()
logger.Info(ctx, "updating current spec revision", logger.F("revision", c.currentSpecRevision))
return nil
}
func (c *Controller) stopAllProxies(ctx context.Context) {
for gatewayID, proxy := range c.proxies {
logger.Info(ctx, "stopping proxy", logger.F("gatewayID", gatewayID))
if err := proxy.Stop(); err != nil {
logger.Error(
ctx, "error while stopping proxy",
logger.F("gatewayID", gatewayID),
logger.E(errors.WithStack(err)),
)
delete(c.proxies, gatewayID)
}
}
}
func (c *Controller) updateProxies(ctx context.Context, spec *gateway.Spec) {
// Stop and remove obsolete gateways
for gatewayID, proxy := range c.proxies {
if _, exists := spec.Gateways[gatewayID]; exists {
continue
}
logger.Info(ctx, "stopping proxy", logger.F("gatewayID", gatewayID))
if err := proxy.Stop(); err != nil {
logger.Error(
ctx, "error while stopping proxy",
logger.F("gatewayID", gatewayID),
logger.E(errors.WithStack(err)),
)
delete(c.proxies, gatewayID)
}
}
// (Re)start gateways
for gatewayID, gatewaySpec := range spec.Gateways {
proxy, exists := c.proxies[gatewayID]
if !exists {
proxy = NewReverseProxy()
c.proxies[gatewayID] = proxy
}
logger.Info(
ctx, "starting proxy",
logger.F("gatewayID", gatewayID),
logger.F("addr", gatewaySpec.Address),
logger.F("target", gatewaySpec.Target),
)
if err := proxy.Start(ctx, gatewaySpec.Address, gatewaySpec.Target); err != nil {
logger.Error(
ctx, "error while starting proxy",
logger.F("gatewayID", gatewayID),
logger.E(errors.WithStack(err)),
)
delete(c.proxies, gatewayID)
}
}
}
func NewController() *Controller {
return &Controller{
proxies: make(map[gateway.ID]*ReverseProxy),
currentSpecRevision: -1,
}
}
var _ agent.Controller = &Controller{}

View File

@ -1,22 +1,28 @@
package proxy package gateway
import ( import (
"context" "context"
"net/http" "net/http"
"sync" "net/http/httputil"
"net/url"
"forge.cadoles.com/Cadoles/emissary/internal/proxy"
"github.com/pkg/errors" "github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/logger"
) )
type ReverseProxy struct { type ReverseProxy struct {
addr string addr string
target string
server *http.Server server *http.Server
mutex sync.RWMutex
} }
func (p *ReverseProxy) Start(ctx context.Context, addr string, funcs ...proxy.OptionFunc) error { func (p *ReverseProxy) Start(ctx context.Context, addr, target string) error {
alreadyRunning := p.server != nil && target == p.target && addr == p.target
if alreadyRunning {
return nil
}
if p.server != nil { if p.server != nil {
if err := p.Stop(); err != nil { if err := p.Stop(); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
@ -27,40 +33,33 @@ func (p *ReverseProxy) Start(ctx context.Context, addr string, funcs ...proxy.Op
Addr: addr, Addr: addr,
} }
proxy := proxy.New(funcs...) url, err := url.Parse(target)
if err != nil {
return errors.WithStack(err)
}
proxy := httputil.NewSingleHostReverseProxy(url)
server.Handler = proxy server.Handler = proxy
p.mutex.Lock()
p.server = server p.server = server
p.addr = addr p.addr = addr
p.mutex.Unlock() p.target = target
go func() { go func() {
defer func() {
if err := p.Stop(); err != nil {
logger.Error(ctx, "error while stopping gateway", logger.E(errors.WithStack(err)))
}
}()
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Error(ctx, "error while listening", logger.E(errors.WithStack(err))) logger.Error(ctx, "error while listening", logger.E(errors.WithStack(err)))
} }
if err := p.Stop(); err != nil {
logger.Error(ctx, "error while stopping gateway", logger.E(errors.WithStack(err)))
}
}() }()
return nil return nil
} }
func (p *ReverseProxy) Running() bool {
p.mutex.RLock()
defer p.mutex.RUnlock()
return p.server != nil
}
func (p *ReverseProxy) Stop() error { func (p *ReverseProxy) Stop() error {
p.mutex.Lock()
defer p.mutex.Unlock()
if p.server == nil { if p.server == nil {
return nil return nil
} }

View File

@ -1,181 +0,0 @@
package mdns
import (
"context"
"net"
"sync"
"forge.cadoles.com/Cadoles/emissary/internal/agent"
mdns "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/mdns/spec"
"github.com/brutella/dnssd"
"github.com/mitchellh/hashstructure/v2"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
const (
DefaultDomain = "local"
)
type Controller struct {
serviceDefHash uint64
cancel context.CancelFunc
responder dnssd.Responder
mutex sync.RWMutex
}
// Name implements node.Controller.
func (c *Controller) Name() string {
return "mdns-controller"
}
// Reconcile implements node.Controller.
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
mdnsSpec := mdns.NewSpec()
if err := state.GetSpec(mdns.Name, mdnsSpec); err != nil {
if errors.Is(err, agent.ErrSpecNotFound) {
logger.Info(ctx, "could not find mdns spec")
c.stopResponder(ctx)
return nil
}
return errors.WithStack(err)
}
logger.Info(ctx, "retrieved spec", logger.F("spec", mdnsSpec.SpecName()), logger.F("revision", mdnsSpec.SpecRevision()))
if err := c.updateResponder(ctx, mdnsSpec); err != nil {
return errors.Wrap(err, "could not update responder")
}
return nil
}
func (c *Controller) stopResponder(ctx context.Context) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.responder == nil {
return
}
c.cancel()
c.responder = nil
c.cancel = nil
}
func (c *Controller) updateResponder(ctx context.Context, spec *mdns.Spec) error {
serviceDef := struct {
Services map[string]mdns.Service
}{
Services: spec.Services,
}
newServerDefHash, err := hashstructure.Hash(serviceDef, hashstructure.FormatV2, nil)
if err != nil {
return errors.WithStack(err)
}
c.mutex.RLock()
if newServerDefHash == c.serviceDefHash && c.responder != nil {
c.mutex.RUnlock()
return nil
}
c.mutex.RUnlock()
c.stopResponder(ctx)
defaultIfaces, err := c.getDefaultIfaces()
if err != nil {
return errors.WithStack(err)
}
services := make([]dnssd.Service, 0, len(spec.Services))
for name, service := range spec.Services {
domain := service.Domain
if domain == "" {
domain = DefaultDomain
}
ifaces := service.Ifaces
if len(ifaces) == 0 {
ifaces = defaultIfaces
}
config := dnssd.Config{
Name: name,
Type: service.Type,
Domain: domain,
Host: service.Host,
Ifaces: ifaces,
Port: service.Port,
}
service, err := dnssd.NewService(config)
if err != nil {
logger.Error(ctx, "could not create mdns service", logger.E(errors.WithStack(err)))
continue
}
services = append(services, service)
}
responder, err := dnssd.NewResponder()
if err != nil {
return errors.WithStack(err)
}
for _, service := range services {
if _, err := responder.Add(service); err != nil {
logger.Error(ctx, "could not add mdns service", logger.E(errors.WithStack(err)))
continue
}
}
ctx, cancel := context.WithCancel(context.Background())
c.responder = responder
c.cancel = cancel
c.serviceDefHash = newServerDefHash
go func() {
defer c.stopResponder(ctx)
if err := responder.Respond(ctx); err != nil && !errors.Is(err, context.Canceled) {
logger.Error(ctx, "could not respond to mdns queries", logger.E(errors.WithStack(err)))
}
}()
return nil
}
func (c *Controller) getDefaultIfaces() ([]string, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, errors.WithStack(err)
}
ifaceNames := make([]string, len(ifaces))
for idx, ifa := range ifaces {
ifaceNames[idx] = ifa.Name
}
return ifaceNames, nil
}
func NewController() *Controller {
return &Controller{
cancel: nil,
responder: nil,
serviceDefHash: 0,
}
}
var _ agent.Controller = &Controller{}

View File

@ -1,17 +0,0 @@
package spec
import (
_ "embed"
"forge.cadoles.com/Cadoles/emissary/internal/spec"
"github.com/pkg/errors"
)
//go:embed schema.json
var schema []byte
func init() {
if err := spec.Register(Name, schema); err != nil {
panic(errors.WithStack(err))
}
}

View File

@ -1,47 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://mdns.edge.emissary.cadoles.com/spec.json",
"title": "MDNSSpec",
"description": "Emissary 'MDNS' specification",
"type": "object",
"properties": {
"services": {
"type": "object",
"patternProperties": {
".*": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"domain": {
"type": "string"
},
"host": {
"type": "string"
},
"ifaces": {
"type": "array",
"items": {
"type": "string"
}
},
"port": {
"type": "number"
}
},
"required": [
"type",
"host",
"port"
],
"additionalProperties": false
}
}
}
},
"required": [
"services"
],
"additionalProperties": false
}

View File

@ -1,42 +0,0 @@
package spec
import (
"forge.cadoles.com/Cadoles/emissary/internal/spec"
)
const Name spec.Name = "mdns.emissary.cadoles.com"
type Spec struct {
Revision int `json:"revision"`
Services map[string]Service `json:"services"`
}
type Service struct {
Type string `json:"type"`
Domain string `json:"domain"`
Host string `json:"host"`
Ifaces []string `json:"ifaces"`
Port int `json:"port"`
}
func (s *Spec) SpecName() spec.Name {
return Name
}
func (s *Spec) SpecRevision() int {
return s.Revision
}
func (s *Spec) SpecData() map[string]any {
return map[string]any{
"services": s.Services,
}
}
func NewSpec() *Spec {
return &Spec{
Revision: -1,
}
}
var _ spec.Spec = &Spec{}

View File

@ -1,15 +0,0 @@
{
"name": "mdns.emissary.cadoles.com",
"data": {
"services": {
"My Website": {
"type": "_http._tcp",
"domain": "local",
"host": "mywebsite",
"ifaces": ["lo", "eth0"],
"port": 80
}
}
},
"revision": 0
}

View File

@ -1,31 +0,0 @@
package openwrt
import (
"crypto/sha256"
"encoding/hex"
"io"
"os"
"github.com/pkg/errors"
)
func hash(path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", errors.WithStack(err)
}
hasher := sha256.New()
defer func() {
if err := file.Close(); err != nil {
panic(errors.WithStack(err))
}
}()
if _, err := io.Copy(hasher, file); err != nil {
return "", errors.WithStack(err)
}
return hex.EncodeToString(hasher.Sum(nil)), nil
}

View File

@ -1,20 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://sysupgrade.openwrt.emissary.cadoles.com/spec.json",
"title": "SysUpgradeSpec",
"description": "Emissary 'SysUpgrade' specification",
"type": "object",
"properties": {
"url": {
"type": "string"
},
"sha256sum": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": ["url", "sha256sum", "version"],
"additionalProperties": false
}

View File

@ -1,9 +0,0 @@
{
"name": "sysupgrade.openwrt.emissary.cadoles.com",
"data": {
"url": "http://example.com/firmware.img",
"sha256sum": "58019192dacdae17755707719707db007e26dac856102280583fbd18427dd352",
"version": "0.0.0"
},
"revision": 0
}

View File

@ -1,65 +0,0 @@
package sysupgrade
import (
"context"
"encoding/json"
"io/ioutil"
"testing"
"forge.cadoles.com/Cadoles/emissary/internal/spec"
"github.com/pkg/errors"
)
type validatorTestCase struct {
Name string
Source string
ShouldFail bool
}
var validatorTestCases = []validatorTestCase{
{
Name: "SpecOK",
Source: "testdata/spec-ok.json",
ShouldFail: false,
},
}
func TestValidator(t *testing.T) {
t.Parallel()
validator := spec.NewValidator()
if err := validator.Register(Name, schema); err != nil {
t.Fatalf("+%v", errors.WithStack(err))
}
for _, tc := range validatorTestCases {
func(tc validatorTestCase) {
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
rawSpec, err := ioutil.ReadFile(tc.Source)
if err != nil {
t.Fatalf("+%v", errors.WithStack(err))
}
var spec spec.RawSpec
if err := json.Unmarshal(rawSpec, &spec); err != nil {
t.Fatalf("+%v", errors.WithStack(err))
}
ctx := context.Background()
err = validator.Validate(ctx, &spec)
if !tc.ShouldFail && err != nil {
t.Errorf("+%v", errors.WithStack(err))
}
if tc.ShouldFail && err == nil {
t.Error("validation should have failed")
}
})
}(tc)
}
}

View File

@ -1,177 +0,0 @@
package openwrt
import (
"context"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"forge.cadoles.com/Cadoles/emissary/internal/agent"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt/spec/sysupgrade"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
type SysUpgradeController struct {
client *http.Client
command string
args []string
firmwareVersion FirmwareVersion
}
// Name implements agent.Controller
func (*SysUpgradeController) Name() string {
return "sysupgrade-controller"
}
// Reconcile implements agent.Controller
func (c *SysUpgradeController) Reconcile(ctx context.Context, state *agent.State) error {
sysSpec := sysupgrade.NewSpec()
if err := state.GetSpec(sysupgrade.Name, sysSpec); err != nil {
if errors.Is(err, agent.ErrSpecNotFound) {
logger.Info(ctx, "could not find sysupgrade spec, doing nothing")
return nil
}
return errors.WithStack(err)
}
firmwareVersion, err := c.firmwareVersion.FirmwareVersion(ctx)
if err != nil {
return errors.WithStack(err)
}
ctx = logger.With(ctx,
logger.F("currentFirmwareVersion", firmwareVersion),
logger.F("newFirmwareVersion", sysSpec.Version),
)
if firmwareVersion == sysSpec.Version {
logger.Info(ctx, "firmware version did not change, doing nothing")
return nil
}
downloadDir, err := os.MkdirTemp(os.TempDir(), "emissary_sysupgrade_*")
if err != nil {
return errors.WithStack(err)
}
defer func() {
if err := os.RemoveAll(downloadDir); err != nil {
logger.Error(
ctx, "could not remove download direction",
logger.E(errors.WithStack(err)),
logger.F("downloadDir", downloadDir),
)
}
}()
firmwareFile := filepath.Join(downloadDir, "firmware.bin")
logger.Info(
ctx, "downloading firmware",
logger.F("url", sysSpec.URL), logger.F("sha256sum", sysSpec.SHA256Sum),
)
if err := c.downloadFile(ctx, sysSpec.URL, sysSpec.SHA256Sum, firmwareFile); err != nil {
return errors.WithStack(err)
}
logger.Info(ctx, "upgrading firmware")
if err := c.upgradeFirmware(ctx, firmwareFile); err != nil {
return errors.WithStack(err)
}
return nil
}
func (c *SysUpgradeController) downloadFile(ctx context.Context, url string, sha256sum string, dest string) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return errors.WithStack(err)
}
res, err := c.client.Do(req)
if err != nil {
return errors.WithStack(err)
}
defer func() {
if err := res.Body.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
panic(errors.WithStack(err))
}
}()
tmp, err := os.CreateTemp(filepath.Dir(dest), "download_")
if err != nil {
return errors.WithStack(err)
}
defer func() {
if err := os.Remove(tmp.Name()); err != nil && !os.IsNotExist(err) {
panic(errors.WithStack(err))
}
}()
if _, err := io.Copy(tmp, res.Body); err != nil {
return errors.WithStack(err)
}
tmpFileHash, err := hash(tmp.Name())
if err != nil {
return errors.WithStack(err)
}
if tmpFileHash != sha256sum {
return errors.Errorf("sha256 sum mismatch: expected '%s', got '%s'", sha256sum, tmpFileHash)
}
if err := os.Rename(tmp.Name(), dest); err != nil {
return errors.WithStack(err)
}
return nil
}
func (c *SysUpgradeController) upgradeFirmware(ctx context.Context, firmwareFile string) error {
templatizedArgs := make([]string, len(c.args))
for i, a := range c.args {
templatizedArgs[i] = strings.Replace(a, FirmwareFileTemplate, firmwareFile, 1)
}
command := exec.CommandContext(ctx, c.command, templatizedArgs...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
logger.Debug(ctx, "executing command", logger.F("command", c.command), logger.F("args", templatizedArgs))
if err := command.Run(); err != nil {
return errors.WithStack(err)
}
return nil
}
func NewSysUpgradeController(funcs ...SysUpgradeOptionFunc) *SysUpgradeController {
opts := defaultSysUpgradeOptions()
for _, fn := range funcs {
fn(opts)
}
return &SysUpgradeController{
command: opts.Command,
args: opts.Args,
client: opts.Client,
firmwareVersion: opts.FirmwareVersion,
}
}
var _ agent.Controller = &SysUpgradeController{}

View File

@ -1,46 +0,0 @@
package openwrt
import (
"bytes"
"context"
"os"
"os/exec"
"strings"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
type ShellFirmwareVersion struct {
command string
args []string
}
// FirmwareVersion implements FirmwareVersion
func (fv *ShellFirmwareVersion) FirmwareVersion(ctx context.Context) (string, error) {
command := exec.CommandContext(ctx, fv.command, fv.args...)
var buf bytes.Buffer
command.Stdout = &buf
command.Stderr = os.Stderr
logger.Debug(ctx, "executing command", logger.F("command", fv.command), logger.F("args", fv.args))
if err := command.Run(); err != nil {
return "", errors.WithStack(err)
}
version := strings.TrimSpace(buf.String())
return version, nil
}
func NewShellFirmwareVersion(command string, args ...string) *ShellFirmwareVersion {
return &ShellFirmwareVersion{
command: command,
args: args,
}
}
var _ FirmwareVersion = &ShellFirmwareVersion{}

View File

@ -1,58 +0,0 @@
package openwrt
import (
"context"
"net/http"
"time"
)
const FirmwareFileTemplate = "%FIRMWARE%"
type FirmwareVersion interface {
FirmwareVersion(context.Context) (string, error)
}
type SysUpgradeOptions struct {
Command string
Args []string
FirmwareVersion FirmwareVersion
Client *http.Client
}
func defaultSysUpgradeOptions() *SysUpgradeOptions {
return &SysUpgradeOptions{
Command: `echo`,
Args: []string{`[DUMMY UPGRADE]`, FirmwareFileTemplate},
Client: &http.Client{
Timeout: 30 * time.Second,
},
FirmwareVersion: NewShellFirmwareVersion(`echo`, "0.0.0-dummy"),
}
}
type SysUpgradeOptionFunc func(*SysUpgradeOptions)
func WithSysUpgradeCommand(command string, args ...string) SysUpgradeOptionFunc {
return func(opts *SysUpgradeOptions) {
opts.Command = command
opts.Args = args
}
}
func WithSysUpgradeFirmwareVersion(firmwareVersion FirmwareVersion) SysUpgradeOptionFunc {
return func(opts *SysUpgradeOptions) {
opts.FirmwareVersion = firmwareVersion
}
}
func WithSysUpgradeShellFirmwareVersion(command string, args ...string) SysUpgradeOptionFunc {
return func(opts *SysUpgradeOptions) {
opts.FirmwareVersion = NewShellFirmwareVersion(command, args...)
}
}
func WithSysUpgradeClient(client *http.Client) SysUpgradeOptionFunc {
return func(opts *SysUpgradeOptions) {
opts.Client = client
}
}

View File

@ -1,180 +0,0 @@
package proxy
import (
"context"
"net/url"
"forge.cadoles.com/Cadoles/emissary/internal/agent"
"forge.cadoles.com/Cadoles/emissary/internal/proxy"
spec "forge.cadoles.com/Cadoles/emissary/internal/spec/proxy"
"github.com/mitchellh/hashstructure/v2"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
type proxyEntry struct {
SpecHash uint64
Proxy *ReverseProxy
}
type Controller struct {
proxies map[spec.ID]*proxyEntry
}
// Name implements node.Controller.
func (c *Controller) Name() string {
return "proxy-controller"
}
// Reconcile implements node.Controller.
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
proxySpec := spec.NewSpec()
if err := state.GetSpec(spec.NameProxy, proxySpec); err != nil {
if errors.Is(err, agent.ErrSpecNotFound) {
logger.Info(ctx, "could not find proxy spec")
c.stopAllProxies(ctx)
return nil
}
return errors.WithStack(err)
}
logger.Info(ctx, "retrieved spec", logger.F("spec", proxySpec.SpecName()), logger.F("revision", proxySpec.SpecRevision()))
c.updateProxies(ctx, proxySpec)
return nil
}
func (c *Controller) stopAllProxies(ctx context.Context) {
if len(c.proxies) > 0 {
logger.Info(ctx, "stopping all proxies")
}
for proxyID, entry := range c.proxies {
logger.Info(ctx, "stopping proxy", logger.F("proxyID", proxyID))
if err := entry.Proxy.Stop(); err != nil {
logger.Error(
ctx, "error while stopping proxy",
logger.F("proxyID", proxyID),
logger.E(errors.WithStack(err)),
)
delete(c.proxies, proxyID)
}
}
}
func (c *Controller) updateProxies(ctx context.Context, spec *spec.Spec) {
// Stop and remove obsolete proxys
for proxyID, entry := range c.proxies {
if _, exists := spec.Proxies[proxyID]; exists {
continue
}
logger.Info(ctx, "stopping proxy", logger.F("proxyID", proxyID))
if err := entry.Proxy.Stop(); err != nil {
logger.Error(
ctx, "error while stopping proxy",
logger.F("proxyID", proxyID),
logger.E(errors.WithStack(err)),
)
delete(c.proxies, proxyID)
}
}
// (Re)start proxys
for proxyID, proxySpec := range spec.Proxies {
proxyCtx := logger.With(ctx, logger.F("proxyID", proxyID))
if err := c.updateProxy(ctx, proxyID, proxySpec); err != nil {
logger.Error(proxyCtx, "could not update proxy", logger.E(errors.WithStack(err)))
continue
}
}
}
func (c *Controller) updateProxy(ctx context.Context, proxyID spec.ID, proxySpec spec.ProxyEntry) (err error) {
newProxySpecHash, err := hashstructure.Hash(proxySpec, hashstructure.FormatV2, nil)
if err != nil {
return errors.WithStack(err)
}
var entry *proxyEntry
entry, exists := c.proxies[proxyID]
if !exists {
logger.Info(ctx, "proxy currently not running")
}
if entry == nil {
entry = &proxyEntry{
Proxy: NewReverseProxy(),
SpecHash: 0,
}
c.proxies[proxyID] = entry
}
specChanged := newProxySpecHash != entry.SpecHash
if entry.Proxy.Running() && !specChanged {
return nil
}
if specChanged && entry.SpecHash != 0 {
logger.Info(
ctx, "restarting proxy",
logger.F("address", proxySpec.Address),
)
} else {
logger.Info(
ctx, "starting proxy",
logger.F("address", proxySpec.Address),
)
}
options := make([]proxy.OptionFunc, 0)
allowedHosts := make([]string, len(proxySpec.Mappings))
mappings := make(map[string]*url.URL, len(proxySpec.Mappings))
for _, m := range proxySpec.Mappings {
target, err := url.Parse(m.Target)
if err != nil {
return errors.WithStack(err)
}
mappings[m.HostPattern] = target
allowedHosts = append(allowedHosts, m.HostPattern)
}
options = append(
options,
proxy.WithAllowedHosts(allowedHosts...),
proxy.WithRewriteHosts(mappings),
)
if err := entry.Proxy.Start(ctx, proxySpec.Address, options...); err != nil {
delete(c.proxies, proxyID)
return errors.Wrap(err, "could not start app")
}
entry.SpecHash = newProxySpecHash
return nil
}
func NewController() *Controller {
return &Controller{
proxies: make(map[spec.ID]*proxyEntry),
}
}
var _ agent.Controller = &Controller{}

View File

@ -21,15 +21,22 @@ func (c *Controller) Name() string {
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error { func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
cl := agent.Client(ctx) cl := agent.Client(ctx)
agent, err := cl.GetAgent( agents, _, err := cl.QueryAgents(
ctx, ctx,
state.AgentID(), client.WithQueryAgentsLimit(1),
client.WithQueryAgentsID(state.AgentID()),
) )
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
if err := c.reconcileAgent(ctx, cl, state, agent); err != nil { if len(agents) == 0 {
logger.Error(ctx, "could not find remote matching agent")
return nil
}
if err := c.reconcileAgent(ctx, cl, state, agents[0]); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"net/http" "net/http"
"strings" "strings"
"time"
"forge.cadoles.com/Cadoles/emissary/internal/auth" "forge.cadoles.com/Cadoles/emissary/internal/auth"
"forge.cadoles.com/Cadoles/emissary/internal/datastore" "forge.cadoles.com/Cadoles/emissary/internal/datastore"
@ -14,11 +13,8 @@ import (
"gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/logger"
) )
const DefaultAcceptableSkew = 5 * time.Minute
type Authenticator struct { type Authenticator struct {
repo datastore.AgentRepository repo datastore.AgentRepository
acceptableSkew time.Duration
} }
// Authenticate implements auth.Authenticator. // Authenticate implements auth.Authenticator.
@ -75,19 +71,11 @@ func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth
[]byte(rawToken), []byte(rawToken),
jwt.WithKeySet(agent.KeySet.Set, jws.WithRequireKid(false)), jwt.WithKeySet(agent.KeySet.Set, jws.WithRequireKid(false)),
jwt.WithValidate(true), jwt.WithValidate(true),
jwt.WithAcceptableSkew(a.acceptableSkew),
) )
if err != nil { if err != nil {
return nil, errors.WithStack(err) return nil, errors.WithStack(err)
} }
contactedAt := time.Now()
agent, err = a.repo.Update(ctx, agent.ID, datastore.WithAgentUpdateContactedAt(contactedAt))
if err != nil {
return nil, errors.WithStack(err)
}
user := &User{ user := &User{
agent: agent, agent: agent,
} }
@ -95,10 +83,9 @@ func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth
return user, nil return user, nil
} }
func NewAuthenticator(repo datastore.AgentRepository, acceptableSkew time.Duration) *Authenticator { func NewAuthenticator(repo datastore.AgentRepository) *Authenticator {
return &Authenticator{ return &Authenticator{
repo: repo, repo: repo,
acceptableSkew: acceptableSkew,
} }
} }

View File

@ -18,7 +18,7 @@ func GenerateToken(key jwk.Key, thumbprint string) (string, error) {
return "", errors.WithStack(err) return "", errors.WithStack(err)
} }
now := time.Now().UTC() now := time.Now()
if err := token.Set(jwt.NotBeforeKey, now); err != nil { if err := token.Set(jwt.NotBeforeKey, now); err != nil {
return "", errors.WithStack(err) return "", errors.WithStack(err)

View File

@ -13,7 +13,7 @@ type User struct {
// Subject implements auth.User // Subject implements auth.User
func (u *User) Subject() string { func (u *User) Subject() string {
return fmt.Sprintf("agent-%d", u.agent.ID) return fmt.Sprintf("agent#%d", u.agent.ID)
} }
func (u *User) Agent() *datastore.Agent { func (u *User) Agent() *datastore.Agent {

View File

@ -20,8 +20,8 @@ const (
contextKeyUser contextKey = "user" contextKeyUser contextKey = "user"
) )
func CtxUser(ctx context.Context) (User, error) { func CtxUser(ctx context.Context) (*User, error) {
user, ok := ctx.Value(contextKeyUser).(User) user, ok := ctx.Value(contextKeyUser).(*User)
if !ok { if !ok {
return nil, errors.Errorf("unexpected user type: expected '%T', got '%T'", new(User), ctx.Value(contextKeyUser)) return nil, errors.Errorf("unexpected user type: expected '%T', got '%T'", new(User), ctx.Value(contextKeyUser))
} }
@ -29,7 +29,10 @@ func CtxUser(ctx context.Context) (User, error) {
return user, nil return user, nil
} }
var ErrUnauthenticated = errors.New("unauthenticated") var (
ErrUnauthenticated = errors.New("unauthenticated")
ErrForbidden = errors.New("forbidden")
)
type User interface { type User interface {
Subject() string Subject() string
@ -52,7 +55,7 @@ func Middleware(authenticators ...Authenticator) func(http.Handler) http.Handler
for _, auth := range authenticators { for _, auth := range authenticators {
user, err = auth.Authenticate(ctx, r) user, err = auth.Authenticate(ctx, r)
if err != nil { if err != nil {
logger.Debug(ctx, "could not authenticate request", logger.E(errors.WithStack(err))) logger.Warn(ctx, "could not authenticate request", logger.E(errors.WithStack(err)))
continue continue
} }
@ -68,7 +71,6 @@ func Middleware(authenticators ...Authenticator) func(http.Handler) http.Handler
return return
} }
ctx = logger.With(ctx, logger.F("user", user.Subject()))
ctx = context.WithValue(ctx, contextKeyUser, user) ctx = context.WithValue(ctx, contextKeyUser, user)
h.ServeHTTP(w, r.WithContext(ctx)) h.ServeHTTP(w, r.WithContext(ctx))

View File

@ -1,10 +1,9 @@
package thirdparty package user
import ( import (
"context" "context"
"net/http" "net/http"
"strings" "strings"
"time"
"forge.cadoles.com/Cadoles/emissary/internal/auth" "forge.cadoles.com/Cadoles/emissary/internal/auth"
"forge.cadoles.com/Cadoles/emissary/internal/jwk" "forge.cadoles.com/Cadoles/emissary/internal/jwk"
@ -12,12 +11,9 @@ import (
"gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/logger"
) )
const DefaultAcceptableSkew = 5 * time.Minute
type Authenticator struct { type Authenticator struct {
keys jwk.Set keys jwk.Set
issuer string issuer string
acceptableSkew time.Duration
} }
// Authenticate implements auth.Authenticator. // Authenticate implements auth.Authenticator.
@ -34,7 +30,7 @@ func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth
return nil, errors.WithStack(auth.ErrUnauthenticated) return nil, errors.WithStack(auth.ErrUnauthenticated)
} }
token, err := parseToken(ctx, a.keys, a.issuer, rawToken, a.acceptableSkew) token, err := parseToken(ctx, a.keys, a.issuer, rawToken)
if err != nil { if err != nil {
return nil, errors.WithStack(err) return nil, errors.WithStack(err)
} }
@ -61,11 +57,10 @@ func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth
return user, nil return user, nil
} }
func NewAuthenticator(keys jwk.Set, issuer string, acceptableSkew time.Duration) *Authenticator { func NewAuthenticator(keys jwk.Set, issuer string) *Authenticator {
return &Authenticator{ return &Authenticator{
keys: keys, keys: keys,
issuer: issuer, issuer: issuer,
acceptableSkew: acceptableSkew,
} }
} }

View File

@ -1,4 +1,4 @@
package thirdparty package user
import ( import (
"context" "context"
@ -13,13 +13,12 @@ import (
const keyRole = "role" const keyRole = "role"
func parseToken(ctx context.Context, keys jwk.Set, issuer string, rawToken string, acceptableSkew time.Duration) (jwt.Token, error) { func parseToken(ctx context.Context, keys jwk.Set, issuer string, rawToken string) (jwt.Token, error) {
token, err := jwt.Parse( token, err := jwt.Parse(
[]byte(rawToken), []byte(rawToken),
jwt.WithKeySet(keys, jws.WithRequireKid(false)), jwt.WithKeySet(keys, jws.WithRequireKid(false)),
jwt.WithIssuer(issuer), jwt.WithIssuer(issuer),
jwt.WithValidate(true), jwt.WithValidate(true),
jwt.WithAcceptableSkew(acceptableSkew),
) )
if err != nil { if err != nil {
return nil, errors.WithStack(err) return nil, errors.WithStack(err)
@ -28,22 +27,14 @@ func parseToken(ctx context.Context, keys jwk.Set, issuer string, rawToken strin
return token, nil return token, nil
} }
func GenerateToken(ctx context.Context, key jwk.Key, issuer, subject string, role Role) (string, error) { func GenerateToken(ctx context.Context, key jwk.Key, role Role) (string, error) {
token := jwt.New() token := jwt.New()
if err := token.Set(jwt.SubjectKey, subject); err != nil {
return "", errors.WithStack(err)
}
if err := token.Set(jwt.IssuerKey, issuer); err != nil {
return "", errors.WithStack(err)
}
if err := token.Set(keyRole, role); err != nil { if err := token.Set(keyRole, role); err != nil {
return "", errors.WithStack(err) return "", errors.WithStack(err)
} }
now := time.Now().UTC() now := time.Now()
if err := token.Set(jwt.NotBeforeKey, now); err != nil { if err := token.Set(jwt.NotBeforeKey, now); err != nil {
return "", errors.WithStack(err) return "", errors.WithStack(err)

View File

@ -1,4 +1,4 @@
package thirdparty package user
import "forge.cadoles.com/Cadoles/emissary/internal/auth" import "forge.cadoles.com/Cadoles/emissary/internal/auth"

View File

@ -1,27 +0,0 @@
package client
import (
"context"
"fmt"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"github.com/pkg/errors"
)
func (c *Client) DeleteAgent(ctx context.Context, agentID datastore.AgentID, funcs ...OptionFunc) (datastore.AgentID, error) {
response := withResponse[struct {
AgentID int64 `json:"agentId"`
}]()
path := fmt.Sprintf("/api/v1/agents/%d", agentID)
if err := c.apiDelete(ctx, path, nil, &response, funcs...); err != nil {
return 0, errors.WithStack(err)
}
if response.Error != nil {
return 0, errors.WithStack(response.Error)
}
return datastore.AgentID(response.Data.AgentID), nil
}

View File

@ -1,34 +0,0 @@
package client
import (
"context"
"fmt"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"forge.cadoles.com/Cadoles/emissary/internal/spec"
"github.com/pkg/errors"
)
func (c *Client) DeleteAgentSpec(ctx context.Context, agentID datastore.AgentID, name spec.Name, funcs ...OptionFunc) (spec.Name, error) {
payload := struct {
Name spec.Name `json:"name"`
}{
Name: name,
}
response := withResponse[struct {
Name spec.Name `json:"name"`
}]()
path := fmt.Sprintf("/api/v1/agents/%d/specs", agentID)
if err := c.apiDelete(ctx, path, payload, &response, funcs...); err != nil {
return "", errors.WithStack(err)
}
if response.Error != nil {
return "", errors.WithStack(response.Error)
}
return response.Data.Name, nil
}

View File

@ -10,7 +10,6 @@ import (
type UpdateAgentOptions struct { type UpdateAgentOptions struct {
Status *int Status *int
Label *string
Options []OptionFunc Options []OptionFunc
} }
@ -22,12 +21,6 @@ func WithAgentStatus(status int) UpdateAgentOptionFunc {
} }
} }
func WithAgentLabel(label string) UpdateAgentOptionFunc {
return func(opts *UpdateAgentOptions) {
opts.Label = &label
}
}
func WithUpdateAgentsOptions(funcs ...OptionFunc) UpdateAgentOptionFunc { func WithUpdateAgentsOptions(funcs ...OptionFunc) UpdateAgentOptionFunc {
return func(opts *UpdateAgentOptions) { return func(opts *UpdateAgentOptions) {
opts.Options = funcs opts.Options = funcs
@ -46,10 +39,6 @@ func (c *Client) UpdateAgent(ctx context.Context, agentID datastore.AgentID, fun
payload["status"] = *opts.Status payload["status"] = *opts.Status
} }
if opts.Label != nil {
payload["label"] = *opts.Label
}
response := withResponse[struct { response := withResponse[struct {
Agent *datastore.Agent `json:"agent"` Agent *datastore.Agent `json:"agent"`
}]() }]()

View File

@ -10,6 +10,7 @@ import (
"forge.cadoles.com/Cadoles/emissary/internal/command/common" "forge.cadoles.com/Cadoles/emissary/internal/command/common"
"forge.cadoles.com/Cadoles/emissary/internal/openwrt/uci" "forge.cadoles.com/Cadoles/emissary/internal/openwrt/uci"
"github.com/pkg/errors" "github.com/pkg/errors"
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )

View File

@ -14,7 +14,6 @@ func Root() *cli.Command {
openwrt.Root(), openwrt.Root(),
config.Root(), config.Root(),
RunCommand(), RunCommand(),
ShowThumbprintCommand(),
}, },
} }
} }

View File

@ -5,10 +5,9 @@ import (
"forge.cadoles.com/Cadoles/emissary/internal/agent" "forge.cadoles.com/Cadoles/emissary/internal/agent"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app" "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/mdns" "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/gateway"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt" "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/persistence" "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/persistence"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/proxy"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/spec" "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/spec"
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata" "forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata/collector/buildinfo" "forge.cadoles.com/Cadoles/emissary/internal/agent/metadata/collector/buildinfo"
@ -18,6 +17,7 @@ import (
"forge.cadoles.com/Cadoles/emissary/internal/jwk" "forge.cadoles.com/Cadoles/emissary/internal/jwk"
"forge.cadoles.com/Cadoles/emissary/internal/machineid" "forge.cadoles.com/Cadoles/emissary/internal/machineid"
"github.com/pkg/errors" "github.com/pkg/errors"
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/logger"
) )
@ -50,6 +50,10 @@ func RunCommand() *cli.Command {
controllers = append(controllers, spec.NewController()) controllers = append(controllers, spec.NewController())
} }
if ctrlConf.Gateway.Enabled {
controllers = append(controllers, gateway.NewController())
}
if ctrlConf.UCI.Enabled { if ctrlConf.UCI.Enabled {
controllers = append(controllers, openwrt.NewUCIController( controllers = append(controllers, openwrt.NewUCIController(
string(ctrlConf.UCI.BinPath), string(ctrlConf.UCI.BinPath),
@ -63,37 +67,6 @@ func RunCommand() *cli.Command {
)) ))
} }
if ctrlConf.Proxy.Enabled {
controllers = append(controllers, proxy.NewController())
}
if ctrlConf.MDNS.Enabled {
controllers = append(controllers, mdns.NewController())
}
if ctrlConf.SysUpgrade.Enabled {
sysUpgradeArgs := make([]string, 0)
if len(ctrlConf.SysUpgrade.SysUpgradeCommand) > 1 {
sysUpgradeArgs = ctrlConf.SysUpgrade.SysUpgradeCommand[1:]
}
firmwareVersionArgs := make([]string, 0)
if len(ctrlConf.SysUpgrade.FirmwareVersionCommand) > 1 {
firmwareVersionArgs = ctrlConf.SysUpgrade.FirmwareVersionCommand[1:]
}
controllers = append(controllers, openwrt.NewSysUpgradeController(
openwrt.WithSysUpgradeCommand(
ctrlConf.SysUpgrade.SysUpgradeCommand[0],
sysUpgradeArgs...,
),
openwrt.WithSysUpgradeShellFirmwareVersion(
ctrlConf.SysUpgrade.FirmwareVersionCommand[0],
firmwareVersionArgs...,
),
))
}
key, err := jwk.LoadOrGenerate(string(conf.Agent.PrivateKeyPath), jwk.DefaultKeySize) key, err := jwk.LoadOrGenerate(string(conf.Agent.PrivateKeyPath), jwk.DefaultKeySize)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)

View File

@ -1,30 +0,0 @@
package agent
import (
"fmt"
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
"forge.cadoles.com/Cadoles/emissary/internal/machineid"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
func ShowThumbprintCommand() *cli.Command {
flags := common.Flags()
return &cli.Command{
Name: "show-thumbprint",
Usage: "Show the current agent thumbprint",
Flags: flags,
Action: func(ctx *cli.Context) error {
thumbprint, err := machineid.Get()
if err != nil {
return errors.WithStack(err)
}
fmt.Println(thumbprint)
return nil
},
}
}

View File

@ -1,56 +0,0 @@
package agent
import (
"os"
"forge.cadoles.com/Cadoles/emissary/internal/client"
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"forge.cadoles.com/Cadoles/emissary/internal/format"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
func DeleteCommand() *cli.Command {
return &cli.Command{
Name: "delete",
Usage: "Delete agent",
Flags: agentFlag.WithAgentFlags(),
Action: func(ctx *cli.Context) error {
baseFlags := clientFlag.GetBaseFlags(ctx)
token, err := clientFlag.GetToken(baseFlags)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
agentID, err := agentFlag.AssertAgentID(ctx)
if err != nil {
return errors.WithStack(err)
}
client := client.New(baseFlags.ServerURL, client.WithToken(token))
agentID, err = client.DeleteAgent(ctx.Context, agentID)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
hints := format.Hints{
OutputMode: baseFlags.OutputMode,
}
if err := format.Write(baseFlags.Format, os.Stdout, hints, struct {
ID datastore.AgentID `json:"id"`
}{
ID: agentID,
}); err != nil {
return errors.WithStack(err)
}
return nil
},
}
}

View File

@ -1,89 +0,0 @@
package agent
import (
"os"
"forge.cadoles.com/Cadoles/emissary/internal/client"
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"forge.cadoles.com/Cadoles/emissary/internal/format"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
func QueryCommand() *cli.Command {
return &cli.Command{
Name: "query",
Usage: "Query agents",
Flags: clientFlag.ComposeFlags(
&cli.StringSliceFlag{
Name: "thumbprints",
Usage: "use `THUMBPRINTS` as query filter",
Value: nil,
},
&cli.Int64SliceFlag{
Name: "statuses",
Usage: "use `STATUSES` as query filter",
},
&cli.Int64SliceFlag{
Name: "ids",
Usage: "use `IDS` as query filter",
},
),
Action: func(ctx *cli.Context) error {
baseFlags := clientFlag.GetBaseFlags(ctx)
token, err := clientFlag.GetToken(baseFlags)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
options := make([]client.QueryAgentsOptionFunc, 0)
thumbprints := ctx.StringSlice("thumbprints")
if thumbprints != nil {
options = append(options, client.WithQueryAgentsThumbprints(thumbprints...))
}
rawIDs := ctx.Int64Slice("ids")
if rawIDs != nil {
agentIDs := func(ids []int64) []datastore.AgentID {
agentIDs := make([]datastore.AgentID, len(ids))
for i, id := range ids {
agentIDs[i] = datastore.AgentID(id)
}
return agentIDs
}(rawIDs)
options = append(options, client.WithQueryAgentsID(agentIDs...))
}
rawStatuses := ctx.Int64Slice("statuses")
if rawStatuses != nil {
statuses := func(rawStatuses []int64) []datastore.AgentStatus {
statuses := make([]datastore.AgentStatus, len(rawStatuses))
for i, status := range rawStatuses {
statuses[i] = datastore.AgentStatus(status)
}
return statuses
}(rawStatuses)
options = append(options, client.WithQueryAgentsStatus(statuses...))
}
client := client.New(baseFlags.ServerURL, client.WithToken(token))
agents, _, err := client.QueryAgents(ctx.Context, options...)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
hints := agentHints(baseFlags.OutputMode)
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(agents)...); err != nil {
return errors.WithStack(err)
}
return nil
},
}
}

View File

@ -1,67 +0,0 @@
package spec
import (
"os"
"forge.cadoles.com/Cadoles/emissary/internal/client"
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
"forge.cadoles.com/Cadoles/emissary/internal/format"
"forge.cadoles.com/Cadoles/emissary/internal/spec"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
func DeleteCommand() *cli.Command {
return &cli.Command{
Name: "delete",
Usage: "Delete spec",
Flags: agentFlag.WithAgentFlags(
&cli.StringFlag{
Name: "spec-name",
Usage: "use `NAME` as specification's name",
},
),
Action: func(ctx *cli.Context) error {
baseFlags := clientFlag.GetBaseFlags(ctx)
token, err := clientFlag.GetToken(baseFlags)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
agentID, err := agentFlag.AssertAgentID(ctx)
if err != nil {
return errors.WithStack(err)
}
specName, err := assertSpecName(ctx)
if err != nil {
return errors.WithStack(err)
}
client := client.New(baseFlags.ServerURL, client.WithToken(token))
specName, err = client.DeleteAgentSpec(ctx.Context, agentID, specName)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
hints := format.Hints{
OutputMode: baseFlags.OutputMode,
}
if err := format.Write(baseFlags.Format, os.Stdout, hints, struct {
Name spec.Name `json:"name"`
}{
Name: specName,
}); err != nil {
return errors.WithStack(err)
}
return nil
},
}
}

View File

@ -1,16 +0,0 @@
package api
import (
"forge.cadoles.com/Cadoles/emissary/internal/command/api/agent"
"github.com/urfave/cli/v2"
)
func Root() *cli.Command {
return &cli.Command{
Name: "api",
Usage: "API related commands",
Subcommands: []*cli.Command{
agent.Root(),
},
}
}

View File

@ -4,8 +4,8 @@ import (
"os" "os"
"forge.cadoles.com/Cadoles/emissary/internal/client" "forge.cadoles.com/Cadoles/emissary/internal/client"
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr" "forge.cadoles.com/Cadoles/emissary/internal/command/client/apierr"
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag" clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/flag"
"forge.cadoles.com/Cadoles/emissary/internal/format" "forge.cadoles.com/Cadoles/emissary/internal/format"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -18,13 +18,7 @@ func CountCommand() *cli.Command {
Flags: clientFlag.ComposeFlags(), Flags: clientFlag.ComposeFlags(),
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
baseFlags := clientFlag.GetBaseFlags(ctx) baseFlags := clientFlag.GetBaseFlags(ctx)
client := client.New(baseFlags.ServerURL)
token, err := clientFlag.GetToken(baseFlags)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
client := client.New(baseFlags.ServerURL, client.WithToken(token))
_, total, err := client.QueryAgents(ctx.Context) _, total, err := client.QueryAgents(ctx.Context)
if err != nil { if err != nil {

View File

@ -3,7 +3,7 @@ package flag
import ( import (
"errors" "errors"
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag" clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/flag"
"forge.cadoles.com/Cadoles/emissary/internal/datastore" "forge.cadoles.com/Cadoles/emissary/internal/datastore"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )

View File

@ -4,9 +4,9 @@ import (
"os" "os"
"forge.cadoles.com/Cadoles/emissary/internal/client" "forge.cadoles.com/Cadoles/emissary/internal/client"
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag" agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/agent/flag"
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr" "forge.cadoles.com/Cadoles/emissary/internal/command/client/apierr"
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag" clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/flag"
"forge.cadoles.com/Cadoles/emissary/internal/format" "forge.cadoles.com/Cadoles/emissary/internal/format"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -20,17 +20,12 @@ func GetCommand() *cli.Command {
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
baseFlags := clientFlag.GetBaseFlags(ctx) baseFlags := clientFlag.GetBaseFlags(ctx)
token, err := clientFlag.GetToken(baseFlags)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
agentID, err := agentFlag.AssertAgentID(ctx) agentID, err := agentFlag.AssertAgentID(ctx)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
client := client.New(baseFlags.ServerURL, client.WithToken(token)) client := client.New(baseFlags.ServerURL)
agent, err := client.GetAgent(ctx.Context, agentID) agent, err := client.GetAgent(ctx.Context, agentID)
if err != nil { if err != nil {

View File

@ -0,0 +1,37 @@
package agent
import (
"os"
"forge.cadoles.com/Cadoles/emissary/internal/client"
"forge.cadoles.com/Cadoles/emissary/internal/command/client/apierr"
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/flag"
"forge.cadoles.com/Cadoles/emissary/internal/format"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
func QueryCommand() *cli.Command {
return &cli.Command{
Name: "query",
Usage: "Query agents",
Flags: clientFlag.ComposeFlags(),
Action: func(ctx *cli.Context) error {
baseFlags := clientFlag.GetBaseFlags(ctx)
client := client.New(baseFlags.ServerURL)
agents, _, err := client.QueryAgents(ctx.Context)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
hints := agentHints(baseFlags.OutputMode)
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(agents)...); err != nil {
return errors.WithStack(err)
}
return nil
},
}
}

View File

@ -1,7 +1,7 @@
package agent package agent
import ( import (
"forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/spec" "forge.cadoles.com/Cadoles/emissary/internal/command/client/agent/spec"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -14,7 +14,6 @@ func Root() *cli.Command {
CountCommand(), CountCommand(),
UpdateCommand(), UpdateCommand(),
GetCommand(), GetCommand(),
DeleteCommand(),
spec.Root(), spec.Root(),
}, },
} }

View File

@ -4,9 +4,9 @@ import (
"os" "os"
"forge.cadoles.com/Cadoles/emissary/internal/client" "forge.cadoles.com/Cadoles/emissary/internal/client"
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag" agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/agent/flag"
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr" "forge.cadoles.com/Cadoles/emissary/internal/command/client/apierr"
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag" clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/flag"
"forge.cadoles.com/Cadoles/emissary/internal/format" "forge.cadoles.com/Cadoles/emissary/internal/format"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -24,12 +24,7 @@ func GetCommand() *cli.Command {
return errors.WithStack(err) return errors.WithStack(err)
} }
token, err := clientFlag.GetToken(baseFlags) client := client.New(baseFlags.ServerURL)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
client := client.New(baseFlags.ServerURL, client.WithToken(token))
specs, err := client.GetAgentSpecs(ctx.Context, agentID) specs, err := client.GetAgentSpecs(ctx.Context, agentID)
if err != nil { if err != nil {

View File

@ -11,7 +11,6 @@ func Root() *cli.Command {
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
GetCommand(), GetCommand(),
UpdateCommand(), UpdateCommand(),
DeleteCommand(),
}, },
} }
} }

View File

@ -5,14 +5,19 @@ import (
"os" "os"
"forge.cadoles.com/Cadoles/emissary/internal/client" "forge.cadoles.com/Cadoles/emissary/internal/client"
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag" agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/agent/flag"
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr" "forge.cadoles.com/Cadoles/emissary/internal/command/client/apierr"
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag" clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/flag"
"forge.cadoles.com/Cadoles/emissary/internal/format" "forge.cadoles.com/Cadoles/emissary/internal/format"
"forge.cadoles.com/Cadoles/emissary/internal/spec" "forge.cadoles.com/Cadoles/emissary/internal/spec"
jsonpatch "github.com/evanphx/json-patch/v5" jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
// Import specs
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/app"
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/gateway"
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/uci"
) )
func UpdateCommand() *cli.Command { func UpdateCommand() *cli.Command {
@ -22,11 +27,11 @@ func UpdateCommand() *cli.Command {
Flags: agentFlag.WithAgentFlags( Flags: agentFlag.WithAgentFlags(
&cli.StringFlag{ &cli.StringFlag{
Name: "spec-name", Name: "spec-name",
Usage: "use `NAME` as specification's name", Usage: "use `NAME` as spec name",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "spec-data", Name: "spec-data",
Usage: "use `DATA` as specification's data, '-' to read from STDIN", Usage: "use `DATA` as spec data, '-' to read from STDIN",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "no-patch", Name: "no-patch",
@ -56,12 +61,7 @@ func UpdateCommand() *cli.Command {
noPatch := ctx.Bool("no-patch") noPatch := ctx.Bool("no-patch")
token, err := clientFlag.GetToken(baseFlags) client := client.New(baseFlags.ServerURL)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
client := client.New(baseFlags.ServerURL, client.WithToken(token))
specs, err := client.GetAgentSpecs(ctx.Context, agentID) specs, err := client.GetAgentSpecs(ctx.Context, agentID)
if err != nil { if err != nil {

View File

@ -4,9 +4,9 @@ import (
"os" "os"
"forge.cadoles.com/Cadoles/emissary/internal/client" "forge.cadoles.com/Cadoles/emissary/internal/client"
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag" agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/agent/flag"
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr" "forge.cadoles.com/Cadoles/emissary/internal/command/client/apierr"
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag" clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/flag"
"forge.cadoles.com/Cadoles/emissary/internal/format" "forge.cadoles.com/Cadoles/emissary/internal/format"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -22,20 +22,10 @@ func UpdateCommand() *cli.Command {
Usage: "Set `STATUS` to selected agent", Usage: "Set `STATUS` to selected agent",
Value: -1, Value: -1,
}, },
&cli.StringFlag{
Name: "label",
Usage: "Set `LABEL` to selected agent",
Value: "",
},
), ),
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
baseFlags := clientFlag.GetBaseFlags(ctx) baseFlags := clientFlag.GetBaseFlags(ctx)
token, err := clientFlag.GetToken(baseFlags)
if err != nil {
return errors.WithStack(apierr.Wrap(err))
}
agentID, err := agentFlag.AssertAgentID(ctx) agentID, err := agentFlag.AssertAgentID(ctx)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
@ -48,12 +38,7 @@ func UpdateCommand() *cli.Command {
options = append(options, client.WithAgentStatus(status)) options = append(options, client.WithAgentStatus(status))
} }
label := ctx.String("label") client := client.New(baseFlags.ServerURL)
if label != "" {
options = append(options, client.WithAgentLabel(label))
}
client := client.New(baseFlags.ServerURL, client.WithToken(token))
agent, err := client.UpdateAgent(ctx.Context, agentID, options...) agent, err := client.UpdateAgent(ctx.Context, agentID, options...)
if err != nil { if err != nil {

View File

@ -7,10 +7,9 @@ func agentHints(outputMode format.OutputMode) format.Hints {
OutputMode: outputMode, OutputMode: outputMode,
Props: []format.Prop{ Props: []format.Prop{
format.NewProp("ID", "ID"), format.NewProp("ID", "ID"),
format.NewProp("Label", "Label"),
format.NewProp("Thumbprint", "Thumbprint"), format.NewProp("Thumbprint", "Thumbprint"),
format.NewProp("Status", "Status"), format.NewProp("Status", "Status"),
format.NewProp("ContactedAt", "ContactedAt"), format.NewProp("CreatedAt", "CreatedAt"),
format.NewProp("UpdatedAt", "UpdatedAt"), format.NewProp("UpdatedAt", "UpdatedAt"),
}, },
} }

View File

@ -2,13 +2,9 @@ package flag
import ( import (
"fmt" "fmt"
"io/ioutil"
"os"
"strings"
"forge.cadoles.com/Cadoles/emissary/internal/format" "forge.cadoles.com/Cadoles/emissary/internal/format"
"forge.cadoles.com/Cadoles/emissary/internal/format/table" "forge.cadoles.com/Cadoles/emissary/internal/format/table"
"github.com/pkg/errors"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -32,17 +28,6 @@ func ComposeFlags(flags ...cli.Flag) []cli.Flag {
Usage: fmt.Sprintf("use `MODE` as output mode (available: %s)", []format.OutputMode{format.OutputModeCompact, format.OutputModeWide}), Usage: fmt.Sprintf("use `MODE` as output mode (available: %s)", []format.OutputMode{format.OutputModeCompact, format.OutputModeWide}),
Value: string(format.OutputModeCompact), Value: string(format.OutputModeCompact),
}, },
&cli.StringFlag{
Name: "token",
Aliases: []string{"t"},
Usage: "use `TOKEN` as authentication token",
},
&cli.StringFlag{
Name: "token-file",
Usage: "use `TOKEN_FILE` as file containing the authentication token",
Value: ".emissary-token",
TakesFile: true,
},
} }
flags = append(flags, baseFlags...) flags = append(flags, baseFlags...)
@ -54,43 +39,16 @@ type BaseFlags struct {
ServerURL string ServerURL string
Format format.Format Format format.Format
OutputMode format.OutputMode OutputMode format.OutputMode
Token string
TokenFile string
} }
func GetBaseFlags(ctx *cli.Context) *BaseFlags { func GetBaseFlags(ctx *cli.Context) *BaseFlags {
serverURL := ctx.String("server") serverURL := ctx.String("server")
rawFormat := ctx.String("format") rawFormat := ctx.String("format")
rawOutputMode := ctx.String("output-mode") rawOutputMode := ctx.String("output-mode")
tokenFile := ctx.String("token-file")
token := ctx.String("token")
return &BaseFlags{ return &BaseFlags{
ServerURL: serverURL, ServerURL: serverURL,
Format: format.Format(rawFormat), Format: format.Format(rawFormat),
OutputMode: format.OutputMode(rawOutputMode), OutputMode: format.OutputMode(rawOutputMode),
Token: token,
TokenFile: tokenFile,
} }
} }
func GetToken(flags *BaseFlags) (string, error) {
if flags.Token != "" {
return flags.Token, nil
}
if flags.TokenFile == "" {
return "", nil
}
rawToken, err := ioutil.ReadFile(flags.TokenFile)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return "", errors.WithStack(err)
}
if rawToken == nil {
return "", nil
}
return strings.TrimSpace(string(rawToken)), nil
}

View File

@ -0,0 +1,20 @@
package client
import (
"forge.cadoles.com/Cadoles/emissary/internal/command/client/agent"
"github.com/urfave/cli/v2"
// Output format
_ "forge.cadoles.com/Cadoles/emissary/internal/format/json"
_ "forge.cadoles.com/Cadoles/emissary/internal/format/table"
)
func Root() *cli.Command {
return &cli.Command{
Name: "client",
Usage: "Client related commands",
Subcommands: []*cli.Command{
agent.Root(),
},
}
}

View File

@ -6,6 +6,7 @@ import (
"forge.cadoles.com/Cadoles/emissary/internal/command/common" "forge.cadoles.com/Cadoles/emissary/internal/command/common"
"forge.cadoles.com/Cadoles/emissary/internal/config" "forge.cadoles.com/Cadoles/emissary/internal/config"
"github.com/pkg/errors" "github.com/pkg/errors"
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/logger"
) )

View File

@ -9,6 +9,10 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
// Spec validation
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/gateway"
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/uci"
) )
func Main(buildDate, projectVersion, gitRef, defaultConfigPath string, commands ...*cli.Command) { func Main(buildDate, projectVersion, gitRef, defaultConfigPath string, commands ...*cli.Command) {

View File

@ -1,54 +0,0 @@
package auth
import (
"fmt"
"forge.cadoles.com/Cadoles/emissary/internal/auth/thirdparty"
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
"github.com/lithammer/shortuuid/v4"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
func CreateTokenCommand() *cli.Command {
return &cli.Command{
Name: "create-token",
Usage: "Create a new authentication token",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "role",
Usage: fmt.Sprintf("associate `ROLE` to the token (available: %v)", []thirdparty.Role{thirdparty.RoleReader, thirdparty.RoleWriter}),
Value: string(thirdparty.RoleReader),
},
&cli.StringFlag{
Name: "subject",
Usage: "associate `SUBJECT` to the token",
Value: fmt.Sprintf("user-%s", shortuuid.New()),
},
},
Action: func(ctx *cli.Context) error {
conf, err := common.LoadConfig(ctx)
if err != nil {
return errors.Wrap(err, "Could not load configuration")
}
subject := ctx.String("subject")
role := ctx.String("role")
key, err := jwk.LoadOrGenerate(string(conf.Server.PrivateKeyPath), jwk.DefaultKeySize)
if err != nil {
return errors.WithStack(err)
}
token, err := thirdparty.GenerateToken(ctx.Context, key, string(conf.Server.Issuer), subject, thirdparty.Role(role))
if err != nil {
return errors.WithStack(err)
}
fmt.Println(token)
return nil
},
}
}

View File

@ -6,10 +6,8 @@ import (
func Root() *cli.Command { func Root() *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "auth", Name: "auth",
Usage: "Authentication related commands", Usage: "Authentication related commands",
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{},
CreateTokenCommand(),
},
} }
} }

View File

@ -7,6 +7,7 @@ import (
"forge.cadoles.com/Cadoles/emissary/internal/command/common" "forge.cadoles.com/Cadoles/emissary/internal/command/common"
"forge.cadoles.com/Cadoles/emissary/internal/server" "forge.cadoles.com/Cadoles/emissary/internal/server"
"github.com/pkg/errors" "github.com/pkg/errors"
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/logger"
) )

View File

@ -1,7 +1,5 @@
package config package config
import "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt"
type AgentConfig struct { type AgentConfig struct {
ServerURL InterpolatedString `yaml:"serverUrl"` ServerURL InterpolatedString `yaml:"serverUrl"`
PrivateKeyPath InterpolatedString `yaml:"privateKeyPath"` PrivateKeyPath InterpolatedString `yaml:"privateKeyPath"`
@ -19,11 +17,9 @@ type ShellCollectorConfig struct {
type ControllersConfig struct { type ControllersConfig struct {
Persistence PersistenceControllerConfig `yaml:"persistence"` Persistence PersistenceControllerConfig `yaml:"persistence"`
Spec SpecControllerConfig `yaml:"spec"` Spec SpecControllerConfig `yaml:"spec"`
Proxy ProxyControllerConfig `yaml:"proxy"` Gateway GatewayControllerConfig `yaml:"gateway"`
UCI UCIControllerConfig `yaml:"uci"` UCI UCIControllerConfig `yaml:"uci"`
App AppControllerConfig `yaml:"app"` App AppControllerConfig `yaml:"app"`
SysUpgrade SysUpgradeControllerConfig `yaml:"sysupgrade"`
MDNS MDNSControllerConfig `yaml:"mdns"`
} }
type PersistenceControllerConfig struct { type PersistenceControllerConfig struct {
@ -34,7 +30,7 @@ type PersistenceControllerConfig struct {
type SpecControllerConfig struct { type SpecControllerConfig struct {
Enabled InterpolatedBool `yaml:"enabled"` Enabled InterpolatedBool `yaml:"enabled"`
} }
type ProxyControllerConfig struct { type GatewayControllerConfig struct {
Enabled InterpolatedBool `yaml:"enabled"` Enabled InterpolatedBool `yaml:"enabled"`
} }
@ -50,16 +46,6 @@ type AppControllerConfig struct {
DownloadDir InterpolatedString `yaml:"downloadDir"` DownloadDir InterpolatedString `yaml:"downloadDir"`
} }
type SysUpgradeControllerConfig struct {
Enabled InterpolatedBool `yaml:"enabled"`
SysUpgradeCommand InterpolatedStringSlice `yaml:"sysupgradeCommand"`
FirmwareVersionCommand InterpolatedStringSlice `yaml:"firmwareVersionCommand"`
}
type MDNSControllerConfig struct {
Enabled InterpolatedBool `yaml:"enabled"`
}
func NewDefaultAgentConfig() AgentConfig { func NewDefaultAgentConfig() AgentConfig {
return AgentConfig{ return AgentConfig{
ServerURL: "http://127.0.0.1:3000", ServerURL: "http://127.0.0.1:3000",
@ -73,7 +59,7 @@ func NewDefaultAgentConfig() AgentConfig {
Enabled: true, Enabled: true,
StateFile: "state.json", StateFile: "state.json",
}, },
Proxy: ProxyControllerConfig{ Gateway: GatewayControllerConfig{
Enabled: true, Enabled: true,
}, },
UCI: UCIControllerConfig{ UCI: UCIControllerConfig{
@ -86,14 +72,6 @@ func NewDefaultAgentConfig() AgentConfig {
DataDir: "apps/data", DataDir: "apps/data",
DownloadDir: "apps/bundles", DownloadDir: "apps/bundles",
}, },
SysUpgrade: SysUpgradeControllerConfig{
Enabled: true,
SysUpgradeCommand: InterpolatedStringSlice{"sysupgrade", "--force", "-u", "-v", openwrt.FirmwareFileTemplate},
FirmwareVersionCommand: InterpolatedStringSlice{"sh", "-c", `source /etc/openwrt_release && echo "$DISTRIB_ID-$DISTRIB_RELEASE-$DISTRIB_REVISION"`},
},
MDNS: MDNSControllerConfig{
Enabled: true,
},
}, },
Collectors: []ShellCollectorConfig{ Collectors: []ShellCollectorConfig{
{ {

View File

@ -15,6 +15,6 @@ type DatabaseConfig struct {
func NewDefaultDatabaseConfig() DatabaseConfig { func NewDefaultDatabaseConfig() DatabaseConfig {
return DatabaseConfig{ return DatabaseConfig{
Driver: "sqlite", Driver: "sqlite",
DSN: "sqlite://emissary.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000", DSN: "sqlite://emissary.sqlite",
} }
} }

View File

@ -20,15 +20,13 @@ const (
) )
type Agent struct { type Agent struct {
ID AgentID `json:"id"` ID AgentID `json:"id"`
Label string `json:"label"` Thumbprint string `json:"thumbprint"`
Thumbprint string `json:"thumbprint"` KeySet *SerializableKeySet `json:"keyset,omitempty"`
KeySet *SerializableKeySet `json:"keyset,omitempty"` Metadata map[string]any `json:"metadata,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"` Status AgentStatus `json:"status"`
Status AgentStatus `json:"status"` CreatedAt time.Time `json:"createdAt"`
CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"`
UpdatedAt time.Time `json:"updatedAt"`
ContactedAt *time.Time `json:"contactedAt,omitempty"`
} }
type SerializableKeySet struct { type SerializableKeySet struct {

View File

@ -2,7 +2,6 @@ package datastore
import ( import (
"context" "context"
"time"
"github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwk"
) )
@ -69,12 +68,10 @@ func WithAgentQueryThumbprints(thumbprints ...string) AgentQueryOptionFunc {
type AgentUpdateOptionFunc func(*AgentUpdateOptions) type AgentUpdateOptionFunc func(*AgentUpdateOptions)
type AgentUpdateOptions struct { type AgentUpdateOptions struct {
Label *string Status *AgentStatus
Status *AgentStatus Metadata *map[string]any
ContactedAt *time.Time KeySet *jwk.Set
Metadata *map[string]any Thumbprint *string
KeySet *jwk.Set
Thumbprint *string
} }
func WithAgentUpdateStatus(status AgentStatus) AgentUpdateOptionFunc { func WithAgentUpdateStatus(status AgentStatus) AgentUpdateOptionFunc {
@ -100,15 +97,3 @@ func WithAgentUpdateThumbprint(thumbprint string) AgentUpdateOptionFunc {
opts.Thumbprint = &thumbprint opts.Thumbprint = &thumbprint
} }
} }
func WithAgentUpdateLabel(label string) AgentUpdateOptionFunc {
return func(opts *AgentUpdateOptions) {
opts.Label = &label
}
}
func WithAgentUpdateContactedAt(contactedAt time.Time) AgentUpdateOptionFunc {
return func(opts *AgentUpdateOptions) {
opts.ContactedAt = &contactedAt
}
}

View File

@ -20,24 +20,9 @@ type AgentRepository struct {
// DeleteSpec implements datastore.AgentRepository. // DeleteSpec implements datastore.AgentRepository.
func (r *AgentRepository) DeleteSpec(ctx context.Context, agentID datastore.AgentID, name string) error { func (r *AgentRepository) DeleteSpec(ctx context.Context, agentID datastore.AgentID, name string) error {
err := r.withTx(ctx, func(tx *sql.Tx) error { query := `DELETE FROM specs WHERE agent_id = $1 AND name = $2`
exists, err := r.agentExists(ctx, tx, agentID)
if err != nil {
return errors.WithStack(err)
}
if !exists { _, err := r.db.ExecContext(ctx, query, agentID, name)
return errors.WithStack(datastore.ErrNotFound)
}
query := `DELETE FROM specs WHERE agent_id = $1 AND name = $2`
if _, err = tx.ExecContext(ctx, query, agentID, name); err != nil {
return errors.WithStack(err)
}
return nil
})
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
@ -49,57 +34,33 @@ func (r *AgentRepository) DeleteSpec(ctx context.Context, agentID datastore.Agen
func (r *AgentRepository) GetSpecs(ctx context.Context, agentID datastore.AgentID) ([]*datastore.Spec, error) { func (r *AgentRepository) GetSpecs(ctx context.Context, agentID datastore.AgentID) ([]*datastore.Spec, error) {
specs := make([]*datastore.Spec, 0) specs := make([]*datastore.Spec, 0)
err := r.withTx(ctx, func(tx *sql.Tx) error { query := `
exists, err := r.agentExists(ctx, tx, agentID)
if err != nil {
return errors.WithStack(err)
}
if !exists {
return errors.WithStack(datastore.ErrNotFound)
}
query := `
SELECT id, name, revision, data, created_at, updated_at SELECT id, name, revision, data, created_at, updated_at
FROM specs FROM specs
WHERE agent_id = $1 WHERE agent_id = $1
` `
rows, err := tx.QueryContext(ctx, query, agentID) rows, err := r.db.QueryContext(ctx, query, agentID)
if err != nil {
return errors.WithStack(err)
}
defer func() {
if err := rows.Close(); err != nil {
logger.Error(ctx, "could not close rows", logger.E(errors.WithStack(err)))
}
}()
for rows.Next() {
spec := &datastore.Spec{}
data := JSONMap{}
if err := rows.Scan(&spec.ID, &spec.Name, &spec.Revision, &data, &spec.CreatedAt, &spec.UpdatedAt); err != nil {
return errors.WithStack(err)
}
spec.Data = data
specs = append(specs, spec)
}
if err := rows.Err(); err != nil {
return errors.WithStack(err)
}
return nil
})
if err != nil { if err != nil {
return nil, errors.WithStack(err) return nil, errors.WithStack(err)
} }
defer rows.Close()
for rows.Next() {
spec := &datastore.Spec{}
data := JSONMap{}
if err := rows.Scan(&spec.ID, &spec.Name, &spec.Revision, &data, &spec.CreatedAt, &spec.UpdatedAt); err != nil {
return nil, errors.WithStack(err)
}
spec.Data = data
specs = append(specs, spec)
}
return specs, nil return specs, nil
} }
@ -108,15 +69,6 @@ func (r *AgentRepository) UpdateSpec(ctx context.Context, agentID datastore.Agen
spec := &datastore.Spec{} spec := &datastore.Spec{}
err := r.withTx(ctx, func(tx *sql.Tx) error { err := r.withTx(ctx, func(tx *sql.Tx) error {
exists, err := r.agentExists(ctx, tx, agentID)
if err != nil {
return errors.WithStack(err)
}
if !exists {
return errors.WithStack(datastore.ErrNotFound)
}
now := time.Now().UTC() now := time.Now().UTC()
query := ` query := `
@ -136,7 +88,7 @@ func (r *AgentRepository) UpdateSpec(ctx context.Context, agentID datastore.Agen
data := JSONMap{} data := JSONMap{}
err = row.Scan(&spec.ID, &spec.Name, &spec.Revision, &data, &spec.CreatedAt, &spec.UpdatedAt) err := row.Scan(&spec.ID, &spec.Name, &spec.Revision, &data, &spec.CreatedAt, &spec.UpdatedAt)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return errors.WithStack(datastore.ErrUnexpectedRevision) return errors.WithStack(datastore.ErrUnexpectedRevision)
@ -167,7 +119,7 @@ func (r *AgentRepository) Query(ctx context.Context, opts ...datastore.AgentQuer
count := 0 count := 0
err := r.withTx(ctx, func(tx *sql.Tx) error { err := r.withTx(ctx, func(tx *sql.Tx) error {
query := `SELECT id, label, thumbprint, status, contacted_at, created_at, updated_at FROM agents` query := `SELECT id, thumbprint, status, created_at, updated_at FROM agents`
limit := 10 limit := 10
if options.Limit != nil { if options.Limit != nil {
@ -224,34 +176,22 @@ func (r *AgentRepository) Query(ctx context.Context, opts ...datastore.AgentQuer
return errors.WithStack(err) return errors.WithStack(err)
} }
defer func() { defer rows.Close()
if err := rows.Close(); err != nil {
logger.Error(ctx, "could not close rows", logger.E(errors.WithStack(err)))
}
}()
for rows.Next() { for rows.Next() {
agent := &datastore.Agent{} agent := &datastore.Agent{}
metadata := JSONMap{} metadata := JSONMap{}
contactedAt := sql.NullTime{}
if err := rows.Scan(&agent.ID, &agent.Label, &agent.Thumbprint, &agent.Status, &contactedAt, &agent.CreatedAt, &agent.UpdatedAt); err != nil { if err := rows.Scan(&agent.ID, &agent.Thumbprint, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
agent.Metadata = metadata agent.Metadata = metadata
if contactedAt.Valid {
agent.ContactedAt = &contactedAt.Time
}
agents = append(agents, agent) agents = append(agents, agent)
} }
if err := rows.Err(); err != nil {
return errors.WithStack(err)
}
row := tx.QueryRowContext(ctx, `SELECT count(id) FROM agents `+filters, args...) row := tx.QueryRowContext(ctx, `SELECT count(id) FROM agents `+filters, args...)
if err := row.Scan(&count); err != nil { if err := row.Scan(&count); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
@ -329,21 +269,9 @@ func (r *AgentRepository) Create(ctx context.Context, thumbprint string, keySet
// Delete implements datastore.AgentRepository // Delete implements datastore.AgentRepository
func (r *AgentRepository) Delete(ctx context.Context, id datastore.AgentID) error { func (r *AgentRepository) Delete(ctx context.Context, id datastore.AgentID) error {
err := r.withTx(ctx, func(tx *sql.Tx) error { query := `DELETE FROM agents WHERE id = $1`
query := `DELETE FROM agents WHERE id = $1`
_, err := r.db.ExecContext(ctx, query, id)
if err != nil {
return errors.WithStack(err)
}
query = `DELETE FROM specs WHERE agent_id = $1` _, err := r.db.ExecContext(ctx, query, id)
_, err = r.db.ExecContext(ctx, query, id)
if err != nil {
return errors.WithStack(err)
}
return nil
})
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
@ -359,7 +287,7 @@ func (r *AgentRepository) Get(ctx context.Context, id datastore.AgentID) (*datas
err := r.withTx(ctx, func(tx *sql.Tx) error { err := r.withTx(ctx, func(tx *sql.Tx) error {
query := ` query := `
SELECT "id", "label", "thumbprint", "keyset", "metadata", "status", "contacted_at", "created_at", "updated_at" SELECT "id", "thumbprint", "keyset", "metadata", "status", "created_at", "updated_at"
FROM agents FROM agents
WHERE id = $1 WHERE id = $1
` `
@ -367,10 +295,9 @@ func (r *AgentRepository) Get(ctx context.Context, id datastore.AgentID) (*datas
row := r.db.QueryRowContext(ctx, query, id) row := r.db.QueryRowContext(ctx, query, id)
metadata := JSONMap{} metadata := JSONMap{}
contactedAt := sql.NullTime{}
var rawKeySet []byte var rawKeySet []byte
if err := row.Scan(&agent.ID, &agent.Label, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &contactedAt, &agent.CreatedAt, &agent.UpdatedAt); err != nil { if err := row.Scan(&agent.ID, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return datastore.ErrNotFound return datastore.ErrNotFound
} }
@ -379,9 +306,6 @@ func (r *AgentRepository) Get(ctx context.Context, id datastore.AgentID) (*datas
} }
agent.Metadata = metadata agent.Metadata = metadata
if contactedAt.Valid {
agent.ContactedAt = &contactedAt.Time
}
keySet := jwk.NewSet() keySet := jwk.NewSet()
if err := json.Unmarshal(rawKeySet, &keySet); err != nil { if err := json.Unmarshal(rawKeySet, &keySet); err != nil {
@ -410,11 +334,15 @@ func (r *AgentRepository) Update(ctx context.Context, id datastore.AgentID, opts
err := r.withTx(ctx, func(tx *sql.Tx) error { err := r.withTx(ctx, func(tx *sql.Tx) error {
query := ` query := `
UPDATE agents SET id = $1 UPDATE agents SET updated_at = $2
` `
args := []any{id} now := time.Now().UTC()
index := 2
args := []any{
id, now,
}
index := 3
if options.Status != nil { if options.Status != nil {
query += fmt.Sprintf(`, status = $%d`, index) query += fmt.Sprintf(`, status = $%d`, index)
@ -439,51 +367,23 @@ func (r *AgentRepository) Update(ctx context.Context, id datastore.AgentID, opts
index++ index++
} }
if options.Label != nil {
query += fmt.Sprintf(`, label = $%d`, index)
args = append(args, *options.Label)
index++
}
if options.ContactedAt != nil {
query += fmt.Sprintf(`, contacted_at = $%d`, index)
utc := options.ContactedAt.UTC()
args = append(args, utc)
index++
}
if options.Metadata != nil { if options.Metadata != nil {
query += fmt.Sprintf(`, metadata = $%d`, index) query += fmt.Sprintf(`, metadata = $%d`, index)
args = append(args, JSONMap(*options.Metadata)) args = append(args, JSONMap(*options.Metadata))
index++ index++
} }
updated := options.Metadata != nil ||
options.Status != nil ||
options.Label != nil ||
options.KeySet != nil ||
options.Thumbprint != nil
if updated {
now := time.Now().UTC()
query += fmt.Sprintf(`, updated_at = $%d`, index)
args = append(args, now)
index++
}
query += ` query += `
WHERE id = $1 WHERE id = $1
RETURNING "id", "label", "thumbprint", "keyset", "metadata", "status", "contacted_at", "created_at", "updated_at" RETURNING "id", "thumbprint", "keyset", "metadata", "status", "created_at", "updated_at"
` `
logger.Debug(ctx, "executing query", logger.F("query", query), logger.F("args", args))
row := tx.QueryRowContext(ctx, query, args...) row := tx.QueryRowContext(ctx, query, args...)
metadata := JSONMap{} metadata := JSONMap{}
contactedAt := sql.NullTime{}
var rawKeySet []byte var rawKeySet []byte
if err := row.Scan(&agent.ID, &agent.Label, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &contactedAt, &agent.CreatedAt, &agent.UpdatedAt); err != nil { if err := row.Scan(&agent.ID, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return datastore.ErrNotFound return datastore.ErrNotFound
} }
@ -492,9 +392,6 @@ func (r *AgentRepository) Update(ctx context.Context, id datastore.AgentID, opts
} }
agent.Metadata = metadata agent.Metadata = metadata
if contactedAt.Valid {
agent.ContactedAt = &contactedAt.Time
}
keySet := jwk.NewSet() keySet := jwk.NewSet()
if err := json.Unmarshal(rawKeySet, &keySet); err != nil { if err := json.Unmarshal(rawKeySet, &keySet); err != nil {
@ -512,28 +409,8 @@ func (r *AgentRepository) Update(ctx context.Context, id datastore.AgentID, opts
return agent, nil return agent, nil
} }
func (r *AgentRepository) agentExists(ctx context.Context, tx *sql.Tx, agentID datastore.AgentID) (bool, error) {
row := tx.QueryRowContext(ctx, `SELECT count(id) FROM agents WHERE id = $1`, agentID)
var count int
if err := row.Scan(&count); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return false, errors.WithStack(datastore.ErrNotFound)
}
return false, errors.WithStack(err)
}
if count == 0 {
return false, errors.WithStack(datastore.ErrNotFound)
}
return true, nil
}
func (r *AgentRepository) withTx(ctx context.Context, fn func(*sql.Tx) error) error { func (r *AgentRepository) withTx(ctx context.Context, fn func(*sql.Tx) error) error {
tx, err := r.db.BeginTx(ctx, nil) tx, err := r.db.Begin()
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }

View File

@ -1,46 +0,0 @@
package sqlite
import (
"database/sql"
"fmt"
"os"
"testing"
"time"
"forge.cadoles.com/Cadoles/emissary/internal/datastore/testsuite"
"forge.cadoles.com/Cadoles/emissary/internal/migrate"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
_ "modernc.org/sqlite"
)
func TestSQLiteAgentRepository(t *testing.T) {
logger.SetLevel(logger.LevelDebug)
file := "testdata/agent_repository_test.sqlite"
if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) {
t.Fatalf("%+v", errors.WithStack(err))
}
dsn := fmt.Sprintf("%s?_pragma=foreign_keys(1)&_pragma=busy_timeout=%d", file, (60 * time.Second).Milliseconds())
migr, err := migrate.New("../../../migrations", "sqlite", "sqlite://"+dsn)
if err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
if err := migr.Up(); err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
db, err := sql.Open("sqlite", dsn)
if err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
repo := NewAgentRepository(db)
testsuite.TestAgentRepository(t, repo)
}

View File

@ -1 +0,0 @@
*.sqlite*

View File

@ -1,14 +0,0 @@
package testsuite
import (
"testing"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
)
func TestAgentRepository(t *testing.T, repo datastore.AgentRepository) {
t.Run("Cases", func(t *testing.T) {
t.Parallel()
runAgentRepositoryTests(t, repo)
})
}

View File

@ -1,129 +0,0 @@
package testsuite
import (
"context"
"testing"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/mdns/spec"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
"github.com/pkg/errors"
)
type agentRepositoryTestCase struct {
Name string
Skip bool
Run func(ctx context.Context, repo datastore.AgentRepository) error
}
var agentRepositoryTestCases = []agentRepositoryTestCase{
{
Name: "Create a new agent",
Run: func(ctx context.Context, repo datastore.AgentRepository) error {
thumbprint := "foo"
keySet := jwk.NewSet()
var metadata map[string]any
agent, err := repo.Create(ctx, thumbprint, keySet, metadata)
if err != nil {
return errors.WithStack(err)
}
if agent.CreatedAt.IsZero() {
return errors.Errorf("agent.CreatedAt should not be zero time")
}
if agent.UpdatedAt.IsZero() {
return errors.Errorf("agent.UpdatedAt should not be zero time")
}
if e, g := datastore.AgentStatusPending, agent.Status; e != g {
return errors.Errorf("agent.Status: expected '%v', got '%v'", e, g)
}
return nil
},
},
{
Name: "Try to update spec for an unexistant agent",
Run: func(ctx context.Context, repo datastore.AgentRepository) error {
var unexistantAgentID datastore.AgentID = 9999
var specData map[string]any
agent, err := repo.UpdateSpec(ctx, unexistantAgentID, string(spec.Name), 0, specData)
if err == nil {
return errors.New("error should not be nil")
}
if !errors.Is(err, datastore.ErrNotFound) {
return errors.Errorf("error should be datastore.ErrNotFound, got '%+v'", err)
}
if agent != nil {
return errors.New("agent should be nil")
}
return nil
},
},
{
Name: "Try to delete spec of an unexistant agent",
Run: func(ctx context.Context, repo datastore.AgentRepository) error {
var unexistantAgentID datastore.AgentID = 9999
err := repo.DeleteSpec(ctx, unexistantAgentID, string(spec.Name))
if err == nil {
return errors.New("error should not be nil")
}
if !errors.Is(err, datastore.ErrNotFound) {
return errors.Errorf("error should be datastore.ErrNotFound, got '%+v'", err)
}
return nil
},
},
{
Name: "Try to get specs of an unexistant agent",
Run: func(ctx context.Context, repo datastore.AgentRepository) error {
var unexistantAgentID datastore.AgentID = 9999
specs, err := repo.GetSpecs(ctx, unexistantAgentID)
if err == nil {
return errors.New("error should not be nil")
}
if !errors.Is(err, datastore.ErrNotFound) {
return errors.Errorf("error should be datastore.ErrNotFound, got '%+v'", err)
}
if specs != nil {
return errors.Errorf("specs should be nil, got '%+v'", err)
}
return nil
},
},
}
func runAgentRepositoryTests(t *testing.T, repo datastore.AgentRepository) {
for _, tc := range agentRepositoryTestCases {
func(tc agentRepositoryTestCase) {
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
if tc.Skip {
t.SkipNow()
return
}
ctx := context.Background()
if err := tc.Run(ctx, repo); err != nil {
t.Errorf("%+v", errors.WithStack(err))
}
})
}(tc)
}
}

View File

@ -1,6 +0,0 @@
package format
import (
_ "forge.cadoles.com/Cadoles/emissary/internal/format/json"
_ "forge.cadoles.com/Cadoles/emissary/internal/format/table"
)

View File

@ -1,6 +0,0 @@
package passwd
import (
_ "forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd/argon2id"
_ "forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd/plain"
)

View File

@ -1,9 +0,0 @@
package spec
import (
_ "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
_ "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/mdns/spec"
_ "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt/spec/sysupgrade"
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/proxy"
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/uci"
)

View File

@ -1,6 +0,0 @@
package sql
import (
_ "github.com/jackc/pgx/v5/stdlib"
_ "modernc.org/sqlite"
)

View File

@ -23,13 +23,6 @@ type (
ParseOption = jwk.ParseOption ParseOption = jwk.ParseOption
) )
var (
FromRaw = jwk.FromRaw
NewSet = jwk.NewSet
)
const AlgorithmKey = jwk.AlgorithmKey
func Parse(src []byte, options ...jwk.ParseOption) (Set, error) { func Parse(src []byte, options ...jwk.ParseOption) (Set, error) {
return jwk.Parse(src, options...) return jwk.Parse(src, options...)
} }

View File

@ -2,6 +2,7 @@ package migrate
import ( import (
"fmt" "fmt"
"log"
"github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/database/postgres"
@ -22,6 +23,8 @@ func New(migrationDir, driver, dsn string) (*migrate.Migrate, error) {
fmt.Sprintf("file://%s/%s", migrationDir, driver), fmt.Sprintf("file://%s/%s", migrationDir, driver),
dsn, dsn,
) )
log.Println(migrationDir, driver, dsn)
if err != nil { if err != nil {
return nil, errors.WithStack(err) return nil, errors.WithStack(err)
} }

View File

@ -1,29 +0,0 @@
package proxy
import (
"net/http"
"forge.cadoles.com/Cadoles/emissary/internal/proxy/wildcard"
)
func FilterHosts(allowedHostPatterns ...string) Middleware {
return func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
if matches := wildcard.MatchAny(r.Host, allowedHostPatterns...); !matches {
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}
func WithAllowedHosts(allowedHostPatterns ...string) OptionFunc {
return func(o *Options) {
o.Middlewares = append(o.Middlewares, FilterHosts(allowedHostPatterns...))
}
}

View File

@ -1,66 +0,0 @@
package proxy
import (
"net/http"
"net/url"
"sort"
"forge.cadoles.com/Cadoles/emissary/internal/proxy/wildcard"
"gitlab.com/wpetit/goweb/logger"
)
func RewriteHosts(mappings map[string]*url.URL) Middleware {
patterns := make([]string, len(mappings))
for p := range mappings {
patterns = append(patterns, p)
}
sort.Strings(patterns)
reverse(patterns)
return func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var match *url.URL
for _, p := range patterns {
logger.Debug(ctx, "matching host to pattern", logger.F("host", r.Host), logger.F("pattern", p))
if matches := wildcard.Match(r.Host, p); !matches {
continue
}
match = mappings[p]
break
}
if match == nil {
h.ServeHTTP(w, r)
return
}
ctx = logger.With(ctx, logger.F("originalHost", r.Host))
r = r.WithContext(ctx)
originalURL := r.URL.String()
r.URL.Host = match.Host
r.URL.Scheme = match.Scheme
logger.Debug(ctx, "rewriting url", logger.F("from", originalURL), logger.F("to", r.URL.String()))
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}
func WithRewriteHosts(mappings map[string]*url.URL) OptionFunc {
return func(o *Options) {
o.Middlewares = append(o.Middlewares, RewriteHosts(mappings))
}
}

View File

@ -1,33 +0,0 @@
package proxy
import "net/http"
type Middleware func(h http.Handler) http.Handler
type ProxyResponseTransformer interface {
TransformResponse(*http.Response) error
}
type defaultProxyResponseTransformer struct{}
// TransformResponse implements ProxyResponseTransformer
func (*defaultProxyResponseTransformer) TransformResponse(*http.Response) error {
return nil
}
var _ ProxyResponseTransformer = &defaultProxyResponseTransformer{}
type ProxyResponseMiddleware func(ProxyResponseTransformer) ProxyResponseTransformer
type ProxyRequestTransformer interface {
TransformRequest(*http.Request)
}
type ProxyRequestMiddleware func(ProxyRequestTransformer) ProxyRequestTransformer
type defaultProxyRequestTransformer struct{}
// TransformRequest implements ProxyRequestTransformer
func (*defaultProxyRequestTransformer) TransformRequest(*http.Request) {}
var _ ProxyRequestTransformer = &defaultProxyRequestTransformer{}

View File

@ -1,29 +0,0 @@
package proxy
type Options struct {
Middlewares []Middleware
ProxyRequestMiddlewares []ProxyRequestMiddleware
ProxyResponseMiddlewares []ProxyResponseMiddleware
}
func defaultOptions() *Options {
return &Options{
Middlewares: make([]Middleware, 0),
ProxyRequestMiddlewares: make([]ProxyRequestMiddleware, 0),
ProxyResponseMiddlewares: make([]ProxyResponseMiddleware, 0),
}
}
type OptionFunc func(*Options)
func WithProxyRequestMiddlewares(middlewares ...ProxyRequestMiddleware) OptionFunc {
return func(o *Options) {
o.ProxyRequestMiddlewares = middlewares
}
}
func WithproxyResponseMiddlewares(middlewares ...ProxyResponseMiddleware) OptionFunc {
return func(o *Options) {
o.ProxyResponseMiddlewares = middlewares
}
}

View File

@ -1,131 +0,0 @@
package proxy
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"sync"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
type Proxy struct {
reversers sync.Map
handler http.Handler
proxyResponseTransformer ProxyResponseTransformer
proxyRequestTransformer ProxyRequestTransformer
}
// ServeHTTP implements http.Handler
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
p.handler.ServeHTTP(w, r)
}
func (p *Proxy) proxyRequest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reverser *httputil.ReverseProxy
key := fmt.Sprintf("%s://%s", r.URL.Scheme, r.URL.Host)
createAndStore := func() {
target := &url.URL{
Scheme: r.URL.Scheme,
Host: r.URL.Host,
}
reverser = httputil.NewSingleHostReverseProxy(target)
originalDirector := reverser.Director
if p.proxyRequestTransformer != nil {
reverser.Director = func(r *http.Request) {
originalURL := r.URL.String()
originalDirector(r)
p.proxyRequestTransformer.TransformRequest(r)
logger.Debug(ctx, "proxying request", logger.F("targetURL", r.URL.String()), logger.F("originalURL", originalURL))
}
}
if p.proxyResponseTransformer != nil {
reverser.ModifyResponse = func(r *http.Response) error {
if err := p.proxyResponseTransformer.TransformResponse(r); err != nil {
return errors.WithStack(err)
}
return nil
}
}
p.reversers.Store(key, reverser)
}
raw, exists := p.reversers.Load(key)
if !exists {
createAndStore()
}
reverser, ok := raw.(*httputil.ReverseProxy)
if !ok {
createAndStore()
}
reverser.ServeHTTP(w, r)
}
func New(funcs ...OptionFunc) *Proxy {
opts := defaultOptions()
for _, fn := range funcs {
fn(opts)
}
proxy := &Proxy{}
handler := http.HandlerFunc(proxy.proxyRequest)
proxy.handler = createMiddlewareChain(handler, opts.Middlewares)
proxy.proxyRequestTransformer = createProxyRequestChain(&defaultProxyRequestTransformer{}, opts.ProxyRequestMiddlewares)
proxy.proxyResponseTransformer = createProxyResponseChain(&defaultProxyResponseTransformer{}, opts.ProxyResponseMiddlewares)
return proxy
}
var _ http.Handler = &Proxy{}
func createMiddlewareChain(handler http.Handler, middlewares []Middleware) http.Handler {
reverse(middlewares)
for _, m := range middlewares {
handler = m(handler)
}
return handler
}
func createProxyResponseChain(transformer ProxyResponseTransformer, middlewares []ProxyResponseMiddleware) ProxyResponseTransformer {
reverse(middlewares)
for _, m := range middlewares {
transformer = m(transformer)
}
return transformer
}
func createProxyRequestChain(transformer ProxyRequestTransformer, middlewares []ProxyRequestMiddleware) ProxyRequestTransformer {
reverse(middlewares)
for _, m := range middlewares {
transformer = m(transformer)
}
return transformer
}
func reverse[S ~[]E, E any](s S) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}

View File

@ -1,44 +0,0 @@
package wildcard
const wildcard = '*'
func Match(str, pattern string) bool {
if pattern == "" {
return str == pattern
}
if pattern == string(wildcard) {
return true
}
return deepMatchRune([]rune(str), []rune(pattern))
}
func MatchAny(str string, patterns ...string) bool {
for _, p := range patterns {
if matches := Match(str, p); matches {
return matches
}
}
return false
}
func deepMatchRune(str, pattern []rune) bool {
for len(pattern) > 0 {
switch pattern[0] {
default:
if len(str) == 0 || str[0] != pattern[0] {
return false
}
case wildcard:
return deepMatchRune(str, pattern[1:]) ||
(len(str) > 0 && deepMatchRune(str[1:], pattern))
}
str = str[1:]
pattern = pattern[1:]
}
return len(str) == 0 && len(pattern) == 0
}

View File

@ -16,10 +16,9 @@ import (
) )
const ( const (
ErrCodeUnknownError api.ErrorCode = "unknown-error" ErrCodeUnknownError api.ErrorCode = "unknown-error"
ErrCodeNotFound api.ErrorCode = "not-found" ErrCodeNotFound api.ErrorCode = "not-found"
ErrCodeInvalidSignature api.ErrorCode = "invalid-signature" ErrInvalidSignature api.ErrorCode = "invalid-signature"
ErrCodeConflict api.ErrorCode = "conflict"
) )
type registerAgentRequest struct { type registerAgentRequest struct {
@ -47,8 +46,6 @@ func (s *Server) registerAgent(w http.ResponseWriter, r *http.Request) {
ctx = logger.With(ctx, logger.F("agentThumbprint", registerAgentReq.Thumbprint)) ctx = logger.With(ctx, logger.F("agentThumbprint", registerAgentReq.Thumbprint))
// Validate that the existing signature validates the request
validSignature, err := jwk.Verify(keySet, registerAgentReq.Signature, registerAgentReq.Thumbprint, registerAgentReq.Metadata) validSignature, err := jwk.Verify(keySet, registerAgentReq.Signature, registerAgentReq.Thumbprint, registerAgentReq.Metadata)
if err != nil { if err != nil {
logger.Error(ctx, "could not validate signature", logger.E(errors.WithStack(err))) logger.Error(ctx, "could not validate signature", logger.E(errors.WithStack(err)))
@ -58,8 +55,8 @@ func (s *Server) registerAgent(w http.ResponseWriter, r *http.Request) {
} }
if !validSignature { if !validSignature {
logger.Error(ctx, "conflicting signature", logger.F("signature", registerAgentReq.Signature)) logger.Error(ctx, "invalid signature", logger.F("signature", registerAgentReq.Signature))
api.ErrorResponse(w, http.StatusConflict, ErrCodeConflict, nil) api.ErrorResponse(w, http.StatusBadRequest, ErrInvalidSignature, nil)
return return
} }
@ -100,28 +97,6 @@ func (s *Server) registerAgent(w http.ResponseWriter, r *http.Request) {
return return
} }
agentID := agents[0].ID
agent, err = s.agentRepo.Get(ctx, agentID)
if err != nil {
logger.Error(
ctx, "could not retrieve agent",
logger.E(errors.WithStack(err)), logger.F("agentID", agentID),
)
api.ErrorResponse(w, http.StatusInternalServerError, ErrCodeUnknownError, nil)
return
}
validSignature, err = jwk.Verify(agent.KeySet.Set, registerAgentReq.Signature, registerAgentReq.Thumbprint, registerAgentReq.Metadata)
if err != nil {
logger.Error(ctx, "could not validate signature using previous keyset", logger.E(errors.WithStack(err)))
api.ErrorResponse(w, http.StatusConflict, ErrCodeConflict, nil)
return
}
agent, err = s.agentRepo.Update( agent, err = s.agentRepo.Update(
ctx, agents[0].ID, ctx, agents[0].ID,
datastore.WithAgentUpdateKeySet(keySet), datastore.WithAgentUpdateKeySet(keySet),
@ -145,7 +120,6 @@ func (s *Server) registerAgent(w http.ResponseWriter, r *http.Request) {
type updateAgentRequest struct { type updateAgentRequest struct {
Status *datastore.AgentStatus `json:"status" validate:"omitempty,oneof=0 1 2 3"` Status *datastore.AgentStatus `json:"status" validate:"omitempty,oneof=0 1 2 3"`
Label *string `json:"label" validate:"omitempty"`
} }
func (s *Server) updateAgent(w http.ResponseWriter, r *http.Request) { func (s *Server) updateAgent(w http.ResponseWriter, r *http.Request) {
@ -167,10 +141,6 @@ func (s *Server) updateAgent(w http.ResponseWriter, r *http.Request) {
options = append(options, datastore.WithAgentUpdateStatus(*updateAgentReq.Status)) options = append(options, datastore.WithAgentUpdateStatus(*updateAgentReq.Status))
} }
if updateAgentReq.Label != nil {
options = append(options, datastore.WithAgentUpdateLabel(*updateAgentReq.Label))
}
agent, err := s.agentRepo.Update( agent, err := s.agentRepo.Update(
ctx, ctx,
datastore.AgentID(agentID), datastore.AgentID(agentID),

View File

@ -1,155 +0,0 @@
package server
import (
"context"
"fmt"
"net/http"
"forge.cadoles.com/Cadoles/emissary/internal/auth"
"forge.cadoles.com/Cadoles/emissary/internal/auth/agent"
"forge.cadoles.com/Cadoles/emissary/internal/auth/thirdparty"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/api"
"gitlab.com/wpetit/goweb/logger"
)
var ErrCodeForbidden api.ErrorCode = "forbidden"
func assertGlobalReadAccess(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
reqUser, ok := assertRequestUser(w, r)
if !ok {
return
}
switch user := reqUser.(type) {
case *thirdparty.User:
role := user.Role()
if role == thirdparty.RoleReader || role == thirdparty.RoleWriter {
h.ServeHTTP(w, r)
return
}
case *agent.User:
// Agents dont have global read access
default:
logUnexpectedUserType(r.Context(), reqUser)
}
forbidden(w, r)
}
return http.HandlerFunc(fn)
}
func assertAgentWriteAccess(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
reqUser, ok := assertRequestUser(w, r)
if !ok {
return
}
agentID, ok := getAgentID(w, r)
if !ok {
return
}
switch user := reqUser.(type) {
case *thirdparty.User:
role := user.Role()
if role == thirdparty.RoleWriter {
h.ServeHTTP(w, r)
return
}
case *agent.User:
if user.Agent().ID == agentID {
h.ServeHTTP(w, r)
return
}
default:
logUnexpectedUserType(r.Context(), reqUser)
}
forbidden(w, r)
}
return http.HandlerFunc(fn)
}
func assertAgentReadAccess(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
reqUser, ok := assertRequestUser(w, r)
if !ok {
return
}
agentID, ok := getAgentID(w, r)
if !ok {
return
}
switch user := reqUser.(type) {
case *thirdparty.User:
role := user.Role()
if role == thirdparty.RoleReader || role == thirdparty.RoleWriter {
h.ServeHTTP(w, r)
return
}
case *agent.User:
if user.Agent().ID == agentID {
h.ServeHTTP(w, r)
return
}
default:
logUnexpectedUserType(r.Context(), reqUser)
}
forbidden(w, r)
}
return http.HandlerFunc(fn)
}
func assertRequestUser(w http.ResponseWriter, r *http.Request) (auth.User, bool) {
ctx := r.Context()
user, err := auth.CtxUser(ctx)
if err != nil {
logger.Error(ctx, "could not retrieve user", logger.E(errors.WithStack(err)))
forbidden(w, r)
return nil, false
}
if user == nil {
forbidden(w, r)
return nil, false
}
return user, true
}
func forbidden(w http.ResponseWriter, r *http.Request) {
logger.Warn(r.Context(), "forbidden", logger.F("path", r.URL.Path))
api.ErrorResponse(w, http.StatusForbidden, ErrCodeForbidden, nil)
}
func logUnexpectedUserType(ctx context.Context, user auth.User) {
logger.Error(
ctx, "unexpected user type",
logger.F("subject", user.Subject()),
logger.F("type", fmt.Sprintf("%T", user)),
)
}

View File

@ -9,7 +9,7 @@ import (
"forge.cadoles.com/Cadoles/emissary/internal/auth" "forge.cadoles.com/Cadoles/emissary/internal/auth"
"forge.cadoles.com/Cadoles/emissary/internal/auth/agent" "forge.cadoles.com/Cadoles/emissary/internal/auth/agent"
"forge.cadoles.com/Cadoles/emissary/internal/auth/thirdparty" "forge.cadoles.com/Cadoles/emissary/internal/auth/user"
"forge.cadoles.com/Cadoles/emissary/internal/config" "forge.cadoles.com/Cadoles/emissary/internal/config"
"forge.cadoles.com/Cadoles/emissary/internal/datastore" "forge.cadoles.com/Cadoles/emissary/internal/datastore"
"forge.cadoles.com/Cadoles/emissary/internal/jwk" "forge.cadoles.com/Cadoles/emissary/internal/jwk"
@ -105,19 +105,19 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
r.Use(auth.Middleware( r.Use(auth.Middleware(
thirdparty.NewAuthenticator(keys, string(s.conf.Issuer), thirdparty.DefaultAcceptableSkew), user.NewAuthenticator(keys, string(s.conf.Issuer)),
agent.NewAuthenticator(s.agentRepo, agent.DefaultAcceptableSkew), agent.NewAuthenticator(s.agentRepo),
)) ))
r.Route("/agents", func(r chi.Router) { r.Route("/agents", func(r chi.Router) {
r.With(assertGlobalReadAccess).Get("/", s.queryAgents) r.Get("/", s.queryAgents)
r.With(assertAgentReadAccess).Get("/{agentID}", s.getAgent) r.Get("/{agentID}", s.getAgent)
r.With(assertAgentWriteAccess).Put("/{agentID}", s.updateAgent) r.Put("/{agentID}", s.updateAgent)
r.With(assertAgentWriteAccess).Delete("/{agentID}", s.deleteAgent) r.Delete("/{agentID}", s.deleteAgent)
r.With(assertAgentReadAccess).Get("/{agentID}/specs", s.getAgentSpecs) r.Get("/{agentID}/specs", s.getAgentSpecs)
r.With(assertAgentWriteAccess).Post("/{agentID}/specs", s.updateSpec) r.Post("/{agentID}/specs", s.updateSpec)
r.With(assertAgentWriteAccess).Delete("/{agentID}/specs", s.deleteSpec) r.Delete("/{agentID}/specs", s.deleteSpec)
}) })
}) })
}) })

View File

@ -10,6 +10,11 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gitlab.com/wpetit/goweb/api" "gitlab.com/wpetit/goweb/api"
"gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/logger"
// Import specs
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/app"
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/gateway"
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/uci"
) )
const ( const (
@ -58,12 +63,6 @@ func (s *Server) updateSpec(w http.ResponseWriter, r *http.Request) {
updateSpecReq.SpecData(), updateSpecReq.SpecData(),
) )
if err != nil { if err != nil {
if errors.Is(err, datastore.ErrNotFound) {
api.ErrorResponse(w, http.StatusNotFound, ErrCodeNotFound, nil)
return
}
if errors.Is(err, datastore.ErrUnexpectedRevision) { if errors.Is(err, datastore.ErrUnexpectedRevision) {
api.ErrorResponse(w, http.StatusConflict, ErrCodeUnexpectedRevision, nil) api.ErrorResponse(w, http.StatusConflict, ErrCodeUnexpectedRevision, nil)
@ -93,12 +92,6 @@ func (s *Server) getAgentSpecs(w http.ResponseWriter, r *http.Request) {
specs, err := s.agentRepo.GetSpecs(ctx, agentID) specs, err := s.agentRepo.GetSpecs(ctx, agentID)
if err != nil { if err != nil {
if errors.Is(err, datastore.ErrNotFound) {
api.ErrorResponse(w, http.StatusNotFound, ErrCodeNotFound, nil)
return
}
logger.Error(ctx, "could not list specs", logger.E(errors.WithStack(err))) logger.Error(ctx, "could not list specs", logger.E(errors.WithStack(err)))
api.ErrorResponse(w, http.StatusInternalServerError, ErrCodeUnknownError, nil) api.ErrorResponse(w, http.StatusInternalServerError, ErrCodeUnknownError, nil)

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