From e832b7dedda688e1fe64c313e82d503f5c4ed469 Mon Sep 17 00:00:00 2001 From: William Petit Date: Tue, 28 Mar 2023 20:43:45 +0200 Subject: [PATCH] feat(controller,app): add app module --- Makefile | 8 +- go.mod | 11 +- go.sum | 21 ++ internal/agent/controller/app/app_handler.go | 190 ++++++++++++++++++ .../agent/controller/app/app_repository.go | 104 ++++++++++ internal/agent/controller/app/controller.go | 96 ++++----- internal/agent/controller/app/server.go | 145 +++++-------- .../app => agent/controller/app/spec}/init.go | 4 +- .../controller/app/spec}/schema.json | 11 + .../app => agent/controller/app/spec}/spec.go | 23 ++- .../app/spec}/testdata/spec-ok.json | 0 .../controller/app/spec}/validator_test.go | 4 +- internal/imports/spec/spec_import.go | 2 +- internal/jwk/jwk.go | 7 + internal/proxy/host_filter.go | 2 +- internal/proxy/host_rewrite.go | 3 +- misc/rest/app-spec-data.json | 10 - misc/rest/server.rest | 65 ------ misc/rest/uci-spec.payload.json | 163 --------------- .../app.emissary.cadoles.com.json | 45 +++++ .../proxy.emissary.cadoles.com.json | 25 +++ 21 files changed, 542 insertions(+), 397 deletions(-) create mode 100644 internal/agent/controller/app/app_handler.go create mode 100644 internal/agent/controller/app/app_repository.go rename internal/{spec/app => agent/controller/app/spec}/init.go (74%) rename internal/{spec/app => agent/controller/app/spec}/schema.json (89%) rename internal/{spec/app => agent/controller/app/spec}/spec.go (59%) rename internal/{spec/app => agent/controller/app/spec}/testdata/spec-ok.json (100%) rename internal/{spec/app => agent/controller/app/spec}/validator_test.go (94%) delete mode 100644 misc/rest/app-spec-data.json delete mode 100644 misc/rest/server.rest delete mode 100644 misc/rest/uci-spec.payload.json create mode 100644 misc/spec-samples/app.emissary.cadoles.com.json create mode 100644 misc/spec-samples/proxy.emissary.cadoles.com.json diff --git a/Makefile b/Makefile index f6631eb..ec28dad 100644 --- a/Makefile +++ b/Makefile @@ -144,4 +144,10 @@ 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" \ No newline at end of file + $(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 \ No newline at end of file diff --git a/go.mod b/go.mod index 178c347..df1a619 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module forge.cadoles.com/Cadoles/emissary go 1.19 require ( - forge.cadoles.com/arcad/edge v0.0.0-20230322170544-cf8a3f8ac077 + forge.cadoles.com/arcad/edge v0.0.0-20230328183829-d8ce2901d2ab github.com/alecthomas/participle/v2 v2.0.0-beta.5 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 github.com/btcsuite/btcd/btcutil v1.1.3 @@ -29,6 +29,9 @@ require ( ) require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // 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/gabriel-vasile/mimetype v1.4.1 // indirect @@ -37,10 +40,16 @@ require ( github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect github.com/gorilla/websocket v1.5.0 // 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/imdario/mergo v0.3.12 // indirect github.com/miekg/dns v1.1.51 // 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/orcaman/concurrent-map v1.0.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/spf13/cast v1.3.1 // indirect golang.org/x/net v0.8.0 // indirect google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 0152916..20f9a5d 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,10 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= forge.cadoles.com/arcad/edge v0.0.0-20230322170544-cf8a3f8ac077 h1:vsYcNHZevZrs0VeOTasvJoqvPynb8OvH+MMpIUvNT6Q= forge.cadoles.com/arcad/edge v0.0.0-20230322170544-cf8a3f8ac077/go.mod h1:ONd6vyQ0IM0vHi1i+bmZBRc1Fd0BoXMuDdY/+0sZefw= +forge.cadoles.com/arcad/edge v0.0.0-20230328081549-e09de0b0a4f4 h1:ZBBOOKqCEt6F9/Ikkwc2xwYDr7JpLybvxtoRJwXt7Gw= +forge.cadoles.com/arcad/edge v0.0.0-20230328081549-e09de0b0a4f4/go.mod h1:ONd6vyQ0IM0vHi1i+bmZBRc1Fd0BoXMuDdY/+0sZefw= +forge.cadoles.com/arcad/edge v0.0.0-20230328183829-d8ce2901d2ab h1:xOtzLAYOUcKd/VBx/PzL2riC0zNuQ/cxxf5r3AmEvJE= +forge.cadoles.com/arcad/edge v0.0.0-20230328183829-d8ce2901d2ab/go.mod h1:ONd6vyQ0IM0vHi1i+bmZBRc1Fd0BoXMuDdY/+0sZefw= 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/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= @@ -84,6 +88,12 @@ 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/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= 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.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -761,6 +771,8 @@ 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/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 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/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= @@ -771,6 +783,7 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ 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.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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= @@ -974,6 +987,8 @@ github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW 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/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.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -987,6 +1002,8 @@ 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/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/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/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= @@ -1180,6 +1197,7 @@ 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/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 v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= 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/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -1204,6 +1222,8 @@ 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.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.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.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= @@ -1379,6 +1399,7 @@ 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-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.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= diff --git a/internal/agent/controller/app/app_handler.go b/internal/agent/controller/app/app_handler.go new file mode 100644 index 0000000..11362e4 --- /dev/null +++ b/internal/agent/controller/app/app_handler.go @@ -0,0 +1,190 @@ +package app + +import ( + "bytes" + "context" + "database/sql" + "path/filepath" + "sync" + "text/template" + + "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" + appSpec "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/auth" + "forge.cadoles.com/arcad/edge/pkg/module/blob" + "forge.cadoles.com/arcad/edge/pkg/module/cast" + "forge.cadoles.com/arcad/edge/pkg/module/net" + "forge.cadoles.com/arcad/edge/pkg/storage/sqlite" + "github.com/Masterminds/sprig/v3" + "github.com/dop251/goja" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/pkg/errors" +) + +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) + if err != nil { + return nil, errors.Wrapf(err, "could not open database file '%s'", dbFile) + } + + keySet, err := getAuthKeySet(specs.Config) + if err != nil { + return nil, errors.Wrap(err, "could not retrieve auth key set") + } + + bundles := make([]string, 0, len(specs.Apps)) + for appKey, app := range specs.Apps { + path := c.getAppBundlePath(appKey, app.Format) + bundles = append(bundles, path) + } + + getAppURL := createGetAppURL(specs) + + bus := memory.NewBus() + modules := getAppModules(bus, db, specs, keySet, getAppURL, bundles) + + options := []edgeHTTP.HandlerOptionFunc{ + edgeHTTP.WithBus(bus), + edgeHTTP.WithServerModules(modules...), + } + + 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 createGetAppURL(specs *spec.Spec) GetURLFunc { + var ( + compileOnce sync.Once + urlTemplate *template.Template + err error + ) + + return func(ctx context.Context, manifest *app.Manifest) (string, error) { + if err != nil { + return "", errors.WithStack(err) + } + + var appURLTemplate string + + if specs.Config == nil || specs.Config.AppURLTemplate == "" { + appURLTemplate = `http://{{ last ( splitList "." ( toString .Manifest.ID ) ) }}.local` + } else { + appURLTemplate = specs.Config.AppURLTemplate + } + + compileOnce.Do(func() { + urlTemplate, err = template.New("").Funcs(sprig.TxtFuncMap()).Parse(appURLTemplate) + }) + + var buf bytes.Buffer + + data := struct { + Manifest *app.Manifest + Specs *spec.Spec + }{ + Manifest: manifest, + Specs: specs, + } + + if err := urlTemplate.Execute(&buf, data); err != nil { + return "", errors.WithStack(err) + } + + return buf.String(), nil + } +} + +func getAppModules(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, keySet jwk.Set, getAppURL GetURLFunc, bundles []string) []app.ServerModuleFactory { + ds := sqlite.NewDocumentStoreWithDB(db) + bs := sqlite.NewBlobStoreWithDB(db) + + return []app.ServerModuleFactory{ + module.ContextModuleFactory(), + module.ConsoleModuleFactory(), + cast.CastModuleFactory(), + module.LifecycleModuleFactory(), + net.ModuleFactory(bus), + module.RPCModuleFactory(bus), + module.StoreModuleFactory(ds), + blob.ModuleFactory(bus, bs), + module.Extends( + auth.ModuleFactory( + auth.WithJWT(func() (jwk.Set, error) { + return keySet, nil + }), + ), + func(o *goja.Object) { + if err := o.Set("CLAIM_TENANT", "arcad_tenant"); err != nil { + panic(errors.New("could not set 'CLAIM_TENANT' property")) + } + + if err := o.Set("CLAIM_ENTRYPOINT", "arcad_entrypoint"); err != nil { + panic(errors.New("could not set 'CLAIM_ENTRYPOINT' property")) + } + + if err := o.Set("CLAIM_ROLE", "arcad_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")) + } + }, + ), + appModule.ModuleFactory(NewAppRepository(getAppURL, bundles...)), + } +} diff --git a/internal/agent/controller/app/app_repository.go b/internal/agent/controller/app/app_repository.go new file mode 100644 index 0000000..e140193 --- /dev/null +++ b/internal/agent/controller/app/app_repository.go @@ -0,0 +1,104 @@ +package app + +import ( + "context" + + "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 GetURLFunc func(context.Context, *app.Manifest) (string, error) + +type AppRepository struct { + getURL GetURLFunc + bundles []string +} + +// Get implements app.Repository +func (r *AppRepository) Get(ctx context.Context, id app.ID) (*app.Manifest, error) { + 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) (string, error) { + manifest, err := r.findManifest(ctx, id) + if err != nil { + return "", errors.WithStack(err) + } + + url, err := r.getURL(ctx, manifest) + if err != nil { + return "", errors.WithStack(err) + } + + return url, nil +} + +// List implements app.Repository +func (r *AppRepository) List(ctx context.Context) ([]*app.Manifest, error) { + 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) + } + + return manifests, nil +} + +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(getURL GetURLFunc, bundles ...string) *AppRepository { + return &AppRepository{getURL, bundles} +} + +var _ appModule.Repository = &AppRepository{} diff --git a/internal/agent/controller/app/controller.go b/internal/agent/controller/app/controller.go index 85324f4..7137605 100644 --- a/internal/agent/controller/app/controller.go +++ b/internal/agent/controller/app/controller.go @@ -8,9 +8,8 @@ import ( "path/filepath" "forge.cadoles.com/Cadoles/emissary/internal/agent" - "forge.cadoles.com/Cadoles/emissary/internal/spec/app" + "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" "forge.cadoles.com/arcad/edge/pkg/bundle" - "forge.cadoles.com/arcad/edge/pkg/storage/sqlite" "github.com/mitchellh/hashstructure/v2" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/logger" @@ -35,9 +34,9 @@ func (c *Controller) Name() string { // Reconcile implements node.Controller. func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error { - appSpec := app.NewSpec() + appSpec := spec.NewSpec() - if err := state.GetSpec(app.NameApp, appSpec); err != nil { + if err := state.GetSpec(spec.Name, appSpec); err != nil { if errors.Is(err, agent.ErrSpecNotFound) { logger.Info(ctx, "could not find app spec") @@ -56,7 +55,7 @@ func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error { return nil } -func (c *Controller) stopAllApps(ctx context.Context, spec *app.Spec) { +func (c *Controller) stopAllApps(ctx context.Context, spec *spec.Spec) { if len(c.servers) > 0 { logger.Info(ctx, "stopping all apps") } @@ -76,122 +75,121 @@ func (c *Controller) stopAllApps(ctx context.Context, spec *app.Spec) { } } -func (c *Controller) updateApps(ctx context.Context, spec *app.Spec) { +func (c *Controller) updateApps(ctx context.Context, specs *spec.Spec) { // Stop and remove obsolete apps - for appID, entry := range c.servers { - if _, exists := spec.Apps[appID]; exists { + for appKey, server := range c.servers { + if _, exists := specs.Apps[appKey]; exists { continue } - logger.Info(ctx, "stopping app", logger.F("appID", appID)) + logger.Info(ctx, "stopping app", logger.F("appKey", appKey)) - if err := entry.Server.Stop(); err != nil { + if err := server.Server.Stop(); err != nil { logger.Error( ctx, "error while stopping app", - logger.F("gatewayID", appID), + logger.F("appKey", appKey), logger.E(errors.WithStack(err)), ) - delete(c.servers, appID) + delete(c.servers, appKey) } } // (Re)start apps - for appID, appSpec := range spec.Apps { - appCtx := logger.With(ctx, logger.F("appID", appID)) + for appKey := range specs.Apps { + appCtx := logger.With(ctx, logger.F("appKey", appKey)) - if err := c.updateApp(ctx, appID, appSpec, spec.Auth); err != nil { + if err := c.updateApp(ctx, specs, appKey); err != nil { logger.Error(appCtx, "could not update app", logger.E(errors.WithStack(err))) continue } } } -func (c *Controller) updateApp(ctx context.Context, appID string, appSpec app.AppEntry, auth *app.Auth) (err error) { - newAppSpecHash, err := hashstructure.Hash(appSpec, hashstructure.FormatV2, nil) +func (c *Controller) updateApp(ctx context.Context, specs *spec.Spec, appKey string) (err error) { + appEntry := specs.Apps[appKey] + + newAppSpecHash, err := hashstructure.Hash(appEntry, hashstructure.FormatV2, nil) if err != nil { return errors.WithStack(err) } - bundle, sha256sum, err := c.ensureAppBundle(ctx, appID, appSpec) + bundle, sha256sum, err := c.ensureAppBundle(ctx, appKey, appEntry) if err != nil { return errors.Wrap(err, "could not download app bundle") } - dataDir, err := c.ensureAppDataDir(ctx, appID) - if err != nil { - return errors.Wrap(err, "could not retrieve app data dir") - } - - var entry *serverEntry - - entry, exists := c.servers[appID] + server, exists := c.servers[appKey] if !exists { logger.Info(ctx, "app currently not running") - } else if sha256sum != appSpec.SHA256Sum { + } else if sha256sum != appEntry.SHA256Sum { logger.Info( ctx, "bundle hash mismatch, stopping app", logger.F("currentHash", sha256sum), - logger.F("specHash", appSpec.SHA256Sum), + logger.F("specHash", appEntry.SHA256Sum), ) - if err := entry.Server.Stop(); err != nil { + if err := server.Server.Stop(); err != nil { return errors.Wrap(err, "could not stop app") } - entry = nil + server = nil } - if entry == nil { - dbFile := filepath.Join(dataDir, appID+".sqlite") - db, err := sqlite.Open(dbFile) + if server == nil { + options, err := c.getHandlerOptions(ctx, appKey, specs) if err != nil { - return errors.Wrapf(err, "could not opend database file '%s'", dbFile) + return errors.Wrap(err, "could not create handler options") } - entry = &serverEntry{ - Server: NewServer(bundle, db, auth), + var auth *spec.Auth + if specs.Config != nil { + auth = specs.Config.Auth + } + + server = &serverEntry{ + Server: NewServer(bundle, auth, options...), SpecHash: 0, } - c.servers[appID] = entry + c.servers[appKey] = server } - specChanged := newAppSpecHash != entry.SpecHash + specChanged := newAppSpecHash != server.SpecHash - if entry.Server.Running() && !specChanged { + if server.Server.Running() && !specChanged { return nil } - if specChanged && entry.SpecHash != 0 { + if specChanged && server.SpecHash != 0 { logger.Info( ctx, "restarting app", - logger.F("address", appSpec.Address), + logger.F("address", appEntry.Address), ) } else { logger.Info( ctx, "starting app", - logger.F("address", appSpec.Address), + logger.F("address", appEntry.Address), ) } - if err := entry.Server.Start(ctx, appSpec.Address); err != nil { - delete(c.servers, appID) + if err := server.Server.Start(ctx, appEntry.Address); err != nil { + delete(c.servers, appKey) return errors.Wrap(err, "could not start app") } - entry.SpecHash = newAppSpecHash + server.SpecHash = newAppSpecHash return nil } -func (c *Controller) ensureAppBundle(ctx context.Context, appID string, spec app.AppEntry) (bundle.Bundle, string, error) { +func (c *Controller) ensureAppBundle(ctx context.Context, appID string, spec spec.AppEntry) (bundle.Bundle, string, error) { if err := os.MkdirAll(c.downloadDir, os.ModePerm); err != nil { return nil, "", errors.WithStack(err) } - bundlePath := filepath.Join(c.downloadDir, appID+"."+spec.Format) + bundlePath := c.getAppBundlePath(appID, spec.Format) _, err := os.Stat(bundlePath) if err != nil && !errors.Is(err, os.ErrNotExist) { @@ -285,6 +283,10 @@ func (c *Controller) ensureAppDataDir(ctx context.Context, appID string) (string return dataDir, nil } +func (c *Controller) getAppBundlePath(appKey string, format string) string { + return filepath.Join(c.downloadDir, appKey+"."+format) +} + func NewController(funcs ...OptionFunc) *Controller { opts := defaultOptions() for _, fn := range funcs { diff --git a/internal/agent/controller/app/server.go b/internal/agent/controller/app/server.go index fd9ed6b..b3bbfc7 100644 --- a/internal/agent/controller/app/server.go +++ b/internal/agent/controller/app/server.go @@ -2,26 +2,17 @@ package app import ( "context" - "database/sql" "net/http" "sync" + "time" - appSpec "forge.cadoles.com/Cadoles/emissary/internal/spec/app" - "forge.cadoles.com/arcad/edge/pkg/app" - "forge.cadoles.com/arcad/edge/pkg/bus" - "forge.cadoles.com/arcad/edge/pkg/bus/memory" + "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" + appSpec "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http" - "forge.cadoles.com/arcad/edge/pkg/module" - "forge.cadoles.com/arcad/edge/pkg/module/auth" authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http" - "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" "gitlab.com/wpetit/goweb/logger" "forge.cadoles.com/arcad/edge/pkg/bundle" - "github.com/dop251/goja" "github.com/go-chi/chi/middleware" "github.com/go-chi/chi/v5" "github.com/lestrrat-go/jwx/v2/jwa" @@ -31,13 +22,14 @@ import ( _ "forge.cadoles.com/Cadoles/emissary/internal/imports/passwd" ) +const defaultCookieDuration time.Duration = 24 * time.Hour + type Server struct { - bundle bundle.Bundle - db *sql.DB - server *http.Server - serverMutex sync.RWMutex - auth *appSpec.Auth - keySet jwk.Set + bundle bundle.Bundle + handlerOptions []edgeHTTP.HandlerOptionFunc + server *http.Server + serverMutex sync.RWMutex + auth *appSpec.Auth } func (s *Server) Start(ctx context.Context, addr string) (err error) { @@ -51,47 +43,13 @@ func (s *Server) Start(ctx context.Context, addr string) (err error) { router.Use(middleware.Logger) - 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)...), - ) + handler := edgeHTTP.NewHandler(s.handlerOptions...) if err := handler.Load(s.bundle); err != nil { return errors.Wrap(err, "could not load app bundle") } - if s.auth != nil { - if s.auth.Local != nil { - var rawKey any = s.auth.Local.Key - if strKey, ok := rawKey.(string); ok { - rawKey = []byte(strKey) - } - - key, err := jwk.FromRaw(rawKey) - if err != nil { - return errors.WithStack(err) - } - - if err := key.Set(jwk.AlgorithmKey, jwa.HS256); err != nil { - return errors.WithStack(err) - } - - keySet := jwk.NewSet() - if err := keySet.AddKey(key); err != nil { - return errors.WithStack(err) - } - - s.keySet = keySet - - router.Handle("/auth/*", authHTTP.NewLocalHandler( - jwa.HS256, key, - authHTTP.WithRoutePrefix("/auth"), - authHTTP.WithAccounts(s.auth.Local.Accounts...), - )) - } + if err := s.configureAuth(router, s.auth); err != nil { + return errors.WithStack(err) } router.Handle("/*", handler) @@ -157,49 +115,46 @@ func (s *Server) Stop() error { return nil } -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(s.getJWTKeySet), - ), - func(o *goja.Object) { - if err := o.Set("CLAIM_TENANT", "arcad_tenant"); err != nil { - panic(errors.New("could not set 'CLAIM_TENANT' property")) - } - - if err := o.Set("CLAIM_ENTRYPOINT", "arcad_entrypoint"); err != nil { - panic(errors.New("could not set 'CLAIM_ENTRYPOINT' property")) - } - - if err := o.Set("CLAIM_ROLE", "arcad_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 (s *Server) configureAuth(router chi.Router, auth *spec.Auth) error { + if auth == nil { + return nil } + + switch { + case auth.Local != nil: + var rawKey any = s.auth.Local.Key + if strKey, ok := rawKey.(string); ok { + rawKey = []byte(strKey) + } + + key, err := jwk.FromRaw(rawKey) + if err != nil { + return errors.WithStack(err) + } + + cookieDuration := defaultCookieDuration + if s.auth.Local.CookieDuration != "" { + cookieDuration, err = time.ParseDuration(s.auth.Local.CookieDuration) + if err != nil { + return errors.WithStack(err) + } + } + + router.Handle("/auth/*", authHTTP.NewLocalHandler( + jwa.HS256, key, + authHTTP.WithRoutePrefix("/auth"), + authHTTP.WithAccounts(s.auth.Local.Accounts...), + authHTTP.WithCookieOptions(s.auth.Local.CookieDomain, cookieDuration), + )) + } + + return nil } -func (s *Server) getJWTKeySet() (jwk.Set, error) { - return s.keySet, nil -} - -func NewServer(bundle bundle.Bundle, db *sql.DB, auth *appSpec.Auth) *Server { +func NewServer(bundle bundle.Bundle, auth *appSpec.Auth, handlerOptions ...edgeHTTP.HandlerOptionFunc) *Server { return &Server{ - bundle: bundle, - db: db, - auth: auth, + bundle: bundle, + auth: auth, + handlerOptions: handlerOptions, } } diff --git a/internal/spec/app/init.go b/internal/agent/controller/app/spec/init.go similarity index 74% rename from internal/spec/app/init.go rename to internal/agent/controller/app/spec/init.go index 117b8d9..e6983ae 100644 --- a/internal/spec/app/init.go +++ b/internal/agent/controller/app/spec/init.go @@ -1,4 +1,4 @@ -package app +package spec import ( _ "embed" @@ -11,7 +11,7 @@ import ( var schema []byte func init() { - if err := spec.Register(NameApp, schema); err != nil { + if err := spec.Register(Name, schema); err != nil { panic(errors.WithStack(err)) } } diff --git a/internal/spec/app/schema.json b/internal/agent/controller/app/spec/schema.json similarity index 89% rename from internal/spec/app/schema.json rename to internal/agent/controller/app/spec/schema.json index 6afdd48..7c6a6c3 100644 --- a/internal/spec/app/schema.json +++ b/internal/agent/controller/app/spec/schema.json @@ -71,6 +71,12 @@ "algo" ] } + }, + "cookieDomain": { + "type": "string" + }, + "cookieDuration": { + "type": "string" } }, "required": [ @@ -78,6 +84,11 @@ ] } } + }, + "config": { + "appUrlTemplate": { + "type": "string" + } } }, "required": [ diff --git a/internal/spec/app/spec.go b/internal/agent/controller/app/spec/spec.go similarity index 59% rename from internal/spec/app/spec.go rename to internal/agent/controller/app/spec/spec.go index 3c41c65..1179fa3 100644 --- a/internal/spec/app/spec.go +++ b/internal/agent/controller/app/spec/spec.go @@ -1,16 +1,16 @@ -package app +package spec import ( "forge.cadoles.com/Cadoles/emissary/internal/spec" edgeAuth "forge.cadoles.com/arcad/edge/pkg/module/auth/http" ) -const NameApp spec.Name = "app.emissary.cadoles.com" +const Name spec.Name = "app.emissary.cadoles.com" type Spec struct { Revision int `json:"revision"` Apps map[string]AppEntry `json:"apps"` - Auth *Auth `json:"auth"` + Config *Config `json:"config"` } type AppEntry struct { @@ -25,12 +25,19 @@ type Auth struct { } type LocalAuth struct { - Key any `json:"key"` - Accounts []edgeAuth.LocalAccount `json:"accounts"` + Key any `json:"key"` + Accounts []edgeAuth.LocalAccount `json:"accounts"` + CookieDomain string `json:"cookieDomain"` + CookieDuration string `json:"cookieDuration"` +} + +type Config struct { + Auth *Auth `json:"auth"` + AppURLTemplate string `json:"appUrlTemplate"` } func (s *Spec) SpecName() spec.Name { - return NameApp + return Name } func (s *Spec) SpecRevision() int { @@ -39,8 +46,8 @@ func (s *Spec) SpecRevision() int { func (s *Spec) SpecData() map[string]any { return map[string]any{ - "apps": s.Apps, - "auth": s.Auth, + "apps": s.Apps, + "config": s.Config, } } diff --git a/internal/spec/app/testdata/spec-ok.json b/internal/agent/controller/app/spec/testdata/spec-ok.json similarity index 100% rename from internal/spec/app/testdata/spec-ok.json rename to internal/agent/controller/app/spec/testdata/spec-ok.json diff --git a/internal/spec/app/validator_test.go b/internal/agent/controller/app/spec/validator_test.go similarity index 94% rename from internal/spec/app/validator_test.go rename to internal/agent/controller/app/spec/validator_test.go index b03c378..b360f65 100644 --- a/internal/spec/app/validator_test.go +++ b/internal/agent/controller/app/spec/validator_test.go @@ -1,4 +1,4 @@ -package app +package spec import ( "context" @@ -28,7 +28,7 @@ func TestValidator(t *testing.T) { t.Parallel() validator := spec.NewValidator() - if err := validator.Register(NameApp, schema); err != nil { + if err := validator.Register(Name, schema); err != nil { t.Fatalf("+%v", errors.WithStack(err)) } diff --git a/internal/imports/spec/spec_import.go b/internal/imports/spec/spec_import.go index 6ec4588..1ae3365 100644 --- a/internal/imports/spec/spec_import.go +++ b/internal/imports/spec/spec_import.go @@ -1,8 +1,8 @@ package spec import ( + _ "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" _ "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt/spec/sysupgrade" - _ "forge.cadoles.com/Cadoles/emissary/internal/spec/app" _ "forge.cadoles.com/Cadoles/emissary/internal/spec/proxy" _ "forge.cadoles.com/Cadoles/emissary/internal/spec/uci" ) diff --git a/internal/jwk/jwk.go b/internal/jwk/jwk.go index b51df4e..afa0bdf 100644 --- a/internal/jwk/jwk.go +++ b/internal/jwk/jwk.go @@ -23,6 +23,13 @@ type ( ParseOption = jwk.ParseOption ) +var ( + FromRaw = jwk.FromRaw + NewSet = jwk.NewSet +) + +const AlgorithmKey = jwk.AlgorithmKey + func Parse(src []byte, options ...jwk.ParseOption) (Set, error) { return jwk.Parse(src, options...) } diff --git a/internal/proxy/host_filter.go b/internal/proxy/host_filter.go index 713968a..bafc5f1 100644 --- a/internal/proxy/host_filter.go +++ b/internal/proxy/host_filter.go @@ -3,7 +3,7 @@ package proxy import ( "net/http" - "forge.cadoles.com/arcad/edge/pkg/proxy/wildcard" + "forge.cadoles.com/Cadoles/emissary/internal/proxy/wildcard" ) func FilterHosts(allowedHostPatterns ...string) Middleware { diff --git a/internal/proxy/host_rewrite.go b/internal/proxy/host_rewrite.go index 2a55bd8..86d4cb0 100644 --- a/internal/proxy/host_rewrite.go +++ b/internal/proxy/host_rewrite.go @@ -5,7 +5,7 @@ import ( "net/url" "sort" - "forge.cadoles.com/arcad/edge/pkg/proxy/wildcard" + "forge.cadoles.com/Cadoles/emissary/internal/proxy/wildcard" "gitlab.com/wpetit/goweb/logger" ) @@ -17,6 +17,7 @@ func RewriteHosts(mappings map[string]*url.URL) Middleware { } sort.Strings(patterns) + reverse(patterns) return func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { diff --git a/misc/rest/app-spec-data.json b/misc/rest/app-spec-data.json deleted file mode 100644 index 3a23aba..0000000 --- a/misc/rest/app-spec-data.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "apps": { - "edge.sdk.client.test": { - "url": "http://localhost:3001/edge.sdk.client.test_0.0.0.zip", - "sha256sum": "58019192dacdae17755707719707db007e26dac856102280583fbd18427dd352", - "format": "zip", - "address": "127.0.0.1:8081" - } - } -} \ No newline at end of file diff --git a/misc/rest/server.rest b/misc/rest/server.rest deleted file mode 100644 index d3141eb..0000000 --- a/misc/rest/server.rest +++ /dev/null @@ -1,65 +0,0 @@ -@baseUrl = http://localhost:3000 - -### Get agents - -# @name getAgents -GET {{ baseUrl }}/api/v1/agents -Content-Type: application/json - -@agentId = {{ getAgents.response.body.Data.Agents.0.ID }} - -### Update an agent (accept it) - -PUT {{ baseUrl }}/api/v1/agents/{{ agentId }} -Content-Type: application/json - -{ - "Status": 1 -} - -### Get an agent - -GET {{ baseUrl }}/api/v1/agents/{{ agentId }} -Content-Type: application/json - -### Get an agent specs - -# @name getSpecs -GET {{ baseUrl }}/api/v1/agents/{{ agentId }}/specs -Content-Type: application/json - -@specName = {{ getSpecs.response.body.Data.Specs.0.Name }} - -### Update an agent specs - -POST {{ baseUrl }}/api/v1/agents/{{ agentId }}/specs -Content-Type: application/json - -{ - "Name": "gateway.emissary.cadoles.com", - "Revision": 2, - "Data": { - "gateways": { - "cadoles.com":{ - "address":":3003", - "target":"https://www.cadoles.com" - } - } - } -} - -### Delete an agent spec - -DELETE {{ baseUrl }}/api/v1/agents/{{ agentId }}/specs -Content-Type: application/json - -{ - "Name": "gateway.emissary.cadoles.com" -} - -### Update UCI spec with uhttpd config - -POST {{ baseUrl }}/api/v1/agents/2/specs -Content-Type: application/json - -< ./uci-spec.payload.json \ No newline at end of file diff --git a/misc/rest/uci-spec.payload.json b/misc/rest/uci-spec.payload.json deleted file mode 100644 index 69dffcb..0000000 --- a/misc/rest/uci-spec.payload.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "Name": "uci.emissary.cadoles.com", - "Revision": 6, - "Data": { - "config": { - "packages": [ - { - "name": "uhttpd", - "configs": [ - { - "name": "uhttpd", - "section": "main", - "options": [ - { - "type": "list", - "name": "listen_http", - "value": "0.0.0.0:8080" - }, - { - "type": "list", - "name": "listen_http", - "value": "[::]:8080" - }, - { - "type": "list", - "name": "listen_https", - "value": "0.0.0.0:8443" - }, - { - "type": "list", - "name": "listen_https", - "value": "[::]:8443" - }, - { - "type": "option", - "name": "redirect_https", - "value": "0" - }, - { - "type": "option", - "name": "home", - "value": "/www" - }, - { - "type": "option", - "name": "rfc1918_filter", - "value": "1" - }, - { - "type": "option", - "name": "max_requests", - "value": "3" - }, - { - "type": "option", - "name": "max_connections", - "value": "100" - }, - { - "type": "option", - "name": "cert", - "value": "/etc/uhttpd.crt" - }, - { - "type": "option", - "name": "key", - "value": "/etc/uhttpd.key" - }, - { - "type": "option", - "name": "cgi_prefix", - "value": "/cgi-bin" - }, - { - "type": "list", - "name": "lua_prefix", - "value": "/cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua" - }, - { - "type": "option", - "name": "script_timeout", - "value": "60" - }, - { - "type": "option", - "name": "network_timeout", - "value": "30" - }, - { - "type": "option", - "name": "http_keepalive", - "value": "20" - }, - { - "type": "option", - "name": "tcp_keepalive", - "value": "1" - }, - { - "type": "option", - "name": "ubus_prefix", - "value": "/ubus" - } - ] - }, - { - "name": "cert", - "section": "defaults", - "options": [ - { - "type": "option", - "name": "days", - "value": "730" - }, - { - "type": "option", - "name": "key_type", - "value": "ec" - }, - { - "type": "option", - "name": "bits", - "value": "2048" - }, - { - "type": "option", - "name": "ec_curve", - "value": "P-256" - }, - { - "type": "option", - "name": "country", - "value": "ZZ" - }, - { - "type": "option", - "name": "state", - "value": "Somewhere" - }, - { - "type": "option", - "name": "location", - "value": "Unknown" - }, - { - "type": "option", - "name": "commonname", - "value": "OpenWrt" - } - ] - } - ] - } - ] - }, - "postImportCommands": [ - { - "command": "reload_config", - "args": [] - } - ] - } -} \ No newline at end of file diff --git a/misc/spec-samples/app.emissary.cadoles.com.json b/misc/spec-samples/app.emissary.cadoles.com.json new file mode 100644 index 0000000..43cc10b --- /dev/null +++ b/misc/spec-samples/app.emissary.cadoles.com.json @@ -0,0 +1,45 @@ +{ + "apps": { + "portal": { + "url": "https://emissary.cadol.es/files/apps/arcad.portal_v2023.3.28-3feda80.zip", + "sha256sum": "921402c44a5fa554d5b630d1284957b05416aa6872b402314cf52e964e06fac5", + "address": "127.0.0.1:8082", + "format": "zip" + }, + "hextris": { + "url": "https://emissary.cadol.es/files/apps/app.arcad.edge.hextris_v2023.3.22-33ece28.zip", + "sha256sum": "5f9f3c8d6f22796beb051d747d7ff12efa17af9d1552c0ab08baef13703a2aba", + "address": "127.0.0.1:8083", + "format": "zip" + }, + "test": { + "url": "https://emissary.cadol.es/files/apps/edge.sdk.client.test_v2023.3.24-ed535b6.zip", + "sha256sum": "e97b7b79159bb5d6a13b05644c091272b02a1a3cbb1b613dd5eda37e1eb84623", + "address": "127.0.0.1:8084", + "format": "zip" + } + }, + "config": { + "appUrlTemplate": "http://{{ last ( splitList \".\" ( toString .Manifest.ID ) ) }}.arcad.local:8080", + "auth": { + "local": { + "key": "absolutlynotsecret", + "cookieDomain": ".arcad.local", + "cookieDuration": "1h", + "accounts": [ + { + "username": "admin", + "algo": "plain", + "password": "admin", + "claims": { + "arcad_role": "admin", + "arcad_tenant": "x86", + "preferred_username": "Admin", + "sub": "admin" + } + } + ] + } + } + } +} diff --git a/misc/spec-samples/proxy.emissary.cadoles.com.json b/misc/spec-samples/proxy.emissary.cadoles.com.json new file mode 100644 index 0000000..3b657c7 --- /dev/null +++ b/misc/spec-samples/proxy.emissary.cadoles.com.json @@ -0,0 +1,25 @@ +{ + "proxies": { + "main": { + "address": ":8080", + "mappings": [ + { + "hostPattern": "portal.arcad.local:*", + "target": "http://localhost:8082" + }, + { + "hostPattern": "hextris.arcad.local:*", + "target": "http://localhost:8083" + }, + { + "hostPattern": "test.arcad.local:*", + "target": "http://localhost:8084" + }, + { + "hostPattern": "*", + "target": "http://localhost:8082" + } + ] + } + } +} \ No newline at end of file